 there oh hey you were there was I not muted no they're not sponsored not yet maybe next year anyway how've you been it's been quite a year huh yeah things been hard i hope you're still doing okay what's that Oh, how is Ember? I'm glad you asked. Let's talk about that. This meeting is being recorded. Thanks for tuning in to EmberCon. This is Godfrey here on behalf of Ember Team I'd like to give you an update on what's been happening this year. Normally, this is where we go through all the new features we lined up. As you know, we're kicking off a new edition, so let's talk about that instead. One of the pillars for Polaris is bringing Ember more in alignment with the more-than-NPM package ecosystem. There are other things too like improvements to the reactivity system and accessibility. Those are important, but it's relatively to look at them and understand why those things are useful and impactful. On the other hand, this fuse abstract. I'd like to use this time to unpack what it means and what matters. Packages, how do they work? When you import moment from moment in your Ember app, what does it actually do? Now, you might be thinking, what do you mean? I can start moment from MPM. It's right here in the no-modules, and that's how it works. Import moment from moment, and you'll be right. But how does it really work though? Because you're not uploading your no-modules to the server, right? So, how does it work when you're right in the browser? To answer that and to fully appreciate what makes that work, we have to go back in time. This is Backbone.js, a front-end library from the same era when Ember was born. All 2,106 lines of it to be exact. Now, this is not the concatenated version of Backbone.js or something. This is the Backbone.js. If you want to open a pull request, this is the file that you edit. If you want to use this in the app, this is the file you download from the website. You download that plus the dependencies underscore and jQuery, and you load all of them into browsers with script tags. At the time, that is pretty much how all of us developed for the web, one script tag at a time. That doesn't really work for us though. We don't think we can build an ambitious web framework in a single script, and we don't think our users can build ambitious long-lasting applications like that either. Now, there are some workarounds. One way you can get around it is by breaking up your app into smaller files, then right before loading them into your browser, run something that would glue them back together into a single script. I would like to take the opportunity to define some terminologies here. At the most basic form, the act of taking a bunch of loose files, processing them into a single file, optimized for a browser is called bundling. The result of that process is a bundle, and the thing that does it is called a bundler. These terminologies will come back a few times today, so I just want to get them out of the way now. Back to the topic. Now, the primitive approach of just gluing files together have some pretty obvious limitations. Variables that you define in any one of the files is automatically shared by everything else whether you want it or not. A simple change in one of the files can easily break everything else. It just doesn't scale to where our ambition is, so we needed something better. Luckily for us, there was something better. At the time, there was a proposal to add a module system to JavaScript, which will solve all these problems. Each module will have its own isolated scope, so they can accidentally pollute each other, and you have to be very deliberate about sharing things between modules with imports and exports. You're familiar with this, of course, because we have been using it since the very early days of Ember CLI. Now, this would be a good time to address the element in the room. If we have adopted modules since the beginning, and we have been using it happily ever after since, then problem solved, right? Why are we talking about them now? Well, as they say, the dev was in the details, the disk detail to be specific. You see, when modules were added to JavaScript, they left out some pretty important details. Namely, what is this string? In a spec, it's just called the module specifier, but what does that mean? Where do you find the Ember module? Well, according to the spec, that's up to the host. Okay, so how do you load these modules in the browser? How do you actually use them? According to the spec, well, that's the browser's job. They can go figure that out later. So in the meantime, the syntax exists, it's in the language, but it's really up to you to define all of these things to make them useful for yourself. So we took the liberty and defined those things for us, and designed Ember CLI around it, which would evolve into the build pipeline that you're now used to. Since we can't use modules in the browsers directly, we adopted this format called AMD, a library called, using a library called loader.js. It's an implementation of modules, but using only features that were available at the browser at the time. If you look at the files in your disk folder today, this is roughly what your app bundle would look like. What we do in a build is we take all the modules from your app folder and we compile each of them into an equivalent AMD module like this. The file name of the module becomes the module specifier, the imports become these dependencies, and the rest of the file gets wrapped in a function for scope isolation. When the module is needed, loader.js will first load its dependencies and pass them into this function. But where does it find these dependencies? Well, loader.js has an internal registry that it keep for keeping track of all of these defined calls. When a dependency is needed, it tried to load for a module, defined inside is internal registry that matches the import module specifier that you have there. If that module hasn't been loaded already, it will instantiate the module by calling the function and remembering the return value for later and passing that back into where it came from. So that's how a module have access to the things that imports and that's the basics of AMD modules. A little bit simplified, but not by much. That's how we made modules work, but wait, those are just the modules in your app. What about these external modules? Where do they come from? These are defined in the vendor bundle, but that just begs the question, how do they get into the vendor bundle in the first place? The short answer is they come from add-ons. Usually an add-on would add a module with the same name as the add-on itself. So if you install Ember concurrency, you will get to import from module called Ember concurrency. But also they might do something different. For example, it used to be the case that the Ember movement add-on would bundle moment.js the library. So installing Ember movement is actually how you get to import from the moment module. And then of course, there is Ember source, which is the thing that adds all of your Ember modules to your bundle. So that's how it used to work. With Octane, things have changed somewhat with Ember auto import. Now, instead of installing the Ember movement add-on, you would just install moment from MPM directly and Ember auto import would be generating one of these AMD module shims for you behind the scenes. That make things work a lot better from a usability perspective, but it's still fundamentally based on the same system which has some flaws and limitations. For example, when you install something and somehow it just didn't work, it could be quite confusing and quite frustrating to figure out where things went wrong. When multiple add-ons try to bring the same modules, they would just override each other and that could be a pain to figure out. And when you have an import statement in your app that you don't see a corresponding entry in your package JSON, it could be quite tricky to figure out where that module actually came from and which version of the library you're actually using. So in summary, to answer our original questions, when you import from moment, how does it work? Where does the module come from? The answer is loaded JS. It will try to find it in the bundle registry at runtime and it will try to find it in your bundle at runtime in the browser. As long as it's in the bundle, somehow it will work. As far as how they got into the bundle in the first place, well, that's anyone's guess. Intuitively these days, we all kind of expect it to work more like this instead. With Ember Auto import, this views true 90% of the time. Maybe even 95% of the time. But exactly because of that, when it doesn't work, it's even more surprising now. So how do we make Ember work more like this? Well, the good news is the wider ecosystem has basically figured out the answer for us more or less at this point. It's something I like to call the universal package format. Emily, that's just a term I made up, but I need a shorter way to say, all of these things on the slide. So there it is. You don't have to read all of them. You don't have to know what they are. But the point is, there are now enough building blocks in the platform that some sort of de facto standard is starting to emerge. So what is it? Well, it's a set of best practices on how to publish packages to MGM. If you put all the puzzle pieces together in just the right way, there is now a way to make your package just work everywhere across the ecosystem. You're working node, you're working the browser, your editor will know what's up, TypeScript will know what to do with it, Webpack, Role App Storebook, Skypack, all of the tools. And here is why this is happening in the ecosystem right now. Once upon a time, MPM existed as a place to hold node packages. Over time, developers have figured out, you know what? Let's just hold our front end packages there too. It's free, it exists, why not? Part of it is that the line between front end and back end packages isn't necessarily so clear. Packages like Lowdash might have started out live as a browser thing, but they're really equally useful in node and in the browser. Also, a lot of JavaScript developers work on both the front end and back end code. So it makes sense to want to use the same set of tools for the same problem. That created a set of new challenges though. How do you actually write a package that is usable in both places? After all, node and the browser are pretty different environments. Like what kind of files do you put in a package? What module system do you use? What features are allowed and how are dependencies handled? That kind of stuff. It was difficult, but developers were motivated to make it work. So the ecosystem adapted to make it feasible. For instance, node and browser both have recently implemented native support for JavaScript modules now. Another example is that node recently added support for the Fetch API, the same one that you use in the browser. So if you're writing something like an API client, soon you're able to use the exact same code on both environments. It's not just node and the browser's either. A lot of tools like CDNs, TypeScript, Editor, GitHub, they all want to understand what are in these packages. VS Code wants you to jump to the right place when you command click on something and so does GitHub. TypeScript wants to know where things came from, show you inline documentation and do type checking. And then of course, on top of all of that, at the end of the day, front-end developers actually use bundlers to bundle the code for the browser. So tools like Webpack, Ro-Up, Beats, they all need to share the same understanding of what packages are and what are in these packages. So for that to work, you have to stick to a set of defective standards and best practices. Things like having a valid packages on, publish, lose ESX modules, not common JS. Let the consumer decide whether they want to transpile or minify, respect the node resolution algorithm, declare dependencies correctly, and only import from what you depend on. Don't bundle your dependencies, use export maps to specify entry points. And if you are going to include type, use TypeScript DTS files, that kind of stuff. That's a lot. And don't worry, you're not expected to have known all of them, but increasingly the JavaScript ecosystem would, in the JavaScript ecosystem, that's how developers would have come to expect. Tools are built around these defective standards and you might not know every single detail of it, but you would expect your editor to work in your project. You would expect TypeScript to work if you choose to use it. You would want to use something, if you want to use something like Tailwind, in the documentation, they might ask you to add a plugin to your bundler and expect that to mean something to you. And when these things don't just work in Ember apps, more and more it will start to feel out of place. So the bottom line is, this is a time of convergence in the wider ecosystem. And Ember would benefit a lot by adopting and aligning Ember with the same standards and best practices in order to participate in and tap into the wealth of shared solutions and tools from the wider ecosystem. And Polaris is a great time to do that. So what do we have to do actually? I tried to summarize it into three aspirational guiding principles for Polaris. So let's talk about how we want things to be in the Polaris world and then we can work backwards to see what we have to do to get there. The first guiding principle is add-ons should be published as Universal MPM packages. As we saw earlier, add-ons today aren't really published as libraries, they're published as programs that generate stuff when you run the Ember build. In reality, they can do just about anything in the middle of your build. They can add arbitrary modules to your bundle, they can go download stuff from the internet, they can spin up a row of build nested inside your Ember build. These things made add-ons incredibly powerful and it's thanks to that flexibility, things like Ember auto import can exist. But it came at a cost. They made your build slow and fragile. When you suddenly get a row of errors in your build, it could be very surprising. And it's just very unexpected these days. Normal MPM packages can't do that. When you install a library, you expect to have installed a library, not a program that dynamically generate the library from the fly. This is not to say that add-ons author were making bad choices. In the past, these patterns were pretty much encouraged by their affordances. It's just that they were given an extremely flexible API that encouraged patterns that we don't want to encourage anymore. In Polaris, add-ons are published as universal packages, which is to say, most add-ons should just be normal MPM packages following ecosystem best practices. To that end, we have published this V2 add-on format that goes into a lot of the details. We'll also encourage add-on authors to ship typescript types. We'll publish tools, blueprints, guidelines, recommendations for doing all that correctly. You won't have to figure out everything yourself. Now, that might sound like taking some flexibility away from the add-ons, but for the most part, it just moves things around. For example, you can still choose to develop for your add-on in typescript if you want to. However, instead of requiring the app to run the typescript build for you, you just have to set up a hope to compile your typescript into normal JavaScript right before you publish the MPM. That way, add-on authors can still have a lot of flexibility in what they do, but the apps won't have to know about any of that. You can still choose to run that rollup build for your add-on, but it would just have to be done at publishing time instead of inside apps build. So that's the first getting principle. The second principle is imports in Ember app should mean standard imports. What I mean is that when you encounter an import statement in your app, it should mean what you think it means. If you go to your package JSON, you should see the corresponding entry for the package and which version you're using. If you go into your node modules, you should find a package right where when you expect it. Because add-ons and pullers are published as universal MPM packages, this applies to add-ons like Ember concurrency, just as much as they apply to libraries like Lowdash. Notably, this is not exactly true today, even for very well-behaved add-ons like Ember concurrency. If you try looking into the node modules in the past, you might know that the files are nested inside the add-on folder. This might seem like a small detail, but these kind of misalignment means your editor cannot automatically jump into the right place. You would have to do extra things for TypeScript and you have to explain that structure to your teammates and all of these friction just adds up. By following the standard conventions, your editor can now jump into the right place. TypeScript can just work. If you made a typo in the import, you can get a red line in your editor right away instead of having to find that out later when you go to the browser and refresh and encounter a broken app. On the flip side, as an add-on author, if you want to customize things or if you want to keep some of your modules private, you can now use standard solutions like export maps and expect that to work with Ember's module resolution algorithm. It's still something that you would have to learn, but now you're learning a standard feature that works everywhere instead of some Ember specific hooks that solve the same problem. That all sounds pretty good, but what is actually doing all of this? Where does this resolution algorithm live and what is actually linking up the modules? This is a good time to finally not say the word I can't pronounce. What is it? What does it do? Is it replacing Ember CLI? Is it replacing Ember build? How do you say it? So many questions. Here is the answers. It's Embrider, I think. 90% sure, don't call me on that. Anyway, it's a new build architecture and no, it's not replacing Ember CLI. You're still going to have the same Ember build, Ember has all the same commands that you're used to. It's similar to glimmer VM in a way in that it's how things work under the hood behind the scenes. It exists, it has a name, it's dropped in, but for the most part, you almost never have to interact with it directly, but it's good to know that it's there. Okay, so it's a build architecture. It runs the build, makes the bundle. Great, but how is it different? Well, if I were to summarize it, the overall strategy here in Embrider is to be extremely lazy, which is to say, do as little work as possible and do them as late as possible. One of the benefits you get in Polaris is faster build times. Because add-ons are now just regular NPM packages, there's no need to run extra build steps for the add-ons anymore, resulting in much less total work and much faster builds. Also, since the modules are aesthetically resolvable at build time, we will be able to give you better and earlier errors when you try to import something that doesn't exist. In addition to that, the actual build algorithm is lazier as well. One of the Embrider features you could have mentioned is tree shaking. One way to describe tree shaking is that you have too much stuff in your bundle and you need to figure out what you can safely delete, but that's not how it's actually implemented. In reality, we start with the set of files that we know we would need for sharing the bundle, which are called the entry points. Then, each time we see an import in these entry point files, we'll pull the referenced modules into the build and then we'll go process those reference modules as well and follow the same process and so on. By the end of the process, we will have only exactly what we need in the build. So what we call tree shaking is, in a sense, just a side effect of being lazy. Not only does it make the build smaller, it makes the build faster too, because you are not wasting any of your time analyzing and building the files that you don't need running BabelTransform on them and so on. Now, you might be thinking, I might have read about that somewhere else. Are we sure we invented it? No, we didn't. The goal of Polaris, the point of Polaris is to align ourselves with ecosystem standards so that we can reduce emperor-specific problems into standard problems with standard solutions. Staying true to the principle of doing as little work as possible and probably it doesn't actually do any of the work itself, it outsources the work entirely to Webpack and off-the-shelf bundler. By aligning with the Wattery JavaScript ecosystem, we can now, we now don't need emperor-specific tools for these kind of standard tasks. To be clear, you won't have to spend hours learning about Webpack, configuring it. Think about it like Babel, it comes with Ember, we give you good defaults and usually you never have to touch it, but if you find yourself wanting to experiment with new JavaScript features, you could just install a custom Babel plugin like everyone else. Same with Webpack, it comes with Ember by default, we set up for you, you generally don't have to worry about it, but if you're using something like Post-CSS and in the documentation, they ask you to add something to your Webpack config, well, now you have a place to put it. That being said, Embrider is a pluggable architecture, so Webpack just happens to be the default. If you want to use something else like rollup or vit, you could do that too. Okay, so that's Embrider, but if Webpack is actually doing all the work, then we're back to the original question, what is Embrider actually? What does it actually do? To answer that, let's talk about the final guiding principle for Polaris, expressing dependencies with imports as much as possible. Here is a thought experiment. This is our bundles today, which we saw earlier. Now, these are AMD modules, but they're still modules, right? So it seems like there should be enough information in there for tree-shaking. Can't we just apply the same algorithm we saw earlier, start with some entry points, keep track of what the modules are used, and then delete the rest? Yes, we can. However, if you try this experiment on your app today, it will probably crash in the browser. Why is that? Well, consider the router file here where we defined a post-route. As a seasoned Ember developer, you have learned to expect that there is a corresponding route file and a corresponding controller file to go with this. But this is just a convention, right? There are no imports linking the files together directly. So those route and controller files might appear to be unused and get dropped from the build unexpectedly. So it's not that we needed to wait to adopt Webpack before we can do tree-shaking. It's that for a tool to analyze an Ember app, it would also have to know about these implicit conventions. And Embruder is that tool. It knows about the same Ember conventions that you know, and based on that, it feeds a set of known entry points into Webpack. As files are included into the build, it rewrites them as necessary to turn these implicit relationships into actual import statements that Webpack can understand natively. Because Ember apps have pretty strong conventions, they usually, this usually work out very well. But here is a word of caution. Embruder rely heavily on Ember standard naming conventions, but there are certain API that exists today that will let you deviate from them. For example, the controller name property on a route will let you change what controller is used. And if you do that, Embruder won't work. There are workarounds, but it's just really confusing anyway, so just don't do it in the first place. As we've obtained, part of the work for the Polaris edition is to try and review off APIs and identify and deprecate these kind of APIs that encourages patterns that we no longer consider enigmatic. Okay, so it's great that Embruder can link up the files for us, but it's worth mentioning that that only works in the build. Other tools like your editor, TypeScript, GitHub, they all still need to be configured separately to be taught about these conventions, which is not ideal. And in some cases, you just can't configure it. Just like we've obtained, the other aspect of the work in Polaris is to take the opportunity to reconsider some of our APIs and see if we can come up with something else that aligns better with the direction that we want to pursue. One of these areas is templates. Let's see how they work today. When we encounter a component, the first thing we need to do is to find a component. And the way we do that is via a factory lookup on the owner. Under the hood, that goes through a package called Embruder Resolver, and that goes right back to our old friend, Loader.js and AMD Modules Registry. Once we found a component, the next steps are to instantiate and render the component. I'll leave those on the slide, feel free to pause and read them if you want, but the resolution step is what I want to focus on, specifically this step in the resolution step. Here, we essentially have another one of these implicit convention-based imports that Webpack cannot see. Fortunately, Embruder can usually figure that out and there's an option to enable tree shaking for components if you would like. But then again, even if it works in a build, there are other costs too. You need to learn about these conventions when you teach them. We need special syntaxes like the double colon syntax for accessing nested components. It's tricky to navigate around templates in a big app because your editors cannot jump you around for you. Furthermore, it can sometimes be pretty tricky to figure out which component came from which add-on, especially if you're not the person who installed an add-on in the first place. In addition to making things difficult, they also behave a little bit strangely. If you're used to JavaScript, you might intuitively expect that since the component is in scope in the template, you should be able to pass it around like maybe passing it into the log helper for debugging, for example, just like you could in JavaScript. However, that doesn't actually work. On the other hand, there are other situations where it does work in the template. When you have a contextual component, it is a true value that you can pass around like you would expect. And as a bonus, because the component class was muted to us as a value directly, it also sidesteps the need for that name-based resolution ever that we saw earlier. Since Octane, we have worked hard on a series of RCs, deprecations, and internal refactors to the rendering layer to make this behavior more first-class and more of the norm than the exception. The primitives for these have landed for quite some time, allowing us to experiment with them on real apps. With Polaris, the time has come to turn these experiments into reality. In Polaris, we'll introduce a new way to write templates, and that's the GGS file format with the template tag. At its core, you can think of this as a way to write templates with imports. It adds a little bit of boilerplate, but in the same way that we are already accustomed to using ES module syntax. Inside template tags, everything is value-based. No more string-based resolution. All instances of the hello variable in this file points to the same thing, which is the component we imported at the top of the file, just like you would expect. In fact, the same rules apply consistently outside of the template tag as well. That's right. Outside of the template tag is just the same old regular JavaScript module context that you're used to, and you can write arbitrary JavaScript in there. One of the things you can do with that is you're now able to define and access constants in the outer scope. This is nice for refactoring duplications in your templates and allow you to extract things like repeatedly used values into named constants, just like you could do in normal JavaScript. But historically, this has been quite cumbersome in templates. With the Default Helper Manager feature landing, you can also define one-off helpers pretty easily. Just write a plain function in the same file and you'll be able to call it as a helper in the template. In this case, we're defining a sample function that basically randomly picked between any of the given options. Now, just because you could, doesn't mean you should. When you have a general-purpose helper like this, it is probably best to keep it in a central location so that it can be reused in other templates as well. But the good news is you already know how to do that refactor. You just move that into a new file and then after the refactor, the helper is just an import away. But actually we can do even better than that because Ember can now call plain functions as helpers directly. We can just import the sample function from the low-dash library directly and use that in the template rather than having to write our own version of the same thing. Finally, when it's time to add some state to your component, you can do that by nesting the template tag inside the component class. Everything else just worked the way you expect them to. Now, I know what you're thinking. This is awesome. But really, though, I mean, you're thinking, is this JSX? Well, that's a very reasonable question. And the short answer is no, not really, not really the same thing. And the long answer is 16,000 words long. So seriously, go read the RSE and Chris Cratchel's blog post. This is probably the single most well-researched, discussed, debated, designed feature in Ember history. And with dozens of people involved debating and experimenting every alternative along the way over the years. And the good news is Chris wrote off that down, so you can go look at it. I encourage you to go follow the journey, read the blog post. I know you have questions, but Chris has answers. Okay, so that was a really, really big tangent, but I'm glad we made it out alive. So that's the template feature. It helps with the build because we can now import component helpers, modifiers, and two templates the same way that we import these things in our normal JavaScript code. But even though that might be one of the motivations, in reality, it is so much more than that. I think this is going to enable a lot of the compositions that we couldn't do before. And I think you'll really like it. Okay, so that's the third and final principle in the Polaris design. And to summarize, the JavaScript ecosystem is evolving. Going forward, JavaScript modules and packages will play an even bigger role and become the universal building block in the ecosystem. We have got a pretty good head start by adopting modules early. And with Polaris, the timing is right for us to double down and go all in on that. It's a good time to align our program model and our tools with the ecosystem so that we can fully take advantage of the innovations that are happening in this space. So that's where we want to go, but how do we get there? Well, embroider is a big piece of the puzzle. Just like with Octane, we're probably not going to fix everything that we don't like all at once. There will probably still be parts of Ember that isn't fully aligned with the ecosystem convention just yet. Hopefully, it would just be a small set of things, but there will probably be something left. In the meantime, though, with Embruder, it can fill the gaps for us and take us over there while we're still migrating things forward. It's important to point out that Embruder is designed with the imperfect world that we live in right now, rather than for the perfect Polaris world in the future. It can work in your app today. You might not be able to get the full benefits yet, but it will work. And as more things are headed in the Polaris direction, as more APIs are provided, as more adults are migrated, you will incrementally get more benefits from it. So if you're able to, give it a shot and let us know how it goes. If you're an adult author, Embruder has a guide on things that you can do to start preparing for Polaris. You can start by just adding an Ember tri-scenario to your add-on, and in fact, that has been in the default add-on blueprint for a while now. You don't necessarily have to go all the way to V2 today. We understand that not every add-on will be migrated to V2 by the time Polaris is launched. So Embruder is designed to be able to do the translation from V1 to V2 transparently on the fly-inside apps build. Of course, as more of the ecosystem moves over, we'll see more benefit from it too, but it's an important part of the strategy that none of these things are all nothing. We can start capturing benefits incrementally as we move. Likewise, Ember template imports already exist and is an add-on that you can try in your app today. We still have to work on editor integrations and things like that to make it a actually good experience, but you may find that the benefits you get today is enough to persuade you to put out some of the temporary inconveniences in the time being. Of course, those are just the big features. And keep in mind that I only had time to cover only one of the three pillars of Polaris. The rest are just equally important and perhaps even more exciting to you. That being said, just like we've obtained the puzzle pieces beyond the features themselves are just as important if not more important to the whole polished edition experience. We could use your help and together, I think we can pull it off again. We'll hang on this court after if you have any questions and I hope you like what you saw here. I appreciate you spending time with us today and if it's late where you are, thank you for staying up for this. I too stayed up for this. So hopefully we can meet again in person soon. Thank you very much and enjoy the rest of the conference.