Node.js v14.21.3 documentation


Table of contents

Modules: ECMAScript modules#

Stability: 2 - Stable

Introduction#

ECMAScript modules are the official standard format to package JavaScript code for reuse. Modules are defined using a variety of import and export statements.

The following example of an ES module exports a function:

// addTwo.mjs
function addTwo(num) {
  return num + 2;
}

export { addTwo };

The following example of an ES module imports the function from addTwo.mjs:

// app.mjs
import { addTwo } from './addTwo.mjs';

// Prints: 6
console.log(addTwo(4));

Node.js fully supports ECMAScript modules as they are currently specified and provides interoperability between them and its original module format, CommonJS.

Enabling#

Node.js treats JavaScript code as CommonJS modules by default. Authors can tell Node.js to treat JavaScript code as ECMAScript modules via the .mjs file extension, the package.json "type" field, or the --input-type flag. See Modules: Packages for more details.

Packages#

This section was moved to Modules: Packages.

import Specifiers#

Terminology#

The specifier of an import statement is the string after the from keyword, e.g. 'path' in import { sep } from 'path'. Specifiers are also used in export from statements, and as the argument to an import() expression.

There are three types of specifiers:

  • Relative specifiers like './startup.js' or '../config.mjs'. They refer to a path relative to the location of the importing file. The file extension is always necessary for these.

  • Bare specifiers like 'some-package' or 'some-package/shuffle'. They can refer to the main entry point of a package by the package name, or a specific feature module within a package prefixed by the package name as per the examples respectively. Including the file extension is only necessary for packages without an "exports" field.

  • Absolute specifiers like 'file:///opt/nodejs/config.js'. They refer directly and explicitly to a full path.

Bare specifier resolutions are handled by the Node.js module resolution algorithm. All other specifier resolutions are always only resolved with the standard relative URL resolution semantics.

Like in CommonJS, module files within packages can be accessed by appending a path to the package name unless the package’s package.json contains an "exports" field, in which case files within packages can only be accessed via the paths defined in "exports".

For details on these package resolution rules that apply to bare specifiers in the Node.js module resolution, see the packages documentation.

Mandatory file extensions#

A file extension must be provided when using the import keyword to resolve relative or absolute specifiers. Directory indexes (e.g. './startup/index.js') must also be fully specified.

This behavior matches how import behaves in browser environments, assuming a typically configured server.

URLs#

ES modules are resolved and cached as URLs. This means that files containing special characters such as # and ? need to be escaped.

file:, node:, and data: URL schemes are supported. A specifier like 'https://example.com/app.js' is not supported natively in Node.js unless using a custom HTTPS loader.

file: URLs#

Modules are loaded multiple times if the import specifier used to resolve them has a different query or fragment.

import './foo.mjs?query=1'; // loads ./foo.mjs with query of "?query=1"
import './foo.mjs?query=2'; // loads ./foo.mjs with query of "?query=2"

The volume root may be referenced via /, // or file:///. Given the differences between URL and path resolution (such as percent encoding details), it is recommended to use url.pathToFileURL when importing a path.

data: Imports#

data: URLs are supported for importing with the following MIME types:

data: URLs only resolve Bare specifiers for builtin modules and Absolute specifiers. Resolving Relative specifiers does not work because data: is not a special scheme. For example, attempting to load ./foo from data:text/javascript,import "./foo"; fails to resolve because there is no concept of relative resolution for data: URLs. An example of a data: URLs being used is:

import 'data:text/javascript,console.log("hello!");';
import _ from 'data:application/json,"world!"';
node: Imports#

node: URLs are supported as an alternative means to load Node.js builtin modules. This URL scheme allows for builtin modules to be referenced by valid absolute URL strings.

import fs from 'node:fs/promises';

Builtin modules#

Core modules provide named exports of their public API. A default export is also provided which is the value of the CommonJS exports. The default export can be used for, among other things, modifying the named exports. Named exports of builtin modules are updated only by calling module.syncBuiltinESMExports().

import EventEmitter from 'events';
const e = new EventEmitter();
import { readFile } from 'fs';
readFile('./foo.txt', (err, source) => {
  if (err) {
    console.error(err);
  } else {
    console.log(source);
  }
});
import fs, { readFileSync } from 'fs';
import { syncBuiltinESMExports } from 'module';

fs.readFileSync = () => Buffer.from('Hello, ESM');
syncBuiltinESMExports();

fs.readFileSync === readFileSync;

import() expressions#

Dynamic import() is supported in both CommonJS and ES modules. In CommonJS modules it can be used to load ES modules.

import.meta#

The import.meta meta property is an Object that contains the following properties.