 and I'm excited to be speaking with you here again at EmberConf. Before I start my talk here, I just want to say I've been incredibly inspired by the work that Tilda, the conference organizers, my fellow speakers, and many others have done to make this conference virtually feel like a success. My heart and thanks go out to each one of you for the work that you've done to make this feel like a real moment for our community. So last year, I started a new role in engineering at a company called Atapar. We're mostly based in New York City. We work on one of the longest-maintained Ember code bases that's out there, and additionally, on a couple new code bases using Ember Octane. In a big project like ours, you quickly learn to prioritize writing code that's easy to read, and that's one of the reasons that this topic today is important to me. Okay, so to dive in here, there's a joke on the core teams. It goes, no more unification RFCs, and yeah, that's the punchline. Maybe some of you have an idea why. Just before EmberConf last year, we decided to withdraw this RFC, the module unification RFC, from consideration. It might seem late to be talking about this topic, and I'm not gonna talk about module unification in detail, but what you should understand, if you don't already, is that module unification was a last attempt at cleaning up some loose ends, and how we organize and reference files in Ember. So let's say that we're reading this template in an application. Most Ember developers would intuit that when they look for the definition of welcome on disk, they would look at this location in app components, and that's often the correct answer. But if you want to make an implementation of jump to definition say, so that when you hover on welcome, you can jump to the file where it's defined, you would actually need to consider a much larger range of possible places that that thing could be. And the same thing would go for TypeScript if you wanted to let TypeScript find the definition of that file. Ember uses a resolver system that permits components to be defined in a number of different locations in your app or in an add-on, and the logic for deciding which to use is implemented as part of your application's runtime. Now that makes it challenging to support common static analysis tools, things in IDEs and type systems and bundlers that people use across other parts of the JavaScript community. You might already be familiar with the concept of a local maximum. When you're looking for a solution to a problem, there are probably many viable solutions to that particular thing. However, the path from one solution to another solution might not always be linear, and so it can get easy to over focus on the thing that you have in front of you instead of looking at the whole range of design possibilities and solutions that might be out there. You'll often read about this idea when you dig into something like neural network systems. For example, this neural network that generates horses on this horse does not exist.com managed to generate this really realistic image of a horse on the right-hand side as a global maximum, but in other cases, such as along the left here, it was equally confident that had found the right answer, but it had completely missed the mark. So I think that modularification was probably a local maximum. We had identified places like real problems where Ember's resolution system wasn't always clear about what file you were looking at, what component you were trying to invoke. And modularification introduced more formal rules and requirements in an attempt to unify that system. In hindsight, though, it was a local maximum and the rest of the JavaScript community was climbing the hill to a better solution. This talk is about how we're going to align Ember with that better solution. So to illustrate the problem with Ember's resolver system, let's build a little fork of Ember, I'll call Matt's Resolving Ember MicroLib. Here's a component in my framework, and you can see it's just a function that returns a string. And we're gonna presume that it's in a file here called components.js. And here's my framework. I know this looks like a lot of code. The most important bit to look at here is on the bottom, we're calling a render function, passing it a template, and then further up, we've got a template itself. Now there's two different things that we call opcodes in there that say how I want to render this template. The first one is the number zero, which says I want to append text. And the second one is the number one, which says I want to invoke this component. And if you look at the render function above, you can see how those things are done. And further up, there's a setup where we create a map of all of our components and then we render them. So let's see how the ambiguity of using this system, because we're using a string for welcome here. Let's see how the ambiguity presents problems where a lot of tooling can't penetrate the meeting. So first I've run this program through some popular build tools, rollup and tercer. Rollup takes advantage of the fact that ES modules are static. That is the imports and exports from a module can be understood without running the code. Rollup figures out how to take your multiple modules and safely combine them into a single JavaScript program. You can think of it as a simple compiler which takes our dependency, the component, and links it to the main program. Tercer is a minification tool which uses static analysis to make your JavaScript payload smaller. For this version of the program, the output looks like what we would expect. The component here is present and the rendering logic is present. You can see our template in there with the opcode zero and the opcode one as well. Let's go ahead and change our template. So instead of calling opcode one to print the component here to invoke our component, let's just render more text with an append opcode instead. So now I have two text opcodes of zero. To you I say, a font farewell. And this would be akin to you opening up a template in your Ember app and deleting the invocation of a component to replace it with some text. So in this output, again, running it through Rollup and Tercer, you would expect the component logic doesn't need to be present since we stopped referencing it in the templates. But because their relationship was something resolved at runtime and because Rollup and Tercer don't know what the program will actually do, they aren't able to strip that component out. In the Ember ecosystem, we're working on tools like Embroider. Embroider tries to close this gap by teaching the build tools to assume things about the runtime during build time analysis. But I can give you another example of how the resolver's dynamic implementation will frustrate other tooling. So finding a second example of how this ambiguity in our resolver-based library has practical impacts is as easy as looking at the most popular IDE for Ember users, VS Code. Here I've opened up the microlib and I'm attempting to jump to the definition of a component just like it was referenced inside of our template. So it isn't surprising that this doesn't work, right? You can actually read it with your eyes here. We have a string for welcome and just like we don't, without understanding the program, have any context for what that string means, our compilation tools don't have that and our analysis tools in this case don't have that understanding themselves. We could, again, teach the tool about this. We could build a custom language server and encode assumptions about where to look for these definitions. So the first draft of my microlib used dynamic resolution to look up components, the application boots, the available components are put in a map and then templates reference them by strings. In order for our eyes to know where to find a definition and for our tooling to know, we need to teach the systems what those rules for resolution are. And I think that that teaching also comes across for us as individual developers. When I asked you to look at this template and tell me where the welcome component is defined, you could probably give me a pretty reasonable answer, but that's because you've internalized the rules of the resolution system. In contrast to that, in a static system, one based on ECMAScript modules, we're always going to be able to be explicit about where a definition comes from. You don't need to teach any tooling or people those rules. So let's build a second draft of the microlib this time a static version. So in this version of the template, I've referenced the welcome component directly. Down in our template, we have our opcode zero with our string to append. We have our opcode one with the actual argument of welcome, which is the thing that's imported from the top. There's no setup. There's no listed components. There's no resolver here. Furthermore, if you want to see where the welcome component comes from, you can read this really easily, right? You just look at where it's imported from. So now that the template contains a direct reference to the component, we don't need to teach the tooling about any ambiguous cases. So jump to definition just works in a template the way that it would work in most JavaScript code. And here we've got that running. And when the bundlers processes, again through rollup and tercer, the implementation component is actually lifted directly into the template itself. So we can see our opcode zero to UI say, and then our opcode one with the actual component itself inlined into the template. We've made the link between the program and the component static. The tooling can not only understand where the component is being used, but it can also understand if it's not used at all. For example, if I change the second render step back to a fond farewell as an append, instead of a component invocation, the bundler understands that the variable welcome was not referenced and the entire component implementation itself can be dropped. Okay. So Ember's templates are of course a lot more featureful than my microlib. So how can we bring the benefits of a statically linked system back into Ember itself? So Godfrey Tran has been exploring this in RFC 496 here. In that RFC he proposes strict templates in Ember, a strict template mode. We call it mode, because just like strict mode in JavaScript, it opts the user into a version of language where messy edge cases are going to be disabled. So what's going to make a strict mode template, a strict mode template? So really it's going to be a list of things that we apply as constraints. Again, to remove ambiguities from the system. So the first of these is that there's going to be no more implicit this fallback. So if you invoke something like, or if you access something like curly, curly foo here, we're not going to look to this dot foo. If you want to access the component instance, you must use this dot. This is something that's an octane is already linted for. So that's pretty straight ahead. There's going to be no resolution of any invocations. So for example, curly, curly foo dash bar is a component invocation. Our angle bracket welcome was a different kind of component invocation. In a strict mode template, those things won't look up components in the app folder of your program. So this is obviously something that we're going to have to come back to. There's no dynamic resolution. So you can't use the component helper to pass a dynamic string here because this is not analyzable at all. You could pass any string at runtime and there's no way for us to know what you might pass in. And then partials have a similar facility where they can also take a dynamic argument. Partials are already deprecated. So again, this is pretty much already on the happy path. Okay, here's an example of what a handlebars strict mode template could look like. It shouldn't look very different than ember template you might work in today. It's going to contain a couple of template keywords. So in this case, we have each being used in here arguments. So at greetings is an argument to this component that I'm using. It has a block param my greeting that we're then using inside of the block. And we're accessing properties off the component state and that works just like it would in any regular template. However, there's no actual way to invoke a component from app. So for example, if I had put bracket like angle bracket quote in here, it wouldn't actually render a component at all. It would just be an error. And so this brings us to the crux. In order for these static templates to actually be useful we're going to need a static solution for getting other components into the scope of the template. Since we want to bundle our application as JavaScript we want to do something that's going to work with ES modules of course. And to work with ES modules, we need to consider what a strict mode template looks like when it's compiled into JavaScript. So here's that same strict mode template compiled into JavaScript. Only I've changed it this time to invoke the quote component around my greeting. To bring that component into scope, I've imported it from a file that's in the same directory as our component here. Just quote, you can imagine that it exports an ember component, glimmer component. Just like with my static microlib earlier the definition of the component itself is now passed directly into the compiled template. You can see that down where we have a property of scope and then we're passing a function with quote being closed over. Common build tooling would work well with this output and given some work on source maps we can make jump to definition work as well through the template itself. Of course, this API is basically as far as the handlebars strict mode RFC itself goes. And this API is only a primitive, right? We don't write compiled templates by hand. In order to make this readable and usable we need to find a way to lift the import statement that we're writing in JavaScript here into the template itself which is going to take us into a design for template imports. So if we want the ability to bring other components into a strict mode template scope there are a couple different plausible designs that we could go into. To keep the design kind of unsurprising to both new developers and advanced ember developers we think that two constraints are important to keep in mind. The first is that you wanna be able to import a default or a named export. So you want to be able to say that my component from elsewhere or my helper or my modifier is the thing which I've named arbitrarily in my exports and I can pull it into my template. And the second thing is that on the right side of an import the path we want it to work just like it wouldn't in any Node.js resolution. Nothing special to go on there. So given that we accept these constraints it's gonna beg a question. If we're going to constrain ourselves to so closely re-implementing the constraints already provided by ES modules why not simply adopt the ES module syntax into templates themselves. In fact any design that doesn't do that seems to bend common sense a little bit too far. So let's see what that's going to look like to bring the ECMAScript module syntax into templates. So a strict mode template itself doesn't have imports but if we bring in a design for template imports we can bring them into scope here. So we have dash dash dash import quote and then another dash dash dash. The dash dash dash introduces a preample where we're going to be able to write our import statements and then at the bottom we have our template. In between the preamble section we want to constrain what's available in there to only be ECMAScript module syntax. So you can't use variables, you can't define functions or anything like that. And that's really as a first pass. Imports can of course come from any path because we want the thing on the right side of an import statement to work just like it does in normal JavaScript. So here we're importing quote which would be a file that's in our local application. We're also importing titleize which is a helper that the Ember framework itself is providing. And then we're importing animate each from an add-on. Imports are going to open up new organization options for your app. If you have a naturally grouped set of components you can group them on disk wherever it's going to seem appropriate. Additionally, related helpers and modifiers could easily share a single file but be exposed as named imports. For example, as we're doing for the add Ember slash template helpers module here. So how do these imports compile back into JavaScript? Cause this is just our syntax in the template. So just as with the handwritten compilation from earlier these three components are passed in inside of the compiled template closed over from their imports. The imports from our template are lifted into the JavaScript here then we close over the values that are imported and pass them into the create template factory itself. And this is great. It means that when we look at the compiled JavaScript here it's easy for our eyes to understand where quote is coming from which means it's also easy for our tooling to understand. Okay, so what are the next steps in the process of delivering template imports? So before Glimmer components had landed in an Ember stable release we actually landed a primitive into a stable version of Ember component manager. This allowed us to implement Glimmer components inside of an add-on and circulate them amongst like the most passionate users who are eager to try and experiment with that solution and to get their feedback. That feedback kind of helped us shape the Glimmer components feature until a point where we thought it was really ready for a stable release. We want to do the same thing in this case. The handlebar strict mode is an RFC that we have to get into a final comment period and land that primitive. That will then allow us to build an add-on on top of it which provides the template import syntax and then early adopters can experiment with that and let us know what needs to be shaken out in order to make it a success. Kind of in parallel, we're also going to need to do a little bit design around what the ES module API would be like for things like linked to or input. It's probably a little bit of extra design work to do there. So I've talked a lot about the technical nature of static templates in this talk but despite the fact that static templates will have performance in payload size impacts I want to remind you that performance isn't the headline motivation here. Static templates with ES imports make it a template simple to understand for our eyes as well. So in this first example, we have welcome where you as an ember developer need to understand how the resolver rules work but in the second one, even if I've never used ember before it's really clear to me where this thing is coming from and where I need to go to find the definition. Additionally, modeling application had some interesting features you might have heard of like local lookup of explicit imports make those features pretty much unnecessary and import syntax will just allow you to group components naturally in your project without losing the common conventions as suggested by linting and generators. So that's why JavaScript or at least JavaScript import syntax is coming to ember templates. So the ember project was one of the earliest adopters of ES modules and embracing the opportunities that ES modules present in making our templates more readable and better analyzed is something that I'm pretty excited about but more than that it's going to take give us the opportunity to take challenges that ember has in common with other JavaScript projects and allow us to better share common solutions between them. If you want to do further reading on this topic I encourage you to take a look at the links that I've put on the page here and I've also included bit.ly so that you can open up these slides yourself this afternoon. Thanks for joining us here at EmberConf virtually and I look forward to talking about this topic more with you on chat. Thanks. Thank you.