 Okay, thanks everyone for coming to my talk on importing modules as ESM from Node.js. Quick who am I? My name is Turing Siren. My pronouns are he and they. I'm a Senior Cloud Developer Advocate at Microsoft. I'm a collaborator on a bunch of things in Node.js, including being chairperson of the Node.js Community Committee. And my Twitter handle is at BittenBang. So how do we use ESM in Node.js right now? Before we get into that, I want to give a quick disclaimer. ESM in Node is experimental. It is unflagged in 12 and 14, but it is still experimental. Some things might change under the hood. Some things might change in the DX. Just want to keep that in mind. You might not want to push it to production yet, but you might want to start thinking about that if it's something you're interested in doing and if it suits your use case. So there's two approaches to ESM. Implicit and Explicit. Implicit, you add some metadata to your package JSON, and you change, requires, and module.exports to import and export as appropriate. In Explicit ESM, that's literally just the file name. So .mjs and .cjs. Probably heard of .mjs. .cjs is a new edition that's going to come with the development of ESM and Node.js that, you know, .mjs indicates ESM, that the file is ESM. .cjs indicates that the file is .com.js. So those are kind of interesting approaches to taking that explicit. Cool. So how do we use ESM implicitly in Node.js? So there's three steps, at least for this simple, simplest example. Add type modules to your package JSON, so that's, you know, the property type with the value modules, flip over to import and export instead of require and module.exports and run your code. Here's an example of what you need to add. So literally just one property and that, all files that are close to that will be resolved as ESM, so all .js files will be resolved as ESM until there is another package JSON that has either no type or type .com.js. So, you know, you can actually nest different, different package JSONs with different code and they will be able to kind of resolve implicitly according to the nearest package JSON and what that package JSON defines. So here's an example of a .com.js file. So you can see it's index.js, so we're implicitly assuming that it's going to be .com.js. const.sm equals, you know, an object that has some properties and module.exports.sm. Here is the ESM version. We're, you know, sprinkling in some ESM, so we're just going to do same const.sm. And we're just going to be flipping our export to export default ESM rather than module.exports.sm. To run this previously for a bit there, we were having to run node with a flag, the experimental modules flag. Now we're just going to be dropping that flag, so this is just going to be, to run this, we're just going to run node filing. So in our case, we were naming that file index.js, so we're going to do node index.js. Take a look at a quick example. So we can go ahead and start by just doing npm init. I'm just doing my shorthand, it allows me to automatically autofill a lot of that. We are going to add type module, and that should be good. Okay, so now we're going to create esm.js, which, because we just added type module, the package JSON node will parse the .js as ESM. And in esm.js, we're going to do const esm equals object. That object is going to have a few different properties. So runtime is going to be process.versions.node. We're going to also have modules, which is going to be the, the original modules that node's shipping. So this kind of gives you diagnostic information on the module runtime that's being used. And then we're going to do a nice message, which is going to be node, node, node loves ESM. Cool. And then we're going to leave that there. And then we're going to set export default ESM. Cool. So now this is our ESM file. It's, this is going to be run using ESM again because of type module and package JSON. And then from there, we're also going to create log.js, which is simply going to import esm from esm.js. And then we're going to console.log esm. Cool. Now if we node log.js, boom, there's a module version, our runtime version and our message. Let's talk about explicit ESM now. So how exactly can we use explicit ESM? For explicit ESM, we simply have to change the file name from index.js or whatever.js to index.mjs or whatever.mjs. The .mjs is the explicit part where explicitly telling the runtime, hey, this file is, in the case of mjs, ESM. The same is true for comma.js, so taking the case of index.js, if we want to explicitly tell node that this file is comma.js and should always be run as comma.js, we can just change the extension, the file name, to index.cjs. Previously we would have had to run this with node experimental modules, but again now we're just going to run this with node. And then in the case of mjs, node index.mjs, and in the case of cjs, node index.cjs. This will tell node that, hey, we're running this with ESM or comma.js, run it like that. One thing to note here is that you don't have to explicitly run the file. So any time node encounters .mjs or .cjs, it will run those files as if they were aquascript modules or comma.js. Let's get into an example of that. For explicit ESM, we're going to have to do a couple of things here. First, we're going to want to change our file names, so that becomes ESM.mjs. This also becomes ESM.mjs, sorry, log.mjs. And then we're also going to have to update our import to ESM.mjs. And we're going to want to also update our main index.mjs. And here, type module is optional, so if we leave this here, anything that's still .js will continue being parsed as ESM. If we take it out, everything that's .js will now be parsed as comma.js. So if we do node log.mjs, we get the correct thing. It's running with ESM? Perfect. Let's talk about conditional exports. Specifically, these are a tool that we have available to us that allow us to conditionally export ESM and export comma.js. Going line by line, first we have main, which is pointing to modules.cjs. This is for older versions of node, and it helps us kind of define an entry point for those versions that they will work with, which in this case is the comma.js version. The next line creates a exports property that has an object or that is equal to an object. And that object has two properties, import and require. Now this is the most bare bones example we can have of conditional exports. And for the sake of simplicity here, that's what we're going to go with. There's a lot more granularity here that you can kind of use to control this. Definitely recommend taking a look at the docs if you're interested in kind of understanding that more in depth or feel free to ask in the Q&A or chat with me at some point. Feel free to reach out however you're comfortable. But specifically the import is exporting. So basically this is saying when someone imports this, export module.js. And the same is true for require. When someone requires this, export module.cjs. And in the case of the import, we're using module.js because of type module at the bottom there. But this kind of allows us to change on demand what we are giving out. So we've already pre-built these files. We already have kind of done this work ourselves. And we're making sure we're returning the right thing for the right use case, which is a super useful feature for a variety of reasons. Specifically it kind of helps us go down what's delivered to who, helps with things like tree shaking. And it helps teams deliver like a more seamless experience when they're trying to transition from something like Comma.js to ESM. If you want to slowly replace parts of your code base one by one with Comma.js and move them to ESM, this kind of helps simplify that a bit and reduce the immediate need for change and allows you to flip the switch pretty incrementally. Additionally, it kind of helps provide a path for maintainers to provide options for user choice. So I personally have enjoyed Comma.js for years and I'm probably going to be doing more ESM. But for now there's some cases where I just kind of want to be using Comma.js and that's okay. If I'm using a module that has this property that does conditionally export things, I can kind of go from Comma.js. I can use it as Comma.js and then eventually if and when I want to upgrade or change to ESM, I can go ahead and do that and kind of flip that switch. So conditional exports, as I mentioned earlier, are a subset of the package entry points feature. I didn't really name that, but that's kind of what this is kind of a part of. I would highly recommend going and taking more of a look at this. This is in the node docs in the ECMAScriptModules section. It's a pretty complex feature that has a lot of granularity and something that really allows you to control how you want to use it. I've only shown a small bit of it here, but I definitely encourage you to go take a peek at it since the rest of it is relatively comprehensive and allows you to do what you want to do with this. Let's go take a look at a quick example. So we're starting off where we did before except with a few differences. We've reverted back from MJS to .JS. We've made the same change in our package JSON and we've gotten rid of our log files because we don't need those right now. So we're going to do exports. That's going to be an object in our package JSON. We're going to have import b.esm.js and we're going to have require b.common.js.cjs. So we don't have .common.js.cjs yet. So what we're going to do is just we're going to duplicate this file. So we're going to duplicate .esm.js. We name it commonjs.cjs. We're going to change the values to reflect the commonJS exports. In this case, this is something we would want. Generally you don't, but for the demo this is what we're kind of going after. And then we're also going to change the export to module.exports.common.js. Cool. So now we have those both set up. And we have our export set up. Cool. So next thing we're going to do here is we're going to npm init in our new project. So this is just my default npm init. We are going to go ahead and add type module to this. So now JS files are going to be parsed as .esm. And then we're going to create index.js. So because this is .js, this will be parsed as .esm. So we're going to import .esm from simple.esm. And then we're going to console.log.esm. Cool. So if we go ahead and run node index.js. We get our correct output. Now we're going to do the same for common.js. So we're going to create index.cjs. Going to go ahead and do console.common.js equals require simple.esm. And then console.log.common.js. So we're going to go ahead and save that and then do node index.cjs. So we're going to run this file and we get our correct output. Perfect. And with that, I just want to say thanks for joining me. If you have any questions, more than happy to answer them. If you want to reach out to me, you can absolutely. I am at bit and bang on Twitter. You feel free to tweet me, DM me, anything like that. So I'm on the OpenJS Foundation Slack if you'd like to reach me there. And with that, thanks.