 Welcome, EmberCrafts. All right. So a fun thing about prerecorded talks is that this talk is really being given simultaneously by me here in the past and present me, who's available to heckle myself in the chat and answer your questions. But you should listen to that guy, he knows things I don't. All right, I want to motivate this talk by starting with a side-by-side comparison. The scenario is this. You just joined a new team, you cloned their Ember app for the first time, you've got no cache, no dependencies, you've just got the source code, you probably got node, but that's about it. So on the left, we're going to watch you follow today's normal steps to launch into development and on the right, you should use a new approach that I'm going to go into in this talk. So let's let that play. So we'll watch as we do yarn install on the left and we do a new thing on the right and the app is ready on the right. It's already fully booted in development. It's working, we can get a really fast reload of it and we're off to the races. Meanwhile, on the left-hand side, we are waiting for yarn install. Let's let that catch up. Now we're compiling some native packages. Okay, we're ready to start Ember CLI. Our app is available. It's self-reported about nine seconds, but that's not really the full cost. And we made it. We are now also up with a fully running development environment and it also has very fast reloading. Okay, so that was our summary of the two scenarios and here's our waiting time. So here I was careful to measure the waiting time and not the interactive bits. The point of course is that there's a big difference, right? For some weird reason, the new scenario didn't even have to do a yarn install. We'll get into why that is in a second. That was a huge win. But even if we even setting that aside, even if we just look at the actual build times, we were still significantly faster. What's going on there? So that's what I wanna intrigue you with for this talk. We're gonna get into, when your development tools respond instantly, software development's not just faster, right? It can be more fun and qualitatively more creative. So this is a talk about how the tools we used to do web development can be fast and fun without sacrificing the features we need to build reliable, long-lived, stable software on teams that are small and teams that are big. So let's start with some fundamentals. So the fastest build tool is no build tool. You don't really need anything more than a text editor to develop for the web. And of course, this is how most friend and development was done for a long time. And some people still do it that way when they can. Ember first got started in this era, right? You would wire apps together entirely. You could do it entirely in a single HTML file, right? This is an Ember 2 app being developed with no web server at all, just serving the files right off the skin to the browser. And the simple setup and the immediacy are nice, right? But in practice, this gets tedious pretty fast, right? As apps and teams grow and you want to keep the code well organized into modules and packages and test suites, browsers just couldn't help you with that very much, right? And plus the desire to make apps run faster, boot faster, pushed frameworks like us toward ahead of time compilation and bundle optimization, right? And so even around the end of the Ember 2, the early, very early Ember 2 era that this is demonstrating, this was already on its way out. It was already becoming much more favorite to use build tools, right? And that turned out to be the right call, right? Ember was among the first projects to really commit very strongly to requiring a build tool. And that was not a decision without controversy, right? Because the elegance and immediacy with having no setup and just working in the browser was really nice, right? There's a trade-off. We made the right call, right? And in the era that followed with Ember CLI and other framers too doing the same thing, everybody basically accepted that they needed build tools because it gave them a lot of power to develop apps that could just go much beyond what apps could do before that. Build tools and package mentors, right? Gave a lot of power and a lot of sharing of code. So Ember CLI does a lot for you. There's a lot of great docs teaching how to use it. That's the current standard. That's what you should be doing if you're starting a new Ember app today. But I wanna talk about what comes next, right? Here in 2021, browsers and web standards have come a long way since then, right? So it's worth re-asking the question, right? How far can we push in browser development now? How much can we do in the browser by itself, right? So let's take a quick tour together and see what breaks. So you probably already know that browsers support ES modules natively, right? So here we're gonna put a script in type module and we could just import some stuff into it. In this case, we're gonna import off a CDN coming from Skypack and we're gonna use that code and it will work, right? And so this works in browsers, like all modern browsers already. And tools like Snowpack and Veet take advantage of that fact to do a lot less work in their build server and more in the browser and that's a positive development, right? But here I'm asking a more radical question, right? Can we stop doing anything on the server? Can other than maybe just serving up our files and telling us when they change, right? So how far can we push that, right? For example, what about package management, right? So package management isn't really important, right? It's deliberately indirect, right? When you wanna keep your package versioning choices altogether in one place, in a package JSON file accompanied by a yarn lock file or a package lock JSON file, right? And then you want the code you authored to not worry about that stuff. You wanna be able to use abstract names for things, right? That keeps your code much more long-lived, right? It's more maintainable, it's more robust to use to have a real package management system, right? Browsers don't support bearing ports like this, right? So maybe we've hit the dead end, right? Maybe it's time to say we need a server to rewrite this stuff. Well, maybe, but something you might not realize is that browsers are already shipping some features that get around this problem. Now, this particular one is new and not very stable yet and you shouldn't really rely on it. But it works today in Chrome and it's called import maps and there's a draft proposal for a web standard that's not yet really a standard. And it works today and it's cool, right? It lets us say where we're gonna get our libraries from. Again, caution, it's not really a standard yet. Some folks at Chrome are trying to make it V1 and maybe they'll succeed. It hasn't been to details to figure out yet. It has a bunch of good ideas in it and we're gonna use those ideas in this talk. Even without relying on having it natively in the browser, we're gonna use the good ideas from here. The in-browser implementation that works in Chrome right now makes this example, this example really does run. It doesn't support all the features in the draft spec. One of those is called scopes and it's a really important feature. So scopes is the idea that you don't just get to say what your own imports mean or where they come from but you get to say that about your dependencies too, right? So you get control over your transitive dependencies and that's a really critical capability that we've never had on the web before. NPM can do it, right? Like somewhat, right? We wish it had maybe better control and better optimization but it does it, right? Real package managers have to solve this problem, right? You don't want your packages to be statically linked. You don't want all your libraries always statically linked. This is a general concept for all software engineering, right? Not just JavaScript. The idea of static and dynamic linking is the idea that if you statically link things, you're basically building them together into one piece and then they're gonna be one piece forever more. When you dynamically link them, you're leaving the choice till later, right? What libraries your libraries are using is something that you can swap out. You could patch a security vulnerability in one of your deeper dependencies without the intermediate libraries having to act first, right? Or you can deduplicate the choices among transitive dependencies so that they can all share one version, right? That's really critical stuff that you need from package management that you lose when you're trying to get dependencies like completely pre-cooked statically linked off of places like unpackage or sky pack. So let's set aside import maps though for a sec and talk about another problem with our little setup here if we're developing it in the browser, right? Let's move our JavaScript into a separate file, a separate module, right? Which is more realistic anyway. People don't wanna put everything in line in one HTML file. So we're gonna make a separate file and we're gonna keep, continue trying to develop the way we were, which is just reloading things off disk. So let's load this file and uh-oh, it doesn't work. It turns out you can't load modules from file origin. So I guess we're up at the limit here and we can't serve off disk anymore. So we really need a web server. So let's start one up. And I don't think this is a big deal. I think we're still true to our basic story of let's just do in the browser development because web browsers need web servers and a static web server is a very simple thing that runs, it boots up instantly. It's very easy to get and set up. It's basically not a big deal. I still think we're still on our path here of doing pure browser development if we just need a static web server. And that's gonna unlock our access to several different browser features anyway because there's a bunch of stuff like this that just doesn't work with file. It's just weird. Browsers want to be speaking HTTP, right? So let's move on to the next reason people think they might need to build server. Languages, right? People like TypeScript. We like handlebars meaning glimmer templates in Ember, right? Lots of people like SAS. And this is not Ember specific problem because people like view templates and people like JSX and people like spell and browsers don't understand any of this, right? So I guess that's it, right? If you want to use any of these nice things, I guess you can't develop in the browser. I guess we need a build tool. Well, let's just see how far we can get, right? Let's make, let's take our little toy app and convert it to TypeScript. So let's rename our file from JS to TS. Let's add some TypeScript annotations that are just not valid JavaScript, right? Let's change our import of it. And of course this doesn't work, right? In fact, our little web server we started thinks that TS files are MPEG 2 video. So bad guess web server, no, no. So it looks like this is the end of our little adventure of in browser development, right? It's really too bad that we can't just like ask the browser really nicely, like please browser please understand TypeScript. Wouldn't it be nice if that worked, right? Alas. Oh, well, it does work. That's the punchline. What's going on here, right? We're not cheating. There's no, there's nothing going on in this tab that is running that is doing anything weird. If we try to load up the source, we see JavaScript. We don't see TypeScript. Where did the TypeScript go, right? Somehow in between our completely ignorant web server and our plain browser tab, our TypeScript is going away, right? How could we possibly do that? Let's go look at the implementation of this library. Do you see the magic word? Service worker, service worker is the key. So I bet you didn't think this was yet another service worker talk, did ya? That's because most people think service workers are only for offline apps. You've probably sat through talks before called offline apps with service worker, right? But service worker is not about offline apps, right? Service worker is a fully programmable place inside the browser. And you can put whatever you want in there, right? People usually put in offline app support, but instead you can put in a TypeScript compiler. It turns out service worker is an excellent place to put development tools, right? Dev tools play directly into service worker's strengths, right? Service workers are persistent, right? Across page reloads, all your stuff is still there. Just like Ember CLI, just like your dev server is persistent, no matter how many times you reload that tab, right? They're excellent at caching, right? Far better than anything you're gonna be able to do in Node. Browsers are just the best at caching and nothing's gonna get stuff into your browser faster than the browser's own cache. Service workers, when you're doing them with dev tools, actually also avoid service worker's biggest pitfalls, right, which is things like they're not available on the very first page load. In dev, that doesn't matter. It just means you get to see a couple seconds of loading screen while the tools boot up, right? If you're managing version skew in a deployed application with service workers all across the web, that's hard, but that's not the case here. It's just on your machine. You can shut it down when you're done. You could kill it and start it again. And service workers don't support legacy browsers, but that's not such a big deal either, right? The whole current crop of development tools in JavaScript, like Veet and Snowpack, all of them are very, all in on modules, all in on ES modules and modern browsers, and it's really where things are going, right? This is very much in line with that. You can, of course, take the output of these apps and run them through a slower production build that brings them down to working in all browsers, but that's not where you do your development, your daily work, right? You can do that in a browser that supports modules without loss of generality. So that's the core idea in this talk, right? Having build tools in the service worker feels magical. It's like we just taught the browser to natively understand TypeScript, and from the apps perspective, it really does, right? So that's the core idea. I think the next-stage in JavaScript tools is service worker-based, right? So how do we bring that to Ember apps now? That's where Embroider comes in, right? I'm pretty sure at this point in the conference, you have already heard some success stories about apps going to production with Embroider. So I'm not gonna spend a lot of time trying to prove that it works, I'll tell you that it's real. I expect by the time you're watching this, there's probably already an official flag in Ember CLI for generating new apps with Embroider out of the box and a plan for making that flag become the default, right? So Embroider is real. It is not future tech, it's now. I'm already looking forward. I'm moving on, right? Here, I do wanna explain though that Embroider is not strictly speaking a new build system for Ember apps, okay? Incorrect. This is not about replacing one idiosyncratic Ember specific build system with a new one, right? This is, there's a different story here. This is the more nuanced picture, right? Embroider is really a combination of two things. It's a spec for how to share well-behaved JavaScript packages, even when those packages wanna do ambitious things, like be a fully-featured Ember add-on, okay? And it's a compiler for converting our large existing ecosystem of add-ons and apps into that spec, so you get the benefits today without waiting for anybody else to rewrite anything. We've been waiting to make sure that everything you do in Ember has an underlying vanilla representation as standards-confined JavaScript that could be understood by a browser, understood by an arbitrary build tool, and basically making sure that all of the secret sauce or special semantics are all things that you could just say in JavaScript, right? Let me give you an example, right? So you probably know that when you have a JS file and HBS file side-by-side in your components folder, Ember mashes them together somehow and makes a component for you, right? Well, this is the vanilla representation of that, right? And this is not even Embroider-specific. This is the thing that's core in Ember already. This is stable and shipped, right? Under the hood, this is what Ember is doing. This is what it looks like to put together a template and a component, right? And the fact that this exists as a kind of intermediate step in your build, whether or not this file ever actually gets written or whether it kind of gets glossed over in some build tool is really important because it means we're designing a system that is understandable in terms of nothing but web standards, right? You could give these things on the screen, including the template compilation and the component template. They can have runtime implementations or not, right? They can have compiler implementations and that's fine too. The point is though that they, this makes sense as JavaScript, right? And a general JavaScript tool understands it, right? One such tool is Webpack, right? And so if you've already been using Embroider, you've probably been using the Embroider Webpack package. So Embroider Webpack takes care of automatically setting a Webpack for you in a way that's most compatible with Ember and takes care of like lots of the little details of making sure that your app is gonna do all the things you want, right? Your test suite's gonna work right, your fast boot server-strived rendering is gonna work right, et cetera, right? All that though is just a convenience, right? You could just run Webpack separately. Let me show you what that looks like. So this is an example of the changes you'd have to make to go from delegating to Embroider Webpack to doing it yourself. So here we're just gonna drop the Embroider Webpack as a dependency. We're gonna stop setting up a final stage package during Embroider telling it to just leave things alone. We're also gonna customize where Embroider is doing its work because this is where we want Embroider to place the vanilla application for us. Now that we're not getting Embroider Webpack to set things up for us, we're gonna have to set up a few things on our own. So we're gonna add a Webpack loader for handlebars. We're gonna add a Babel loader so that we can transpile stuff that doesn't work in browsers, particularly things like decorators. We're gonna be able to drop Embroider-Scali Live Reload because we're gonna get the native Live Reload out of Webpack. And of course we need a Webpack config, right? It's not that big. You can see the scale of it. It's not that complicated. Certainly it's nice to not have to write it at all if you use Embroider Webpack. The point here though is that if you needed to go down a level, if you needed to try something out or you just wanted to experiment, you could, right? And it works. You can see here, setting up the basics of template compilation, Babel, making sure our index.html file has the right references in it and then we're off to the races. You know, that one screen for the code is all it takes for Webpack to understand arbitrary Ember apps now. Let's see how you would run this, right? So we would still tell Embroider-Scali to start up and it's gonna do a relatively short build because it's only doing the translation from all traditional semantics, all legacy behaviors of all add-ons up to the vanilla spec, right? It's gonna output that for us and then we could take it forward from there with whatever we want, in this case, Webpack. So here we're booting up Webpack. I had to type the full path out just because there's a weirdness going on with yarn and sim linking right there. And then we can boot it up. And here's our app being served natively by Webpack staff server, right? Works fine. And we can make some edits and notice we're editing here the original source, right? We're not editing that vanilla app because we can make a change to the original app and it'll flow very quickly and incrementally through both systems. It'll get translated from classic to vanilla and then Webpack picks it up and we're off to the races. So it works. And we aren't limited to Webpack, of course, because there's lots of other build tools. Let's try to swap into something like Snowpack. Snowpack's different, right? Snowpack's newer and it goes all in on native modules and modern browsers. And it does less work in the build server and more in the browser and let's watch what that looks like. So here I didn't share the setup piece. I'll share some links with it. There's a small number of bugs ahead to fix to get this all working. This really does stress that all of our stuff is truly following the module spec because here it will really run as modules in the browser. So where you were cheating or using compatibility doesn't work here anymore. And there's just like a handful of places in the ecosystem where that pops up just because nothing was checking it before. Even Webpack is more permissive than a true browser running modules. So here you can see the build output of an Ember app built with Snowpack. You can see we still have all our inputs there. The browser's natively doing the imports file to file one at a time. Our dependencies are getting rewritten by, all of those import statements are being rewritten by Snowpack to point at that it's pre-bundled packages. And it all works pretty good, right? One thing you might have noticed that the initial page load here was a little slow, right? And that's actually just because work is being moved around, right? The starting of the Snowpack server is basically instant because it doesn't have to do that much. And that work has moved here into the browser. So the very first time you load the page in the browser is when Snowpack is discovering files one by one and transpiling them. So it's a trade-off that ends up being worth it. And one we'll see again. So the point is not to make every new Ember user choose their own tool chain, of course, right? We always wanna ship with a great out-of-the-back experience that that's just a given, that's basics of why Ember is Ember and what we're trying to achieve here. It's having a really community-curated bunch of things that work well together, right? But it should be possible for people to experiment and for the community to shift naturally over time as new things become possible, right? So that brings me back to the service worker-based build stuff, right? Just like we can swap in Webpack or Snowpack, we can swap in something entirely new, right? So the new, the thing I demonstrated in our opening comparison is a tool I wrote that is brand new, not really production-ready yet, but I'm really excited about how much it can already do and I'm happy excited to show it with you. I'm calling it Moe, and that's the symbol of a Moe, actually. Moe is a kind of not very much used anymore unit of measurement for the opposite of resistance, and I hope that's what Moe is in your development, right? It's something that is the opposite of resistance stopping you from what you wanna do. Moe is not for building Ember apps. It's a general-purpose tool for building JavaScript apps, right? Just like a Webpack or a Snowpack. It just so happens that it's also great at building Ember apps because Ember apps are good web citizens post-embrider, let me switch into my demo of Moe. So just like we did in the little comparison video, we're gonna boot it with NPX, and that was it, that's the whole booting, and even if it was downloaded, it's basically imperceptible because the whole package is one file. That one file is a web server, a pretty small web server written in Rust. It's pure Rust, and all it does is HTTP and it's very good at HTTP and it doesn't understand a thing about JavaScript. It does have embedded inside of it some pre-baked JavaScript files that it can serve, and that's what puts Trap up to the next phase of capability. Let's, oh, so the one other thing it can do though, right, is other than just serving our files, which of course it can do. So if I wanted to look at a file, I could. All right, there's my application template. It serves a manifest of all my files. Very, very quickly it can crawl the files in my project and give me versioning of them. And so the idea here is this really becomes foundational tech within the build tool to do really efficient caching, really efficient interdependent builds, and even efficiently query things like tell me about all of the templates or tell me about all of the files in this subdirectory. We can do that very, very effectively with something like a manifest. So let's take a look at what happens when we visit the root index.html page, and I'm actually gonna disable JavaScript here because otherwise it'll happen too fast for us to talk about it. So when you ask this web server for index.html, you get really just a script, one page that says launching service worker with a script tag, right? And this is the client side library of Moe, Moe client. And there's not much in it, right? It's really just the beach head. All it's here to do is to cause the service worker to load and then wait for it to load and then refresh the page. And that's all it has to do. So most of the interesting stuff's going on here in the worker. And the worker is about nine megabytes of JavaScript, which sounds kind of big if you're used to shipping apps on the web. But it sounds kind of small if you compare it to what it's replacing, which is all the development tools that you have in node modules, which add up to megs and megs of stuff. It also only downloads once, caches forever, and it doesn't even have to reevaluate when you refresh the page because it's living in the service worker, right? So it's extremely efficient. Let's actually let this boot now and we'll turn JavaScript back on. We will let that code run. And we'll see actually how long it takes for that to load in. It's not very long. Now we're already in the initial page load of the Ember app. And just like in the Snowpack case, you can see that the very first one takes a little while. That's because we made a similar trade-off, actually even more extreme trade-off, which is that we're doing nothing in terms of transpilation or package management on the server. Our server is just a dumb web server that knows nothing about JavaScript and only serves files. Doesn't transpile them at all, right? All of the work of discovering the files in our app had to happen here in the browser, right? And so let's look at all of a sudden, we're still on index.html, the same page we were on before, but now it has different content, right? So what's going on there, right? Now that the service worker has taken over, it can intercept that request for index.html and decide what to put there. And in this case, Mo has an Ember plugin that knows, oh, I'm gonna put an Ember app here. I see that you have an index.html file. I see that I need to compile your JavaScript and make sure that I have an entry point module that I'm gonna insert in right here where your other JavaScript used to be and serve that back, right? So let's take a look at some of our specific files in this app, right? They're all still here. So app templates, application, HBS, we can go see it. What we see when we load it is the compiled version of it, because this is what our app will see when we load it. The service worker has compiled this template into the compiled form for us. Mo has a little option we can include for debugging where we can ask it to please lay off and let us see what's actually going on in the network. And this lets me see what is really coming off the web server before it has been transpiled. Here you can see the raw HTML, raw HBS that's being served into the browser, right? Let's do the same thing for a JavaScript file. We have a sample Ember data model here. Let's look at the network version first because that's the simple thing that we authored. So here we've got an Ember data model with one attribute. We've got a bunch going on though because we're importing things from a library and we're using a decorator, right? So when we look at what that has turned into, well, it turned into a bit, Babel had to get rid of things like decorators for us because this app's configured to transpile those. Well, everybody else has to have hands about decorators. They're one of the things that browsers really can't do yet because we're really hoping those will standardize. We got some new imports because Babel has implementation that actually is coming from a library. All of a sudden we see that our libraries are now coming from the web somewhere out on an S3 bucket. We'll talk about that in a moment about how we're controlling that. But here's Ember data too. Here's our Ember data import. Notice it's still a real import, right? The browser's doing the modules for us. We didn't, we never had a step where we had to crawl all the modules and find them all because browsers do that, right? They're good at it. And when those modules are being served out of a service worker cache, it's so fast that it's fine to let the browser do it. So in this case, we're getting Ember data off of this S3 bucket. Let's go see what it looks like over there, right? Here we can see, well, untranspiled stuff. We're seeing bare imports. These aren't gonna run, of course, if we try to run them. So what's going on, right? Well, what's going on here is that I've just left my service worker scope, right? My service worker does not have permission to rewrite everything coming off of amazon.az.com. So we're seeing the raw network version of the thing here, but when my app asks for it, it's gonna go through that same translation process and that translation process includes an import map, right? So even though this is a third-party package coming off of, you know, this is, I depend on a certain version of Ember data. This is part of Ember data. Once it loads into my app, it's gonna run through my import map. The service worker takes care of that for me so that by the time the app sees it, these are turned into URLs. Let's take a look at that import map. Again, it's just a number of files sitting in here. So when you would develop an app this way with Moe, the import map is really the equivalent of Yarnlock. And in fact, this one was generated by translating Yarnlock into the new thing. This is the secret to why was I possibly able to skip running npm install or yarn install to get the app booting? The idea is that when your team is working this way, your team only really needs to do that step once. When you get a new dependency, somebody's gotta npm install it, run the importing tool, get the packages published to your S3 bucket or eventually we hope to a publicly shared one so that you can skip the whole npm stuff entirely, right? With a little bit more package management stuff around that, we can get to the point where all of this is native all the time and nobody has to stop and run npm install, right? We could really use a package manager that's native to the web. That's a bigger project. Keep an eye on this space. The folks at Cardstack are doing interesting work on this area with kind of trying to take on the whole package management problem that would interact well with, that would integrate great with this build tool. Let's also take a look at, I mentioned the entry point file. This is an example of a file that is totally made up, right? And it's Ember specific. So of course, so what's going on here is there's an Ember specific plug into Moe that knows how to synthesize the entry point to your JavaScript. And in an Ember app, that means we've gotta do things like register with Ember, a bunch of stuff that is just Ember expects to exist. So add-ons, for example, can stick stuff in here. So Ember data is registering things into our app, right? This is the tree for app feature that add-ons use. How can that possibly work on the web, right? Add-ons need to stick things in here. Well, all of our packages up on that F3 bucket, they still have their package JSON files. And critically, all of them have already run through Emberator, right? Emberator turned them into spec compliant packages. And Emberator V2 packages have to declare explicitly in package JSON in a very static and easily defined form, exactly what features they're using, including their app.js exports, right? It means that we can very cheaply in the browser crawl those package JSON files once and then cache them and know exactly what features all of our add-ons are using. So for example, how does our familiar toaster graphic work here, right? That's coming out of an add-on. How does it boot in my browser here? I can go to this URL and have an image, but why? This is that kind of feature, right? The Ember plugin to Moe knows how to find all my add-ons find their package JSONs, see what features they're using. In this case, Ember welcome page, right? Is an add-on that we're using. We know that it's in our list of add-ons. We can, our import map can resolve where do I find out about Ember welcome page? It's here. If I go there, here's the package JSON. I can look very statically, this is a V2 Ember add-on. Emberator has converted for me. And when we did that, as part of that build, as part of the import step, when this package was first sucked into this new world of how we do development, it did have to run all kinds of compatibility code to get it up to this point. From here forward, it's all legible and static and easy to find, right? And in particular, this package declares that it has a public asset we need, right? It says, you know, I contain this particular file and I want it to be mounted at this URL in the app. And so that's what we do in our build and that's why it works, right? So we can do lots of those kind of features, very ambitious features of automatically integrating stuff all done here in the browser. And rebuilds, of course, were great. You know, if I come and rewrite this page and I save, I get a very fast rebuild. And if I open up the dev tools here, we can actually see, we got a single cache eviction for that one file. And if we looked at our web server, we would see two requests, right? We refetched the manifest and the manifest would reflect one changed file and then the service worker's gonna fetch that one file, transpile it, serve it back to us and that's it. That's all the work that has to happen. Everything else in this app, when you look at the network tab of loading this app, right? Everything's location is service worker, service worker, service worker, service worker. We download nothing to boot this app, right? That's what keeps it fast despite having so many independent files. And of course, having the independent files is great, right? Because we can debug them the same way we authored them. We don't need a source map to tell us what file something came from. It's still in that file, right? It's very nice. So the last thing I'll say about it is that service workers can get real confusing if you forget they're there and you stop doing development. So this one makes it sure that if we shut down our web server, the service worker code will pretty soon realize that it's server has gone away and shut itself down and unregister itself after which point our browser is back to normal and all of that magical rewriting of our stuff goes away. Cause otherwise it could be very confusing if you switch to a new project and it was still there, it's a mega troll. So one of those little niceties you really wanna get right with service workers. So that concludes the demo piece and just to wrap up, the key takeaways I have for you is really that the future of web build tooling is exciting and very bright and we're gonna make it good now and it's gonna get even better. And that embroider is the Ember community is bridging to that future, right? And we're gonna keep shipping, keep stabilizing, keep making it even easier and more default until every Ember app just works with all of these things I'm showing you without extra effort, right? That's the goal. So thank you all for your attention. I will make sure that I have supporting materials and links from this talk at this permalink. Even if you're viewing this far in the future, you can check in here to see what was the next step in the story. And so I'll leave that on the screen a sec. EF4.com EmberConf 2021 and that's it. Can't wait to see you all next year. Thank you so much. Goodbye.