Node.js 13.2.0 comes with ECMAScript support for modules known for their import and export syntax. Previously, this functionality was behind the --experimental-modules
flag, which is no longer required. However, the implementation is still experimental and is subject to change.
From a translator: this long-awaited feature will finally allow us to use the standard modular syntax already available in modern browsers, and now also in Node.js without flags and transpilers
Activation
Node.js will process the code as ES modules in the following cases:
- Files with the
.mjs
extension - Files with the
.js
extension or without the extension at all, provided that the parentpackage.json
closest to them contains the value"type": "module"
- Code passed through the
—-eval
or STDIN argument, along with the—-input-type=module
flag
In all other cases, the code will be considered CommonJS. This applies to .js
files without "type": "module"
in the nearest package.json
and to the code passed through the command line without specifying --input-type
. This is done in order to maintain backward compatibility. However, since we now have two kinds of modules, CommonJS and ES, it will be better to specify the type of modules explicitly.
You can explicitly mark your code as CommonJS with the following features:
- Files with the
.cjs
extension - Files with the extension
.js
or without extension at all, provided that the closest parentpackage.json
contains the value"type": "“commonjs”"
- Code passed via the
--eval
or STDIN argument with the explicit flag--input-type=commonjs
To learn more about these features, see the "Package Scope and File Extensions" documentation sections and --input-type
Syntax import and export
In the context of the ES module, you can use import
, pointing to other Javascript files. They can be specified in one of the following formats:
- Relative URL:
"./file.mjs"
- Absolute URL c
file://
, for example,"file:///opt/app/file.mjs"
- Package Name:
"es-module-package"
- The path to the file inside the package:
"es-module-package/lib/file.mjs"
In imports, you can use default ( import _ from "es-module-package"
) and named values ( import { shuffle } from "es-module-package"
), as well as import everything as one namespace ( import * as fs from "fs"
). All built-in Node.js packages, like fs
or path
, support all three types of imports.
Imports that point to CommonJS code (that is, all current JavaScript written for Node.js require
and module.exports
) can only use the default option ( import _ from "commonjs-package"
).
Importing other file formats such as JSON and WASM remains experimental, and requires the flags --experimental-json-modules
and --experimental-wasm-modules
respectively. However, you can download these files using the module.createRequire
API, which is available without additional flags.
In your ES modules, you can use the export keyword to export default and named values.
Dynamic expressions with import()
can be used to load ES modules from either CommonJS or ES code. Note that import()
does not return a module but its promise (Promise).
import.meta.url
is import.meta.url
available in the modules, which contains the absolute URL of the current ES module.
Files and the new "type" field in package.json
Add "type": "module"
to the package.json of your project, and Node.js will begin to perceive all .js
files of your project as ES modules.
If some files of your project still use CommonJS and you cannot migrate the whole project at once, you can either use the .cjs
extension for this code, or put it in a separate directory and add package.json
containing { "type": "commonjs" }
, which tells Node.js that it should be treated as CommonJS.
For each downloaded file, Node.js will look at package.json
in the directory containing it, then one level up, and so on until it reaches the root directory. This mechanism is similar to how Babel .babelrc
files . This approach allows Node.js to use package.json
as a source of various metadata about the package and configuration, similar to how it already works in Babel and other tools.
We recommend that all package developers specify a type
field, even if commonjs
is written commonjs
.
Package entry points and the "exports" field in package.json
Now we have two fields for specifying the entry point into the package: main
and exports
. The main
field is supported by all versions of Node.js, but its capabilities are limited: with it you can define only one main entry point in the package. The new exports
field also allows you to define the main entry point, as well as additional paths. This gives additional encapsulation for packages where only the explicit exports
paths are available for import from outside the package. exports
applies to both types of modules, CommonJS and ES, it doesn't matter if they are used through require
or import
.
This functionality will allow pkg/feature
type imports to point to a real path like ./node_modules/pkg/esm/feature.js
. Also, Node.js will throw an error if the import refers to pkg/esm/feature.js
which is not specified in exports
.
An additional, still experimental, feature, conditional exports provides the ability to export different files for different environments. This will allow the package to give CommonJS code to call require("pkg")
and the ES module code to import through import "pkg"
, although writing such a package is not without other problems . You can enable conditional exports with the —-experimental-conditional-exports
flag.
The main rake of the new modules
Required file extensions
When using imports, you must specify the file extension. When importing an index file from a directory, you must also completely specify the path to the file, that is, "./startup/index.js".
This behavior coincides with how imports work in browsers when accessing a regular server without additional configuration.
module.exports
, exports
, module.exports
, __filename
, __dirname
These values from CommonJS are not available in the context of ES modules. However, require
can be imported into an ES module through module.createRequire()
. The equivalents __filename
and __dirname
can be obtained from import.meta.url
.
Creating packages
At the moment, we recommend that package authors use either fully CommonJS or fully ES modules for their Node.js projects. The module working group for Node.js continues to look for ways to improve support for dual packages, with CommonJS for legacy users and ES modules for new ones. Conditional exports are now experimental and we hope to roll out this functionality or its alternative by the end of January 2020, or even earlier.
To learn more about this, see our examples and recommendations for creating dual CommonJS / ES Module packages.
What will happen next
Loaders. Work continues on the API for writing custom loaders, for implementing module transpilation in runtime, redefining import paths (packages or individual files), and also instrumentation of code. The experimental API, accessible under the —-experimental-loader
flag, will be subject to significant alterations before we remove it from the flag.
Dual CommonJS / ES module packages. We want to provide a standard way to publish a package that can be used both through require
in CommonJS and through import
in ES modules. We have more information about this in the documentation . We plan to complete the work and withdraw from the flag by the end of January 2020, if not earlier.
That's all! We hope ECMAScript module support brings Node.js closer to JavaScript standards and brings new compatibility features across the JavaScript ecosystem. The workflow for improving module support is being done publicly here: https://github.com/nodejs/modules .