This site runs best with JavaScript enabled.

Super Simple Start to ESModules in Node.js

Software Engineer, React Training, Testing JavaScript Training

Photo by Solen Feyissa

All supported versions of Node.js support ESModules now. Here's how to get started using them.

On April 30th 2021, Node v10 officially reached its "end of life" (learn more on the Node Releases page). This is exciting because it meant that every supported version of the Node.js JavaScript runtime supports Node's official EcmaScript Modules! There are mixed feelings on this change (some would have preferred we stick with CommonJS in the Node ecosystem), but whatever your opinion on the matter the fact is that ESModules are here and they are the future. So let's learn how to use them in a Node.js environment.

Note: You might be interested in my companion post Super Simple Start to ESModules in the Browser

First, we need the JavaScript we want to run:

1// get-files.js
2import path from 'path'
3import fs from 'fs/promises'
5const isDir = async d => (await fs.lstat(d)).isDirectory()
7async function getFiles(dir) {
8 const list = await fs.readdir(dir)
9 const filePromises = filename => {
10 const filepath = path.join(dir, filename)
11 if (await isDir(filepath)) {
12 return {type: 'dir', filepath}
13 } else {
14 const content = String(await fs.readFile(filepath))
15 return {type: 'file', filepath, content}
16 }
17 })
18 return Promise.all(filePromises)
21export {getFiles}

Next, let's make a JavaScript file that imports this and runs it:

1// index.js
2import {getFiles} from './get-files.js'
4console.log(await getFiles('.'))

Great, now let's try to run it:

1node .
2(node:5369) Warning: To load an ES module, set "type": "module" in the package.json or use the .mjs extension.
3(Use `node --trace-warnings ...` to show where the warning was created)
5import {getFiles} from './get-files.js'
8SyntaxError: Cannot use import statement outside a module
9 at wrapSafe (internal/modules/cjs/loader.js:979:16)
10 at Module._compile (internal/modules/cjs/loader.js:1027:27)
11 at Object.Module._extensions..js (internal/modules/cjs/loader.js:1092:10)
12 at Module.load (internal/modules/cjs/loader.js:928:32)
13 at Function.Module._load (internal/modules/cjs/loader.js:769:14)
14 at Function.executeUserEntryPoint [as runMain] (internal/modules/run_main.js:72:12)
15 at internal/main/run_main_module.js:17:47

Here's the thing. The Node.js project will sometimes experience breaking changes and that's reasonable, but breaking the module system is just a non-starter. So using ESM is an opt-in. You can either opt-in a single file by changing from .js to .mjs, or you can opt-in an entire directory by adding a package.json with "type": "module".

Let's go with the second option so we don't have to rename all our files. Put this in a package.json:

2 "type": "module"

Now let's run it:

1node .
3 {
4 type: 'file',
5 filepath: 'get-files.js',
6 content: "import path from 'path'\n" +
7 // clipped for brevity
8 'export {getFiles}\n'
9 },
10 {
11 type: 'file',
12 filepath: 'index.js',
13 content: "import {getFiles} from './get-files.js'\n" +
14 '\n' +
15 "console.log(await getFiles('.'))\n"
16 },
17 {
18 type: 'file',
19 filepath: 'package.json',
20 content: '{\n "type": "module"\n}\n'
21 }

That's it. We've got native ESM running in Node.js.

There's plenty more to talk about here but this should get you started. You may be interested in reading the official initial announcement of ESM support. Good luck!

Discuss on TwitterEdit post on GitHub

Share article
Kent C. Dodds

Kent C. Dodds is a JavaScript software engineer and teacher. He's taught hundreds of thousands of people how to make the world a better place with quality software development tools and practices. He lives with his wife and four kids in Utah.

Join the Newsletter

Kent C. Dodds