2017-06-06 02:44:56 +02:00
|
|
|
|
# ECMAScript Modules
|
|
|
|
|
|
2017-11-04 09:08:46 +01:00
|
|
|
|
<!--introduced_in=v8.5.0-->
|
2018-04-14 04:05:56 +02:00
|
|
|
|
<!-- type=misc -->
|
2017-06-06 02:44:56 +02:00
|
|
|
|
|
|
|
|
|
> Stability: 1 - Experimental
|
|
|
|
|
|
2018-08-28 17:28:46 +02:00
|
|
|
|
## Introduction
|
|
|
|
|
|
2017-06-06 02:44:56 +02:00
|
|
|
|
<!--name=esm-->
|
|
|
|
|
|
2018-08-28 17:28:46 +02:00
|
|
|
|
ECMAScript modules are [the official standard format][] to package JavaScript
|
|
|
|
|
code for reuse. Modules are defined using a variety of [`import`][] and
|
|
|
|
|
[`export`][] statements.
|
|
|
|
|
|
|
|
|
|
Node.js fully supports ECMAScript modules as they are currently specified and
|
|
|
|
|
provides limited interoperability between them and the existing module format,
|
|
|
|
|
[CommonJS][].
|
|
|
|
|
|
2017-09-10 14:13:47 +02:00
|
|
|
|
Node.js contains support for ES Modules based upon the
|
2018-08-28 17:28:46 +02:00
|
|
|
|
[Node.js EP for ES Modules][] and the [ECMAScript-modules implementation][].
|
2017-06-06 02:44:56 +02:00
|
|
|
|
|
2018-08-28 17:28:46 +02:00
|
|
|
|
Expect major changes in the implementation including interoperability support,
|
|
|
|
|
specifier resolution, and default behavior.
|
2017-06-06 02:44:56 +02:00
|
|
|
|
|
|
|
|
|
## Enabling
|
|
|
|
|
|
|
|
|
|
<!-- type=misc -->
|
|
|
|
|
|
2019-10-11 23:57:13 +02:00
|
|
|
|
Experimental support for ECMAScript modules is enabled by default.
|
|
|
|
|
Node.js will treat the following as ES modules when passed to `node` as the
|
|
|
|
|
initial input, or when referenced by `import` statements within ES module code:
|
2017-06-06 02:44:56 +02:00
|
|
|
|
|
2019-09-13 06:22:29 +02:00
|
|
|
|
* Files ending in `.mjs`.
|
2018-08-28 17:28:46 +02:00
|
|
|
|
|
2019-09-13 06:22:29 +02:00
|
|
|
|
* Files ending in `.js`, or extensionless files, when the nearest parent
|
2019-04-15 19:29:56 +02:00
|
|
|
|
`package.json` file contains a top-level field `"type"` with a value of
|
|
|
|
|
`"module"`.
|
2018-08-28 17:28:46 +02:00
|
|
|
|
|
2019-09-13 06:22:29 +02:00
|
|
|
|
* Strings passed in as an argument to `--eval` or `--print`, or piped to
|
2019-04-15 19:29:56 +02:00
|
|
|
|
`node` via `STDIN`, with the flag `--input-type=module`.
|
2017-06-06 02:44:56 +02:00
|
|
|
|
|
2019-04-15 19:29:56 +02:00
|
|
|
|
Node.js will treat as CommonJS all other forms of input, such as `.js` files
|
|
|
|
|
where the nearest parent `package.json` file contains no top-level `"type"`
|
|
|
|
|
field, or string input without the flag `--input-type`. This behavior is to
|
|
|
|
|
preserve backward compatibility. However, now that Node.js supports both
|
|
|
|
|
CommonJS and ES modules, it is best to be explicit whenever possible. Node.js
|
|
|
|
|
will treat the following as CommonJS when passed to `node` as the initial input,
|
|
|
|
|
or when referenced by `import` statements within ES module code:
|
2018-08-28 17:28:46 +02:00
|
|
|
|
|
2019-09-13 06:22:29 +02:00
|
|
|
|
* Files ending in `.cjs`.
|
2018-08-28 17:28:46 +02:00
|
|
|
|
|
2019-09-13 06:22:29 +02:00
|
|
|
|
* Files ending in `.js`, or extensionless files, when the nearest parent
|
2019-04-15 19:29:56 +02:00
|
|
|
|
`package.json` file contains a top-level field `"type"` with a value of
|
|
|
|
|
`"commonjs"`.
|
2018-08-28 17:28:46 +02:00
|
|
|
|
|
2019-09-13 06:22:29 +02:00
|
|
|
|
* Strings passed in as an argument to `--eval` or `--print`, or piped to
|
2019-04-15 19:29:56 +02:00
|
|
|
|
`node` via `STDIN`, with the flag `--input-type=commonjs`.
|
2018-08-28 17:28:46 +02:00
|
|
|
|
|
2019-12-24 06:32:25 +01:00
|
|
|
|
### `package.json` `"type"` field
|
2018-08-28 17:28:46 +02:00
|
|
|
|
|
2019-05-01 15:27:27 +02:00
|
|
|
|
Files ending with `.js` or lacking any extension will be loaded as ES modules
|
|
|
|
|
when the nearest parent `package.json` file contains a top-level field `"type"`
|
|
|
|
|
with a value of `"module"`.
|
2018-08-28 17:28:46 +02:00
|
|
|
|
|
|
|
|
|
The nearest parent `package.json` is defined as the first `package.json` found
|
|
|
|
|
when searching in the current folder, that folder’s parent, and so on up
|
|
|
|
|
until the root of the volume is reached.
|
|
|
|
|
|
|
|
|
|
<!-- eslint-skip -->
|
|
|
|
|
```js
|
|
|
|
|
// package.json
|
|
|
|
|
{
|
|
|
|
|
"type": "module"
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
```sh
|
|
|
|
|
# In same folder as above package.json
|
2019-10-11 23:57:13 +02:00
|
|
|
|
node my-app.js # Runs as ES module
|
2018-08-28 17:28:46 +02:00
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
If the nearest parent `package.json` lacks a `"type"` field, or contains
|
|
|
|
|
`"type": "commonjs"`, extensionless and `.js` files are treated as CommonJS.
|
|
|
|
|
If the volume root is reached and no `package.json` is found,
|
|
|
|
|
Node.js defers to the default, a `package.json` with no `"type"`
|
2019-11-26 12:30:11 +01:00
|
|
|
|
field. "Extensionless" refers to file paths which do not contain
|
|
|
|
|
an extension as opposed to optionally dropping a file extension in a specifier.
|
2018-08-28 17:28:46 +02:00
|
|
|
|
|
2019-04-15 19:29:56 +02:00
|
|
|
|
`import` statements of `.js` and extensionless files are treated as ES modules
|
|
|
|
|
if the nearest parent `package.json` contains `"type": "module"`.
|
|
|
|
|
|
|
|
|
|
```js
|
|
|
|
|
// my-app.js, part of the same example as above
|
|
|
|
|
import './startup.js'; // Loaded as ES module because of package.json
|
|
|
|
|
```
|
|
|
|
|
|
2019-09-03 03:01:03 +02:00
|
|
|
|
Package authors should include the `"type"` field, even in packages where all
|
|
|
|
|
sources are CommonJS. Being explicit about the `type` of the package will
|
|
|
|
|
future-proof the package in case the default type of Node.js ever changes, and
|
|
|
|
|
it will also make things easier for build tools and loaders to determine how the
|
|
|
|
|
files in the package should be interpreted.
|
|
|
|
|
|
2019-05-01 15:27:27 +02:00
|
|
|
|
Regardless of the value of the `"type"` field, `.mjs` files are always treated
|
|
|
|
|
as ES modules and `.cjs` files are always treated as CommonJS.
|
|
|
|
|
|
2019-09-03 03:01:03 +02:00
|
|
|
|
### Package Scope and File Extensions
|
2018-08-28 17:28:46 +02:00
|
|
|
|
|
|
|
|
|
A folder containing a `package.json` file, and all subfolders below that
|
|
|
|
|
folder down until the next folder containing another `package.json`, is
|
|
|
|
|
considered a _package scope_. The `"type"` field defines how `.js` and
|
|
|
|
|
extensionless files should be treated within a particular `package.json` file’s
|
|
|
|
|
package scope. Every package in a project’s `node_modules` folder contains its
|
|
|
|
|
own `package.json` file, so each project’s dependencies have their own package
|
|
|
|
|
scopes. A `package.json` lacking a `"type"` field is treated as if it contained
|
|
|
|
|
`"type": "commonjs"`.
|
|
|
|
|
|
2019-10-11 23:57:13 +02:00
|
|
|
|
The package scope applies not only to initial entry points (`node my-app.js`)
|
|
|
|
|
but also to files referenced by `import` statements and `import()` expressions.
|
2018-08-28 17:28:46 +02:00
|
|
|
|
|
|
|
|
|
```js
|
|
|
|
|
// my-app.js, in an ES module package scope because there is a package.json
|
|
|
|
|
// file in the same folder with "type": "module".
|
|
|
|
|
|
|
|
|
|
import './startup/init.js';
|
|
|
|
|
// Loaded as ES module since ./startup contains no package.json file,
|
|
|
|
|
// and therefore inherits the ES module package scope from one level up.
|
|
|
|
|
|
|
|
|
|
import 'commonjs-package';
|
|
|
|
|
// Loaded as CommonJS since ./node_modules/commonjs-package/package.json
|
|
|
|
|
// lacks a "type" field or contains "type": "commonjs".
|
|
|
|
|
|
|
|
|
|
import './node_modules/commonjs-package/index.js';
|
|
|
|
|
// Loaded as CommonJS since ./node_modules/commonjs-package/package.json
|
|
|
|
|
// lacks a "type" field or contains "type": "commonjs".
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
Files ending with `.mjs` are always loaded as ES modules regardless of package
|
|
|
|
|
scope.
|
|
|
|
|
|
|
|
|
|
Files ending with `.cjs` are always loaded as CommonJS regardless of package
|
|
|
|
|
scope.
|
|
|
|
|
|
|
|
|
|
```js
|
|
|
|
|
import './legacy-file.cjs';
|
|
|
|
|
// Loaded as CommonJS since .cjs is always loaded as CommonJS.
|
|
|
|
|
|
|
|
|
|
import 'commonjs-package/src/index.mjs';
|
|
|
|
|
// Loaded as ES module since .mjs is always loaded as ES module.
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
The `.mjs` and `.cjs` extensions may be used to mix types within the same
|
|
|
|
|
package scope:
|
|
|
|
|
|
2019-09-13 06:22:29 +02:00
|
|
|
|
* Within a `"type": "module"` package scope, Node.js can be instructed to
|
2018-08-28 17:28:46 +02:00
|
|
|
|
interpret a particular file as CommonJS by naming it with a `.cjs` extension
|
|
|
|
|
(since both `.js` and `.mjs` files are treated as ES modules within a
|
|
|
|
|
`"module"` package scope).
|
|
|
|
|
|
2019-09-13 06:22:29 +02:00
|
|
|
|
* Within a `"type": "commonjs"` package scope, Node.js can be instructed to
|
2018-08-28 17:28:46 +02:00
|
|
|
|
interpret a particular file as an ES module by naming it with an `.mjs`
|
|
|
|
|
extension (since both `.js` and `.cjs` files are treated as CommonJS within a
|
|
|
|
|
`"commonjs"` package scope).
|
|
|
|
|
|
2019-12-24 06:32:25 +01:00
|
|
|
|
### `--input-type` flag
|
2019-04-15 19:29:56 +02:00
|
|
|
|
|
|
|
|
|
Strings passed in as an argument to `--eval` or `--print` (or `-e` or `-p`), or
|
|
|
|
|
piped to `node` via `STDIN`, will be treated as ES modules when the
|
|
|
|
|
`--input-type=module` flag is set.
|
|
|
|
|
|
|
|
|
|
```sh
|
2019-10-11 23:57:13 +02:00
|
|
|
|
node --input-type=module --eval "import { sep } from 'path'; console.log(sep);"
|
2019-04-15 19:29:56 +02:00
|
|
|
|
|
2019-10-11 23:57:13 +02:00
|
|
|
|
echo "import { sep } from 'path'; console.log(sep);" | node --input-type=module
|
2019-04-15 19:29:56 +02:00
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
For completeness there is also `--input-type=commonjs`, for explicitly running
|
|
|
|
|
string input as CommonJS. This is the default behavior if `--input-type` is
|
|
|
|
|
unspecified.
|
|
|
|
|
|
2019-09-03 03:01:03 +02:00
|
|
|
|
## Packages
|
|
|
|
|
|
|
|
|
|
### Package Entry Points
|
2018-08-28 17:28:46 +02:00
|
|
|
|
|
2019-11-09 20:54:13 +01:00
|
|
|
|
There are two fields that can define entry points for a package: `"main"` and
|
|
|
|
|
`"exports"`. The `"main"` field is supported in all versions of Node.js, but its
|
|
|
|
|
capabilities are limited: it only defines the main entry point of the package.
|
|
|
|
|
The `"exports"` field, part of [Package Exports][], provides an alternative to
|
|
|
|
|
`"main"` where the package main entry point can be defined while also
|
|
|
|
|
encapsulating the package, preventing any other entry points besides those
|
|
|
|
|
defined in `"exports"`. If package entry points are defined in both `"main"` and
|
|
|
|
|
`"exports"`, the latter takes precedence in versions of Node.js that support
|
|
|
|
|
`"exports"`. [Conditional Exports][] can also be used within `"exports"` to
|
|
|
|
|
define different package entry points per environment.
|
|
|
|
|
|
2019-12-24 06:32:25 +01:00
|
|
|
|
#### `package.json` `"main"`
|
2019-11-09 20:54:13 +01:00
|
|
|
|
|
2018-08-28 17:28:46 +02:00
|
|
|
|
The `package.json` `"main"` field defines the entry point for a package,
|
|
|
|
|
whether the package is included into CommonJS via `require` or into an ES
|
|
|
|
|
module via `import`.
|
|
|
|
|
|
|
|
|
|
<!-- eslint-skip -->
|
|
|
|
|
```js
|
|
|
|
|
// ./node_modules/es-module-package/package.json
|
|
|
|
|
{
|
|
|
|
|
"type": "module",
|
|
|
|
|
"main": "./src/index.js"
|
|
|
|
|
}
|
|
|
|
|
```
|
2019-08-29 15:28:03 +02:00
|
|
|
|
|
2018-08-28 17:28:46 +02:00
|
|
|
|
```js
|
|
|
|
|
// ./my-app.mjs
|
|
|
|
|
|
|
|
|
|
import { something } from 'es-module-package';
|
|
|
|
|
// Loads from ./node_modules/es-module-package/src/index.js
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
An attempt to `require` the above `es-module-package` would attempt to load
|
|
|
|
|
`./node_modules/es-module-package/src/index.js` as CommonJS, which would throw
|
|
|
|
|
an error as Node.js would not be able to parse the `export` statement in
|
|
|
|
|
CommonJS.
|
|
|
|
|
|
|
|
|
|
As with `import` statements, for ES module usage the value of `"main"` must be
|
|
|
|
|
a full path including extension: `"./index.mjs"`, not `"./index"`.
|
|
|
|
|
|
|
|
|
|
If the `package.json` `"type"` field is omitted, a `.js` file in `"main"` will
|
|
|
|
|
be interpreted as CommonJS.
|
|
|
|
|
|
2019-05-22 07:14:22 +02:00
|
|
|
|
The `"main"` field can point to exactly one file, regardless of whether the
|
|
|
|
|
package is referenced via `require` (in a CommonJS context) or `import` (in an
|
2019-09-03 03:01:03 +02:00
|
|
|
|
ES module context).
|
|
|
|
|
|
2019-11-09 20:54:13 +01:00
|
|
|
|
#### Package Exports
|
2019-01-16 02:11:10 +01:00
|
|
|
|
|
|
|
|
|
By default, all subpaths from a package can be imported (`import 'pkg/x.js'`).
|
|
|
|
|
Custom subpath aliasing and encapsulation can be provided through the
|
|
|
|
|
`"exports"` field.
|
|
|
|
|
|
|
|
|
|
<!-- eslint-skip -->
|
|
|
|
|
```js
|
|
|
|
|
// ./node_modules/es-module-package/package.json
|
|
|
|
|
{
|
|
|
|
|
"exports": {
|
|
|
|
|
"./submodule": "./src/submodule.js"
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
```js
|
|
|
|
|
import submodule from 'es-module-package/submodule';
|
|
|
|
|
// Loads ./node_modules/es-module-package/src/submodule.js
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
In addition to defining an alias, subpaths not defined by `"exports"` will
|
|
|
|
|
throw when an attempt is made to import them:
|
|
|
|
|
|
|
|
|
|
```js
|
|
|
|
|
import submodule from 'es-module-package/private-module.js';
|
2019-10-24 06:28:42 +02:00
|
|
|
|
// Throws ERR_MODULE_NOT_FOUND
|
2019-01-16 02:11:10 +01:00
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
> Note: this is not a strong encapsulation as any private modules can still be
|
|
|
|
|
> loaded by absolute paths.
|
|
|
|
|
|
2019-08-02 07:30:32 +02:00
|
|
|
|
Folders can also be mapped with package exports:
|
2019-01-16 02:11:10 +01:00
|
|
|
|
|
|
|
|
|
<!-- eslint-skip -->
|
|
|
|
|
```js
|
|
|
|
|
// ./node_modules/es-module-package/package.json
|
|
|
|
|
{
|
|
|
|
|
"exports": {
|
|
|
|
|
"./features/": "./src/features/"
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
```js
|
|
|
|
|
import feature from 'es-module-package/features/x.js';
|
|
|
|
|
// Loads ./node_modules/es-module-package/src/features/x.js
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
If a package has no exports, setting `"exports": false` can be used instead of
|
|
|
|
|
`"exports": {}` to indicate the package does not intend for submodules to be
|
|
|
|
|
exposed.
|
2019-08-02 07:30:32 +02:00
|
|
|
|
|
2019-10-14 01:27:39 +02:00
|
|
|
|
Any invalid exports entries will be ignored. This includes exports not
|
|
|
|
|
starting with `"./"` or a missing trailing `"/"` for directory exports.
|
|
|
|
|
|
|
|
|
|
Array fallback support is provided for exports, similarly to import maps
|
|
|
|
|
in order to be forwards-compatible with possible fallback workflows in future:
|
2019-09-08 18:54:19 +02:00
|
|
|
|
|
|
|
|
|
<!-- eslint-skip -->
|
|
|
|
|
```js
|
|
|
|
|
{
|
|
|
|
|
"exports": {
|
2019-10-14 01:27:39 +02:00
|
|
|
|
"./submodule": ["not:valid", "./submodule.js"]
|
2019-09-08 18:54:19 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
2019-10-14 01:27:39 +02:00
|
|
|
|
Since `"not:valid"` is not a supported target, `"./submodule.js"` is used
|
|
|
|
|
instead as the fallback, as if it were the only target.
|
|
|
|
|
|
|
|
|
|
Defining a `"."` export will define the main entry point for the package,
|
|
|
|
|
and will always take precedence over the `"main"` field in the `package.json`.
|
2019-09-08 18:54:19 +02:00
|
|
|
|
|
2019-10-14 01:27:39 +02:00
|
|
|
|
This allows defining a different entry point for Node.js versions that support
|
|
|
|
|
ECMAScript modules and versions that don't, for example:
|
|
|
|
|
|
|
|
|
|
<!-- eslint-skip -->
|
|
|
|
|
```js
|
|
|
|
|
{
|
|
|
|
|
"main": "./main-legacy.cjs",
|
|
|
|
|
"exports": {
|
|
|
|
|
".": "./main-modern.cjs"
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
#### Conditional Exports
|
|
|
|
|
|
|
|
|
|
Conditional exports provide a way to map to different paths depending on
|
|
|
|
|
certain conditions. They are supported for both CommonJS and ES module imports.
|
|
|
|
|
|
|
|
|
|
For example, a package that wants to provide different ES module exports for
|
|
|
|
|
Node.js and the browser can be written:
|
|
|
|
|
|
|
|
|
|
<!-- eslint-skip -->
|
|
|
|
|
```js
|
|
|
|
|
// ./node_modules/pkg/package.json
|
|
|
|
|
{
|
|
|
|
|
"type": "module",
|
|
|
|
|
"main": "./index.js",
|
|
|
|
|
"exports": {
|
|
|
|
|
"./feature": {
|
2019-12-05 03:14:48 +01:00
|
|
|
|
"import": "./feature-default.js",
|
|
|
|
|
"browser": "./feature-browser.js"
|
2019-10-14 01:27:39 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
When resolving the `"."` export, if no matching target is found, the `"main"`
|
|
|
|
|
will be used as the final fallback.
|
|
|
|
|
|
2019-12-17 20:00:21 +01:00
|
|
|
|
The conditions supported in Node.js condition matching:
|
2019-10-14 01:27:39 +02:00
|
|
|
|
|
2019-12-17 20:00:21 +01:00
|
|
|
|
* `"default"` - the generic fallback that will always match. Can be a CommonJS
|
|
|
|
|
or ES module file.
|
|
|
|
|
* `"import"` - matched when the package is loaded via `import` or
|
|
|
|
|
`import()`. Can be any module format, this field does not set the type
|
2019-12-17 06:58:30 +01:00
|
|
|
|
interpretation.
|
2019-12-17 20:00:21 +01:00
|
|
|
|
* `"node"` - matched for any Node.js environment. Can be a CommonJS or ES
|
2019-12-17 06:58:30 +01:00
|
|
|
|
module file.
|
2019-12-17 20:00:21 +01:00
|
|
|
|
* `"require"` - matched when the package is loaded via `require()`.
|
2019-10-14 01:27:39 +02:00
|
|
|
|
|
2019-12-17 20:00:21 +01:00
|
|
|
|
Condition matching is applied in object order from first to last within the
|
|
|
|
|
`"exports"` object.
|
|
|
|
|
|
2019-10-14 01:27:39 +02:00
|
|
|
|
Using the `"require"` condition it is possible to define a package that will
|
|
|
|
|
have a different exported value for CommonJS and ES modules, which can be a
|
|
|
|
|
hazard in that it can result in having two separate instances of the same
|
|
|
|
|
package in use in an application, which can cause a number of bugs.
|
|
|
|
|
|
|
|
|
|
Other conditions such as `"browser"`, `"electron"`, `"deno"`, `"react-native"`,
|
2019-12-17 20:00:21 +01:00
|
|
|
|
etc. could be defined in other runtimes or tools. Condition names must not start
|
|
|
|
|
with `"."` or be numbers. Further restrictions, definitions or guidance on
|
|
|
|
|
condition names may be provided in future.
|
2019-10-14 01:27:39 +02:00
|
|
|
|
|
|
|
|
|
#### Exports Sugar
|
|
|
|
|
|
|
|
|
|
If the `"."` export is the only export, the `"exports"` field provides sugar
|
|
|
|
|
for this case being the direct `"exports"` field value.
|
|
|
|
|
|
|
|
|
|
If the `"."` export has a fallback array or string value, then the `"exports"`
|
|
|
|
|
field can be set to this value directly.
|
|
|
|
|
|
|
|
|
|
<!-- eslint-skip -->
|
|
|
|
|
```js
|
|
|
|
|
{
|
|
|
|
|
"exports": {
|
|
|
|
|
".": "./main.js"
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
can be written:
|
2019-09-08 18:54:19 +02:00
|
|
|
|
|
|
|
|
|
<!-- eslint-skip -->
|
|
|
|
|
```js
|
|
|
|
|
{
|
|
|
|
|
"exports": "./main.js"
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
2019-10-31 07:59:32 +01:00
|
|
|
|
When using [Conditional Exports][], the rule is that all keys in the object
|
|
|
|
|
mapping must not start with a `"."` otherwise they would be indistinguishable
|
|
|
|
|
from exports subpaths.
|
2019-08-02 07:30:32 +02:00
|
|
|
|
|
2019-10-14 01:27:39 +02:00
|
|
|
|
<!-- eslint-skip -->
|
|
|
|
|
```js
|
|
|
|
|
{
|
|
|
|
|
"exports": {
|
|
|
|
|
".": {
|
2019-12-05 03:14:48 +01:00
|
|
|
|
"import": "./main.js",
|
|
|
|
|
"require": "./main.cjs"
|
2019-10-14 01:27:39 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
can be written:
|
2019-08-02 07:30:32 +02:00
|
|
|
|
|
|
|
|
|
<!-- eslint-skip -->
|
|
|
|
|
```js
|
|
|
|
|
{
|
|
|
|
|
"exports": {
|
2019-12-05 03:14:48 +01:00
|
|
|
|
"import": "./main.js",
|
|
|
|
|
"require": "./main.cjs"
|
2019-08-02 07:30:32 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
2019-10-14 01:27:39 +02:00
|
|
|
|
If writing any exports value that mixes up these two forms, an error will be
|
|
|
|
|
thrown:
|
|
|
|
|
|
|
|
|
|
<!-- eslint-skip -->
|
|
|
|
|
```js
|
|
|
|
|
{
|
|
|
|
|
// Throws on resolution!
|
|
|
|
|
"exports": {
|
|
|
|
|
"./feature": "./lib/feature.js",
|
2019-12-05 03:14:48 +01:00
|
|
|
|
"import": "./main.js",
|
|
|
|
|
"require": "./main.cjs"
|
2019-10-14 01:27:39 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
```
|
2019-01-16 02:11:10 +01:00
|
|
|
|
|
2019-10-31 07:59:32 +01:00
|
|
|
|
### Dual CommonJS/ES Module Packages
|
|
|
|
|
|
|
|
|
|
Prior to the introduction of support for ES modules in Node.js, it was a common
|
|
|
|
|
pattern for package authors to include both CommonJS and ES module JavaScript
|
|
|
|
|
sources in their package, with `package.json` `"main"` specifying the CommonJS
|
|
|
|
|
entry point and `package.json` `"module"` specifying the ES module entry point.
|
|
|
|
|
This enabled Node.js to run the CommonJS entry point while build tools such as
|
|
|
|
|
bundlers used the ES module entry point, since Node.js ignored (and still
|
|
|
|
|
ignores) the top-level `"module"` field.
|
|
|
|
|
|
2019-11-09 20:54:13 +01:00
|
|
|
|
Node.js can now run ES module entry points, and a package can contain both
|
|
|
|
|
CommonJS and ES module entry points (either via separate specifiers such as
|
|
|
|
|
`'pkg'` and `'pkg/es-module'`, or both at the same specifier via [Conditional
|
2019-12-17 06:58:30 +01:00
|
|
|
|
Exports][]). Unlike in the scenario where `"module"` is only used by bundlers,
|
|
|
|
|
or ES module files are transpiled into CommonJS on the fly before evaluation by
|
|
|
|
|
Node.js, the files referenced by the ES module entry point are evaluated as ES
|
|
|
|
|
modules.
|
2019-10-31 07:59:32 +01:00
|
|
|
|
|
2019-11-09 20:54:13 +01:00
|
|
|
|
#### Dual Package Hazard
|
2019-10-31 07:59:32 +01:00
|
|
|
|
|
|
|
|
|
When an application is using a package that provides both CommonJS and ES module
|
|
|
|
|
sources, there is a risk of certain bugs if both versions of the package get
|
2019-11-09 20:54:13 +01:00
|
|
|
|
loaded. This potential comes from the fact that the `pkgInstance` created by
|
|
|
|
|
`const pkgInstance = require('pkg')` is not the same as the `pkgInstance`
|
|
|
|
|
created by `import pkgInstance from 'pkg'` (or an alternative main path like
|
|
|
|
|
`'pkg/module'`). This is the “dual package hazard,” where two versions of the
|
|
|
|
|
same package can be loaded within the same runtime environment. While it is
|
|
|
|
|
unlikely that an application or package would intentionally load both versions
|
|
|
|
|
directly, it is common for an application to load one version while a dependency
|
|
|
|
|
of the application loads the other version. This hazard can happen because
|
|
|
|
|
Node.js supports intermixing CommonJS and ES modules, and can lead to unexpected
|
|
|
|
|
behavior.
|
|
|
|
|
|
|
|
|
|
If the package main export is a constructor, an `instanceof` comparison of
|
|
|
|
|
instances created by the two versions returns `false`, and if the export is an
|
|
|
|
|
object, properties added to one (like `pkgInstance.foo = 3`) are not present on
|
|
|
|
|
the other. This differs from how `import` and `require` statements work in
|
|
|
|
|
all-CommonJS or all-ES module environments, respectively, and therefore is
|
|
|
|
|
surprising to users. It also differs from the behavior users are familiar with
|
|
|
|
|
when using transpilation via tools like [Babel][] or [`esm`][].
|
2019-10-31 07:59:32 +01:00
|
|
|
|
|
|
|
|
|
#### Writing Dual Packages While Avoiding or Minimizing Hazards
|
|
|
|
|
|
|
|
|
|
First, the hazard described in the previous section occurs when a package
|
|
|
|
|
contains both CommonJS and ES module sources and both sources are provided for
|
|
|
|
|
use in Node.js, either via separate main entry points or exported paths. A
|
|
|
|
|
package could instead be written where any version of Node.js receives only
|
|
|
|
|
CommonJS sources, and any separate ES module sources the package may contain
|
|
|
|
|
could be intended only for other environments such as browsers. Such a package
|
|
|
|
|
would be usable by any version of Node.js, since `import` can refer to CommonJS
|
|
|
|
|
files; but it would not provide any of the advantages of using ES module syntax.
|
|
|
|
|
|
|
|
|
|
A package could also switch from CommonJS to ES module syntax in a breaking
|
|
|
|
|
change version bump. This has the obvious disadvantage that the newest version
|
|
|
|
|
of the package would only be usable in ES module-supporting versions of Node.js.
|
|
|
|
|
|
|
|
|
|
Every pattern has tradeoffs, but there are two broad approaches that satisfy the
|
|
|
|
|
following conditions:
|
|
|
|
|
|
|
|
|
|
1. The package is usable via both `require` and `import`.
|
|
|
|
|
1. The package is usable in both current Node.js and older versions of Node.js
|
|
|
|
|
that lack support for ES modules.
|
|
|
|
|
1. The package main entry point, e.g. `'pkg'` can be used by both `require` to
|
|
|
|
|
resolve to a CommonJS file and by `import` to resolve to an ES module file.
|
|
|
|
|
(And likewise for exported paths, e.g. `'pkg/feature'`.)
|
|
|
|
|
1. The package provides named exports, e.g. `import { name } from 'pkg'` rather
|
|
|
|
|
than `import pkg from 'pkg'; pkg.name`.
|
|
|
|
|
1. The package is potentially usable in other ES module environments such as
|
|
|
|
|
browsers.
|
|
|
|
|
1. The hazards described in the previous section are avoided or minimized.
|
|
|
|
|
|
|
|
|
|
##### Approach #1: Use an ES Module Wrapper
|
|
|
|
|
|
|
|
|
|
Write the package in CommonJS or transpile ES module sources into CommonJS, and
|
|
|
|
|
create an ES module wrapper file that defines the named exports. Using
|
2019-12-17 06:58:30 +01:00
|
|
|
|
[Conditional Exports][], the ES module wrapper is used for `import` and the
|
|
|
|
|
CommonJS entry point for `require`.
|
2019-10-31 07:59:32 +01:00
|
|
|
|
|
|
|
|
|
<!-- eslint-skip -->
|
|
|
|
|
```js
|
|
|
|
|
// ./node_modules/pkg/package.json
|
|
|
|
|
{
|
|
|
|
|
"type": "module",
|
|
|
|
|
"main": "./index.cjs",
|
|
|
|
|
"exports": {
|
|
|
|
|
"require": "./index.cjs",
|
2019-12-05 03:14:48 +01:00
|
|
|
|
"import": "./wrapper.mjs"
|
2019-10-31 07:59:32 +01:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
```js
|
|
|
|
|
// ./node_modules/pkg/index.cjs
|
|
|
|
|
exports.name = 'value';
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
```js
|
|
|
|
|
// ./node_modules/pkg/wrapper.mjs
|
|
|
|
|
import cjsModule from './index.cjs';
|
|
|
|
|
export const name = cjsModule.name;
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
In this example, the `name` from `import { name } from 'pkg'` is the same
|
|
|
|
|
singleton as the `name` from `const { name } = require('pkg')`. Therefore `===`
|
|
|
|
|
returns `true` when comparing the two `name`s and the divergent specifier hazard
|
|
|
|
|
is avoided.
|
|
|
|
|
|
|
|
|
|
If the module is not simply a list of named exports, but rather contains a
|
|
|
|
|
unique function or object export like `module.exports = function () { ... }`,
|
|
|
|
|
or if support in the wrapper for the `import pkg from 'pkg'` pattern is desired,
|
|
|
|
|
then the wrapper would instead be written to export the default optionally
|
|
|
|
|
along with any named exports as well:
|
|
|
|
|
|
|
|
|
|
```js
|
|
|
|
|
import cjsModule from './index.cjs';
|
|
|
|
|
export const name = cjsModule.name;
|
|
|
|
|
export default cjsModule;
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
This approach is appropriate for any of the following use cases:
|
|
|
|
|
* The package is currently written in CommonJS and the author would prefer not
|
|
|
|
|
to refactor it into ES module syntax, but wishes to provide named exports for
|
|
|
|
|
ES module consumers.
|
|
|
|
|
* The package has other packages that depend on it, and the end user might
|
|
|
|
|
install both this package and those other packages. For example a `utilities`
|
|
|
|
|
package is used directly in an application, and a `utilities-plus` package
|
|
|
|
|
adds a few more functions to `utilities`. Because the wrapper exports
|
|
|
|
|
underlying CommonJS files, it doesn’t matter if `utilities-plus` is written in
|
|
|
|
|
CommonJS or ES module syntax; it will work either way.
|
|
|
|
|
* The package stores internal state, and the package author would prefer not to
|
|
|
|
|
refactor the package to isolate its state management. See the next section.
|
|
|
|
|
|
2019-12-17 06:58:30 +01:00
|
|
|
|
A variant of this approach not requiring conditional exports for consumers could
|
|
|
|
|
be to add an export, e.g. `"./module"`, to point to an all-ES module-syntax
|
|
|
|
|
version of the package. This could be used via `import 'pkg/module'` by users
|
|
|
|
|
who are certain that the CommonJS version will not be loaded anywhere in the
|
|
|
|
|
application, such as by dependencies; or if the CommonJS version can be loaded
|
|
|
|
|
but doesn’t affect the ES module version (for example, because the package is
|
|
|
|
|
stateless):
|
2019-11-09 20:54:13 +01:00
|
|
|
|
|
|
|
|
|
<!-- eslint-skip -->
|
|
|
|
|
```js
|
|
|
|
|
// ./node_modules/pkg/package.json
|
|
|
|
|
{
|
|
|
|
|
"type": "module",
|
|
|
|
|
"main": "./index.cjs",
|
|
|
|
|
"exports": {
|
|
|
|
|
".": "./index.cjs",
|
|
|
|
|
"./module": "./wrapper.mjs"
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
2019-10-31 07:59:32 +01:00
|
|
|
|
##### Approach #2: Isolate State
|
|
|
|
|
|
|
|
|
|
The most straightforward `package.json` would be one that defines the separate
|
2019-12-17 06:58:30 +01:00
|
|
|
|
CommonJS and ES module entry points directly:
|
2019-10-31 07:59:32 +01:00
|
|
|
|
|
|
|
|
|
<!-- eslint-skip -->
|
|
|
|
|
```js
|
|
|
|
|
// ./node_modules/pkg/package.json
|
|
|
|
|
{
|
|
|
|
|
"type": "module",
|
|
|
|
|
"main": "./index.cjs",
|
|
|
|
|
"exports": {
|
2019-12-05 03:14:48 +01:00
|
|
|
|
"import": "./index.mjs",
|
|
|
|
|
"require": "./index.cjs"
|
2019-10-31 07:59:32 +01:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
This can be done if both the CommonJS and ES module versions of the package are
|
|
|
|
|
equivalent, for example because one is the transpiled output of the other; and
|
|
|
|
|
the package’s management of state is carefully isolated (or the package is
|
|
|
|
|
stateless).
|
|
|
|
|
|
|
|
|
|
The reason that state is an issue is because both the CommonJS and ES module
|
|
|
|
|
versions of the package may get used within an application; for example, the
|
|
|
|
|
user’s application code could `import` the ES module version while a dependency
|
|
|
|
|
`require`s the CommonJS version. If that were to occur, two copies of the
|
|
|
|
|
package would be loaded in memory and therefore two separate states would be
|
|
|
|
|
present. This would likely cause hard-to-troubleshoot bugs.
|
|
|
|
|
|
|
|
|
|
Aside from writing a stateless package (if JavaScript’s `Math` were a package,
|
|
|
|
|
for example, it would be stateless as all of its methods are static), there are
|
|
|
|
|
some ways to isolate state so that it’s shared between the potentially loaded
|
|
|
|
|
CommonJS and ES module instances of the package:
|
|
|
|
|
|
|
|
|
|
1. If possible, contain all state within an instantiated object. JavaScript’s
|
|
|
|
|
`Date`, for example, needs to be instantiated to contain state; if it were a
|
|
|
|
|
package, it would be used like this:
|
|
|
|
|
|
|
|
|
|
```js
|
2019-11-09 13:03:49 +01:00
|
|
|
|
import Date from 'date';
|
|
|
|
|
const someDate = new Date();
|
|
|
|
|
// someDate contains state; Date does not
|
2019-10-31 07:59:32 +01:00
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
The `new` keyword isn’t required; a package’s function can return a new
|
|
|
|
|
object, or modify a passed-in object, to keep the state external to the
|
|
|
|
|
package.
|
|
|
|
|
|
|
|
|
|
1. Isolate the state in one or more CommonJS files that are shared between the
|
|
|
|
|
CommonJS and ES module versions of the package. For example, if the CommonJS
|
|
|
|
|
and ES module entry points are `index.cjs` and `index.mjs`, respectively:
|
|
|
|
|
|
|
|
|
|
```js
|
|
|
|
|
// ./node_modules/pkg/index.cjs
|
|
|
|
|
const state = require('./state.cjs');
|
|
|
|
|
module.exports.state = state;
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
```js
|
|
|
|
|
// ./node_modules/pkg/index.mjs
|
2019-11-27 19:43:06 +01:00
|
|
|
|
import state from './state.cjs';
|
|
|
|
|
export {
|
|
|
|
|
state
|
|
|
|
|
};
|
2019-10-31 07:59:32 +01:00
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
Even if `pkg` is used via both `require` and `import` in an application (for
|
|
|
|
|
example, via `import` in application code and via `require` by a dependency)
|
|
|
|
|
each reference of `pkg` will contain the same state; and modifying that
|
|
|
|
|
state from either module system will apply to both.
|
|
|
|
|
|
|
|
|
|
Any plugins that attach to the package’s singleton would need to separately
|
|
|
|
|
attach to both the CommonJS and ES module singletons.
|
|
|
|
|
|
|
|
|
|
This approach is appropriate for any of the following use cases:
|
|
|
|
|
* The package is currently written in ES module syntax and the package author
|
|
|
|
|
wants that version to be used wherever such syntax is supported.
|
|
|
|
|
* The package is stateless or its state can be isolated without too much
|
|
|
|
|
difficulty.
|
|
|
|
|
* The package is unlikely to have other public packages that depend on it, or if
|
|
|
|
|
it does, the package is stateless or has state that need not be shared between
|
|
|
|
|
dependencies or with the overall application.
|
|
|
|
|
|
|
|
|
|
Even with isolated state, there is still the cost of possible extra code
|
|
|
|
|
execution between the CommonJS and ES module versions of a package.
|
|
|
|
|
|
2019-11-09 20:54:13 +01:00
|
|
|
|
As with the previous approach, a variant of this approach not requiring
|
2019-12-17 06:58:30 +01:00
|
|
|
|
conditional exports for consumers could be to add an export, e.g.
|
|
|
|
|
`"./module"`, to point to an all-ES module-syntax version of the package:
|
2019-11-09 20:54:13 +01:00
|
|
|
|
|
|
|
|
|
<!-- eslint-skip -->
|
|
|
|
|
```js
|
|
|
|
|
// ./node_modules/pkg/package.json
|
|
|
|
|
{
|
|
|
|
|
"type": "module",
|
|
|
|
|
"main": "./index.cjs",
|
|
|
|
|
"exports": {
|
|
|
|
|
".": "./index.cjs",
|
|
|
|
|
"./module": "./index.mjs"
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
2019-12-24 06:32:25 +01:00
|
|
|
|
## `import` Specifiers
|
2018-08-28 17:28:46 +02:00
|
|
|
|
|
|
|
|
|
### 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 four types of specifiers:
|
|
|
|
|
|
2019-09-13 06:22:29 +02:00
|
|
|
|
* _Bare specifiers_ like `'some-package'`. They refer to an entry point of a
|
2018-08-28 17:28:46 +02:00
|
|
|
|
package by the package name.
|
|
|
|
|
|
2019-09-13 06:22:29 +02:00
|
|
|
|
* _Deep import specifiers_ like `'some-package/lib/shuffle.mjs'`. They refer to
|
2018-08-28 17:28:46 +02:00
|
|
|
|
a path within a package prefixed by the package name.
|
|
|
|
|
|
2019-09-13 06:22:29 +02:00
|
|
|
|
* _Relative specifiers_ like `'./startup.js'` or `'../config.mjs'`. They refer
|
2018-08-28 17:28:46 +02:00
|
|
|
|
to a path relative to the location of the importing file.
|
|
|
|
|
|
2019-09-13 06:22:29 +02:00
|
|
|
|
* _Absolute specifiers_ like `'file:///opt/nodejs/config.js'`. They refer
|
2018-08-28 17:28:46 +02:00
|
|
|
|
directly and explicitly to a full path.
|
|
|
|
|
|
|
|
|
|
Bare specifiers, and the bare specifier portion of deep import specifiers, are
|
|
|
|
|
strings; but everything else in a specifier is a URL.
|
2017-06-06 02:44:56 +02:00
|
|
|
|
|
2019-07-09 23:03:07 +02:00
|
|
|
|
Only `file:` and `data:` URLs are supported. A specifier like
|
2018-08-28 17:28:46 +02:00
|
|
|
|
`'https://example.com/app.js'` may be supported by browsers but it is not
|
|
|
|
|
supported in Node.js.
|
2017-06-06 02:44:56 +02:00
|
|
|
|
|
2018-08-28 17:28:46 +02:00
|
|
|
|
Specifiers may not begin with `/` or `//`. These are reserved for potential
|
|
|
|
|
future use. The root of the current volume may be referenced via `file:///`.
|
2017-06-06 02:44:56 +02:00
|
|
|
|
|
2019-07-09 23:03:07 +02:00
|
|
|
|
#### `data:` Imports
|
|
|
|
|
|
|
|
|
|
<!-- YAML
|
2019-09-04 00:10:04 +02:00
|
|
|
|
added: v12.10.0
|
2019-07-09 23:03:07 +02:00
|
|
|
|
-->
|
|
|
|
|
|
|
|
|
|
[`data:` URLs][] are supported for importing with the following MIME types:
|
|
|
|
|
|
|
|
|
|
* `text/javascript` for ES Modules
|
|
|
|
|
* `application/json` for JSON
|
|
|
|
|
* `application/wasm` for WASM.
|
|
|
|
|
|
|
|
|
|
`data:` URLs only resolve [_Bare specifiers_][Terminology] for builtin modules
|
|
|
|
|
and [_Absolute specifiers_][Terminology]. Resolving
|
|
|
|
|
[_Relative specifiers_][Terminology] will not work because `data:` is not a
|
|
|
|
|
[special scheme][]. For example, attempting to load `./foo`
|
|
|
|
|
from `data:text/javascript,import "./foo";` will fail to resolve since there
|
|
|
|
|
is no concept of relative resolution for `data:` URLs. An example of a `data:`
|
|
|
|
|
URLs being used is:
|
|
|
|
|
|
2019-08-21 11:03:00 +02:00
|
|
|
|
```js
|
|
|
|
|
import 'data:text/javascript,console.log("hello!");';
|
|
|
|
|
import _ from 'data:application/json,"world!"';
|
2019-07-09 23:03:07 +02:00
|
|
|
|
```
|
|
|
|
|
|
2019-12-24 06:32:25 +01:00
|
|
|
|
## `import.meta`
|
2018-04-15 15:05:55 +02:00
|
|
|
|
|
|
|
|
|
* {Object}
|
2017-12-24 16:26:24 +01:00
|
|
|
|
|
|
|
|
|
The `import.meta` metaproperty is an `Object` that contains the following
|
|
|
|
|
property:
|
2018-04-15 15:05:55 +02:00
|
|
|
|
|
|
|
|
|
* `url` {string} The absolute `file:` URL of the module.
|
2017-12-24 16:26:24 +01:00
|
|
|
|
|
2018-08-28 17:28:46 +02:00
|
|
|
|
## Differences Between ES Modules and CommonJS
|
2017-06-06 02:44:56 +02:00
|
|
|
|
|
2018-08-28 17:28:46 +02:00
|
|
|
|
### Mandatory file extensions
|
|
|
|
|
|
|
|
|
|
A file extension must be provided when using the `import` keyword. Directory
|
|
|
|
|
indexes (e.g. `'./startup/index.js'`) must also be fully specified.
|
2017-06-06 02:44:56 +02:00
|
|
|
|
|
2018-08-28 17:28:46 +02:00
|
|
|
|
This behavior matches how `import` behaves in browser environments, assuming a
|
|
|
|
|
typically configured server.
|
2017-06-06 02:44:56 +02:00
|
|
|
|
|
2019-12-24 06:32:25 +01:00
|
|
|
|
### No `NODE_PATH`
|
2017-06-06 02:44:56 +02:00
|
|
|
|
|
2017-09-10 14:13:47 +02:00
|
|
|
|
`NODE_PATH` is not part of resolving `import` specifiers. Please use symlinks
|
|
|
|
|
if this behavior is desired.
|
2017-06-06 02:44:56 +02:00
|
|
|
|
|
2019-12-24 06:32:25 +01:00
|
|
|
|
### No `require`, `exports`, `module.exports`, `__filename`, `__dirname`
|
2018-08-28 17:28:46 +02:00
|
|
|
|
|
|
|
|
|
These CommonJS variables are not available in ES modules.
|
|
|
|
|
|
2019-05-18 15:17:53 +02:00
|
|
|
|
`require` can be imported into an ES module using [`module.createRequire()`][].
|
2018-08-28 17:28:46 +02:00
|
|
|
|
|
2019-07-13 17:49:23 +02:00
|
|
|
|
Equivalents of `__filename` and `__dirname` can be created inside of each file
|
|
|
|
|
via [`import.meta.url`][].
|
2019-06-19 01:21:23 +02:00
|
|
|
|
|
|
|
|
|
```js
|
|
|
|
|
import { fileURLToPath } from 'url';
|
|
|
|
|
import { dirname } from 'path';
|
|
|
|
|
|
|
|
|
|
const __filename = fileURLToPath(import.meta.url);
|
|
|
|
|
const __dirname = dirname(__filename);
|
|
|
|
|
```
|
2018-08-28 17:28:46 +02:00
|
|
|
|
|
2019-12-24 06:32:25 +01:00
|
|
|
|
### No `require.extensions`
|
2017-06-06 02:44:56 +02:00
|
|
|
|
|
2017-09-10 14:13:47 +02:00
|
|
|
|
`require.extensions` is not used by `import`. The expectation is that loader
|
|
|
|
|
hooks can provide this workflow in the future.
|
2017-06-06 02:44:56 +02:00
|
|
|
|
|
2019-12-24 06:32:25 +01:00
|
|
|
|
### No `require.cache`
|
2017-06-06 02:44:56 +02:00
|
|
|
|
|
|
|
|
|
`require.cache` is not used by `import`. It has a separate cache.
|
|
|
|
|
|
2018-08-28 17:28:46 +02:00
|
|
|
|
### URL-based paths
|
2017-06-06 02:44:56 +02:00
|
|
|
|
|
2018-08-28 17:28:46 +02:00
|
|
|
|
ES modules are resolved and cached based upon
|
|
|
|
|
[URL](https://url.spec.whatwg.org/) semantics. This means that files containing
|
|
|
|
|
special characters such as `#` and `?` need to be escaped.
|
2017-06-06 02:44:56 +02:00
|
|
|
|
|
2017-09-10 14:13:47 +02:00
|
|
|
|
Modules will be loaded multiple times if the `import` specifier used to resolve
|
|
|
|
|
them have a different query or fragment.
|
2017-06-06 02:44:56 +02:00
|
|
|
|
|
|
|
|
|
```js
|
2018-08-28 17:28:46 +02:00
|
|
|
|
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"
|
2017-06-06 02:44:56 +02:00
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
For now, only modules using the `file:` protocol can be loaded.
|
|
|
|
|
|
2018-08-28 17:28:46 +02:00
|
|
|
|
## Interoperability with CommonJS
|
|
|
|
|
|
2019-12-24 06:32:25 +01:00
|
|
|
|
### `require`
|
2017-06-06 02:44:56 +02:00
|
|
|
|
|
2018-08-28 17:28:46 +02:00
|
|
|
|
`require` always treats the files it references as CommonJS. This applies
|
|
|
|
|
whether `require` is used the traditional way within a CommonJS environment, or
|
2019-05-18 15:17:53 +02:00
|
|
|
|
in an ES module environment using [`module.createRequire()`][].
|
2017-06-06 02:44:56 +02:00
|
|
|
|
|
2018-08-28 17:28:46 +02:00
|
|
|
|
To include an ES module into CommonJS, use [`import()`][].
|
2017-06-06 02:44:56 +02:00
|
|
|
|
|
2019-12-24 06:32:25 +01:00
|
|
|
|
### `import` statements
|
2018-08-28 17:28:46 +02:00
|
|
|
|
|
2019-11-17 08:35:16 +01:00
|
|
|
|
An `import` statement can reference an ES module or a CommonJS module. Other
|
|
|
|
|
file types such as JSON or Native modules are not supported. For those, use
|
|
|
|
|
[`module.createRequire()`][].
|
2018-08-28 17:28:46 +02:00
|
|
|
|
|
|
|
|
|
`import` statements are permitted only in ES modules. For similar functionality
|
|
|
|
|
in CommonJS, see [`import()`][].
|
|
|
|
|
|
|
|
|
|
The _specifier_ of an `import` statement (the string after the `from` keyword)
|
|
|
|
|
can either be an URL-style relative path like `'./file.mjs'` or a package name
|
|
|
|
|
like `'fs'`.
|
|
|
|
|
|
|
|
|
|
Like in CommonJS, files within packages can be accessed by appending a path to
|
2019-11-17 08:35:16 +01:00
|
|
|
|
the package name; unless the package’s `package.json` contains an [`"exports"`
|
|
|
|
|
field][], in which case files within packages need to be accessed via the path
|
|
|
|
|
defined in `"exports"`.
|
2017-06-06 02:44:56 +02:00
|
|
|
|
|
|
|
|
|
```js
|
2018-08-28 17:28:46 +02:00
|
|
|
|
import { sin, cos } from 'geometry/trigonometry-functions.mjs';
|
|
|
|
|
```
|
|
|
|
|
|
2019-11-17 08:35:16 +01:00
|
|
|
|
Only the “default export” is supported for CommonJS files or packages:
|
|
|
|
|
|
|
|
|
|
<!-- eslint-disable no-duplicate-imports -->
|
|
|
|
|
```js
|
|
|
|
|
import packageMain from 'commonjs-package'; // Works
|
|
|
|
|
|
|
|
|
|
import { method } from 'commonjs-package'; // Errors
|
|
|
|
|
```
|
2018-04-23 01:56:42 +02:00
|
|
|
|
|
2019-12-24 06:32:25 +01:00
|
|
|
|
### `import()` expressions
|
2018-08-28 17:28:46 +02:00
|
|
|
|
|
|
|
|
|
Dynamic `import()` is supported in both CommonJS and ES modules. It can be used
|
|
|
|
|
to include ES module files from CommonJS code.
|
|
|
|
|
|
|
|
|
|
```js
|
|
|
|
|
(async () => {
|
|
|
|
|
await import('./my-app.mjs');
|
|
|
|
|
})();
|
2018-04-23 01:56:42 +02:00
|
|
|
|
```
|
|
|
|
|
|
2018-08-28 17:28:46 +02:00
|
|
|
|
## CommonJS, JSON, and Native Modules
|
|
|
|
|
|
2019-05-18 15:17:53 +02:00
|
|
|
|
CommonJS, JSON, and Native modules can be used with
|
|
|
|
|
[`module.createRequire()`][].
|
2018-08-28 17:28:46 +02:00
|
|
|
|
|
|
|
|
|
```js
|
2019-11-15 18:29:18 +01:00
|
|
|
|
// cjs.cjs
|
2018-08-28 17:28:46 +02:00
|
|
|
|
module.exports = 'cjs';
|
|
|
|
|
|
|
|
|
|
// esm.mjs
|
2019-05-18 15:17:53 +02:00
|
|
|
|
import { createRequire } from 'module';
|
2018-08-28 17:28:46 +02:00
|
|
|
|
|
2019-11-15 18:29:18 +01:00
|
|
|
|
const require = createRequire(import.meta.url);
|
2018-08-28 17:28:46 +02:00
|
|
|
|
|
2019-11-15 18:29:18 +01:00
|
|
|
|
const cjs = require('./cjs.cjs');
|
2018-08-28 17:28:46 +02:00
|
|
|
|
cjs === 'cjs'; // true
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
## Builtin modules
|
|
|
|
|
|
2019-09-27 19:12:18 +02:00
|
|
|
|
Builtin modules will 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()`][].
|
2018-04-23 01:56:42 +02:00
|
|
|
|
|
|
|
|
|
```js
|
|
|
|
|
import EventEmitter from 'events';
|
|
|
|
|
const e = new EventEmitter();
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
```js
|
|
|
|
|
import { readFile } from 'fs';
|
|
|
|
|
readFile('./foo.txt', (err, source) => {
|
2017-06-06 02:44:56 +02:00
|
|
|
|
if (err) {
|
|
|
|
|
console.error(err);
|
|
|
|
|
} else {
|
2018-04-23 01:56:42 +02:00
|
|
|
|
console.log(source);
|
2017-06-06 02:44:56 +02:00
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
```
|
|
|
|
|
|
2018-04-23 01:56:42 +02:00
|
|
|
|
```js
|
|
|
|
|
import fs, { readFileSync } from 'fs';
|
2019-09-27 19:12:18 +02:00
|
|
|
|
import { syncBuiltinESMExports } from 'module';
|
2018-04-23 01:56:42 +02:00
|
|
|
|
|
|
|
|
|
fs.readFileSync = () => Buffer.from('Hello, ESM');
|
2019-09-27 19:12:18 +02:00
|
|
|
|
syncBuiltinESMExports();
|
2018-04-23 01:56:42 +02:00
|
|
|
|
|
|
|
|
|
fs.readFileSync === readFileSync;
|
|
|
|
|
```
|
|
|
|
|
|
2019-09-28 21:01:23 +02:00
|
|
|
|
## Experimental JSON Modules
|
2018-08-28 17:28:46 +02:00
|
|
|
|
|
2019-09-28 21:01:23 +02:00
|
|
|
|
Currently importing JSON modules are only supported in the `commonjs` mode
|
|
|
|
|
and are loaded using the CJS loader. [WHATWG JSON modules specification][] are
|
|
|
|
|
still being standardized, and are experimentally supported by including the
|
|
|
|
|
additional flag `--experimental-json-modules` when running Node.js.
|
2018-08-28 17:28:46 +02:00
|
|
|
|
|
2019-09-28 21:01:23 +02:00
|
|
|
|
When the `--experimental-json-modules` flag is included both the
|
|
|
|
|
`commonjs` and `module` mode will use the new experimental JSON
|
|
|
|
|
loader. The imported JSON only exposes a `default`, there is no
|
2018-08-28 17:28:46 +02:00
|
|
|
|
support for named exports. A cache entry is created in the CommonJS
|
|
|
|
|
cache, to avoid duplication. The same object will be returned in
|
|
|
|
|
CommonJS if the JSON module has already been imported from the
|
|
|
|
|
same path.
|
|
|
|
|
|
2019-05-13 20:11:04 +02:00
|
|
|
|
Assuming an `index.mjs` with
|
2018-08-28 17:28:46 +02:00
|
|
|
|
|
|
|
|
|
<!-- eslint-skip -->
|
|
|
|
|
```js
|
|
|
|
|
import packageConfig from './package.json';
|
|
|
|
|
```
|
|
|
|
|
|
2019-09-28 21:01:23 +02:00
|
|
|
|
The `--experimental-json-modules` flag is needed for the module
|
|
|
|
|
to work.
|
|
|
|
|
|
|
|
|
|
```bash
|
2019-10-11 23:57:13 +02:00
|
|
|
|
node index.mjs # fails
|
|
|
|
|
node --experimental-json-modules index.mjs # works
|
2019-09-28 21:01:23 +02:00
|
|
|
|
```
|
|
|
|
|
|
2019-04-29 18:27:20 +02:00
|
|
|
|
## Experimental Wasm Modules
|
|
|
|
|
|
|
|
|
|
Importing Web Assembly modules is supported under the
|
|
|
|
|
`--experimental-wasm-modules` flag, allowing any `.wasm` files to be
|
|
|
|
|
imported as normal modules while also supporting their module imports.
|
|
|
|
|
|
|
|
|
|
This integration is in line with the
|
|
|
|
|
[ES Module Integration Proposal for Web Assembly][].
|
|
|
|
|
|
|
|
|
|
For example, an `index.mjs` containing:
|
|
|
|
|
|
|
|
|
|
```js
|
|
|
|
|
import * as M from './module.wasm';
|
|
|
|
|
console.log(M);
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
executed under:
|
|
|
|
|
|
|
|
|
|
```bash
|
2019-10-11 23:57:13 +02:00
|
|
|
|
node --experimental-wasm-modules index.mjs
|
2019-04-29 18:27:20 +02:00
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
would provide the exports interface for the instantiation of `module.wasm`.
|
|
|
|
|
|
2019-12-15 04:27:48 +01:00
|
|
|
|
## Experimental Loaders
|
2018-08-28 17:28:46 +02:00
|
|
|
|
|
|
|
|
|
**Note: This API is currently being redesigned and will still change.**
|
2017-09-03 13:20:06 +02:00
|
|
|
|
|
|
|
|
|
<!-- type=misc -->
|
|
|
|
|
|
|
|
|
|
To customize the default module resolution, loader hooks can optionally be
|
2019-09-28 18:51:15 +02:00
|
|
|
|
provided via a `--experimental-loader ./loader-name.mjs` argument to Node.js.
|
2017-09-03 13:20:06 +02:00
|
|
|
|
|
|
|
|
|
When hooks are used they only apply to ES module loading and not to any
|
|
|
|
|
CommonJS modules loaded.
|
|
|
|
|
|
2019-12-15 04:27:48 +01:00
|
|
|
|
### Hooks
|
2017-09-03 13:20:06 +02:00
|
|
|
|
|
2019-12-15 04:27:48 +01:00
|
|
|
|
#### <code>resolve</code> hook
|
2017-09-03 13:20:06 +02:00
|
|
|
|
|
2019-12-15 04:27:48 +01:00
|
|
|
|
> Note: The loaders API is being redesigned. This hook may disappear or its
|
|
|
|
|
> signature may change. Do not rely on the API described below.
|
2019-08-30 05:09:08 +02:00
|
|
|
|
|
2019-12-15 04:27:48 +01:00
|
|
|
|
The `resolve` hook returns the resolved file URL for a given module specifier
|
|
|
|
|
and parent URL. The module specifier is the string in an `import` statement or
|
|
|
|
|
`import()` expression, and the parent URL is the URL of the module that imported
|
|
|
|
|
this one, or `undefined` if this is the main entry point for the application.
|
|
|
|
|
|
|
|
|
|
```js
|
2019-08-30 05:09:08 +02:00
|
|
|
|
/**
|
|
|
|
|
* @param {string} specifier
|
2019-12-15 04:27:48 +01:00
|
|
|
|
* @param {object} context
|
|
|
|
|
* @param {string} context.parentURL
|
|
|
|
|
* @param {function} defaultResolve
|
|
|
|
|
* @returns {object} response
|
|
|
|
|
* @returns {string} response.url
|
2019-08-30 05:09:08 +02:00
|
|
|
|
*/
|
2019-12-15 04:27:48 +01:00
|
|
|
|
export async function resolve(specifier, context, defaultResolve) {
|
|
|
|
|
const { parentURL = null } = context;
|
|
|
|
|
if (someCondition) {
|
|
|
|
|
// For some or all specifiers, do some custom logic for resolving.
|
|
|
|
|
// Always return an object of the form {url: <string>}
|
|
|
|
|
return {
|
|
|
|
|
url: (parentURL) ?
|
|
|
|
|
new URL(specifier, parentURL).href : new URL(specifier).href
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
// Defer to Node.js for all other specifiers.
|
|
|
|
|
return defaultResolve(specifier, context, defaultResolve);
|
2017-09-03 13:20:06 +02:00
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
2019-12-15 04:27:48 +01:00
|
|
|
|
#### <code>getFormat</code> hook
|
2018-02-12 12:02:42 +01:00
|
|
|
|
|
2019-12-15 04:27:48 +01:00
|
|
|
|
> Note: The loaders API is being redesigned. This hook may disappear or its
|
|
|
|
|
> signature may change. Do not rely on the API described below.
|
2017-09-03 13:20:06 +02:00
|
|
|
|
|
2019-12-15 04:27:48 +01:00
|
|
|
|
The `getFormat` hook provides a way to define a custom method of determining how
|
|
|
|
|
a URL should be interpreted. This can be one of the following:
|
2017-09-03 13:20:06 +02:00
|
|
|
|
|
2017-10-22 06:03:18 +02:00
|
|
|
|
| `format` | Description |
|
|
|
|
|
| --- | --- |
|
2018-08-28 17:28:46 +02:00
|
|
|
|
| `'builtin'` | Load a Node.js builtin module |
|
2019-04-29 18:27:20 +02:00
|
|
|
|
| `'commonjs'` | Load a Node.js CommonJS module |
|
2018-04-02 07:38:48 +02:00
|
|
|
|
| `'dynamic'` | Use a [dynamic instantiate hook][] |
|
2019-04-29 18:27:20 +02:00
|
|
|
|
| `'json'` | Load a JSON file |
|
2019-12-15 04:27:48 +01:00
|
|
|
|
| `'module'` | Load a standard JavaScript module (ES module) |
|
2019-04-29 18:27:20 +02:00
|
|
|
|
| `'wasm'` | Load a WebAssembly module |
|
2017-10-22 06:03:18 +02:00
|
|
|
|
|
2017-09-03 13:20:06 +02:00
|
|
|
|
```js
|
2019-12-15 04:27:48 +01:00
|
|
|
|
/**
|
|
|
|
|
* @param {string} url
|
|
|
|
|
* @param {object} context (currently empty)
|
|
|
|
|
* @param {function} defaultGetFormat
|
|
|
|
|
* @returns {object} response
|
|
|
|
|
* @returns {string} response.format
|
|
|
|
|
*/
|
|
|
|
|
export async function getFormat(url, context, defaultGetFormat) {
|
|
|
|
|
if (someCondition) {
|
|
|
|
|
// For some or all URLs, do some custom logic for determining format.
|
|
|
|
|
// Always return an object of the form {format: <string>}, where the
|
|
|
|
|
// format is one of the strings in the table above.
|
|
|
|
|
return {
|
|
|
|
|
format: 'module'
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
// Defer to Node.js for all other URLs.
|
|
|
|
|
return defaultGetFormat(url, context, defaultGetFormat);
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
#### <code>getSource</code> hook
|
2017-09-03 13:20:06 +02:00
|
|
|
|
|
2019-12-15 04:27:48 +01:00
|
|
|
|
> Note: The loaders API is being redesigned. This hook may disappear or its
|
|
|
|
|
> signature may change. Do not rely on the API described below.
|
2017-09-03 13:20:06 +02:00
|
|
|
|
|
2019-12-15 04:27:48 +01:00
|
|
|
|
The `getSource` hook provides a way to define a custom method for retrieving
|
|
|
|
|
the source code of an ES module specifier. This would allow a loader to
|
|
|
|
|
potentially avoid reading files from disk.
|
2018-02-12 12:02:42 +01:00
|
|
|
|
|
2019-12-15 04:27:48 +01:00
|
|
|
|
```js
|
2019-08-30 05:09:08 +02:00
|
|
|
|
/**
|
2019-12-15 04:27:48 +01:00
|
|
|
|
* @param {string} url
|
|
|
|
|
* @param {object} context
|
|
|
|
|
* @param {string} context.format
|
|
|
|
|
* @param {function} defaultGetSource
|
|
|
|
|
* @returns {object} response
|
|
|
|
|
* @returns {string|buffer} response.source
|
2019-08-30 05:09:08 +02:00
|
|
|
|
*/
|
2019-12-15 04:27:48 +01:00
|
|
|
|
export async function getSource(url, context, defaultGetSource) {
|
|
|
|
|
const { format } = context;
|
|
|
|
|
if (someCondition) {
|
|
|
|
|
// For some or all URLs, do some custom logic for retrieving the source.
|
|
|
|
|
// Always return an object of the form {source: <string|buffer>}.
|
2017-09-03 13:20:06 +02:00
|
|
|
|
return {
|
2019-12-15 04:27:48 +01:00
|
|
|
|
source: '...'
|
2017-09-03 13:20:06 +02:00
|
|
|
|
};
|
|
|
|
|
}
|
2019-12-15 04:27:48 +01:00
|
|
|
|
// Defer to Node.js for all other URLs.
|
|
|
|
|
return defaultGetSource(url, context, defaultGetSource);
|
2017-09-03 13:20:06 +02:00
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
2019-12-15 04:27:48 +01:00
|
|
|
|
#### <code>transformSource</code> hook
|
2017-09-03 13:20:06 +02:00
|
|
|
|
|
2019-12-15 04:27:48 +01:00
|
|
|
|
> Note: The loaders API is being redesigned. This hook may disappear or its
|
|
|
|
|
> signature may change. Do not rely on the API described below.
|
|
|
|
|
|
|
|
|
|
The `transformSource` hook provides a way to modify the source code of a loaded
|
|
|
|
|
ES module file after the source string has been loaded but before Node.js has
|
|
|
|
|
done anything with it.
|
|
|
|
|
|
|
|
|
|
If this hook is used to convert unknown-to-Node.js file types into executable
|
|
|
|
|
JavaScript, a resolve hook is also necessary in order to register any
|
|
|
|
|
unknown-to-Node.js file extensions. See the [transpiler loader example][] below.
|
|
|
|
|
|
|
|
|
|
```js
|
|
|
|
|
/**
|
|
|
|
|
* @param {string|buffer} source
|
|
|
|
|
* @param {object} context
|
|
|
|
|
* @param {string} context.url
|
|
|
|
|
* @param {string} context.format
|
|
|
|
|
* @param {function} defaultTransformSource
|
|
|
|
|
* @returns {object} response
|
|
|
|
|
* @returns {string|buffer} response.source
|
|
|
|
|
*/
|
|
|
|
|
export async function transformSource(source,
|
|
|
|
|
context,
|
|
|
|
|
defaultTransformSource) {
|
|
|
|
|
const { url, format } = context;
|
|
|
|
|
if (someCondition) {
|
|
|
|
|
// For some or all URLs, do some custom logic for modifying the source.
|
|
|
|
|
// Always return an object of the form {source: <string|buffer>}.
|
|
|
|
|
return {
|
|
|
|
|
source: '...'
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
// Defer to Node.js for all other sources.
|
|
|
|
|
return defaultTransformSource(
|
|
|
|
|
source, context, defaultTransformSource);
|
|
|
|
|
}
|
2017-09-03 13:20:06 +02:00
|
|
|
|
```
|
|
|
|
|
|
2019-12-15 04:27:48 +01:00
|
|
|
|
#### <code>dynamicInstantiate</code> hook
|
2017-09-03 13:20:06 +02:00
|
|
|
|
|
2019-12-15 04:27:48 +01:00
|
|
|
|
> Note: The loaders API is being redesigned. This hook may disappear or its
|
|
|
|
|
> signature may change. Do not rely on the API described below.
|
2017-09-03 13:20:06 +02:00
|
|
|
|
|
|
|
|
|
To create a custom dynamic module that doesn't correspond to one of the
|
|
|
|
|
existing `format` interpretations, the `dynamicInstantiate` hook can be used.
|
2018-04-02 07:38:48 +02:00
|
|
|
|
This hook is called only for modules that return `format: 'dynamic'` from
|
2019-12-15 04:27:48 +01:00
|
|
|
|
the [`getFormat` hook][].
|
2017-09-03 13:20:06 +02:00
|
|
|
|
|
|
|
|
|
```js
|
2019-12-15 04:27:48 +01:00
|
|
|
|
/**
|
|
|
|
|
* @param {string} url
|
|
|
|
|
* @returns {object} response
|
|
|
|
|
* @returns {array} response.exports
|
|
|
|
|
* @returns {function} response.execute
|
|
|
|
|
*/
|
2017-09-03 13:20:06 +02:00
|
|
|
|
export async function dynamicInstantiate(url) {
|
|
|
|
|
return {
|
|
|
|
|
exports: ['customExportName'],
|
|
|
|
|
execute: (exports) => {
|
2018-12-10 13:27:32 +01:00
|
|
|
|
// Get and set functions provided for pre-allocated export names
|
2017-09-03 13:20:06 +02:00
|
|
|
|
exports.customExportName.set('value');
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
With the list of module exports provided upfront, the `execute` function will
|
2017-12-31 10:45:43 +01:00
|
|
|
|
then be called at the exact point of module evaluation order for that module
|
2017-09-03 13:20:06 +02:00
|
|
|
|
in the import tree.
|
|
|
|
|
|
2019-12-15 04:27:48 +01:00
|
|
|
|
### Examples
|
|
|
|
|
|
|
|
|
|
The various loader hooks can be used together to accomplish wide-ranging
|
|
|
|
|
customizations of Node.js’ code loading and evaluation behaviors.
|
|
|
|
|
|
|
|
|
|
#### HTTPS loader
|
|
|
|
|
|
|
|
|
|
In current Node.js, specifiers starting with `https://` are unsupported. The
|
|
|
|
|
loader below registers hooks to enable rudimentary support for such specifiers.
|
|
|
|
|
While this may seem like a significant improvement to Node.js core
|
|
|
|
|
functionality, there are substantial downsides to actually using this loader:
|
|
|
|
|
performance is much slower than loading files from disk, there is no caching,
|
|
|
|
|
and there is no security.
|
|
|
|
|
|
|
|
|
|
```js
|
|
|
|
|
// https-loader.mjs
|
|
|
|
|
import { get } from 'https';
|
|
|
|
|
|
|
|
|
|
export function resolve(specifier, context, defaultResolve) {
|
|
|
|
|
const { parentURL = null } = context;
|
|
|
|
|
|
|
|
|
|
// Normally Node.js would error on specifiers starting with 'https://', so
|
|
|
|
|
// this hook intercepts them and converts them into absolute URLs to be
|
|
|
|
|
// passed along to the later hooks below.
|
|
|
|
|
if (specifier.startsWith('https://')) {
|
|
|
|
|
return {
|
|
|
|
|
url: specifier
|
|
|
|
|
};
|
|
|
|
|
} else if (parentURL && parentURL.startsWith('https://')) {
|
|
|
|
|
return {
|
|
|
|
|
url: new URL(specifier, parentURL).href
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Let Node.js handle all other specifiers.
|
|
|
|
|
return defaultResolve(specifier, context, defaultResolve);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function getFormat(url, context, defaultGetFormat) {
|
|
|
|
|
// This loader assumes all network-provided JavaScript is ES module code.
|
|
|
|
|
if (url.startsWith('https://')) {
|
|
|
|
|
return {
|
|
|
|
|
format: 'module'
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Let Node.js handle all other URLs.
|
|
|
|
|
return defaultGetFormat(url, context, defaultGetFormat);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function getSource(url, context, defaultGetSource) {
|
|
|
|
|
// For JavaScript to be loaded over the network, we need to fetch and
|
|
|
|
|
// return it.
|
|
|
|
|
if (url.startsWith('https://')) {
|
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
|
get(url, (res) => {
|
|
|
|
|
let data = '';
|
|
|
|
|
res.on('data', (chunk) => data += chunk);
|
|
|
|
|
res.on('end', () => resolve({ source: data }));
|
|
|
|
|
}).on('error', (err) => reject(err));
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Let Node.js handle all other URLs.
|
|
|
|
|
return defaultGetSource(url, context, defaultGetSource);
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
```js
|
|
|
|
|
// main.mjs
|
|
|
|
|
import { VERSION } from 'https://coffeescript.org/browser-compiler-modern/coffeescript.js';
|
|
|
|
|
|
|
|
|
|
console.log(VERSION);
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
With this loader, running:
|
|
|
|
|
|
|
|
|
|
```console
|
|
|
|
|
node --experimental-loader ./https-loader.mjs ./main.js
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
Will print the current version of CoffeeScript per the module at the URL in
|
|
|
|
|
`main.mjs`.
|
|
|
|
|
|
|
|
|
|
#### Transpiler loader
|
|
|
|
|
|
|
|
|
|
Sources that are in formats Node.js doesn’t understand can be converted into
|
|
|
|
|
JavaScript using the [`transformSource` hook][]. Before that hook gets called,
|
|
|
|
|
however, other hooks need to tell Node.js not to throw an error on unknown file
|
|
|
|
|
types; and to tell Node.js how to load this new file type.
|
|
|
|
|
|
|
|
|
|
This is obviously less performant than transpiling source files before running
|
|
|
|
|
Node.js; a transpiler loader should only be used for development and testing
|
|
|
|
|
purposes.
|
|
|
|
|
|
|
|
|
|
```js
|
|
|
|
|
// coffeescript-loader.mjs
|
|
|
|
|
import { URL, pathToFileURL } from 'url';
|
|
|
|
|
import CoffeeScript from 'coffeescript';
|
|
|
|
|
|
|
|
|
|
const baseURL = pathToFileURL(`${process.cwd()}/`).href;
|
|
|
|
|
|
|
|
|
|
// CoffeeScript files end in .coffee, .litcoffee or .coffee.md.
|
|
|
|
|
const extensionsRegex = /\.coffee$|\.litcoffee$|\.coffee\.md$/;
|
|
|
|
|
|
|
|
|
|
export function resolve(specifier, context, defaultResolve) {
|
|
|
|
|
const { parentURL = baseURL } = context;
|
|
|
|
|
|
|
|
|
|
// Node.js normally errors on unknown file extensions, so return a URL for
|
|
|
|
|
// specifiers ending in the CoffeeScript file extensions.
|
|
|
|
|
if (extensionsRegex.test(specifier)) {
|
|
|
|
|
return {
|
|
|
|
|
url: new URL(specifier, parentURL).href
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Let Node.js handle all other specifiers.
|
|
|
|
|
return defaultResolve(specifier, context, defaultResolve);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function getFormat(url, context, defaultGetFormat) {
|
|
|
|
|
// Now that we patched resolve to let CoffeeScript URLs through, we need to
|
|
|
|
|
// tell Node.js what format such URLs should be interpreted as. For the
|
|
|
|
|
// purposes of this loader, all CoffeeScript URLs are ES modules.
|
|
|
|
|
if (extensionsRegex.test(url)) {
|
|
|
|
|
return {
|
|
|
|
|
format: 'module'
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Let Node.js handle all other URLs.
|
|
|
|
|
return defaultGetFormat(url, context, defaultGetFormat);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function transformSource(source, context, defaultTransformSource) {
|
|
|
|
|
const { url, format } = context;
|
|
|
|
|
|
|
|
|
|
if (extensionsRegex.test(url)) {
|
|
|
|
|
return {
|
|
|
|
|
source: CoffeeScript.compile(source, { bare: true })
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Let Node.js handle all other sources.
|
|
|
|
|
return defaultTransformSource(source, context, defaultTransformSource);
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
```coffee
|
|
|
|
|
# main.coffee
|
|
|
|
|
import { scream } from './scream.coffee'
|
|
|
|
|
console.log scream 'hello, world'
|
|
|
|
|
|
|
|
|
|
import { version } from 'process'
|
|
|
|
|
console.log "Brought to you by Node.js version #{version}"
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
```coffee
|
|
|
|
|
# scream.coffee
|
|
|
|
|
export scream = (str) -> str.toUpperCase()
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
With this loader, running:
|
|
|
|
|
|
|
|
|
|
```console
|
|
|
|
|
node --experimental-loader ./coffeescript-loader.mjs main.coffee
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
Will cause `main.coffee` to be turned into JavaScript after its source code is
|
|
|
|
|
loaded from disk but before Node.js executes it; and so on for any `.coffee`,
|
|
|
|
|
`.litcoffee` or `.coffee.md` files referenced via `import` statements of any
|
|
|
|
|
loaded file.
|
|
|
|
|
|
2018-08-28 17:28:46 +02:00
|
|
|
|
## Resolution Algorithm
|
|
|
|
|
|
|
|
|
|
### Features
|
|
|
|
|
|
|
|
|
|
The resolver has the following properties:
|
|
|
|
|
|
|
|
|
|
* FileURL-based resolution as is used by ES modules
|
|
|
|
|
* Support for builtin module loading
|
|
|
|
|
* Relative and absolute URL resolution
|
|
|
|
|
* No default extensions
|
|
|
|
|
* No folder mains
|
|
|
|
|
* Bare specifier package resolution lookup through node_modules
|
|
|
|
|
|
|
|
|
|
### Resolver Algorithm
|
|
|
|
|
|
|
|
|
|
The algorithm to load an ES module specifier is given through the
|
|
|
|
|
**ESM_RESOLVE** method below. It returns the resolved URL for a
|
|
|
|
|
module specifier relative to a parentURL, in addition to the unique module
|
|
|
|
|
format for that resolved URL given by the **ESM_FORMAT** routine.
|
|
|
|
|
|
|
|
|
|
The _"module"_ format is returned for an ECMAScript Module, while the
|
|
|
|
|
_"commonjs"_ format is used to indicate loading through the legacy
|
2019-04-29 18:27:20 +02:00
|
|
|
|
CommonJS loader. Additional formats such as _"addon"_ can be extended in future
|
|
|
|
|
updates.
|
2018-08-28 17:28:46 +02:00
|
|
|
|
|
|
|
|
|
In the following algorithms, all subroutine errors are propagated as errors
|
2019-08-02 07:30:32 +02:00
|
|
|
|
of these top-level routines unless stated otherwise.
|
2018-08-28 17:28:46 +02:00
|
|
|
|
|
2019-10-14 01:27:39 +02:00
|
|
|
|
_defaultEnv_ is the conditional environment name priority array,
|
2019-12-05 03:14:48 +01:00
|
|
|
|
`["node", "import"]`.
|
2019-10-14 01:27:39 +02:00
|
|
|
|
|
2018-08-28 17:28:46 +02:00
|
|
|
|
<details>
|
|
|
|
|
<summary>Resolver algorithm specification</summary>
|
|
|
|
|
|
2019-12-18 16:28:11 +01:00
|
|
|
|
**ESM_RESOLVE**(_specifier_, _parentURL_)
|
2019-09-06 07:42:22 +02:00
|
|
|
|
|
2018-08-28 17:28:46 +02:00
|
|
|
|
> 1. Let _resolvedURL_ be **undefined**.
|
|
|
|
|
> 1. If _specifier_ is a valid URL, then
|
|
|
|
|
> 1. Set _resolvedURL_ to the result of parsing and reserializing
|
|
|
|
|
> _specifier_ as a URL.
|
|
|
|
|
> 1. Otherwise, if _specifier_ starts with _"/"_, then
|
|
|
|
|
> 1. Throw an _Invalid Specifier_ error.
|
|
|
|
|
> 1. Otherwise, if _specifier_ starts with _"./"_ or _"../"_, then
|
|
|
|
|
> 1. Set _resolvedURL_ to the URL resolution of _specifier_ relative to
|
|
|
|
|
> _parentURL_.
|
|
|
|
|
> 1. Otherwise,
|
|
|
|
|
> 1. Note: _specifier_ is now a bare specifier.
|
|
|
|
|
> 1. Set _resolvedURL_ the result of
|
|
|
|
|
> **PACKAGE_RESOLVE**(_specifier_, _parentURL_).
|
2019-08-02 07:30:32 +02:00
|
|
|
|
> 1. If _resolvedURL_ contains any percent encodings of _"/"_ or _"\\"_ (_"%2f"_
|
|
|
|
|
> and _"%5C"_ respectively), then
|
|
|
|
|
> 1. Throw an _Invalid Specifier_ error.
|
2018-08-28 17:28:46 +02:00
|
|
|
|
> 1. If the file at _resolvedURL_ does not exist, then
|
|
|
|
|
> 1. Throw a _Module Not Found_ error.
|
|
|
|
|
> 1. Set _resolvedURL_ to the real path of _resolvedURL_.
|
2019-12-18 16:28:11 +01:00
|
|
|
|
> 1. Let _format_ be the result of **ESM_FORMAT**(_resolvedURL_).
|
2018-08-28 17:28:46 +02:00
|
|
|
|
> 1. Load _resolvedURL_ as module format, _format_.
|
|
|
|
|
|
2019-07-28 23:58:10 +02:00
|
|
|
|
**PACKAGE_RESOLVE**(_packageSpecifier_, _parentURL_)
|
2019-09-06 07:42:22 +02:00
|
|
|
|
|
2018-08-28 17:28:46 +02:00
|
|
|
|
> 1. Let _packageName_ be *undefined*.
|
|
|
|
|
> 1. Let _packageSubpath_ be *undefined*.
|
|
|
|
|
> 1. If _packageSpecifier_ is an empty string, then
|
|
|
|
|
> 1. Throw an _Invalid Specifier_ error.
|
|
|
|
|
> 1. Otherwise,
|
|
|
|
|
> 1. If _packageSpecifier_ does not contain a _"/"_ separator, then
|
|
|
|
|
> 1. Throw an _Invalid Specifier_ error.
|
|
|
|
|
> 1. Set _packageName_ to the substring of _packageSpecifier_
|
|
|
|
|
> until the second _"/"_ separator or the end of the string.
|
2019-08-05 08:24:54 +02:00
|
|
|
|
> 1. If _packageName_ starts with _"."_ or contains _"\\"_ or _"%"_, then
|
|
|
|
|
> 1. Throw an _Invalid Specifier_ error.
|
|
|
|
|
> 1. Let _packageSubpath_ be _undefined_.
|
|
|
|
|
> 1. If the length of _packageSpecifier_ is greater than the length of
|
|
|
|
|
> _packageName_, then
|
|
|
|
|
> 1. Set _packageSubpath_ to _"."_ concatenated with the substring of
|
|
|
|
|
> _packageSpecifier_ from the position at the length of _packageName_.
|
2018-08-28 17:28:46 +02:00
|
|
|
|
> 1. If _packageSubpath_ contains any _"."_ or _".."_ segments or percent
|
2019-08-20 05:59:25 +02:00
|
|
|
|
> encoded strings for _"/"_ or _"\\"_, then
|
2018-08-28 17:28:46 +02:00
|
|
|
|
> 1. Throw an _Invalid Specifier_ error.
|
2019-12-17 07:58:19 +01:00
|
|
|
|
> 1. Set _selfUrl_ to the result of
|
|
|
|
|
> **SELF_REFERENCE_RESOLVE**(_packageName_, _packageSubpath_, _parentURL_).
|
|
|
|
|
> 1. If _selfUrl_ isn't empty, return _selfUrl_.
|
2019-08-05 08:24:54 +02:00
|
|
|
|
> 1. If _packageSubpath_ is _undefined_ and _packageName_ is a Node.js builtin
|
2018-08-28 17:28:46 +02:00
|
|
|
|
> module, then
|
|
|
|
|
> 1. Return the string _"node:"_ concatenated with _packageSpecifier_.
|
|
|
|
|
> 1. While _parentURL_ is not the file system root,
|
2018-12-11 17:27:13 +01:00
|
|
|
|
> 1. Let _packageURL_ be the URL resolution of _"node_modules/"_
|
2018-08-28 17:28:46 +02:00
|
|
|
|
> concatenated with _packageSpecifier_, relative to _parentURL_.
|
|
|
|
|
> 1. Set _parentURL_ to the parent folder URL of _parentURL_.
|
|
|
|
|
> 1. If the folder at _packageURL_ does not exist, then
|
|
|
|
|
> 1. Set _parentURL_ to the parent URL path of _parentURL_.
|
|
|
|
|
> 1. Continue the next loop iteration.
|
|
|
|
|
> 1. Let _pjson_ be the result of **READ_PACKAGE_JSON**(_packageURL_).
|
2019-08-05 08:24:54 +02:00
|
|
|
|
> 1. If _packageSubpath_ is _undefined__, then
|
2018-08-28 17:28:46 +02:00
|
|
|
|
> 1. Return the result of **PACKAGE_MAIN_RESOLVE**(_packageURL_,
|
|
|
|
|
> _pjson_).
|
|
|
|
|
> 1. Otherwise,
|
2018-12-11 17:27:13 +01:00
|
|
|
|
> 1. If _pjson_ is not **null** and _pjson_ has an _"exports"_ key, then
|
|
|
|
|
> 1. Let _exports_ be _pjson.exports_.
|
|
|
|
|
> 1. If _exports_ is not **null** or **undefined**, then
|
|
|
|
|
> 1. Return **PACKAGE_EXPORTS_RESOLVE**(_packageURL_,
|
2019-08-05 08:24:54 +02:00
|
|
|
|
> _packageSubpath_, _pjson.exports_).
|
|
|
|
|
> 1. Return the URL resolution of _packageSubpath_ in _packageURL_.
|
2018-08-28 17:28:46 +02:00
|
|
|
|
> 1. Throw a _Module Not Found_ error.
|
|
|
|
|
|
2019-12-17 07:58:19 +01:00
|
|
|
|
**SELF_REFERENCE_RESOLVE**(_packageName_, _packageSubpath_, _parentURL_)
|
2019-08-20 05:59:25 +02:00
|
|
|
|
|
|
|
|
|
> 1. Let _packageURL_ be the result of **READ_PACKAGE_SCOPE**(_parentURL_).
|
|
|
|
|
> 1. If _packageURL_ is **null**, then
|
2019-12-17 07:58:19 +01:00
|
|
|
|
> 1. Return **undefined**.
|
2019-08-20 05:59:25 +02:00
|
|
|
|
> 1. Let _pjson_ be the result of **READ_PACKAGE_JSON**(_packageURL_).
|
2019-12-17 07:58:19 +01:00
|
|
|
|
> 1. If _pjson_ does not include an _"exports"_ property, then
|
|
|
|
|
> 1. Return **undefined**.
|
|
|
|
|
> 1. If _pjson.name_ is equal to _packageName_, then
|
|
|
|
|
> 1. If _packageSubpath_ is _undefined_, then
|
|
|
|
|
> 1. Return the result of **PACKAGE_MAIN_RESOLVE**(_packageURL_, _pjson_).
|
|
|
|
|
> 1. Otherwise,
|
|
|
|
|
> 1. If _pjson_ is not **null** and _pjson_ has an _"exports"_ key, then
|
|
|
|
|
> 1. Let _exports_ be _pjson.exports_.
|
|
|
|
|
> 1. If _exports_ is not **null** or **undefined**, then
|
|
|
|
|
> 1. Return **PACKAGE_EXPORTS_RESOLVE**(_packageURL_, _subpath_,
|
|
|
|
|
> _pjson.exports_).
|
|
|
|
|
> 1. Return the URL resolution of _subpath_ in _packageURL_.
|
|
|
|
|
> 1. Otherwise, return **undefined**.
|
2019-08-20 05:59:25 +02:00
|
|
|
|
|
2019-07-28 23:58:10 +02:00
|
|
|
|
**PACKAGE_MAIN_RESOLVE**(_packageURL_, _pjson_)
|
2019-09-06 07:42:22 +02:00
|
|
|
|
|
2018-08-28 17:28:46 +02:00
|
|
|
|
> 1. If _pjson_ is **null**, then
|
|
|
|
|
> 1. Throw a _Module Not Found_ error.
|
2019-09-08 18:54:19 +02:00
|
|
|
|
> 1. If _pjson.exports_ is not **null** or **undefined**, then
|
2019-10-14 01:27:39 +02:00
|
|
|
|
> 1. If _exports_ is an Object with both a key starting with _"."_ and a key
|
2019-11-09 13:03:49 +01:00
|
|
|
|
> not starting with _"."_, throw an "Invalid Package Configuration" error.
|
2019-10-14 01:27:39 +02:00
|
|
|
|
> 1. If _pjson.exports_ is a String or Array, or an Object containing no
|
|
|
|
|
> keys starting with _"."_, then
|
|
|
|
|
> 1. Return **PACKAGE_EXPORTS_TARGET_RESOLVE**(_packageURL_,
|
|
|
|
|
> _pjson.exports_, _""_).
|
|
|
|
|
> 1. If _pjson.exports_ is an Object containing a _"."_ property, then
|
|
|
|
|
> 1. Let _mainExport_ be the _"."_ property in _pjson.exports_.
|
2019-09-30 21:55:59 +02:00
|
|
|
|
> 1. Return **PACKAGE_EXPORTS_TARGET_RESOLVE**(_packageURL_,
|
2019-10-14 01:27:39 +02:00
|
|
|
|
> _mainExport_, _""_).
|
2018-08-28 17:28:46 +02:00
|
|
|
|
> 1. If _pjson.main_ is a String, then
|
2019-08-02 07:30:32 +02:00
|
|
|
|
> 1. Let _resolvedMain_ be the URL resolution of _packageURL_, "/", and
|
2018-08-28 17:28:46 +02:00
|
|
|
|
> _pjson.main_.
|
|
|
|
|
> 1. If the file at _resolvedMain_ exists, then
|
|
|
|
|
> 1. Return _resolvedMain_.
|
|
|
|
|
> 1. If _pjson.type_ is equal to _"module"_, then
|
|
|
|
|
> 1. Throw a _Module Not Found_ error.
|
|
|
|
|
> 1. Let _legacyMainURL_ be the result applying the legacy
|
|
|
|
|
> **LOAD_AS_DIRECTORY** CommonJS resolver to _packageURL_, throwing a
|
|
|
|
|
> _Module Not Found_ error for no resolution.
|
|
|
|
|
> 1. Return _legacyMainURL_.
|
|
|
|
|
|
2018-12-11 17:27:13 +01:00
|
|
|
|
**PACKAGE_EXPORTS_RESOLVE**(_packageURL_, _packagePath_, _exports_)
|
2019-10-14 01:27:39 +02:00
|
|
|
|
> 1. If _exports_ is an Object with both a key starting with _"."_ and a key not
|
|
|
|
|
> starting with _"."_, throw an "Invalid Package Configuration" error.
|
|
|
|
|
> 1. If _exports_ is an Object and all keys of _exports_ start with _"."_, then
|
2018-12-11 17:27:13 +01:00
|
|
|
|
> 1. Set _packagePath_ to _"./"_ concatenated with _packagePath_.
|
|
|
|
|
> 1. If _packagePath_ is a key of _exports_, then
|
2019-10-10 00:49:08 +02:00
|
|
|
|
> 1. Let _target_ be the value of _exports\[packagePath\]_.
|
2019-08-02 07:30:32 +02:00
|
|
|
|
> 1. Return **PACKAGE_EXPORTS_TARGET_RESOLVE**(_packageURL_, _target_,
|
2019-10-14 01:27:39 +02:00
|
|
|
|
> _""_, _defaultEnv_).
|
2018-12-11 17:27:13 +01:00
|
|
|
|
> 1. Let _directoryKeys_ be the list of keys of _exports_ ending in
|
|
|
|
|
> _"/"_, sorted by length descending.
|
|
|
|
|
> 1. For each key _directory_ in _directoryKeys_, do
|
|
|
|
|
> 1. If _packagePath_ starts with _directory_, then
|
2019-10-10 00:49:08 +02:00
|
|
|
|
> 1. Let _target_ be the value of _exports\[directory\]_.
|
2018-12-11 17:27:13 +01:00
|
|
|
|
> 1. Let _subpath_ be the substring of _target_ starting at the index
|
|
|
|
|
> of the length of _directory_.
|
2019-08-02 07:30:32 +02:00
|
|
|
|
> 1. Return **PACKAGE_EXPORTS_TARGET_RESOLVE**(_packageURL_, _target_,
|
2019-10-14 01:27:39 +02:00
|
|
|
|
> _subpath_, _defaultEnv_).
|
2019-08-02 07:30:32 +02:00
|
|
|
|
> 1. Throw a _Module Not Found_ error.
|
|
|
|
|
|
2019-10-14 01:27:39 +02:00
|
|
|
|
**PACKAGE_EXPORTS_TARGET_RESOLVE**(_packageURL_, _target_, _subpath_, _env_)
|
2019-09-06 07:42:22 +02:00
|
|
|
|
|
2019-08-02 07:30:32 +02:00
|
|
|
|
> 1. If _target_ is a String, then
|
|
|
|
|
> 1. If _target_ does not start with _"./"_, throw a _Module Not Found_
|
|
|
|
|
> error.
|
|
|
|
|
> 1. If _subpath_ has non-zero length and _target_ does not end with _"/"_,
|
|
|
|
|
> throw a _Module Not Found_ error.
|
|
|
|
|
> 1. If _target_ or _subpath_ contain any _"node_modules"_ segments including
|
|
|
|
|
> _"node_modules"_ percent-encoding, throw a _Module Not Found_ error.
|
|
|
|
|
> 1. Let _resolvedTarget_ be the URL resolution of the concatenation of
|
|
|
|
|
> _packageURL_ and _target_.
|
|
|
|
|
> 1. If _resolvedTarget_ is contained in _packageURL_, then
|
|
|
|
|
> 1. Let _resolved_ be the URL resolution of the concatenation of
|
|
|
|
|
> _subpath_ and _resolvedTarget_.
|
|
|
|
|
> 1. If _resolved_ is contained in _resolvedTarget_, then
|
|
|
|
|
> 1. Return _resolved_.
|
2019-10-14 01:27:39 +02:00
|
|
|
|
> 1. Otherwise, if _target_ is a non-null Object, then
|
2019-12-17 20:00:21 +01:00
|
|
|
|
> 1. If _exports_ contains any index property keys, as defined in ECMA-262
|
|
|
|
|
> [6.1.7 Array Index][], throw an _Invalid Package Configuration_ error.
|
|
|
|
|
> 1. For each property _p_ of _target_, in object insertion order as,
|
|
|
|
|
> 1. If _env_ contains an entry for _p_, then
|
|
|
|
|
> 1. Let _targetValue_ be the value of the _p_ property in _target_.
|
|
|
|
|
> 1. Let _resolved_ be the result of **PACKAGE_EXPORTS_TARGET_RESOLVE**
|
|
|
|
|
> (_packageURL_, _targetValue_, _subpath_, _env_).
|
|
|
|
|
> 1. Assert: _resolved_ is a String.
|
|
|
|
|
> 1. Return _resolved_.
|
2019-08-02 07:30:32 +02:00
|
|
|
|
> 1. Otherwise, if _target_ is an Array, then
|
|
|
|
|
> 1. For each item _targetValue_ in _target_, do
|
2019-10-14 01:27:39 +02:00
|
|
|
|
> 1. If _targetValue_ is an Array, continue the loop.
|
2019-08-02 07:30:32 +02:00
|
|
|
|
> 1. Let _resolved_ be the result of
|
|
|
|
|
> **PACKAGE_EXPORTS_TARGET_RESOLVE**(_packageURL_, _targetValue_,
|
2019-10-14 01:27:39 +02:00
|
|
|
|
> _subpath_, _env_), continuing the loop on abrupt completion.
|
2019-08-02 07:30:32 +02:00
|
|
|
|
> 1. Assert: _resolved_ is a String.
|
|
|
|
|
> 1. Return _resolved_.
|
2018-12-11 17:27:13 +01:00
|
|
|
|
> 1. Throw a _Module Not Found_ error.
|
|
|
|
|
|
2019-12-18 16:28:11 +01:00
|
|
|
|
**ESM_FORMAT**(_url_)
|
2019-09-06 07:42:22 +02:00
|
|
|
|
|
2019-12-18 16:28:11 +01:00
|
|
|
|
> 1. Assert: _url_ corresponds to an existing file pathname.
|
2018-08-28 17:28:46 +02:00
|
|
|
|
> 1. Let _pjson_ be the result of **READ_PACKAGE_SCOPE**(_url_).
|
|
|
|
|
> 1. If _url_ ends in _".mjs"_, then
|
|
|
|
|
> 1. Return _"module"_.
|
|
|
|
|
> 1. If _url_ ends in _".cjs"_, then
|
|
|
|
|
> 1. Return _"commonjs"_.
|
|
|
|
|
> 1. If _pjson?.type_ exists and is _"module"_, then
|
2019-12-18 16:28:11 +01:00
|
|
|
|
> 1. If _url_ ends in _".js"_ or lacks a file extension, then
|
2018-08-28 17:28:46 +02:00
|
|
|
|
> 1. Return _"module"_.
|
|
|
|
|
> 1. Throw an _Unsupported File Extension_ error.
|
|
|
|
|
> 1. Otherwise,
|
2019-12-18 16:28:11 +01:00
|
|
|
|
> 1. If _url_ lacks a file extension, then
|
2018-08-28 17:28:46 +02:00
|
|
|
|
> 1. Return _"commonjs"_.
|
|
|
|
|
> 1. Throw an _Unsupported File Extension_ error.
|
|
|
|
|
|
2019-07-28 23:58:10 +02:00
|
|
|
|
**READ_PACKAGE_SCOPE**(_url_)
|
2019-09-06 07:42:22 +02:00
|
|
|
|
|
2018-08-28 17:28:46 +02:00
|
|
|
|
> 1. Let _scopeURL_ be _url_.
|
|
|
|
|
> 1. While _scopeURL_ is not the file system root,
|
2019-08-02 07:30:32 +02:00
|
|
|
|
> 1. If _scopeURL_ ends in a _"node_modules"_ path segment, return **null**.
|
2018-08-28 17:28:46 +02:00
|
|
|
|
> 1. Let _pjson_ be the result of **READ_PACKAGE_JSON**(_scopeURL_).
|
|
|
|
|
> 1. If _pjson_ is not **null**, then
|
|
|
|
|
> 1. Return _pjson_.
|
|
|
|
|
> 1. Set _scopeURL_ to the parent URL of _scopeURL_.
|
|
|
|
|
> 1. Return **null**.
|
|
|
|
|
|
2019-07-28 23:58:10 +02:00
|
|
|
|
**READ_PACKAGE_JSON**(_packageURL_)
|
2019-09-06 07:42:22 +02:00
|
|
|
|
|
2018-08-28 17:28:46 +02:00
|
|
|
|
> 1. Let _pjsonURL_ be the resolution of _"package.json"_ within _packageURL_.
|
|
|
|
|
> 1. If the file at _pjsonURL_ does not exist, then
|
|
|
|
|
> 1. Return **null**.
|
|
|
|
|
> 1. If the file at _packageURL_ does not parse as valid JSON, then
|
|
|
|
|
> 1. Throw an _Invalid Package Configuration_ error.
|
|
|
|
|
> 1. Return the parsed JSON source of the file at _pjsonURL_.
|
|
|
|
|
|
|
|
|
|
</details>
|
|
|
|
|
|
|
|
|
|
### Customizing ESM specifier resolution algorithm
|
|
|
|
|
|
|
|
|
|
The current specifier resolution does not support all default behavior of
|
|
|
|
|
the CommonJS loader. One of the behavior differences is automatic resolution
|
|
|
|
|
of file extensions and the ability to import directories that have an index
|
|
|
|
|
file.
|
|
|
|
|
|
2019-11-27 07:54:23 +01:00
|
|
|
|
The `--experimental-specifier-resolution=[mode]` flag can be used to customize
|
2018-08-28 17:28:46 +02:00
|
|
|
|
the extension resolution algorithm. The default mode is `explicit`, which
|
|
|
|
|
requires the full path to a module be provided to the loader. To enable the
|
|
|
|
|
automatic extension resolution and importing from directories that include an
|
|
|
|
|
index file use the `node` mode.
|
|
|
|
|
|
|
|
|
|
```bash
|
2019-10-11 23:57:13 +02:00
|
|
|
|
$ node index.mjs
|
2018-08-28 17:28:46 +02:00
|
|
|
|
success!
|
2019-10-11 23:57:13 +02:00
|
|
|
|
$ node index # Failure!
|
2018-08-28 17:28:46 +02:00
|
|
|
|
Error: Cannot find module
|
2019-11-27 07:54:23 +01:00
|
|
|
|
$ node --experimental-specifier-resolution=node index
|
2018-08-28 17:28:46 +02:00
|
|
|
|
success!
|
|
|
|
|
```
|
|
|
|
|
|
2019-10-31 07:59:32 +01:00
|
|
|
|
[Babel]: https://babeljs.io/
|
2019-08-21 11:03:00 +02:00
|
|
|
|
[CommonJS]: modules.html
|
2019-10-14 01:27:39 +02:00
|
|
|
|
[Conditional Exports]: #esm_conditional_exports
|
2019-08-21 11:03:00 +02:00
|
|
|
|
[ECMAScript-modules implementation]: https://github.com/nodejs/modules/blob/master/doc/plan-for-new-modules-implementation.md
|
|
|
|
|
[ES Module Integration Proposal for Web Assembly]: https://github.com/webassembly/esm-integration
|
|
|
|
|
[Node.js EP for ES Modules]: https://github.com/nodejs/node-eps/blob/master/002-es-modules.md
|
2019-11-09 20:54:13 +01:00
|
|
|
|
[Package Exports]: #esm_package_exports
|
2019-07-09 23:03:07 +02:00
|
|
|
|
[Terminology]: #esm_terminology
|
2019-08-21 11:03:00 +02:00
|
|
|
|
[WHATWG JSON modules specification]: https://html.spec.whatwg.org/#creating-a-json-module-script
|
2019-11-17 08:35:16 +01:00
|
|
|
|
[`"exports"` field]: #esm_package_exports
|
2019-07-09 23:03:07 +02:00
|
|
|
|
[`data:` URLs]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URIs
|
2019-10-31 07:59:32 +01:00
|
|
|
|
[`esm`]: https://github.com/standard-things/esm#readme
|
2018-08-28 17:28:46 +02:00
|
|
|
|
[`export`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/export
|
2019-12-15 04:27:48 +01:00
|
|
|
|
[`getFormat` hook]: #esm_code_getformat_code_hook
|
2018-08-28 17:28:46 +02:00
|
|
|
|
[`import()`]: #esm_import-expressions
|
|
|
|
|
[`import.meta.url`]: #esm_import_meta
|
2019-08-21 11:03:00 +02:00
|
|
|
|
[`import`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import
|
2019-05-18 15:17:53 +02:00
|
|
|
|
[`module.createRequire()`]: modules.html#modules_module_createrequire_filename
|
2019-09-27 19:12:18 +02:00
|
|
|
|
[`module.syncBuiltinESMExports()`]: modules.html#modules_module_syncbuiltinesmexports
|
2019-12-15 04:27:48 +01:00
|
|
|
|
[`transformSource` hook]: #esm_code_transformsource_code_hook
|
|
|
|
|
[dynamic instantiate hook]: #esm_code_dynamicinstantiate_code_hook
|
2019-08-21 11:03:00 +02:00
|
|
|
|
[special scheme]: https://url.spec.whatwg.org/#special-scheme
|
2018-08-28 17:28:46 +02:00
|
|
|
|
[the official standard format]: https://tc39.github.io/ecma262/#sec-modules
|
2019-12-15 04:27:48 +01:00
|
|
|
|
[transpiler loader example]: #esm_transpiler_loader
|
2019-12-17 20:00:21 +01:00
|
|
|
|
[6.1.7 Array Index]: https://tc39.es/ecma262/#integer-index
|