Transcript from the "Code Splitting" Lesson
>> We are going to move on to code splitting, or more specifically, advanced React performance. We're gonna do two different things. We're gonna do code splitting, and then we're gonna do server side rendering together. Which is kind of exciting for me because I used to have to do these sections separately, because code splitting and server side rendering used to not play together very well.
[00:00:20] Finally, with React 18, they made them play well together, which I'm very happy about. So if you're like me and you were working on a copy of context, you can just go ahead and dump that. And I am going to grab another copy of, Let's see, I'll just do it this way.
[00:00:47] Copy. Okay, call this current again. I'm gonna have to stop this, and then I'm gonna just reinstall my node modules again. So, code splitting. React by itself is about 40-ish kilobytes. And if we go in here, I think I have a link to that, I do. 42 kilobytes to get react-dom and then react is about itself 2.5 kilobytes.
[00:01:31] So altogether just under 45 kilobytes. If you're trying to keep your performance budget under 100 kilobytes for your data initial page load, you probably need to do code splitting. So code splitting is the idea that you just load the bare minimum necessary code to get your app up and running, so the user sees something immediately.
[00:01:52] And then you hurry in the background and you load everything else. You need some sort of strategy to be able to split things out. Luckily for us, React and Vite work together really well to make that happen for you. By the way, I'm not implying that you have to have a code budget of 100 kilobytes, but you should have a size budget of something for your application.
[00:02:16] Something that you're trying to stay under or some performance timing you're trying to hit. You should be thinking about what's a reasonable amount of time that it takes me for my app to load in front of a user. And it's gonna differ by your app, your users, those kind of things.
[00:02:32] If you're writing code that calls, it's like Uber for helicopters. They probably have a nice phone. They're probably on a decent connection. You can probably have a pretty high-performance budget. If you are writing something that keeps track of a farmer that's watering their crops, frequently out in the boonies they have terrible 2G connection, whether that's in rural South Dakota or in India or Australia, right?
[00:03:01] They're gonna have bad reception, so your performance budget is very sensitive. Things like this become very helpful. So what we're gonna do is we're going to start splitting things off of our main package. So let's go to app.jsx And up here where we import react, Right here, please import useState, lazy and Suspense with a capital S.
[00:03:39] Okay, I want you to delete details and SearchParams, the imports here. We're gonna split off our various different routes from our app, so that it's only loading the route the user is loading. So I'm gonna say const Details = lazy, and then you give it a function, import, ./Details.
[00:04:41] And this works like require use to for common JS. These are static imports at the top, as in these are not dynamic, but import is dynamic import for ES6 modules, which is what require was for common JS. So this is saying, if something tries to render details, panic and load details.
[00:05:08] Vite is smart enough to look at that and say, hey, this is not required on the initial page load. I'm not gonna load it right away. Okay? And then now we end up with these details and these SearchParams components, and these will not actually load until their route is actually loaded.
[00:05:29] Does that make sense? So we need to tell it okay, we're gonna load this later. But what happens when we hit something that we need to load? We're gonna do that with Suspense. So inside your QueryClientProvider, put a component here called Suspense, And that'll wrap everything inside of QueryClientProvider, and then we need to tell what the fallback is.
[00:06:02] If something inside of this is loading, what do we do? Well, we're gonna show just a nice little, A loading-pane, With a h2 className=, put whatever emoji you want in here, this is the loader. I have just a spiral here, but feel free to put whatever emoji you wanna put in there.
[00:06:34] If you wanna have a loading dog, actually, let's do that, that'll be cute. We'll put a dog and then we'll have like a spinning dog head. So that's what Suspense does, it basically says, hey, if you hit this high up in the render tree and something is loading, show the fallback until the Suspense has returned.
[00:06:55] And I need to restart my app, npm run dev. Notice when I load the page, just for a very split second, it's all localhost, so everything's going really fast. You probably even missed it, cuz I missed it. Let's just put this for a second on fast 3G, and refresh the page.
[00:07:16] You see the spinning dog for a second and then it loads all those components in the background. So just to prove my point, if I go to my Network tab and I look at all. So if I refresh the page, It can kinda be a little bit hard to make sense of this because of how Vite loads everything in dev.
[00:07:44] The way that Vite works is it actually uses the browser's built in module loader for all of your locally served things. So that's why you see so much of this being loaded separately. But what you don't see here, this chunk business, this is all coming from React, right?
[00:08:09] So when you're loading this in production, it's loading chunks at a time, right? So what's really cool about this is now if I load this, And I'm just gonna close this. If I click on Luna here, it's gonna load the details page, which has not been loaded yet, right?
[00:08:29] So I click on this, notice that it loaded this DOM chunk down here. All this stuff only came afterwards, right? So we've now split out this particular piece of code. That is code splitting. And honestly, what's cool about React is that's it. That's all you need to know about splitting code in React.
[00:08:48] Right now we're splitting on route, which is kind of like the most brain-dead way of doing it, right? There's just not like, okay, we're gonna split based on route, but there's lots of other things you can code split on, right? I actually don't need the modal until someone actually clicks on the modal, right?
[00:09:06] So what do you do there? Let's just pop over and do it really quick. We're in details, instead of loading modal, just delete that and say const Modal = lazy, which will load from React. Function import./modal, right? Now, if I click Adopt Luna, it's gonna flash for just a second on the loading, and then it'll show this, right?
[00:09:38] Again, we're going fast enough here. Let's just hard refresh the page here. You see the spinning dog head. If I click Adopt Luna again, it's gonna show that spinner, right, because it's loading modal on the fly. Now, everything that I've shown you so far is a massive waste, right?
[00:10:01] We've objectively made our apps slower, because none of these bundles are very big, our modal bundle is gonna be a kilobyte at best, right? So splitting off a kilobyte, not worth it, right? You wanna be splitting off 30 kilobytes, 50 kilobytes, 100 kilobytes, right? So let's pretend that the modal loaded all of Moment JS for some weird reason.
[00:10:28] Great, code split that, that makes a ton of sense. But just to split something for the sake of splitting it, you're better off just leaving it in your bundle. So it's not a silver bullet. Please make sure that you're measuring, that you're actually making your user experience better rather than worse.
[00:10:44] You need to be splitting tens of kilobytes for code splitting to make sense. Does that makes sense? So yeah, that's one of the points of caution with both this and server-side rendering. It's not necessarily always a better thing to do this. You need to make sure that you're measuring your app, how your users are responding, those kind of metrics for you to really know, I'm actually making a positive difference in my user's life.
[00:11:09] Because it's really kind of a shell game of performance. If I move something to make my initial paint faster, I'm making my time deck interactivity worse, or things like that. So just make sure you're keeping all that stuff in your mind. So very contrived example, but it can be super useful.
[00:11:31] If you have a huge application you have to do code splitting in order to get decent performance. Okay, any questions about code splitting?
>> Where does the code rest when we put it lazy? Will it be downloaded immediately once the app loads or downloaded only when required?
>> So, what happens here with import.
[00:11:55] Right now, the way that we have this set up, modal will only be requested, notice this is a function, right, once this function is invoked, right? So lazy will only call this function once it's actually trying to be rendered, right? And then Suspense will catch that and Suspense will allow that to be paused while we load modal in the background.
[00:12:20] A very valid next question to ask there is can we get optimistic about it? Can we load stuff in the background? The answer is yes, you definitely can. We're now adding another factor of complexity here by doing that. The best way that I found to do that is you have a service worker.
[00:12:41] If you're not familiar with service workers, basically what they do is you have your application, you have your API server. A service worker sits in the middle, so any time that your application makes a request to your API, it goes through your service worker and out to the API, right?
[00:12:57] That's just how service workers work. What you can do is you can basically have your service worker, once it loads, once your app is loaded, it can say, okay, the user is trying to load modal. The the user might try and load details, they might try and load this.
[00:13:12] So they basically go and grab all the pieces in the background, so that whenever modal gets requested to the service worker, it can just say, guess what, I already have it. We're not gonna talk to the API right now, here you go, right? And so by having that service worker sit in the middle of that, you can basically just serve those requests as if they were happening instantaneously.
[00:13:34] And your user never has to know, and you don't have to write your code any differently either, the service worker can handle all of that. So the way that you do that is you have Vite or Parcel or Webpack generate a manifest of all the chunks. The service worker, once it's loaded and once your app is loaded, can look at that manifest and say, all right, cool, give me all of those chunks.
[00:13:56] And then, it'll do that for you. Something like Remix actually does this for you already. It's actually one of the cool parts of Remix that they kind of take care of that entire architecture for you if you do it correctly, but it is possible to do yourself as well.
[00:14:11] Hopefully, you can see why I'm not teaching that today. It's another half hour that we'd be going over setting over service workers, and this is not a class on service workers. So is there a class on service workers, Mark?
>> We actually have two, we have one on progressive web apps with Maximiliano Bergman.
>> Okay, that'd be a good one.
>> And we also have a deep dive on service worker with Kyle Simpson.
>> Yeah, if you don't know what I'm talking about, those would be two good courses to go take.