It's finally happened: nearly 4 years after the import keyword was introduced in ES6, Node.js introduced experimental support for ES6 imports and exports. In Node.js 12, you can use import and export in your project if you do both of the below items.

1) Add the --experimental-modules flag when running Node.js

2) Use the .mjs extension or set "type": "module" in your package.json.

In this article, I'll walk you through a basic "Hello, World" example of using import in Node.js, and demonstrate using imports with existing npm modules.

Hello, Imports

To start, let's set up 3 files: index.js, test.js, and package.json. The index.js file will import from test.js. Here's index.js:

import test from './test.js';

test();

Note that, with ES6 imports, you must put the file extension .js, except for so-called "bare paths" for importing packages like lodash. from your ./node_modules. Here's test.js:

export default function foo() {
  console.log('Hello, World!'); 
}

And, finally, package.json. This package.json is very important. The type: "module" property tells Node.js to treat .js files as ESM modules. In other words, {"type":"module"} tells Node.js to expect import and export statements in .js files.

{ "type": "module" }

These files are also available on this GitHub gist.

Run index.js with Node.js 12 and you should see the below output. Don't forget the --experimental-modules flag. You need both.

node --experimental-modules index.js 
(node:7289) ExperimentalWarning: The ESM module loader is experimental.
Hello, world!

If you forget the --experimental-modules flag, you'll get the below error.

$ node index.js 
/index.js:1
import test from './test.js';
       ^^^^

SyntaxError: Unexpected identifier

You'll get a similar error if your package.json file doesn't contain {"type":"module"}.

$ rm package.json
$ node --experimental-modules index.js 
(node:9089) ExperimentalWarning: The ESM module loader is experimental.
/index.js:1
import test from './test.js';
       ^^^^

SyntaxError: Unexpected identifier

Using .mjs

You can avoid the need for {"type":"module"} by using the .mjs extension, instead of .js. For example, here's index.mjs:

import test from './test.mjs';

test();

Note that you need to change the file extension in the import statement as well: './test.js' to './test.mjs'. Here's test.mjs:

export default function foo() {
  console.log('Hello, world!');
}

Running index.mjs will give you the below output.

$ node --experimental-modules index.mjs
(node:10191) ExperimentalWarning: The ESM module loader is experimental.
Hello, world!

Note that, unlike index.js, Node.js doesn't resolve index.mjs if you run node index or node ..

$ node --experimental-modules .
(node:10296) ExperimentalWarning: The ESM module loader is experimental.
internal/modules/esm/default_resolve.js:69
  let url = moduleWrapResolve(specifier, parentURL);
            ^

Error: Cannot find module '/' imported from /

Importing CommonJS npm Modules

The import and export keywords are part of the ECMAScript Modules spec, or ESM for short. Most Node.js projects are written using CommonJS, using the require() function to import other files.

Node.js' ESM loader supports CommonJS modules. That means npm packages that are written in CommonJS, like Mongoose, work fine in .mjs files or with {"type":"module"} in your package.json.

// Works even though Mongoose uses CommonJS
import mongoose from 'mongoose';

console.log(mongoose.version);

Bare paths like 'mongoose' work, but keep in mind you need to put file extensions if you want to import subpaths. For example, in CommonJS you can import lodash's at() function using the below syntax.

const at = require('lodash/at');

You can still import at with ES6 imports, with the caveat that you must add .js at the end.

// The `.js` is important. If you omit `.js`, Node.js will throw a
// "Cannot find module" error.
import at from 'lodash/at.js';

console.log(at({ a: { b: 'test' } }, ['a.b'])); // ['test']

Moving On

ESM modules in Node.js means that you can finally write fully isomorphic JavaScript without transpilers. As long as you don't rely on Node.js core libraries like fs, your JavaScript will run in both Chrome and Node. Once ESM imports are no longer behind a flag, you won't need transpilers for full ES6 support.

Found a typo or error? Open up a pull request! This post is available as markdown on Github
comments powered by Disqus