Asynchronous Svelte
Just about every app is going to involve async operations (e.g. fetching data). What if you could just… await it right in the framework? In Svelte, you can! Rich Harris teaches us how.
Resources & Links
Read the transcript
Captions provided by White Coat Captioning (https://whitecoatcaptioning.com/). Communication Access Realtime Translation (CART) is provided in order to facilitate communication accessibility and may not be a totally verbatim record of the proceedings.
JASON: Hello, and welcome to another episode of Learn with Jason. Today on the show, we're gonna dig into something that I think has been pretty frustrating for a lot of people for the entirety of building for the web. Which is how do you handle when apps become real world? You know? Any sufficiently complex app is going to have a lot of different places where you've got frontend, you've got backend, you've got APIs, you've got databases, you've got external requirements. And they have to interact with each other in a way that feels good. And we have tried so many ways as developers to solve these problems. MVC and patterns like it, tRPC, we've had serverless. We've had the API boundaries and all these things that sort of drive the experience of how we build for the web. And today we seem to be shifting toward another wave of innovation in this space. We've seen things like HTMX is making a resurgence, the React server components paradigm. And we're also seeing new innovation in the space. New people kind of experimenting with different patterns for how we can do that. And that's what I want to dig in today with somebody who I respect as one of the most forward-thinking people in the JavaScript community. So, please join me in giving a warm welcome to Rich Harris.
RICH: Hi.
JASON: Rich, how are you doing?
RICH: I'm going to level with you, Jason. I'm exhausted. We have been going full tilt on the Svelte team to show things like this. I was up until 2 a.m. fixing bugs. I'm very confident that we will find some more on the stream today. In fact, I'll be a little disappointed if we don't.
JASON: Thank you for taking the time. I didn't realize I was assigning you this when I asked you on the show. I'm thrilled to get to talk to you about this stuff because I feel like there are people who approach this like a -- like a contest that needs to be won and there are people who approach this like a fun puzzle needs to be solved. And I'm always a big fan of people who approach it as the latter. We're always trying to attempt different ways of fitting these pieces together and which ones feel good for the current era. And not worrying about whether we're the world's best or the only option. That's your ethos. You're not worried about a winner take all mentality here.
RICH: That's actually the case. We're building this thing first and foremost for ourselves. We're trying to design the thing that like we feel like is the most elegant solution to the problems that we're facing. At the same time we are 100% competitive. Like we are very motivated by having ideas. Like put out into the world and then inspire other people to do similar things in their projects. But you're right. It is very much a rising tide lifts all boats situation. And the wonderful thing about the frontend community is that people are very Open about what they're working on and keen to their what they're working on.
JASON: Yeah.
RICH: And generally speaking, we're all friends and we all want each other to succeed. I'm happy that is how it appears from the outside, too.
JASON: It wouldn't be worth working on if it wasn't worth arguing about. If it was such a solved problem, we didn't have different opinions, why reinvent wheels? But in the current state of things, we have seen huge leaps forward in what browsers are capable of and how networks work and different approaches to APIs. We had the GraphQL stuff come out, tRPC, these things coming out that make it possible to feasibly approach real-time in an app. That's just the stuff I can think of off the top of my head. I feel like the space is moving so fast. It leaves the old approaches lacking. It doesn't make sense to make an MVC. An M is a big thing, third-party model? Third-party SaaS model? Database model? Other database model? You end up with a lot of questions how to solve this stuff in a way that feels good to build and use. This is where my core contention comes in. Right now I feel like we're choosing one or the other. There's either feels good to build, and the DX is incredible. It's weird, shipping a ton of JavaScript to the user. We've got a lot of tradeoffs, I would say. Or it's the other way around. We can make this incredible user experience, but as the developer, you feel like you're jumping through a lot of hoops or dodging a lot of sharp edges to get there. How are you -- like I guess from your standpoint, do you feel the same tension? Or are you looking at something completely different from what I'm thinking about?
RICH: No. I think that tension definitely exists. Although I would maybe draw the tradeoffs in a slightly different place. You know, when we're thinking about how to solve some of the problems that you articulated and you mention things like HTMX, there are basically two schools of thought. Web apps are fundamentally distributed systems, have a server, client, have a CDN serving somewhere in between. Meaning as a web developer, you spend a lot of time thinking and talking about solving asynchronous problems. And historically frameworks haven't been good at this. So, the people I think who have made the most headway in recent years in solving the DX problem have been the people that say, you know what? We're just gonna kind of retreat from this problem. We're gonna make this less distributed by doing as much as we can on the server and kind of treating the client as like a done terminal that's just gonna receive updates. So, you know --
JASON: Yeah.
RICH: -- HTMX is an example of that idea. All of your logic and all of your state on the server and the client is just fetching snippets of HTML. And maybe doing a little bit to patch that in a non-destructive way and things like that. And PA frameworks like Astro are the same basic idea. With Astro, obviously you can have client-side logic. But it doesn't give you help with it, you're very much on your own like that. The real value of a framework like that, all the stuff is happening in a predictable place on the server where you can control it.
JASON: Right.
RICH: And you don't need to think about the complexity of building a distributed system. But with all of these approaches, you kind of hit a ceiling on the richness and the fidelity of the experiences that you can create. Because if I have a powerful device in my hand or on my desk, which I do most of the time, I want to put it to use. I want to do as much of the computation there as possible. I don't want to have to have to go back to the server just to get a tiny bit of updated data. Or in the MPA case, an entirely new page. That doesn't make sense and belongs in the past. But we haven't made it easy and straightforward to build those high fidelity and rich client-side experiences. That's what we have been spending the last few months thinking deeply about.
JASON: And when you're talking about that, just to repeat it in my own words and make sure I'm grasping it. You're talking about if we approach a website as being client-side, which was the default for quite a while up until the last few years, you can do all these things, like client-side navigation, you can do little animations, optimistic rendering, the business logic and filtering can happen on the client side. And all that sort of stuff is fast and it feels good and you get these little interaction bonuses. But because whether you start getting into complex systems, that starts to add a lot of weird things about how do you make sure that things are happening in the right order? Voiding race condition? Suddenly you're working on a full server in your client, leads to a lot of duplicated code, a lot of complexity. It's hard to reason about the app, some of the business logic is in the server and the API, now you don't know where to look whether something goes weird. That was the problem I would find at IBM. We were kind of engineering frontends that were mini servers and managed on backends by different teams. When something broke, tracing that issue was really, really hard. So, I think the solution that you're mentioning is, well, just pull it all out of the client. This was too hard. We clearly made it a mistake. Pull it back to the server and clients are there as a representation of server state. Am I missing nuance in that?
RICH: No. That's exactly it. And it's a really successful approach for certain problems. And, you know, the metrics look good when you do this. If you try and eliminate any of the logic from the client, just do everything on the server, then you can really strip down the amount of JavaScript that needs to run in the browser to a bare minimum. And so, like your core web vitals are gonna look great. But then as soon as you click on a link, there's gonna be a delay.
JASON: Right.
RICH: And that's not accounted for in the metrics. So, we've become an industry that is very metrics-focused. And I think people need to give themselves permission to actually use their eyes a little bit. And see how the experience of using sites built in different ways differs. And, you know, I'm very optimistic in general about technology. I think that the platform is incredible. And it's getting better all the time. And I think if we -- if we lean into technological innovation, we can build more impressive and ambitious applications than if we kind of pretend that the innovations of the last decade or so never happened.
JASON: Yes. I think that's maybe -- you're vocalizing a tension that I've felt with some of the advancements in different framework approaches. Which has felt very much like go back in time. Like it -- like in a kind of nostalgic way. But in a sort of like this was all a mistake way. Which that always feels -- I don't know. It's like, not... it leaves me feeling a little bit like we've given up. Right? And we're saying, ah, you know what? This is too hard to solve. It's never gonna be solved. Let's just walk away from it all and do it the old way. And I can -- I mean, I respect that tiredness. Like, look, I'm tired, I get it.
RICH: We're all tired. We're all tired.
JASON: But at the same time, I think that the... pushing this medium forward and trying to figure out how we can, you know, how can we fit these things together? Because we've seen it done, right? Like there's teams out there that are shipping these web apps that are lightning fast. They've got great core web vitals, they navigate super-quickly and they're doing it in every single framework everywhere. It's possible. We just haven't figured out how to make it the default, right?
RICH: Yep.
JASON: We are exploring and making the choices that the really talented teams are spending effort and make it right. how to make it more baked in and when a developer is not the most experienced developer in the world, pick up a framework, and say go, it's accessible, fast, graceful degradation, not going to let them accidently step into traps where things like you get into async await, wow. This is great. But you don't know how it works and it's a promise under-the-hood and you accidently serialize all of them. And you're making 15 calls and you have to wait for each of the ones before it. That's what you can get wrong unless we think about our tools and be smart. These are the things that we -- I'm saying "We" like I do a lot of open source work -- but the open source world can work on building the web. I like that it's not everybody saying, yeah, let's give up on client-side entirely. It's just there to show us what we did in the backend.
RICH: Exactly. Like networks aren't always 100% reliable. I'm in New York City. I take the subway a lot. And I want ideally for my things to continue working if I'm in a tunnel.
JASON: Yeah.
RICH: Or even if I'm just like leaving my flat. Guy down the stairs and switch from Wi-Fi to cellular. If I lose connection. That's no good.
JASON: Yeah.
RICH: We have supercomputers in our pockets, we should use them.
JASON: Absolutely. In the kind of high-level sense, this is the problem you have been focused on in Svelte for a little while, or one of the problems. Maybe the 10,000-foot view, how are you actually approaching this? What are you making choices on inside the framework allowing this to improve? Or that the right to ask?
RICH: As an open source maintainer, if you get any traction at all, you will fill up with feature requests. Wouldn't it be great if we could do this or that. And this thing could be a little bit better. Here is the solution they propose. And, you know, the temptation is to say, oh, yeah. Let's do all of these things. But if you don't, if you take a step back and you start to collect all of these pain points and just kind of let them marinate for a bit, you often find that there's a commonality between them. And that's what we've seen among the Svelte user base. There are a bunch of different overlapping problems that people have encountered while building applications that we think can be solved kind of holistically. And we're taking a layered approach to this. The first is how do we deal with asynchronicity in components? At the most basic level. And this is something that historically components have been really bad at. Even though I said earlier that you spend a lot of time thinking about async as a web developer. It's a very baked-in concept that you have to deal with. It has been even before promises and async await were a thing, since the time of AJAX. And we haven't had a really good unified way to do so. And I think part of that is because the component model kind of predates promises and async await. There wasn't a standard way of thinking about asynchronicity. And so, promises came along. And so, we've kind of experimented can different ways of solving this problem. And the first wave of solutions to this was just I'm gonna have some local state in my component and then I'm gonna do a fetch request maybe inside an effect or something like that. And then when that comes back, I'm gonna assign the result to my local variable. But I also have to have some local state indicating whether we're loading and whether there was an error. So, that's three pieces of state for one piece of data, and I have to have that in all of my different components. And if I have multiple components that are all doing asynchronous work simultaneously, they're got their own loading and error states.
JASON: And they're unaware of each other.
RICH: Unaware of each other. There's no coordination. That is, sadly, a very common experience on websites today. When I go to my bank's website, like, spinners all over the place. And that's the fundamental reason is that all of these things, they're not talking to each other. So, that was how we used to solve it back in the day. And then application frameworks came along and said, well, what if instead of the component being responsible for doing this, we take that outside of the component and do it at the level of the router. And so, Next has get server side props. Remix has loaders. Svelte Kit has a load function. And the idea there is you can do all of your async stuff in this kind of out of band function. And then once that resolves and the framework is finished coordinating it, and maybe does other stuff like serializing it into the HTML so you can hydrate quickly and this, that, and the other, it will pass that as props to the components. And that's great. Because now you get the coordination. So, if you click on a link, things will load before you navigate. Parallelize requests like if you have nested roots like in SvelteKit. And that's basically how the problem has been solved for the last few years.
JASON: Right.
RICH: With the idea of loaders. But as we've gone deep entire this world, we've collectively discovered that loaders don't scale. There are some problems with that approach. Because you have to fight to get food type-safety. We do some magic in SvelteKit so that the props that you receive have the types that were returned from the load function that corresponds to the component. But it's a little bit of magic.
JASON: Well, I think like type-safety and routers, I think that was like the problem that kept Tanner Linsley locked in a basement for two years.
RICH: Yeah, it's a hard problem with multiple dimensions on it.
JASON: Yeah.
RICH: But you have different dependencies have to be represented in the same loader. Maybe one is out of date and you have to re-fetch it. You have to rerun the entire function. Over-invalidate, get more data than you need between pages. There's other problems with it. Maybe you're building an application, and there's a small component on the bottom of the page, has a dependency on some piece of data. Your load function now needs to get that data and drill down through layers of components to get it where it needs to go. And every point, like the component receiving it needs to have the type annotations all over again. It's just a real pain. And so, if you take a big step back and think: What is the fundamental nature of this problem? And how does it correspond to other similar problems that we solved in the past? The problem is one of colocation. In general, it's better for things to be colocated, right? We used to think it's good for your HTML and your CSS and JavaScript to exist in separate files because they're different languages. Nowadays, no one really thinks that -- a few cranks do -- but no run really thinks that. That's a bad way to think about the problem. And components are an encapsulation of those three concerns.
JASON: Right.
RICH: That act that is these kind of layered blocks that you can compose together in a really nice way. It makes sense. That is a mental model that is very, very successful. Similar with import declarations, use the statically analyzable syntax, letting the person writing and reading the code and the tooling that operates on that code understand what else we need to have in scope for this component to do its thing. Data is just another example of that. If you can say inside the component this is the data that I need, like this is the fetch request that is gonna get the data that I need to render. Then you can sort of start to solve some of these problems. Like you have the type-safety and that you need because the call that gets the data is where that type gets set.
JASON: Right.
RICH: If you delete the component, you're no longer gonna fetch that data.
JASON: Right.
RICH: The only problem that we need to solve is the coordination. If the component over here has an asynchronous dependency, and this component over here has an asynchronous dependency, I don't want those to appear at different times. When I click the link, I want the page to show up. If I want a skeleton UI while a particular piece of data is being fetched, but in general, I want things to be coordinated. The first step to solve is how to do that? How to do the async components in a ways that coordinated across your application?
JASON: Yes. And this reminds me of in 2016 or 2017 when I was getting heavy into GraphQL for IBM. One of the big promises they had was this idea of colocating your data via GraphQL fragments. So, each component would say, this is whey need, this is my fragment, and then it would export that. It would have its layout and everything. Similar idea. Like all concerns together. It would export its fragment. And then that sort of did a router thing as well. All those fragments hoisted up to the router where that query was assembled into one big fetch call. That solves the problem of colocation. If you get rid of the component, you get rid of the data that it requires. And solves deduplication. And if they fragment out, they dedupe out in the query. But didn't solve over-validation. Any time you had to change a component, the GraphQL had to run in entirety depending on what it was getting. And depending on how that was set up, that might be the entire application because of how GraphQL works. When you're approaching this problem, how have you sliced this up to kind of solve these different issues that happen when you start separating data from when it's called maybe two components are sitting the same API request with an overlapping set of things they need, but not necessarily the exactly the same things. How did you start slicing that up?
RICH: This is the second layer of the problem. The first layer is -- the first layer is just being able to use promises inside components.
JASON: Sure. I'm jumping the gun, then.
RICH: That is like a syntax problem and a coordination problem.
JASON: Yes.
RICH: And a runtime reactivity problem. And that part is kind of done. We've actually shipped that in Svelte under an experimental flag. We have some demos that we could walk through if that's helpful. But then the second layer is the stuff that you're talking about. Now that we have the ability to consume data from an external source or consume data from our server, how do we make it such that we can get that data without doing the over invalidation and the over fetching. How can we cache things intelligently and how can we make sure these things are coordinated? And that is something we call remote functions. That is a SvelteKit feature that's been in development for the last couple of months or so. And which we are on the cusp of shipping, again, under an experimental flag.
JASON: Okay. Sure.
RICH: Maybe tomorrow, probably next week we will be shipping this.
JASON: I'm actually pretty excited. Let's look another this step one. I feel like that alone is a very cool thing to just be able to approach. And since we're gonna with showing a screen, let me really quickly do some shoutouts here. First and foremost, we are talking to Rich today. Let me get some banners up and everything. This is the Bluesky account. If you're not on Bluesky, where you been, man? Get over there. We are talking about Svelte. I didn't actually pull up the Svelte website. I pulled up a bunch of other stuff. So, let me grab just the home Svelte stuff. And we'll drop that in here for anybody who needs a reference. And so, you mentioned that step one of this process was solving the ability to use promises inside of components. And so, you sent me a bunch of examples. I've got them in order from one to five here. Should I hit this up one.
RICH: Yeah. Let's do it.
JASON: Okay. All right. So, looking at this -- let me bump the size just a little bit -- we've got -- we're importing something from an API. We've got time. We've got our state. And then out here, we're doing await inside of our components. Which this is exciting to me because I don't think I've seen this -- I've seen top-level await in Astro. And I've seen top-level await in some other frameworks. But it's always in kind of the server setup stuff. I don't think I've ever seen it inside the component model here.
RICH: So, the one place that you can use await in a client component today outside Svelte is in Vue. But it's only --
JASON: Okay.
RICH: -- when the component is first created. After that, you're kind of on your own. It doesn't really solve the problem. I think this is the first framework that has been able to do this. And the reason why is because as a compiler, we get to play fast and loose with the semantics of JavaScript. We keep the syntax, but we vary the semantics to solve the problems that you're trying to solve. And so, here, for example, you see five separate awaits which kind of look sequential. But because they're in the template, you can see the expressions and run in parallel. Click on that, go over to the ATPS file, you see that multiply API, just drag that over to the right a little bit, the divider. You'll see that we're defaulting to half a second for each promise.
JASON: Oh!
RICH: This is simulating a slow, unreliable network or whatever. Each of the promises is taking somewhere between zero and 500 milliseconds.
JASON: And we can see, too, they almost update almost in sequence. Where if they were blocking, it would be bop, bop, bop, bop --
RICH: Exactly. Not only do the asynchronous values update simultaneously. And not only are they happening in parallel. But also the synchronous variables update. It would be no good if you click the button and the number on the left updated before the number on the right updated. Because then you would have 2 times 2 equals 2.
JASON: Right.
RICH: We have this global coordination --
JASON: Oh! I didn't even think about that. That was some magic that I expected to work, and because it just worked, I didn't examine it further. But we're awaiting here, and this is not awaited. But it's not updating until this await finishes. That's... really cool, actually. I didn't -- I would have glazed right over that.
RICH: Yeah. I mean, it's the same if you call an asynchronous function like normally. It waits until the entire asynchronous function has worked, right? And then you get the result. This is kind of like that. If the UI is a function of state, it's now an asynchronous function of state, and you get the UI in one go. And what's really cool, if you uncomment the line at the bottom. Click on that and do the command slash thing. So, now you see that we have some fast updating state. This is updating every millisecond, or every frame, rather. And you can continue to press that button. And it's not gonna block those updates because those are independent updates. So, the work that begins when you click the button kind of gets put off to one side and when we update the rest of the screen, we just kind of undo that update to end because that's not ready yet. That trench of work isn't complete. And then once it's complete, we can then apply that. And so, we get -- we call this time traveling. We get to -- we get to have fast updates while slow updates are still ongoing.
JASON: And so, Charles is asking, and I have the same question: Under-the-hood, how are you determining that, like, this affects this? But doesn't affect this? Is it just usage of the same reference to state?
RICH: It is. But not in the sense of static analysis. It's not looking at the code and saying, oh, this uses N, in uses N so we're gonna bring this together.
JASON: Okay.
RICH: It's all based on runtime dependency tracking. This is what you get with a signal-based reactivity model.
JASON: Okay.
RICH: I don't know how deep we should go into how signals work. But the idea is you have a source and you have an effect. And the effect is a function that runs and will read some sources, one or more sources. And when that happens, the source says, oh. This is now one of my dependents. And so, when I change, I'm gonna let that effect know that it needs to re-run.
JASON: Got it.
RICH: And so, because you have that ability to track dependencies through the tree of your application, we know exactly which things need to be updated in response to a particular piece of state changing.
JASON: Got it. Okay. So, this is -- this is extremely cool. And yeah. I feel like there's a couple people who want to go real deep here. I am tempted, but I want to avoid going way too deep into the weeds because I feel like we are -- we are barely touching the iceberg of what's actually possible here and what you're actively working on. Okay. So, we know that this works. This is extremely cool. We started talking about like the second phase, which is the -- like dependency tracking and deduplication of data across different components and make sure that as we get outside of a single file, right? Now we've got -- you're tracking your promises in this one file that's got the time, but then maybe in another one, we've also got a piece of state that's tracking a thing and then maybe they're both making fetch calls. Now we start getting into the really messy part of a real app is pulling data from all over the place and different pieces of data from different API endpoints are gonna get pulled into different components. And the only way to do this, which we have historically done, blow it away and request it all from the server again to get a clean version. Let's dig into the second layer.
RICH: All right. How should we go about this? Do you want to rattle through the playground demos or start a project? Svelte Kit?
JASON: I will do whatever you want. I can sit her and pepper you with questions the rest of the time.
RICH: Why don't we get the demo project up and running and get a tour of that. And we can make some edits to that and you can ask questions along the way.
JASON: Okay. Let me get this project going. I'm gonna clone this into a repo here. Let's go back to...
RICH: So, this uses PNPM.
JASON: Okay.
RICH: And that's how you'll get the correct dependencies. I would use PNPM install.
JASON: That works out. I just recently switched to PNPM as my thing -- I'm in the wrong thing. Let me see. Remote...
RICH: So, the reason that's important is that we're using the latest versions of most dependencies. But the Svelte Kit installation that you have here is from a specific branch. It's being installed from a service called package.pr.new.
JASON: Okay.
RICH: Which allows people to use in development branches directly in their projects without it having to be published at npm.
JASON: Okay. I got it. Bump up the size a little bit here. Inside here we have our package JSON. We're using Svelte. This is the package.new thing you were talking about. And then we have nothing wild. No kind of unexpected adapters in here or packages. And then we've got our source going. So, what -- like I guess what do you want to show off on here? Where do you want to start?
RICH: Get the Dev server up and running. If you want to open a terminal up and running and do npm Dev.
JASON: Okay. Let's jump over here. There we go.
RICH: Right. I have no idea how well this is gonna work on for if there's gonna be some horrible bugs that are about to rear their heads. But what you're looking at right now is the home page. Which in a SvelteKit app is powered by source/roots/+ page.svelte.
JASON: Plus page.svelte . Got it.
RICH: And actually, if you enable the inspector with the plugin Svelte, then you should be able to activate the -- I'm not sure what the default key combo is.
JASON: Let's see, yeah. I actually don't remember how to do the inspector.
RICH: I think if you... ah... this is a great question.
JASON: I can look it up real quick. Vite plugin Svelte inspector. It knew what I wanted. Oh, do I need to install this?
RICH: It should be part of -- it's part of Vite plugin Svelte.
JASON: Okay.
RICH: Ah, okay. You know what you need to do? Open your Svelte config JS.
JASON: Okay. Yeah. Let me undo this. There's Svelte.
RICH: I have this kind of setup as part of my bash RC. I never have to remember to do this. But in here, few you add a new top-level option called VitePlugin.
JASON: Okay. Is that an array?
RICH: It's an object.
JASON: Okay.
RICH: And then inside that, do inspector.
JASON: True.
RICH: And you can do true, and that will use the default key combo. Or if you want to pick a key combo. I like meta shift S.
JASON: Does that add in...
RICH: No, create, correct. And then toggleKeyCombo. I do meta-shift-S.
JASON: Like that?
RICH: Yeah. Let's try that. See if it works.
JASON: Okay. So, then if I did that right, what I should be able to do is -- is that meta? What is the meta?
RICH: The kit command key.
JASON: Oh, got it.
RICH: Oh, that's just -- that's just safety.
JASON: Oh, it did the thing. I need to make it something that is not any save button. So, let's make it -- we'll do...
RICH: Interesting...
JASON: G.
RICH: Well, maybe it needs to be lower case. I'm actually not sure. That seems to have worked.
JASON: Yeah. That did it. It was doing it while I was doing the shift S, it was trying to save the page. I'm use arc.
RICH: I see, I see.
JASON: This is cool. All right. So, we've got our inspector going here.
RICH: Yep. So, if you just click on an element, it will take you to the --
JASON: Cool. All right. This is slick.
RICH: Now, a little caveat here. Because this is still pretty new, we haven't yet implemented asynchronous server side rendering. What you're looking at right now is just a client-only single-page app. That's something I rail against. I'm a pro-server side rendering. It's great for progressive enhancement and overall resilience. Means that you get a faster initial render. It means that you're pushing data from the server, instead of going to the JavaScript, starting the app and then getting the information from the server. But we can't do that yet. For right now, this is just a blank page that magically populates when the application starts.
JASON: Okay.
RICH: But if you click over to the blog page, we can see our first remote function.
JASON: Okay. So, I think I need to install the Svelte extension. Because I don't think I've run it in this one yet.
RICH: Yeah.
JASON: Let me do that real quick. Svelte --
RICH: That's the one.
JASON: -- for AS code. Yeah? Okay. And we running now?
RICH: It looks like those angle brackets are a different color.
JASON: TypeScript plugin, enable. That looks fine. Let me open up this other page. There we go. Now we got our syntax highlighting. Back in business.
RICH: Okay. You see we have this -- if you click on the blog page on the left-hand side in the NAV. There we go. We have some data that's being pulled from the server. And we have an each loop in our component. And inside there, await get summaries to get that data. And then we're creating some links. All pretty straightforward stuff.
JASON: Yeah.
RICH: So, the new thing here is this data.remote file. If you command click on get summaries, it will take you to where that is being defined.
JASON: So, we've got our get summary. So, it's a query. It's async. It returns the post, which is what I would expect. We have got our post hard coded up here.
RICH: Yep. For demo purposes, hard coded data is...
JASON: Yeah. Easy, easy. So, we have get summaries. That is running -- and this query looks like it's coming out of the -- I'm assuming is this a shortcut to the compiler or is this a shortcut in the file system?
RICH: So, dollar app is a set of generated modules that are specific to your application. So, anything that -- like the stuff that comes from your own configuration. Stuff that is to do with the roots that you have in your app, that's all under this dollar app namespace, which is distinguished from just regular whatever helper functions that you can import from the framework itself.
JASON: Right.
RICH: And the interesting thing about dollar app/server, you cannot put anything inside -- from this module into any code that could run in the client. Because the server is where you have all of your secrets. It's where you have your API keys and your database credentials and whatever else. And it's really important that you don't have that inside code that gets to the client for obvious reasons.
JASON: Right.
RICH: In SvelteKit, we've had a strict separation. Anything that needs an environment variable, if you try to import it into client-side code, your build will blow up. It will say, please don't do this.
JASON: I really like that that's starting to happen. Where, you know, because like y'all putting this in Svelte. I've seen it in Astro as well. You can kind of say, this is server stuff, there is client stuff. If you define your environment variables, it will only let you import the ones marked as client-side. I like that way, instead of the old way, you have to prefix things and then you just got to know that.
RICH: Yeah, exactly. What's interesting about a file called data.remote.ts, because it has the dot remote in the file name, it's a bridge between the server-only code and running in the client.
JASON: Gotcha.
RICH: On the server, that's just a function. It's just the exact thing that gets returned from query. But in the browser, what you're importing is actually a thin wrapper around fetch. And that --
JASON: Oh, nice.
RICH: How to get data from your server. As far as TypeScript is concerned, we can talk about validation and stuff in a moment. But what's actually happening is the framework is making a request to the backend on your behalf.
JASON: Yeah.
RICH: And then it's gonna serialize it, and it can serialize all sorts of different things and you can serialize how it happens. And then caching and all that stuff automatically on your behalf. But as far as you, the developer, are concerned, you're just writing some very basic logic that returns the data that the component needs.
JASON: And this is where I really get excited about the DX of this stuff. Because, you know, this is what it feels like to write an API when you control the API layer and the client layer. Well, I'm writing my API function and hit my API endpoint. And it just feels nice. I did the thing that I need to do, and got the thing they needed from this file. And then got to show my stuff. But as you mentioned, you have to get into caching and into all of these complicated things to make sure that you don't accidently rate limit yourself or grind yourself to a halt. Or the promise chains that end up blocking for 10 seconds while it waits for stuff to load. Again, it's not all hard to solve once you know it's there. But it really sucks to learn the lessons in production. Especially when dealing with multiple teams and huge apps and a lot of complexity in the stuff that you're getting. Just knowing that it's just gonna work.
RICH: Yeah.
JASON: I know that you've made good default decisions and I don't have to worry that I've accidently created a trap for myself in the future.
RICH: And because this is all just functions. Like if you right click on get summaries and do find all references, you can find which components in your app are using that. I guess that didn't work for whatever hellish TypeScript reason.
JASON: There are so many reasons that I've broken all of my -- like I have everything installed on everything now. It's probably my fault.
RICH: Well, it works on my machine, Jason.
JASON: Good, good. And it will probably work on your machine. Don't use my machine as a reference for anything because it is a hot burning pile of garbage. But so we've got get summaries here and then if we come over into the blog here, we get -- we do get back the TypeScript, right? We can see that we're getting back a slug and a title. And the slug and title are showing up in here. This is great. This is exactly where I wanted to be, right?
RICH: What I want you to do is go back to the browser type and open the developer tool to see the network request that are being made.
JASON: Okay. Let's get the network going. All right.
RICH: And now, click into -- maybe you want to clear it so that we can actually see what's happening next.
JASON: Got it.
RICH: And then if you click on one of those links, if you scroll down the page you'll see that those summaries are actually still on the page. But they're being rendered by a different component.
JASON: Oh. Okay. Okay.
RICH: This page also depends on get summaries. Just as the prior page did. But because we went straight from a page to another page that had it, and because we didn't tell the framework that data had changed in the meantime, it was like you know what? I'm just gonna reuse the data that we already had. It's still good. And because of that, all that we need to get to render this page is that bit of data that you're looking at. Instead of getting the entire page, like all of the HTML and all that have crap.
JASON: That's really nice. And so, how -- under-the-hood, you're just sort of saying, like, this is the data that was requested. And if the queries match within a certain -- I assume there's a certain default time out -- if the queries match within whatever time reference, get the same thing?
RICH: It's not a time out, it's a micro task.
JASON: Okay.
RICH: This is the great thing about having the data fetching integrated into the rendering framework is that we have the ability to know which queries are currently being actively rendered on-screen and so, we can say that if a query is rendered in multiple places and it has the same arguments, then it's the same data and so, we don't need to do any data requests at all. Keep using the same thing. If you were to navigate to the home page and back to the blog page and to an individual blog post, at that point we would get the data again.
JASON: Right.
RICH: Because it's a query and it might have changed in the meantime.
JASON: Sure.
RICH: But if you're keeping same piece of data on the screen consistently, then there's no reason to go back to the network for it unless it gets invalidated. And maybe that's the next thing we should talk about.
JASON: Okay. I'm into it. I think this makes a lot of sense. Where do you want to go next?
RICH: Let's try logging in and creating a post.
JASON: Okay.
RICH: So, if you close out the Dev tools so that we can see more of the screen. And then just type your name. This is a very crude login system. You're now logged in as Jason.
JASON: Like setting a cookie, basically.
RICH: Yeah. And you can look at the logic for that. It's in a file called auth components.
JASON: Okay. So, we grab the name. We set a cookie called user and then redirect. We have a logout to remove the cookie. Get user -- where is our get user. It's from auth server. Okay. And it looks like that's -- I'm assuming that just loads the cookie?
RICH: It does. It does. So, inside get user we are accessing the current event -- the current request context with this get request event function. And this is something that you can use inside any query or form action or whatever it is, you can get details about whatever is happening, including cookies that the user has. And it uses async on the server, you don't need to pass the context around, which is really awkward. And because inside one of these functions you can throw a redirect or throw an error, it makes it really easy to build these sort of composable primitive the for ensuring that you always have a logged in user, or guarding a particular route as requiring you to be logged in. Redirect error or redirected to the login page.
JASON: Nice.
RICH: And that's get user. That's a server module. So, we can use that inside any remote module. But it's not something that you can use directly from the client because it requires cookies.
JASON: Got it. Yep. Makes sense, makes sense.
RICH: So, in auth remote.ts, we have these two forms. And if you are to do the command shift G thing on the logout button, you can see where we're actually using --
JASON: Alt command shift G on the logout.
RICH: You can see that we're taking that form, that logout form, and we're spreading it on to a form element.
JASON: Oh!
RICH: That gives the full method equals post. The action which is the generated URL that sends the data to the server.
JASON: I don't think I've ever seen this approach before. And I think this is very cool.
RICH: Yeah. And what's really neat about this is if we had server side rendering, which we don't, but if we did, this would all work without JavaScript. Because it's just a regular form. But because we do have JavaScript, we can progressively enhance that form submission.
JASON: Right.
RICH: We can go to the server, get the updated data for the page without you having to reload the page and without you having to configure any progressive enhancement logic. It's all just kind of there for free. You can customize what happens in a progressively enhanced form submission. But we probably don't need to get into that. It all just kind of does the right thing on your behalf.
JASON: That's slick. I like that kind of API. Because that's -- I mean, I care a lot about making sure that things don't work when the JavaScript turns off. But knowing that -- you know, I think everybody's kind of getting into this form actions vibe where you can write a form and then you sort of submit it to their action. And so, this is is not a completely unique feature. But I like it deciding if it's a post or a get. I don't care if it's a get or a put or a post, I want it to do the thing when I submit the form. This is nice. Getting back to it again. We just imported this logout function and spread it right into the form component and that does the whole thing. I like that. That's very cool.
RICH: Yeah. And because it's calling that await me query there, if you log out, that query is gonna get invalidated and so it will then show the login page. And if you go to the blog index page before -- before doing that.
JASON: Okay.
RICH: You'll see at the bottom now, because you're logged in, you have the ability to create a new post. If you were to click the logout button, that would disappear. Because both places on the page where it says await me.
JASON: So it doesn't have to do a full page refresh.
RICH: Exactly.
JASON: Slick. Okay. Now I can go in here and create a new post. And my post...
RICH: I had a suspicion this would happen. I was chasing a bug today.
JASON: Okay. The route.
RICH: Yeah.
JASON: And it is there now. It didn't do the form switch over, but it did everything else.
RICH: The redirect part failed. Clearly this isn't quite production-ready yet. But you get the idea.
JASON: This is tool, though. So, then in here, we've talked about how do we -- and this handles auth, right? So, somebody is asking about auth earlier. So, the auth is handled through these remote functions and through being able to touch the cookies in the get request event. We've got the -- the invalidation being client-side and instant, which is really cool. Because I literally just was fighting with that in Astro because I had people logged in. And, like, my logout was too fast. So, the cookies weren't gone yet. And so, they looked logged in until they refreshed the page. I had to manually add a time out until it was gone when they went to the next page it would be locked out-looking. They were logged out. This is a nice way to approach the problem. So, we are loading data. We have the proof that when we do the async await, it's not blocking, those are all kind of working. And it's coordinating where data that's affected by the awaited promise isn't jump dated until all the promises related to it are complete. So, we're solving a lot of the UI problems. What else do you want to dig into here?
RICH: So, I think the thing that I'm excited about -- I mean, there's a bunch of things that I'm excited about -- but the ability to have a little bit more intentional about where you put all of your logic. So, the way that it works today is you need to get the data at a special root component, essentially. Because those are the hooks that the framework has to give you your stuff. You have to receive it inside a plus page.svelte file and then if you need to pass it to another component, then you receive it as a prop and pass it as a prop to another component. And that kind of sucks. So, if you look at this page, await get summaries, and then the create new post beneath it. If you go on the blog/slug page, blog/slug/layout -- I forget where it is. But you can see the other place where we're using get summaries. I guess it's in the adjacent layout page.
JASON: There we go.
RICH: There you go. We can see down here, we have essentially identical markup. We have a UL with await get summaries. And then if await me, then create new post. And today with loaders, like you can move that into another function, but then you got to pass your data around. It's kind of annoying. But now you can just like copy that code, yoink it out, put it in a new component, and use it in as many places as relevant.
JASON: If I take this, I've got my roots --
RICH: Call it like summaries. And you can put it wherever. Like inside the block directory or something like that.
JASON: Components, and I can call it summaries.ts.
RICH: Dot Svelte.
JASON: Oh, dot Svelte. I knew that.
RICH: Yeah.
JASON: If I remember how to write it -- I'm about to humiliate myself here. A script up at the top and a component down at the bottom. So, we have our script, which is gonna need --
RICH: You can just paste it straight in, I think. And then just paste the stuff that you copied.
JASON: Paste that stuff in directly.
RICH: And then if you just like do the quick fix thing, it should know how to import them.
JASON: And use -- wait. What are you mad about? I'm not finding get summaries? You're right here. What have I done?
RICH: Interesting. I don't know why that's happening.
JASON: Blog to remote...
RICH: One thing we could try, put the summaries.svelte file inside the blog directory.
JASON: Inside the blog directory.
RICH: Yeah. Instead of having the source/components.
JASON: Okay. And then it --
RICH: It seems to like that. I'm not sure why.
JASON: It's happy about that. I mean, who knows. I'm -- then we got our auth remote and it did the lib thing for that.
RICH: Yeah.
JASON: And so, now that just works and then so I can come back over to our blog slug and I want this instead to be our --
RICH: Yeah, just start typing capital S summaries. Is it gonna -- there it is.
JASON: There it is. And then I can take this and I can do the same thing on the --
RICH: Page.svelte.
JASON: -- page.svelte here.
RICH: Yeah.
JASON: And that's gonna take all of this. And now we have -- get rid of these imports. Oh, and we can get rid of the imports on here, too, because we're not using them anymore. So, we've now simplified this site. And it continues to function exactly as it did before. And I didn't have to mess around with data. I just got to, like, move the data where I wanted it to be.
RICH: Exactly. And the React people watching this are like, oh, so what? We can do that with server components. But this is isn't a server component. Like you can still use state, you can still use everything. It's one unified consistent mental model from front to back. You don't have to understand the rules of composition. Like it's all just contained in the way that you would expect.
JASON: Yeah.
RICH: Within those limits.
JASON: And I think one of the things that gets me when I get into React world is that a lot of times I've got something that's coming in async. And I don't necessarily want to re-render the page. I just want to update like one little thing. And I end up in these sort of, like, okay, so this would be a server component. But then I need an effect to do a thing there. And then in order to get the effect to not re-render, I'm getting into refs now. What am I doing? I feel like I'm wrestling with the rules of a puzzle game as opposed to building a website. I think that's been one of my big challenges --
RICH: The use cases.
JASON: And I think they're adapting to a mental model that I think they've got a vision -- I think with React compiler a lot of that trouble goes away and stuff like to. But I keep feeling like I'm not writing websites anymore. I'm like writing React. And that, to me, feels at odds with my personal values as a developer. I want to be building for the web. And the harder it is to do webby things, the more I get frustrated with the thing that I'm building. What I like with what I'm seeing here, everything happening here feels like it's a component. It's a markup. I'm putting it here. If I want to make a request, if I was in vanilla JavaScript, I would make a script tag and load some stuff. I have some bonuses like being able to template. But I feel like I'm doing webby stuff. Which feels like what I'm after. It's one of the big reasons I'm a big Astro fan. It feel like doing webby stuff. I like that we're finding ways to kind of simplify. Like we move the frameworks and make them feel more like we're just using what the browsers are offering us now that these APIs are catch up and getting so powerful.
RICH: We're trying to stay out of your way, ultimately.
JASON: I very much appreciate that. Let's see. I think we might have a couple questions here. Let's see. You need to have an auth function on all remotes that you need? Can't you determine which folder needs to pass through hooks or something like that? Do you mean the -- I think you would have the auth function to determine whether or not somebody's logged in, right?
RICH: You can make a high roller function and use that inside your query call that enforces the user exists on your behalf. But doing things at like the folder level. It's not a good idea. And part of the framework's responsibility is steering people away from bad ideas. Because what's gonna happen is you're gonna have some logic that isn't in the right folder. Or the folder is gonna get misconfigured. And one day you're gonna have a vulnerability because a file got innocently moved to the wrong place and it no longer matched whatever convention you set up. And all of a sudden it's gonna break down. What we want is to have code that you can trace back to the source. Like inside the query you can see because of the code that is used for that query what its dependencies are. What its requirements are. And you want to be able to trace those function calls back to where they are to see where they're defined. As soon as you start having magical configuration and convention and stuff, that's where your app becomes a spaghetti mess.
JASON: Yes.
RICH: We're not doing that.
JASON: A couple related questions: Can a remote function return a Svelte component?
RICH: No, it can not.
JASON: It's gonna return data and you're gonna build your components out that have data.
RICH: Exactly. Exactly. One of the examples that I shared with you just before we started, I think it's number 4, is the ability to lazily import components. So, you know, there's a lot of different things that you can do with the ability to await stuff inside. It's not just about data loading. It's also about delegating work to a web worker. It's about lazily importing components. It's about pre-loading images and all of these other things. So, that is generally the way that you would approach that problem of conditionally loading the component is you just await import right there inside the component.
JASON: Nice. And then a related question is what about a promise rejection?
RICH: Great question. Svelte has something called error boundaries. And if you have a boundary that contains some UI or an error handler, then anything that happens inside that boundary, including promise rejections, will trigger that boundary. If you go into the layout.svelte here, the root layout --
JASON: Root layout.
RICH: -- you can actually see one of these boundaries.
JASON: Here.
RICH: Okay. Snippet pending -- this is what would show while the code is loading. Ultimately we want to get rid of that. Start using await directly inside of your application. And the asynchronous server side rendering will block until the promise a awaited and send up the result along with the data. But for now, we have to have a snippet pending so that you can use await. But right below it is this failed snippet. This is what gets showed if an error occurs during rendering inside this boundary, whether it's an asynchronous or synchronous error. So, the error argument is what got thrown. And the reset callback is what you can call to try and reset the state once you fix whatever the error was, if it's possible to do.
JASON: So, if I fake this by let's say going to -- where was our --
RICH: Yeah, in the dot remote, throw the summaries. Fingers crossed this bit isn't buggy.
JASON: Yeah. We'll just say, like, unknown. And then something's gonna explode and we come back out here. And we go home. Go to blog and it shows there's an error boundary.
RICH: It's not telling you unknown. It's telling you internal error. That's important because if an error occurs on the server, you don't know where it's coming from. It could be coming from a library. Candidly, you don't want your users to see. First of all, they can't do anything about it. Second of all, it might be providing information to attackers, like your stack trace, give them information that's useful to you. If you want to control your error message, instead of just throwing, you want to use the error helper from SvelteKit itself.
JASON: Okay.
RICH: Replace throw new error with a lower case error.
JASON: Error.
RICH: You don't need to throw it, it throws itself.
JASON: Okay. Error.
RICH: The first should be a status code, an HTTPS status code.
JASON: Like a 500.
RICH: Yep.
JASON: And then this is like --
RICH: Exactly.
JASON: That will be actually shown out here. Okay. So, you do have the ability to send a helpful message, but you have to opt in to exposing things to the user.
RICH: Exactly.
JASON: That's -- I mean, another thing that I don't really think about very often is the ways that we inadvertently leak stuff. And make it -- because, you know, if you go hammer on my website, I'm sure you can find a way to make it 500. And I'm definitely throwing just whatever error. And it's definitely showing whatever is under-the-hood.
RICH: Yeah. By the grace of God, it's impossible to remember to keep all of these things straight.
JASON: Yeah.
RICH: The framework's job is to keep it straight. If you go back to data.remote and look at the get post function.
JASON: Data.remote, get post. Here we go.
RICH: That first argument, that v.string, that is a validator. V is valley bot --
JASON: Oh, yeah.
RICH: And that's a standard scheme of validators, absorb and all the libraries that are there to -- which checks that the incoming data is what you expect it to be. Once we have done that, we're able to type slug inside the function. And that's really important because these remote functions, they're not just private implementation details of your application. For it to work, we actually need to create a publicly accessible endpoint. And as soon as you create a publicly accessibility endpoint, people are going to throw random data at it. And a that's important --
JASON: As a clarification point, if I throw in a Valibot schema, I can do a gnarly one and accept a complex object. Is it throwing if it doesn't match the schema. I throw it in, and type the string, and then through in throw? Some JavaScript code. Does it say this is JavaScript code or this is a string.
RICH: If you don't send a string, it will respond with a 400 error and that's a bad request. By the nature, it's not going to tell you the error. You're probably under attack. Someone is trying to find the velociraptors in the electric cage and probe the defenses. We don't want to give a man of the defenses. There is a hook that you can add, handle validation error. Details are unimportant. But you can say, okay, here are the issues. This is the message that I want to craft in response to that. But the default is the error will just fail with the 400 status code and it won't get into your app. You're not gonna concoct a database query using a value that doesn't match the types that you expect. Which in some circumstances can result in people having access to data that they shouldn't have access to.
JASON: Yeah.
RICH: So, it's a little bit annoying, honestly, having to validate your stuff. Like it feels like a bit nannyish. But this is the sort of thing that is gonna keep your sys Ops people from...
JASON: And I feel like I was initially resistant to typing -- like building schemas. And then I, like, I -- the projects I work in all use -- I got really into, oh, a nice, well-defined Zod schema, schema.parse everything that comes through. That API response is definitely different than it used to be. It just broke all of our schemas and now we know. Right? It's -- David of XDate always says that you should make the impossible -- like make impossible states impossible. Right? If you don't -- if you don't make something explicit, you are implicitly trusting that the program will always work. And schema validation is one of those things. You're just trusting that you always get the right kind of data and in a lot of cases, that doesn't matter, you know? If it's like a single -- like a search query or whatever. Like people type whatever they want. Who cares? But if you're accepting form input, you're accepting data, and somebody accidently formats it incorrectly. It's not up to you as the developer to get a random failure or up to the user. But if you have that schema validation in place, if immediately knows where it failed. You didn't set this as an array, you set it as an object. Instant fix that would have taken forever to read by eye. It's stuff like that. I'm very sold on taking the little bit of time up front to solve these problems.
RICH: Yep. Me too. There's one more thing --
JASON: A corollary question before we move on from this, and I also put a schema into form to validate the form input? Or would that just be a manual thing here?
RICH: The form data object is inherently a little bit hostile to TypeScript.
JASON: Yes.
RICH: As many people have discovered. And there's really nothing that we can do outside this function to validate the data inside the form data. Because we know it's a form data object. If a request comes in that's not a form submission, then we don't even get to the point of calling this. But then inside there you have the ability to interact with the data however you want. And at this point, this is where typically you actually don't want to throw immediately. Because if -- inside a form function, if the data doesn't match your expectations, it's probably like a legitimate validation error. Like some of them mistype their email address and it's no longer a valid email. At that point, if you fail validation, you want to send the submitted data back in case it's a full page reload that's gonna happen so that you can repopulate the form controls, give them a human-readable error message and let them fix the error and try again.
JASON: Got it. Got it.
RICH: There may be some helpers that we can add to make that process a little bit more seamless and idiomatic. Not totally sure what that look like yet. But it's pretty easy to solve in user land so we're kind of punting on it for now.
JASON: My way of solving this is to put the Zod sky ma there instead of this.
RICH: That's absolutely --
JASON: Parse it real quick and make a decision on what I do next. You were about to say something before I jumped in with that question.
RICH: Yeah, there's one last thing in this demo app that I think is worth talking about and that's optimistic updates.
JASON: Okay.
RICH: The same way we can do form submissions using remote functions, we can do things that are not forms using commands. If you go back to the app and click on the button page. You can see this.
JASON: Go back to the app. Button. Nice.
RICH: Okay.
JASON: I don't know if I've got my sound piped through, but it's doing a squeaky bike horn.
RICH: It does have a squeaky bike horn. I have been amusing myself a lot with that this week. Every so often my wife will be like, what was that! Because I'm Spamming the button. You probably noticed as I was clicking it, it was a little bit slow.
JASON: I'll -- oh, it comes out of this year -- does that come through? [Squeaky bike horn] -- I don't know if that's coming there but --
RICH: You press it, and it comes through a second later.
JASON: Yes.
RICH: The network tab and see what's going on.
JASON: Okay. I have the network tab here. I'm going to refresh the page. Clear this and then increment clicks. And then it runs the get me and the get clicks. And let's see... looks like the -- is there just a time out on here on one of these?
RICH: Yeah. The first thing is making three separate requests for one button click. If you do the command shift G thing, click on the button, then we can start to understand what's happening here.
JASON: Okay. Here we go. We're on the button. It calls honk. Very apropos for your shirt.
RICH: Yes. Okay. So, the await get clicks indicates that this is coming from another remote function. It's another query. The increment clicks is a new type of remote function that we haven't seen so far called a command.
JASON: Okay.
RICH: And if you command click on increment clicks to see how that's implemented. Some left in await sleep. Leave that in for now because I want to show you a useful thing that you can do inside this command. If you add a new line below line 13, and just do await getClicks. And then call it. And then dot refresh.
JASON: Oh, god! Oh, god!
RICH: It was with your editor.
JASON: It was working, it was working. And then I hit the wrong button. I'm assuming that's a function call.
RICH: Yep.
JASON: Okay.
RICH: And then if you click the button again... you'll see that it's no longer making multiple requests.
JASON: Yep.
RICH: Oh, and embarrassingly, it seems like I must have broken something. Because you're not getting the updated data. So, let's --
JASON: And do we need to pass something through here?
RICH: Honestly. I don't know. I think this is just a bug. But that's okay. What that was designed to show is inside the command, you can specify which queries were a failure. If you don't specify queries, you must update everything. To be safe, like in a logout command, make sure the user is not seeing things they don't have to access to. But to specify, this is get clicks, then it's not gonna do that. Unfortunately, this part didn't work. But that's okay. We can delete that line of code. We don't need it anymore. And go back into the client. And this time we're gonna edit the click handler. That honk function.
JASON: Okay. Back to the honk function. I'm just gonna take my shortcut here. Honk.
RICH: Okay. So, increment clicks parentheses.updates. And then inside, open the parentheses. And then just do call getClicks again.
JASON: Like that?
RICH: And then hit save. Okay. So, it's working this time. And it's doing all that in a single request. If you click on increment clicks in the network tab, you can see that the data that it is sending back includes this refreshes object. So, it's actually the framework has said to the server, I want you to run this increment clicks function. And then before you send the data back, I want you to also refresh the queries that I've specified in my updates method. So, that way we don't need to get the user data anymore. We're not calling that me function. And more importantly, we're not running the command, waiting for the data to get back and then running the query again to see if it's changed.
JASON: Right.
RICH: But we want it stop there, the chances are if you're clicking a button that increments things, you can't know in advance when the next state is going to be. We don't really need to wait for the server for that information. So, inside that increment clicks updates call, after getClicks, we're gonna do dot withOverride. And then press a function that takes the current value and takes the next value. And then return N+1, for example.
JASON: Okay. And so, this is our optimistic update.
RICH: Yep.
JASON: Okay. So, now what should happen is when I click -- and so, we'll watch, here, we click. And now it's instant. Except I broke it. It's -- does it need to be reactive or something?
RICH: It's a very good question. It worked for me this morning. So, I don't know. Seems like a you problem.
JASON: Oh, wait. Now it's working. I think maybe because I didn't -- I needed to refresh the page. But now it's working.
RICH: Yeah, there's definitely some kinks here. But yes. It basically works.
JASON: It's instant. And you can see the requests actually coming and watch the network tab. So, click. Network tab. So, it's that optimistic refresh is definitely working there. That's great.
RICH: And if that command were to fail as a network error or whatever it is, it would automatically roll back the override that you applied.
JASON: Oh, okay. All right. Okay. Very cool. People are asking if we can go deep on signals. Ryan's here.
RICH: Yeah, when we're designing this stuff, the first version of the design didn't have single flight mutations. And we were like, is that a problem? I mean, it's probably not, you actually don't know what the next data is, there's a risk of doing single flight mutations. But if we don't have single flight mutations, Ryan is going to kill us. He's going to murder us on a stream. So, it's until we found a design that we like.
JASON: Carniato-driven development.
RICH: Exactly. That's the story of frontend for the last couple of years, right?
JASON: Indeed, indeed. We've got about 10 minutes left. So, chat, now is your chance. If you've got questions, please do throw them out. Something more specific than go deep on signals because we don't have that kind of time. Rich, is there anything else you want to show with the last few minutes here?
RICH: Not show so much. I mean, if you want to look at the other playground demos, then we can.
JASON: We've got them. Let's pull them up. We will peek at them while we're here. We looked that the one. This is the awaits not blocking the fast update and also running in parallel, which is really cool. What's happening with this one?
RICH: Oh, okay. This one, we're getting some data on API. And if you -- like in that input, type food or something like that. And you may have noticed that you actually have four separate requests there. The data --
JASON: Oh, yeah.
RICH: You got the results for F and FO and FO -- which a lot of the time isn't what you want. You could de-bounce. But that's kind of sucky. Because then you're delaying getting the data that you want.
JASON: Right.
RICH: This is demonstrating the fact that you use an abort signal to get the data to trigger additional work. Uncomment that. We're now calling fetch with an abort signal this automatically gets rid of any stale data.
JASON: Nice.
RICH: You don't need to control whether it gets run. The framework does it for you.
JASON: That's really handy. Because abort signal are one of the things that I should be using but I definitely am not. I loved if someone learned them for me so I don't have to go super-deep on them. The third one here, this is prime checker.
RICH: It's actually to show, it's not just about data. Being able to defer to a web worker in an idiomatic way is useful. Computing whether a very large number is prime, you probably don't want to do that on the main thread. So, you can just change that and, again, because of the --
JASON: I hope you're not expecting me to know a very large prime number off the top of my head.
RICH: Let's do is 12347 I think is one I found recently. It turns out it's actually not that expensive to compute.
JASON: Get into the 12-digit ones or whatever, yeah.
RICH: Yeah. That's something I would love to see more people using web workers for this sort of thing. And it's really easy now.
JASON: Got it. Okay. So, this is literally just -- this is the computation I would do. But by wrapping it in worker, you're moving it off the main thread.
RICH: Yeah. I mean, this isn't part of the framework. Just like that function down below is literally all that's powering this. Just a little --
JASON: Oh, I got it.
RICH: -- trick for creating workers out of a stringified function.
JASON: Cool. A way to parallelize things. That's nice. Looked at that one. Lazily loading components based on derived data, which is pretty dang cool. And this is the sort of thing that I have historically just avoided entirely because any of the await import stuff just introduces so much hassle that I just give up, right? So, seeing this in a demo where it's like, wait a minute. This is in derived data, which means that this has to update, then this needs to run, then this needs to load, which I get this and then I can render on the page, all right.
RICH: With minimal on any that have. And those components, they can have their own asynchronous work and so on.
JASON: That's really slick. Okay. And then our fifth one here.
RICH: This is just to demonstrate another use of await inline is inside the Pokemon.svelte component. Using await to fetch the data. But also using it in the markup to preload the source.
JASON: Oh!
RICH: Bear in mind when you're using stuff on the web and you open a different -- like you clicked a different tab or something, an image will appear, and the image hasn't loaded yet and that's no good.
JASON: Yes.
RICH: We can fix that. It's really easy now to just fix it that. I might even suggest that we put this into the framework directly so you don't even need to do the await preload source. It's just automatic for images that don't have a loading = lazy attribute. I think React is exploring something similar as well.
JASON: I mean, I don't think it's necessarily -- I think as long as you can choose to turn it off, right? Like if as a -- there's always some weird edge case where I need a thing to do a thing and the framework is helping and I'm like, wait, stop helping. Honestly, I don't know. I don't know if I've got a good reason that I wouldn't -- that I would want the image to appear unloaded first.
RICH: Yeah.
JASON: I don't know. I like it. It would just be nice, right? It's nice for that to be a good default. That feels good.
RICH: Yeah.
JASON: Let's see. Are there plans to add support for calling remote functions directly without Svelte components?
RICH: So, you can call them from anywhere inside your app. It doesn't have to be from a component. You can call it inside a regular module.
JASON: Okay.
RICH: Yeah. So, what you get back when you call a remote function, it depends on which remote function it is, a query, for example. You get back a promise. But it's also a promise with these extra bits. Like the, you know, with override and all these other things. But it also has dot current, the latest value, and dot error, the value that the promise rejects, that gets populated and all of this stuff. There's a lot of ways that you can compose and combine these to do whatever it is you're trying to do. It's not just inside the markup of your component that you can use these.
JASON: And when you say -- so, we get the details back, it's got a remote query. That's what you're talking about that has the current in it.
RICH: Exactly, yeah.
JASON: Cool, cool. The follow-up to that is can you call them with a REST API rather than exposing them as endpoints?
RICH: No. No, you can't. And the -- the reason for that --
JASON: But if we wanted to, we could just are create an endpoint that calls --
RICH: Absolutely.
JASON: If you opt into it directly, no compiler magic to make that happen.
RICH: You can create a server endpoint, a get handler. And inside that, you need to deserialize the arguments yourself from awaitRequest.json and figure out how to handle that. You can pass it to the remote function which on the server is just a direct function call. And then you can return a JSON-ified version of that value. If you want to expose an API, it's super-easy to do so. Maybe we'll create some helpers around doing that. But we don't want to expose the remote functions directly.
JASON: Yeah, and also, when you start thinking about how you want the API to work. I could see that as extremely hard to automate. Whereas, wrapping it, checking for specific cookies or unwrap the bear token or verify a specific request, all the things that people do with REST calls, that would be difficult to build into a compiler-automated thing. Everyone's is different. Whereas if I'm importing a server function. Doing my company's how to verify a REST call thing and then calling the remote function, that's very straightforward.
RICH: Yeah.
JASON: So, yeah. I think I'm on board with keeping it simple like that. But okay. So, we are effectively out of time here. I wanted to just say thank you again for taking some time with us today. Really excited about everything you're working on here. Every time I see Svelte, it looks better and better. And I'm excited to see how all of this lands. You're in super-alpha on this, right?
RICH: We are. There's a whole roadmap ahead of us. And this is just like the foundation layer. Once we have that in place, we can start to get a little bit more ambitious about like do we have -- start to have things that are built in for internationalization and auth and storage? All of these things that we can build on top of these primitives, I think that's where things get really exciting. For now, we are focusing on building the best possible foundation for all of this stuff and I'm really stoked with what the team has been able to achieve so far. And I'm really excited to see where it goes from here.
JASON: Yeah. I can't wait. People in the chat are very excited. If folks want to follow along, keep up on the roadmap, what's coming, where they can opt into experimental flags and stuff, where's the best place to go to do that?
RICH: Our Discord is where a lot of stuff gets, Svelte.dev.chat. Straight to the Discords server. Most of the actual discussion, RFCs, pull requests, that gets through GitHub on Svelte repo. And people should also follow Svelte on Bluesky. @svelte.dev on Bluesky. And Svelte society on Bluesky. Which shares a lot of updates. We have weekly hangouts on YouTube, meetups around the globe, all is that sort of thing. There's a whole bunch of places where people can keep in touch with what we're doing. We also have a blog with an RSS feed. But we don't update it as frequently as we should.
JASON: I know that feeling. I spent time building an RSS feed because I realized mine was broken and I have not published a single thing since.
RICH: Yep.
JASON: All right. Well, Rich, thank you so, so much. I threw a link to Rich's Bluesky into the chat as well. If you are not following these links, you can find them in the CodeTV Discord. Always, always, please like the video, leave a comment, subscribe, share it with people. Talk about it. That helps YouTube see people are enjoying themselves and more people should see it. Rich, super excited to see where it goes. Thank you for being here. Thanks again to Amanda from White Coat Captioning for doing the live captions today. And we will see you all next time.