Qwik City for Resumable, Dynamic Apps
Links & Resources
Click to expand the full 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, everyone. And welcome to another episode of Learn With Jason. Today on the show, we’re bringing back Miško. How you doing?
MIŠKO: I am doing well. I am glad to be here again. Always a pleasure to talk to you, man.
JASON: I am super pumped to have you on the show. It is always very enlightening to have you sharing what you’ve been working on. So I’m super, super excited about that. Before we jump into the tech, let’s talk about you for a second. For folks who aren’t familiar with your work, do you want to give us a bit of a background, who you are, what you do?
MIŠKO: Sure. Currently I’m a CTO after builder.io. It’s a headless visual development platform. A bunch of complicated words. I like to explain it simply. Basically it’s like Wix but headless, meaning you can embed it into your existing application. All right. Whereas, you know, something like Wix is only hosted, which means you have to host it on a third-party place. I’ve done other things in my life. Previously, there’s this thing called angular.js that became super popular. Then there was a follow-up called Angular. Before even I did web frameworks, I did this other thing called clean code talk, where I was trying to convince the world that testing is a good idea, you should do it. Then I had something to do with Karma. Now I’m doing this thing called Qwik. Apparently it’s becoming a bad habit of mine to keep building frameworks. (Laughter)
JASON: So I’m actually really curious about this. You know, Qwik is a framework that I assume arose out of a need that you had in your own web dev. I assume Angular kind of came from the same general need. So in your career, why do you think you have created multiple frameworks? Like, what’s the driver that leads you to do that?
MIŠKO: I just want to make it easier to build web apps is really what it comes down to. You have a goal of being able to build something. You realize the amount of effort that goes into it and you go, ugh, and it doesn’t get built. If you can lower the barrier of entry for people, then cooler things can get built. As of right now, I think we’re in a world where it’s relatively easy to build just about anything you want. The thing that isn’t necessarily easy is to make it also performant. And this is where Qwik comes in.
MIŠKO: Yeah, so — sorry. My head is like going in 20 different directions.
JASON: I did ask you like five questions at once. Sorry.
MIŠKO: When you go and interact with a particular component, let’s say you click a button that adds to the shopping cart, then all you see is the shopping cart. Let’s say shopping cart is eight. You just see eight re-renders and nothing else. In a classical system, even if you have islands or whatever, we can argue about when exactly one, two, three, four, five, six, seven, eight, nine, ten will execute. But it will execute at some point. More importantly what I want to point out is that it executes. Those console logs will show up once on a server and once on a client. So now the only thing with partial hydration or lazy hydration or progressive hydration we’re arguing about is when exactly do these numbers show up. Do they show up altogether? Are they spread across in time? Are they islands that happen at some point? But they still happen at some point. The beauty of resumability is, well, they already happened on a server. And therefore, they will not happen again in the client. So because they don’t happen again on the client, huge amount of code never has to be shipped to the client. But the code is just fundamentally not there. And because the code isn’t there, you know, you don’t execute it, you don’t download it. You have huge amount of savings that you can get. Most importantly, you can get into this idea of streaming. You can get to this idea that as you’re using your application, more and more code shows up that’s specific to the particular button or interaction that you have done. I really want to point it out because at this point a lot of people are going it’s going to be slow on first interaction. There’s a whole system built into Qwik for pre-fetching code so it makes sure that even on a slow, spotty network under a tunnel, you will still get your interaction instantly from the user’s point of view.
MIŠKO: That’s right.
JASON: So you’ve not only shipped the data twice to the browser, but you’ve also now executed the same code two different times in a way that is in the case of a blog or a marketing site pretty wasteful.
MIŠKO: Yeah, rendering happens twice. Once on a server, once on a client.
MIŠKO: That’s right.
JASON: I feel like this is something that people have talked about for a long time, and it doesn’t typically get done. So what do you think the barrier was, and how did you specifically overcome that barrier to get this serialization between server and client to work?
MIŠKO: Yeah, so the problem really, if you think about it, is you have a listener, right. The problem that frameworks face is you navigate to a page, and the framework has no idea, is this interactive or not, I don’t know. And the only way to kind of figure out is to visit every single component from the root to every single leaf and say, hey, do you have a click listener or do you have any kind of listener? If you have a listener, the thing that the component gives to the framework is a closure that says this is my listener. Then once the framework has the closures, it can do its job. The problem is that, you know, if you have a single — you know, if you have no closures on the page, the framework still has to go and do the whole thing, and then people are like, yeah, but I can just remove the script. Sure, but as a developer, you did it. The framework didn’t figure it out automatically. You did it. But the moment you have a single button that’s interactive, you need to have the framework. And the way the framework finds that button is it starts at the root and just navigates through all the pieces and figures it out. Eventually it gets a hold of the closure, and now you can click on that particular button. So what’s unique about Qwik is that we still do that on a server, but in the server, the framework is like, I don’t know what to do with closure. I’m just going to throw it away. But what Qwik does is says I’m going to save this closure. Then when you wake up on a client, instead of going through the tree and saying where are all my closures, the framework just knows. The closure is right here on this button. This button has a closure. Not only that, this is the closure, if you need to execute it. Right. And this is the hard part because this is the part that’s super difficult. Like, how exactly do you move a closure from the server to the client? That’s the mind-blowing part. It’s super hard.
JASON: Well, right, because a closure is like not just a function. It’s a function that has state contained within it. And that’s a super challenging thing to serialize. I know that I’ve worked on projects in the past where a good example of this is, you know, when we were working on some Gatsby features way back in the day. One of the goals was to try to pull in, like, statically rendered things and parse them and replace them in the code. So the very first thing that you run into is that you can’t just grab the code. You have to understand the entire code base because somebody could set a variable anywhere. Somebody could do like anything, anywhere in the code base. So you’re not just looking at this one function and saying, okay, grab that function. You have to, like, grab everything, all of the code, and then parse it all down and understand, well, this variable was actually set to this thing. Then you’re wrapping all that up in this closure. That’s an incredibly, sneakily hard problem to solve.
JASON: So how — how do you solve that?
JASON: To me, this is the part that is most technically impressive about Qwik. I have looked into trying to do this, and I always hit enough edge cases and weird walls that I’m just not willing to push through and try to actually build out that compiler step because, I mean, this is a hard problem to solve. It’s very difficult to actually crawl the full tree and not miss things and not get swallowed up by edge cases. So when I saw this working, it blew my mind a little bit. So let’s talk a little bit about Qwik City. Qwik City is to Qwik as Next.js is to React or Svelte Kit is to Svelte. What are we doing in Qwik City that’s not available in Qwik the framework itself?
MIŠKO: So Qwik City is just a meta framework like many others. The biggest kind of win is that it runs on top of Qwik. So it gets this resumability out of the box. What I like to talk about is, you know, when you build applications, the path of least resistance, meaning the easiest thing for the developer to do in order to get the job done, and the path of best performance are not the same thing. With Qwik City, we tried really hard to make sure that the path of least resistance is actually the path of best performance. As a result, we get this. The only reason we can do that is because we have this Qwik. We have this superpower that we know how to serialize closures and lazy load them and do all kinds of tricks that existing systems can’t really do. So that’s kind of the uniqueness. But we’re also trying to do some innovative stuff in Qwik City. Mainly, we want to bring type information into routing, into search parameters, and basically make it so that the type information flows not just in your code but also through URLs, search parameters, and so on. Still a work in progress. But the other piece is already there.
JASON: Yeah. Okay. So then that means that as a dev, if I’m reaching for Qwik City, it’s going to give me some of the familiar things that I have in Remix or Next or Astro or any of these frameworks where I have the file-based routing and that kind of stuff. And that’s great.
MIŠKO: You’re going to get this lazy execution and resumability and breaking up your code and lazy loading. All these pieces. Pre-fetching of the code, without any sort of effort, which is the unique bit.
JASON: Yeah, and I’ve heard this put — so I’ve written about this idea in the past, of make the right thing the easy thing. I’ve heard it put in another way that I really liked, which is make the right things easy and the wrong things hard, when you’re talking about the path of least resistance, or the better way to put it is when a developer is under a deadline and they’re going to take every available shortcut, the shortcuts that are available to them should lead to the best decisions. Like, cutting all the corners should lead to the optimal outcome. I think that’s something that we’ve started to learn as a community, where more and more of the frameworks are focusing on, hey, if you just use the defaults, you’re getting the best possible outcome. Instead of what it was before, where you know, you would get all right, here install Webpack, then if you want it to be optimized, you need to install all of these plug-ins. That was fine, but ultimately it meant a dev in a hurry was going to cut some of those corners. I can’t get this minimizer plug-in configured properly, so I’m going to leave it out. Okay, now performance suffers a little bit. I can’t figure out this other thing. Leave it out. More performance suffering. So I’m happy to see that inverts, where now you almost have to go in and turn things off to make the modern crop of meta frameworks bad, which makes me happy. I love that by default, we’re incentivizing using the defaults because the defaults are the right thing. That makes it easier to learn. It makes it easier to switch between projects. It makes it easier to port code between places because you’re not fighting plug-in config and all these subtle differences that really trip you up back in the day when you were in the battle Webpack, et cetera world. So I feel like this is a good — I’m very happy to see this evolution of building for the web, where this is what we’re focused on.
MIŠKO: Yes, and I have an interesting story about this. I recently wrote a blog post comparing different movie apps. You know, there’s this test.js, which allows you to build movie apps on different frameworks. It didn’t go over as well as I hoped. You know, basically every single framework author came out of the woodwork and said that’s unfair because you didn’t do this optimization or the app that you’re comparing is badly written because of this. And I’m sitting there kind of going like, that’s kind of my point. Yes, these are sub-optimally written things, but why are the defaults not optimal, right? There was nothing special that the Qwik developer had to do in order to do this. It just came out of the box this way. Now everybody else is basically saying, yeah, you’re comparing it to this framework, X, but the person who wrote it, they did all these things and should have done these other things. It’s not performant. Let me change a bunch of things. Now it’s performant. But why did you have to do this to begin with?
JASON: Yeah, I think that’s — we’re seeing less — I think, you know, before the tooling was as good as it is today, it was very reasonable to say, like, look, I can’t cover all the edge cases. It was too big. Things were too new. As we’re seeing advancement in tooling, it’s getting harder and harder to stand behind that particular excuse where you blame the developer. You know, there’s proof out there that you can build great defaults and get great experiences. You know, to the counter of that, I’ve also seen there’s a certain flavor of developer that wants to get in and touch everything, and anybody can make anything slow with significant effort, right. (Laughter)
MIŠKO: Yes, you can always do that.
JASON: So I think the trick is, you know, it’s almost — it’s resetting expectations. The framework authors are starting to flip around the way that they think about it, and I’m seeing this with what you just said where it’s not the developer’s fault if the framework is slow. It’s the framework developer, unless the developer has specifically changed something about the configuration that intentionally makes it slow because they want to, I don’t know, add some transformation thing they liked and it shortcuts all of the optimizations and breaks stuff. Sure, of course. If a developer does that, then yeah, that’s on the developer. There’s no way the framework author can prevent somebody from customizing things.
MIŠKO: But I want to point out, in the current frameworks, it’s actually hard to do that because they do ship the whole tree, right. Like whether — if you have a complicated site and the only piece of interactivity is this counter or this menu, you’re still shipping the whole app, even though none of that stuff is needed. And it is extremely difficult to not ship the whole app. Like, you got to do some serious hacking to kind of figure out how not to do that. Fundamentally, that’s how frameworks work. They start at the root component, and they iterate and visit every single component on a page, looking for listeners.
JASON: Right. And I think the — so this is actually something I just had Shaunde Person on the show, and we were talking about performance and the optimization of performance and where is the line in an application between I am optimizing performance because it’s better for my users and I’m optimizing performance because I want to see my score go up. One of the things that I do find —
MIŠKO: Oh, I have a story for you.
JASON: (Laughter) But one of the things I found interesting in this space is that there’s definitely, like, good enough and then there’s ideal and then there’s, okay, now you’re code golfing performance. So this is something — I think it’s an exercise for every developer to find the right level of pragmatism. Sometimes you’ve got years and years and years of code built up, and it’s not feasible to, like, burn your entire app to the ground and port it to another framework. So this isn’t about the FOMO of you’re doing it right or you’re doing it wrong. It’s about making the right trade-offs and decisions for your code base and recognizing that you might be able to eke out micro performance at certain places, but if the users aren’t noticing those changes, then it’s not necessarily worth the effort. So it’s more about making decisions about new things that we build or looking at the things that are really bad. Like if you’re looking at a piece of your app and it’s absolute trash from a performance standpoint — and I’ve been on teams like this, where pages were taking 10-plus, 20-plus seconds to load because it was so poorly architected just because it had accumulated geological layers of crap over time.
MIŠKO: Yes, that’s a good way of putting it.
JASON: So that was worth burning down, rebuilding in the most performant possible way. But I want to be careful. Whenever we start talking about performance, especially when we’re talking about something like Qwik that’s such a big shift from where things are, I just don’t want anybody to leave the show going, oh, god, I got to rebuild everything. No, no, no, no. (Laughter) You know, it’s good to be thinking about this. Keep it in mind for future projects. If you’ve got something that’s urgently in need of it, that’s a great opportunity to go give this a shot. But please don’t go try to convince your boss to burn down your entire app if the app is already good enough.
MIŠKO: I would argue that it’s a good idea, even if you don’t rebuild anything, to just play with Qwik and understand the different point of view and different mental model because understanding these things is important.
MIŠKO: So do play with it.
JASON: Yes. The learning and the play is so critical. I think that’s been core to my success. I think it’s a really important thing to just embrace, this idea of constant learning. Then I think it’s the opposite of curiosity in place, the pragmatism and the self-control and the ability to edit, to recognize when is an effort going to actually have enough upside for you, for your business, for your customers that — like, why is that worth doing versus this feature your customers have asked for, things like that. There is a line where it makes sense. But you know, value is the most important thing. A lot of times performance is ultra high value to customers. That’s when it’s worth considering. If you’ve already got great performance, maybe build on the feature.
JASON: Anyway, that was a little bit of a tangent. So okay.
MIŠKO: Shall we do some coding?
JASON: Let’s do some coding. I’m going to flip us into the pair programming view. And
MIŠKO: While you’re doing this, I’m going to tell you this story I told you about.
JASON: Please do.
JASON: I’ve heard that called RUM, real user measurement. That is — yeah, that’s the data we should be looking at. Lighthouse is great to observe as like a heuristic, directional information. But what real people are actually experiencing in your application is definitely the critical piece to be paying attention to there.
MIŠKO: That’s right.
JASON: Okay. So let’s see here. Before we get started, I want to do a quick shout out. We have Rachel here with us today from White Coat Captioning taking down all these words. Thank you so much, Rachel. That’s available on the homepage of the site, which I will grab and drop in the chat, if I can find my chat window. There it is. And that is made possible through the support of our sponsors, Netlify, Nx, New Relic, all kicking in to make this show more accessible to more people, which I very much appreciate. And then we are talking today to Miško, who is on Twitter @mhevery. You can follow that there. And we are talking about Qwik, which I’ll drop a link here. Then we’re also talking about Qwik City, which I will drop a link to here. It looks like what I would do, if I didn’t have you sitting here with me, is start running through this doc here. But since I do have you sitting here with me, I’m going to open a terminal and wait for you to tell me what to do.
MIŠKO: Npm create Qwik @latest. And we can just probably take all the defaults.
JASON: Oh, I got to move into a new folder. Try it again.
MIŠKO: Make sure you have Node 16. It’s going to complain if you don’t.
JASON: Node 16?
MIŠKO: Yeah. It didn’t complain. That’s fine.
JASON: I’m at 18.
MIŠKO: Yeah, 18 is bigger than 16.
JASON: Okay. So it’s a minimum, not a required version.
MIŠKO: Yes, yes, yes.
JASON: Okay. So let’s go Qwik City, and —
MIŠKO: Just the basic one, yeah.
JASON: I do want the dependencies. I’m going to go into Qwik City here. I’ll git init and open it up in VSCode. Then I will also start a live share project once that turns on. Okay. I’ve got a URL here. I’m going to drop that in our secret chat. That’ll let you join, Miško. Okay. So I’m now looking at a Qwik City app. I’m going to go side by side here so we can get things started. Then if I want to run this —
MIŠKO: You should just type in npm run dev.
JASON: Opened in the wrong tab. Over here. Here we go. All right. So I’ve got my local host running here. 5173. And I’m looking at the raw code and I’m ready. What’s next?
MIŠKO: Right. So let’s take a quick tour. There’s a source folder. If you go in there, you’ll find a special folder called routes. Inside of routes, we have index.tsx. We have layout.tsx. We have a subfolder called flower. So basically, this application has two routes. There’s a root route, a slash which is pointing to the route/index.tsx. And there’s a sub-route called flower/index.tsx.
JASON: So if I go to flower —
MIŠKO: If you navigate to the bottom of the page, there’s a button to go to the flower. There we go. That’s what it is. So that’s this particular part. The only other part to kind of pay attention to is that there’s the layout file. The layout file basically does the header and a footer and all the stuff that you need around your particular page. The slot is the location where your index.tsx gets inserted. You can have nested layouts and all kinds of other magical bits.
JASON: Got it. So slots are, I think Vue has the concept of slots. There are outlets in Remix. React children.
JASON: Yeah, I think web components have slots as well. So if you’ve used other frameworks, hopefully this is familiar. Okay. So that makes sense. We got a header component. We got our footer component. All makes sense to me. So we go into our index here, and I’m going to head back to the homepage. Oops. Let’s go back to my home page. Here we go.
MIŠKO: That’s not the homepage. That’s the actual homepage.
JASON: Yeah, right. So here we have our route. I can say something like, hi, chat. I’m going to save. And we’ve got live reloading. Okay. So we’ve got all the things I expect from a meta framework. It’s doing the things I want. What should we dig into first? I’m not sure what the right features are to highlight. So I’ll let you kind of guide us here.
JASON: Okay. I’m reloading.
JASON: Click on the JS. There it is.
MIŠKO: It says all fetch. There’s one that says JS. There we go. So that shows just the JS. Vite doesn’t count because we’re in a dev mode. I usually in the filters say Vite and click the button that says invert to say show me everything but Vite. This is dev mode. In production, we will not have Vite. Then hit refresh. So I see a bunch of other things in there. Why do I see other things?
JASON: That’s a great question. Oh, these are Chrome extensions.
MIŠKO: Yeah, do an incognito window.
JASON: And we’re going to hit here. Going to turn on the network tab again. We’ll do Vite, invert, refresh. And we got index.quick.js, core.
MIŠKO: Oh, I see. I think what’s happening is you’re hovering over certain commands and the server is like he’s about to click it, so I’m going to pre-fetch the code. But if you go to the flowers at the bottom, you will see that only the flower-related code got downloaded. This is going to make more sense when we build our own page. So let’s start.
JASON: Let’s do it.
MIŠKO: Let’s make a new folder under routes. I usually build like a contacts application. But whatever you think is a good folder. Inside the folder, create a new file called index.tsx.
JASON: Got it.
MIŠKO: Okay. And so the only requirement is that this particular file has to have a default export of a component. So you can — we can put in export default. And just return a function, return some JSX. Okay. The thing that’s unique about Qwik is you have to wrap the function inside of component dollar sign. So export default component dollar sign. Not the JSX, the function itself.
JASON: Oh, the function itself. Okay. So I want to do — like if I do one of these, then I can do an —
MIŠKO: You can do it all in line.
JASON: Oh, okay. So export default component dollar sign, parentheses.
JASON: Dollar sign. That’s the one I’m looking for.
MIŠKO: Then we have to import the component dollar sign. It should auto import.
JASON: That’s the one. Add import. Hey.
MIŠKO: There you go. Then get rid of the component dollar sign from the return JSX part.
JASON: Oh, right.
MIŠKO: Now, here Vite sometimes has trouble. Let’s navigate to the page, see if it works. If not, Vite needs to be restarted. Let’s try it. When you create a new route, Vite is sometimes not doing something right. If you don’t see anything, restart the Vite. So go to the command line, shut down the server, and restart it.
JASON: Okay. Here we go.
MIŠKO: So now if you refresh the page, you should see it over there now. Notice that we didn’t do anything about layout. So already we have existing components on the page, right.
JASON: Interesting. Okay. So it’s picking up all of the — everything from the layouts automatically.
MIŠKO: That’s right.
JASON: So this is a magic file. If we put something in here, this will get used.
MIŠKO: Which one, the layout? Yes. That’s right. If you don’t want to use it or you want to override it, you can add an exclamation point on your file name. That would basically say don’t descend into a layout.
JASON: Okay. I got it.
JASON: Let’s do the hello world. I think that’s kind of useful to do. So we’ll have a little div. We’ll do a span that’s going to have the count. And we’ll set that to zero. We’ll do a button and say increment. That’ll give us kind of some basics. Then we need to make these actually function. So to do that, I don’t remember how —
MIŠKO: You say const count equals use signal. And you pass in zero. You actually do count.value, but I think count also works. For the increment, for the button, you say on click dollar sign. There you just say count.value + +.
MIŠKO: So when you click on it, it down loads the code. I want to point out something interesting. Let’s click at the first file that it downloaded. So the first file con stains, if you look at the content of the file and the response, that contains our count.value + +, right? And the second file it downloaded is just the framework itself. It’s huge because it’s in Vit, and has all the comments.
JASON: This is like the uncompressed dev build.
MIŠKO: It’s about 20 kilobytes. What it didn’t download is the JSX. So let’s say inside of our component, we say, before we do return, we just say console log, you know, counter or something like that. Sorry, counter as in like the word. So inside of strings. So if you refresh the page, you’ll see the word counter shows up in your console. Not the console, but your terminal. Because it executed on the server, right. So on a server, we had to execute that piece of code. There’s no way around this thing. But notice that no matter what you do on the client, the counter will never show up. Like the console never says this.
JASON: Oh, okay. Okay. So this, I think, is — this would be a little bit of head bendy for me as a dev coming in fresh to this where my experience is that components execute on the client side. So there’s a new model here where I actually have to explicitly call something out to be client side if I need it to run.
MIŠKO: No, no.
JASON: No? Okay.
MIŠKO: It’s automatic. But in this particular case, we looked at the page and said actually, there’s no need to run this on the client because the system is smart enough because we use signals. Signals are smart enough to realize, oh, you are just updating the span. So I only thing I need to know is if you increment the count, I need to update the span. If I do that, then everything just works the way you would expect it.
JASON: Got it, got it. Okay.
MIŠKO: Now, let’s break the signals for a second, just to prove a point. So where it says count.value, put a negative in front of it. You want to print the negative value. As of right now, this causes a de-opt inside of Qwik, but in the future we’ll hand this will particular case. Let’s go for it, for now. So we can no longer track the fact this count is attached to this dom value. We broke this, right.
JASON: Oh, so now we get the code.
MIŠKO: Now we get the code. But notice we didn’t get the code for the layout or the Qwik meta framework or all the other stuff that needs to be running. We literally just got the code for the JSX. And now if you go to the console, you actually see that the counter is incrementing. This is a different mental model here. Your component function might be — it’s not guaranteed to run. It may or may not run. We will see. And it may run on the server, and it may run on the client. We will see. Oh, by the way, just to kind of prove a point, it always runs on the server. The initial render had to happen on the server. So you always get the counter piece. You may or may not get the counter piece on a client. But as a developer, you don’t think about this. So if you remove the negative sign from the span, you know, all the right stuff just happens. It just doesn’t download and doesn’t run. And there’s nothing special you had to do as a developer. This is what we mean by the system just smart enough, the happy path, the normal path you do is actually the path that you want. So let’s make a difference — maybe do one more interesting example here.
MIŠKO: Let’s say we want to run some code on the client eagerly. So let’s make a new component underneath it called the clock, let’s say. So the same exact thing. Say export const clock equals component dollar sign. You know, the standard things.
JASON: I’ll do one of these then.
MIŠKO: Yeah, that’s fine. And let’s just return a div and say data binding to time. So now we have a time. We’re going to create a signal. Time equals use signal. Let’s just start with — we can do that. But let’s just do an empty string for a second.
MIŠKO: Now we need to say use client effect. It’s going to have a function. This function will, let’s say, have to set up a timer, set interval, that executes every one second. And every one second we’ll do new date time.
JASON: So time.value equals new date to string.
MIŠKO: Sure, we can do that.
JASON: Or you wanted date time, right?
MIŠKO: Doesn’t matter. It proves the same point. So you want to extract that function to a local variable because the set interval won’t run it for a second. So there’s going to be this delay. Really, you want to invoke that function once manually and then every one second.
JASON: I got you. So we’ll do function update clock.
MIŠKO: You can just do const equals.
JASON: And we’ll also call it once.
MIŠKO: Correct. Okay. So now if you — oh, yeah, and we have to refer to our clock inside the span. So let’s just refer to it. You see it updates every one second. It’s exactly what you would expect. If you look at the network tab, you see the code associated with the use client effect downloads but nothing else. Now if you click on the increment button, it will download manufacture code. Now increment needs to be like, oh, yeah, I need that code over here. Remember, in production, we would have all kinds of fancy pre-fetching, et cetera, so the code will always be loaded eagerly so you will never get in a situation where you’re in a tunnel and something is not happening. But I want to show you an interesting bit that I think will blow your mind. So above the clock, create a div.
JASON: Okay. Just one second here. All right. Here we go. Above the clock. In here or in here?
MIŠKO: Line 13. Just create a div. And just say scroll down or something like that. Then the div needs to have a style. So let’s say style height is, let’s say, 500 pixels.
JASON: Did that work or is it a string value?
MIŠKO: Because the system just has this mental model of, like, I just want to do the bare minimum to get away with what the user — you know, the developer asked me to do. Because the component is currently not visible, there’s no need to download it. So let’s back up a little further. Let’s talk about use client effect for a second. Use client effect runs on a client only, and the question then becomes, well, when should we run it. In existing systems, the answer is obvious. You run it during hydration. Where else would you run it?
MIŠKO: But when you don’t have hydration, the question becomes when are you supposed to run it. So we thought about this for a while. We kind of came to the conclusion, well, there’s really two choices. One choice is that you can run it eagerly when the system loads. And the other choice is that you can delay it until the component becomes visible. So we made the visible part the default. You can also get the other behavior if you want, but we just made it a default the other way. And it has this super cool behavior because usually use client effect is used for things like looking at the DOM, measuring size or to kind of do an update or something specific. But in here, what we’re basically saying is if it’s below the fold, not even visible, why bother? You’re doing work that’s unnecessary.
JASON: Right. So this is — and this is going back to that concept of make the right things easy and the wrong things hard. By doing this like this, I’m not having to — like, I don’t have to have a mental model of what the UI looks like, which is interesting because a lot of times when I’m writing interfaces, I’m trying to think about not just does my code work but where is this code going to live in the app, exactly how is somebody going to use it? Is it the first thing they see or second thing they see? If they don’t do the thing we want them to do, it’s also available? There’s so many little considerations like that, that lead to, you know, when you’re doing it in the most customer centric way, it’s a lot of mental overhead. If you’re in a rush, it’s a lot of corners that tend to get cut because you don’t have time. Hey, render it all. People will figure it out. It’s not that much slower. I think you see that argument a lot. It’s like, it’s not that big a deal. That’s true sometimes.
MIŠKO: It adds up, right? Every single one of these things is a small thing until it’s not a small thing. But I want to point out one thing. As a developer, first of all, you put everything in a single file. You can have separate files, but in this case, we put everything in a single file because it was the easiest thing to do.
MIŠKO: Also, we didn’t really think about lazy loading at all.
MIŠKO: Lazy loading was just not a thing we even thought about. Yet, the way the system behaves is this ideal representation of, like, yeah, let me just extract a function and let me just download the code you need and ignore everything else.
JASON: That actually — you said that, and it just clicked that these are — this is code coming from the same file that I wrote, but it’s loaded at different times. That is extremely cool. So I’ve got no code at all, and both my counter and my clock are in the same file. So when I scroll down, here comes my clock, but it’s just the clock. And then —
MIŠKO: Or just the client effect for the clock. Not even the clock itself.
JASON: Right. Just the actual functional logic to make this clock operate. Then here, when I click the increment button to actually interact with it, then I get this bit of client logic to make the increment work. That’s really cool.
MIŠKO: I think so.
MIŠKO: I think we load some data from the server, right.
JASON: Yes, let’s do it. So I have — do you have an API you prefer? Or you want me to grab one I know of?
MIŠKO: I usually cheat and just make up some data on a server. So let’s just create a new function here on line 2 or 2 1/2. Just insert new function.
JASON: 2 1/2, (laughter).
MIŠKO: That’s how I tell people to do something in between. Export const on get, colon, request handler.
JASON: Oh, like TypeScript. Got it.
MIŠKO: Then equals and your standard function. And let’s just return some value here. So say return and let’s do an object. You don’t have to do an array. Sure, you can do an array. You do you. Return something. Whatever you want.
JASON: Okay. Let’s just do something simple here. We’re do an ID. It’s a contact form. So we’ll do a name. We’ll do one for me and add one more. This is going to be —
MIŠKO: Sounds good. Wow, even got the carrot in there. I love you, man.
JASON: Right. So we’ve got basic contacts here.
MIŠKO: Yes, yes, yes. Okay. So let’s go to our component and let’s get a hold of this data. So the way you get a hold of this data is say const endpoint equals use endpoint.
JASON: Endpoint equals use endpoint. There it is.
MIŠKO: And no arguments to it because it knows. But it’s a function call. So you have to call it.
JASON: Got it. Hold on. When I say use endpoint, it just knows that we registered a get and it works?
JASON: So folks who have played with Remix, the way that action and —
JASON: Yeah, action and loader work. This is sort of like loader.
JASON: Nice. Very nice. Okay.
MIŠKO: But we need to get typing in there. So use endpoint. Get the brackets in there and say type of on get.
JASON: Like that? No, that was the wrong thing. This one.
MIŠKO: Correct. And so in this case, your request handler — if you hover over on get, is it smart enough to infer the type? Sorry, line four. If you hover over. Yeah, it’s unknown. So give it a type. So request handler bracket. And in here, you can just say it’s going to be an array of ID string names.
JASON: I think we can fake this out real quick. Let’s do one of these. So we’ll do a type contact. Okay. So now we’ve gotten a actual thing here. And this is going to give us our on get. Oh, there’s our contacts.
MIŠKO: So we know it’s a contact.
JASON: That’s great. Okay.
MIŠKO: Now, the thing to understand here is that typically the way frameworks do this is the component says I need this data. If you think about it, that kind of doesn’t really work with streaming. By the time you stream the component, like it might be several milliseconds later. Then to go and fetch the data, it’s kind of backwards. Instead, what you want to do is you want to start fetching data then start streaming the component as soon as you can. And so the on get executes first. In your on get, you will go and talk to a database. On get can be asynchronous. So you talk to your database. While you’re talking to the database, the layout starts rendering. The component starts rendering. At some point, you’re going to get to the component where you want to render the contacts from the server. Hopefully, by the time you get to this point, the database has responded. If not, we’ll wait for it. Later, we can do some other stuff we’re working on. The point I’m trying to make is there’s a reason is why the fetching happens in the on get method. The on get method executes before any sort of JSX is rendering. It’s the first thing that comes in. We want to get the database call going as soon as possible.
MIŠKO: So now I want to actually print it out. Maybe get rid of the — oh, we can keep the buttons. That’s fine. On line 33, say resource with capital R. Value equals endpoint. Then we can do on loading. But we can skip that. You would normally say loading or something. Then just say on resolved. So that’s going to be through the curlies. In here you put a function, which will get the data. So notice what you get is — actually, it’s not data. It’ll be contacts. Notice the system correctly infers you’re going to get contacts over here. So we do our standard return. Probably contacts.map.
JASON: Yeah, a UL and a contacts.map. And it knows that, which is freaking cool. So I’m going to do like contact and there’s the auto complete for name. And I broke it.
MIŠKO: First of all, you forgot to close your resource.
JASON: Very important.
MIŠKO: Then I think on line 37, you have to say return. Yes.
JASON: There we go. I did it.
MIŠKO: So we got data from the server to the client.
JASON: So just breaking this down real quick, we did a request for data. That gives us this on get, just executes — does this execute server side? Is this being —
MIŠKO: Yes, on get is always server side.
JASON: So this is server-side fetching. So it loads our data. When we call this use endpoint, it grabs in the data returns from here. So whatever we return, that’s what we get here. That then gets passed into this resource, which is a built-in helper.
MIŠKO: You can think of endpoint as a promise, if that helps.
JASON: It’s a promise. Got it. Okay, okay.
MIŠKO: It’s a little more complicated than a promise, but fundamentally it acts as a promise.
MIŠKO: So in this case, we’re basically saying, look, there’s a promise. You need to resolve it before you do something useful with it. And so at this point, the resource allows you to say on pending, which you can say something like load in your contacts or something like that. Then on resolved will do the right stuff. Now, what’s interesting here is that the use end point is intelligent enough to understand if I’m on a server, I can just go on get directly. But if I’m on the client, then I need to call on get through a fetch.
MIŠKO: But you don’t have to worry about it as a developer. You’re like, fundamentally what’s happening is use end point will get data from on get.
JASON: Okay. And so it’s doing this on the server side. So when would this ever run on the client?
JASON: Okay. So we’ll do like a search input.
MIŠKO: There’s an input called search? I did not know that. I’m learning.
JASON: It gives you like type hints like in mobile at least. A little magnifying glass and stuff.
MIŠKO: Oh, okay.
JASON: It doesn’t give you anything here, but still cool.
MIŠKO: So I always forget this. Is it on change or on value? What is it?
JASON: Let’s find out. There is an on change. There is not an on value. So we’re going to go with on change. And that is the magic of TypeScript. (Laughter)
MIŠKO: But I feel like it’s still not the right one. I think on change only fire when is you lose focus or something. I always forget this one.
JASON: Maybe it’s on input. Oh, no, did I break it? On input?
MIŠKO: Yeah, that’s what it is. I’ve done this so many times, and I can never remember. Anyway, let’s write a function here. So this function takes E, which is their event. Basically, we just want to say filter.value equals e.target.value. I believe you have to cast that into HTML input. That’s event. You have to manually do it and say as HTML.
JASON: Oh, yeah, yeah, yeah. I see.
MIŠKO: It’s HTML input element or something. There, now it’s happy. So that should work, except now we’re not doing anything with the endpoint. I want to point out something interesting, though. Oh, you’re doing fancy stuff.
JASON: Doing a little search here so that it stops yelling at me about accessibility. And then we’ll give it one of these. We’ll give it a search. Now we have an accessible input. We click on it, and it does the whole thing.
MIŠKO: Nice, nice, nice.
JASON: I’ll do a little type. And it’s now saving the filter, but we’re not actually doing anything with it.
MIŠKO: That’s right. So where do we have the filter? It is line 57. So it should say contacts.filter. Then you pass in a function, which will —
JASON: It’ll be like contact — no. What do I want? Contact. —
MIŠKO: Contact.name.to lower case.index of.
JASON: Sure, yeah, that’ll work. I think it will work.
MIŠKO: That’s not equal to negative one or something. I think to compare to negative one or something.
JASON: You know what, I think we can even do contains or includes. Includes.
MIŠKO: Yeah, there you go. I keep thinking the old way of doing it. There you go. Look at that.
MIŠKO: That’s right. It’s very hard.
MIŠKO: Maybe we should contrast this against micro island architectures. I think it’s a good point to talk about right now.
MIŠKO: So one thing you would have to do with the micro islands is you would have to tell the system where the island is. So somewhere, let’s say this component we have just written is our own island. So you would do that, and you would also have to basically say under which condition should it hydrate. And you would probably say something like on mouse over or something like that. In the case of a clock, it’s even more complicated because it’s like now you kind of have to hydrate it eagerly, whether you like it or not. There’s no way to tell micro island architecture to hydrate where some portion of it is visible. You can probably say when the component is visible, rather the island is visible, but you can’t say portion of the island is visible.
JASON: Yeah, that would be really challenging. Like for our single-file component here, we’d probably have to load the whole thing if we built it like this in an island architecture.
MIŠKO: So first of all, you would explicitly have to say where the island boundary is. You would explicitly have to say under which conditions it loads. Once it hydrates, you would execute the whole thing, right. The framework would have to go and visit every single component that is in that island and see if there’s any listeners, just to collect the listeners and attach them and make them interactive. So this whole file would have to download. Again, this file is tiny. Who cares. But again, think about as the app gets bigger and bigger and bigger, the amount of code that you would have to eagerly download and execute kind of increases. So the cool bit here is you don’t have to do anything as a developer. The right stuff just happened.
JASON: Yeah, I think to me, this feels like the core innovation here. We’ve inverted our thinking about apps to where by being lazy, I am getting the best possible outcome. It’s probably important to do a little reminder here that we can do things to de-opt this. If I do this bit of processing here, I can get myself into trouble.
MIŠKO: Not really trouble. You slightly increase the amount of code it downloaded.
JASON: Sure, sure.
MIŠKO: You only messed up that one component. You didn’t mess up the whole app. Just extra component showed up.
JASON: That’s actually a great point. Because even if I do screw up in Qwik, the blast radius is contained.
MIŠKO: That’s right.
JASON: Okay. So even that kind of feels like an interesting side benefit of now I’m able to go. Mostly it’s going to go well. If I do something that’s not optimal, it’s only contained to that one component. So naturally, with any amount of applied effort, anybody can make a mess out of anything. So your mileage may vary here. But the default, the happy path, is pretty happy.
JASON: That’s one of my all-time favorite things about being a developer. Wondering what would somebody would happen who had no context were to walk into the middle of this conversation. Somebody walks in from the next room. That child is inert! (Laughter)
MIŠKO: But you could do it on something more interesting. So create a new component called clock —
JASON: I’m going to do a quick time check. We have like seven more minutes. Is that enough time?
MIŠKO: Yeah, we’ll do the clock wrapper, prove a point, and call it a day.
MIŠKO: So create. You know the standard stuff. So in here, you just return the clock or P or whatever you want to do. Then put a clock inside of it.
JASON: So we’re going to start with clock. Nope. All right.
MIŠKO: Now, the thing I want to point out is the clock is running. The component on the top is interactive. But the component in between the clock wrapper, that never downloads and never executes. As a matter of fact, there’s nothing you can do as a user to cause that thing to execute.
JASON: Okay. So as we get down here, we’ve got our clock component, but we don’t have anything in here. This is just — and the other cool thing about this is we didn’t really look under the hood, but you can kind of see as things are happening. The Qwik inspector is the dev mode, right?
MIŠKO: Yeah, so the inspector is this cool thing. I didn’t know it was already published. I believe the control key and then click anywhere in the UI. Control maybe, alt. One of those keys. There you go, click. Takes you directly to the source code.
JASON: Holy shit. Oh, no, I broke it. It’s okay. Well, it was cool the first time. All right. Let’s try one more time. We’ll go here. Okay. Maybe I’m ahead of the curve on this. This is extremely cool.
MIŠKO: Yeah, yeah, yeah. So for example, click on the examples on the top. If you hover over it — don’t click on it. Use the special —
JASON: Oh, I got you.
MIŠKO: The special key. I don’t know which one it is. Was it alt? You’ll see a different hover. There you go. That thing. If you click there, then it should take you to the correct source code. No? It’s not taking you?
JASON: I think something may have just gone wrong.
MIŠKO: It worked the first time.
JASON: It worked the first time, then it crashed VSCode the second time. I probably need to restart things to get it to work. Did it, like, crash all the way on me? It did. So maybe that’s the problem. Get back if here. We’ll try one more time. Alt, click. It is.
MIŠKO: Yes, there you go. Isn’t that amazing?
JASON: That is sick. That’s sick. Okay.
MIŠKO: So imagine you come to a code base you’re not a familiar with. You’re like, where the hell is this thing? Click, there it is.
JASON: That is extremely useful. Like, I can definitely see — I mean, there’s huge value in those convenience things. I would be very surprised if we don’t see a bunch of frameworks start picking up that idea. That’s real, real smart. Okay. So with our last five minutes before I break us down here, is there anything else that you want to show off or resources you want people to be aware of? Like what should we do with these last couple minutes?
MIŠKO: Yeah. So let’s talk about the future, I think.
JASON: Let’s do it.
MIŠKO: One of the things we’re working on is we want to make sure type safety is all throughout the system. You kind of saw it, how the type safety went between the on get method and use endpoint. It kind of knew the data goes through it. But we want to take it everywhere. We want to take it to type, to routes. If you type a URL, it will complain if the URL doesn’t have a route associated. The search parameters will complain. Hey, you said whatever is required and you didn’t provide it. Or you asked for a number and you’re giving me a string. Something of that sort.
MIŠKO: Same thing for forms as well. And also for form validation and also URL validation. So when you say, oh, I take — page offset is a number. If you give me anything but a number, I’m going to complain and say, nope, that’s not how this works. This is important because a lot of times developers just kind of trust the input of the URL. And URL input is so easily hackable.
JASON: URL input is really challenging.
JASON: Like, it’s just — I mean, it’s a freeform text input at the end of the day.
MIŠKO: Yeah, it is. So we just want to kind of make it out of the box. It’s going to be set up in a way where in order to get the type system, you automatically have to provide a validator, then it will complain. You said it’s a string, but it’s not. So I’m not going to let you do this.
JASON: Yeah, yeah. That’s super cool. What’s the best place if people want to follow along with development when these features are ready for testing or for primetime?
MIŠKO: Our discord is definitely the place to go. Oh, the other thing I want to mention I completely forgot is we have a bridge to React. So you can take your React components and run them inside of Qwik. You won’t get all the magical benefits I’ve showed you here because it’s a React code base. But we can delay hydrate Qwik components. Basically, you get architecture for React with Qwik and out of the box.
JASON: Where would one look into that?
MIŠKO: Go to our website, Qwik.builder.io. Search for React. There’s a search box on the top.
JASON: Oh, there it is. Got it.
MIŠKO: Okay, there it is.
JASON: This one?
JASON: Let’s drop that in here. Very, very cool. So the Qwik add React. That’s very cool. Awesome. All right. So then I’m also going to direct people to the GitHub. Good place to ask questions, follow along with development, all those good things. Give it a star. You know, that’s always a good call. Then I will also drop Miško’s Twitter one more time. Anywhere else? I’ve dropped the Discord chat. We’ve shared the links to Qwik, Qwik City, Qwik React. Anywhere else that people should be looking if they want to do more with Qwik?
MIŠKO: I think you got the basics. I think the biggest thing is just come check out our community on Discord. And you can talk to lots and lots of like-minded folks about these things.
JASON: Excellent. All right. Well, Miško, I think with that, we’re going to go ahead and call this one a resounding success. So this episode, like every episode, has been closed captioned. Thank you to Rachel for coming over here from White Coat Captioning to make that possible. That is sponsored by Netlify, Nx, and New Relic, who all kick in to make the show more accessible to more people, which means a lot to me. While you’re clicking on things on the website, please make sure you check out the schedule. We have all sorts of great stuff coming up on the show. On Thursday, I’m going to be porting the Learn With Jason over to Astro. If you want to learn about Auth0 actions, we’ll have Will Johnson on the show. Then this one actually got rescheduled. I need to change it. I’m going to be out of town next Thursday. So tune in. Make sure you subscribe. I have a newsletter now. Please subscribe to that newsletter because I need it. Please and thank you. But yes, please do the thing. Do the subscribe. Follow on twitch. Subscribe on YouTube. Subscribe to the newsletter. All of that is very good for my own ego when I see the numbers go up. Miško, thank you so much for hanging out today. This was an absolute blast.
MIŠKO: Jason, thanks for having me again. You’re awesome.
JASON: Of course. Of course. All right. We’re going to go find somebody to raid. Thank you all so much for hanging out. We will see you next time.
Closed captioning and more are made possible by our sponsors: