diff --git a/.eslintrc.js b/.eslintrc.js
index 475c75ff96..a720db8c45 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -38,6 +38,8 @@ module.exports = {
{
files: [
'doc/api/esm.md',
+ 'test/es-module/test-esm-type-flag.js',
+ 'test/es-module/test-esm-type-flag-alias.js',
'*.mjs',
'test/es-module/test-esm-example-loader.js',
],
diff --git a/doc/api/cli.md b/doc/api/cli.md
index 7e7ef3e285..4939574e88 100644
--- a/doc/api/cli.md
+++ b/doc/api/cli.md
@@ -511,6 +511,18 @@ added: v2.4.0
Track heap object allocations for heap snapshots.
+### `-m`, `--type=type`
+
+Used with `--experimental-modules`, this configures Node.js to interpret the
+initial entry point as CommonJS or as an ES module.
+
+Valid values are `"commonjs"` and `"module"`. The default is to infer from
+the file extension and the `"type"` field in the nearest parent `package.json`.
+
+Works for executing a file as well as `--eval`, `--print`, `STDIN`.
+
+`-m` is an alias for `--type=module`.
+
### `--use-bundled-ca`, `--use-openssl-ca`
Node.js contains support for ES Modules based upon the
-[Node.js EP for ES Modules][].
+[Node.js EP for ES Modules][] and the [ecmascript-modules implementation][].
-Not all features of the EP are complete and will be landing as both VM support
-and implementation is ready. Error messages are still being polished.
+Expect major changes in the implementation including interoperability support,
+specifier resolution, and default behavior.
## Enabling
-The `--experimental-modules` flag can be used to enable features for loading
-ESM modules.
+The `--experimental-modules` flag can be used to enable support for
+ECMAScript modules (ES modules).
-Once this has been set, files ending with `.mjs` will be able to be loaded
-as ES Modules.
+## Running Node.js with an ECMAScript Module
+
+There are a few ways to start Node.js with an ES module as its input.
+
+### Initial entry point with an .mjs extension
+
+A file ending with `.mjs` passed to Node.js as an initial entry point will be
+loaded as an ES module.
```sh
node --experimental-modules my-app.mjs
```
-## Features
+### --type=module / -m flag
-
+Files ending with `.js` or `.mjs`, or lacking any extension,
+will be loaded as ES modules when the `--type=module` flag is set.
+This flag also has a shorthand alias `-m`.
+
+```sh
+node --experimental-modules --type=module my-app.js
+# or
+node --experimental-modules -m my-app.js
+```
+
+For completeness there is also `--type=commonjs`, for explicitly running a `.js`
+file as CommonJS. This is the default behavior if `--type` or `-m` is
+unspecified.
+
+The `--type=module` or `-m` flags can also be used to configure Node.js to treat
+as an ES module input sent in via `--eval` or `--print` (or `-e` or `-p`) or
+piped to Node.js via `STDIN`.
+
+```sh
+node --experimental-modules --type=module --eval \
+ "import { sep } from 'path'; console.log(sep);"
+
+echo "import { sep } from 'path'; console.log(sep);" | \
+ node --experimental-modules --type=module
+```
+
+### package.json"type" field
+
+Files ending with `.js` or `.mjs`, 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"`.
+
+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.
+
+
+```js
+// package.json
+{
+ "type": "module"
+}
+```
+
+```sh
+# In same folder as above package.json
+node --experimental-modules my-app.js # Runs as ES module
+```
-### Supported
+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 deafult, a `package.json` with no `"type"`
+field.
-Only the CLI argument for the main entry point to the program can be an entry
-point into an ESM graph. Dynamic import can also be used to create entry points
-into ESM graphs at runtime.
+## Package Scope and File Extensions
-#### import.meta
+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"`.
+
+The package scope applies not only to initial entry points (`node
+--experimental-modules my-app.js`) but also to files referenced by `import`
+statements and `import()` expressions.
+
+```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
+```
+
+You can use the `.mjs` and `.cjs` extensions to mix types within the same
+package scope:
+
+- Within a `"type": "module"` package scope, Node.js can be instructed to
+ 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).
+
+- Within a `"type": "commonjs"` package scope, Node.js can be instructed to
+ 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).
+
+## Package Entry Points
+
+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`.
+
+
+```js
+// ./node_modules/es-module-package/package.json
+{
+ "type": "module",
+ "main": "./src/index.js"
+}
+```
+```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.
+
+> Currently a package can define _either_ a CommonJS entry point **or** an ES
+> module entry point; there is no way to specify separate entry points for
+> CommonJS and ES module usage. This means that a package entry point can be
+> included via `require` or via `import` but not both.
+>
+> Such a limitation makes it difficult for packages to support both new versions
+> of Node.js that understand ES modules and older versions of Node.js that
+> understand only CommonJS. There is work ongoing to remove this limitation, and
+> it will very likely entail changes to the behavior of `"main"` as defined
+> here.
+
+## import Specifiers
+
+### Terminology
+
+The _specifier_ of an `import` statement is the string after the `from` keyword,
+e.g. `'path'` in `import { sep } from 'path'`. Specifiers are also used in
+`export from` statements, and as the argument to an `import()` expression.
+
+There are four types of specifiers:
+
+- _Bare specifiers_ like `'some-package'`. They refer to an entry point of a
+ package by the package name.
+
+- _Deep import specifiers_ like `'some-package/lib/shuffle.mjs'`. They refer to
+ a path within a package prefixed by the package name.
+
+- _Relative specifiers_ like `'./startup.js'` or `'../config.mjs'`. They refer
+ to a path relative to the location of the importing file.
+
+- _Absolute specifiers_ like `'file:///opt/nodejs/config.js'`. They refer
+ 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.
+
+Only `file://` URLs are supported. A specifier like
+`'https://example.com/app.js'` may be supported by browsers but it is not
+supported in Node.js.
+
+Specifiers may not begin with `/` or `//`. These are reserved for potential
+future use. The root of the current volume may be referenced via `file:///`.
+
+## import.meta
* {Object}
@@ -46,63 +233,128 @@ property:
* `url` {string} The absolute `file:` URL of the module.
-### Unsupported
+## Differences Between ES Modules and CommonJS
-| Feature | Reason |
-| --- | --- |
-| `require('./foo.mjs')` | ES Modules have differing resolution and timing, use dynamic import |
+### Mandatory file extensions
+
+You must provide a file extension when using the `import` keyword. Directory
+indexes (e.g. `'./startup/index.js'`) must also be fully specified.
-## Notable differences between `import` and `require`
+This behavior matches how `import` behaves in browser environments, assuming a
+typically configured server.
-### No NODE_PATH
+### No NODE_PATH
`NODE_PATH` is not part of resolving `import` specifiers. Please use symlinks
if this behavior is desired.
-### No `require.extensions`
+### No require, exports, module.exports, \_\_filename, \_\_dirname
+
+These CommonJS variables are not available in ES modules.
+
+`require` can be imported into an ES module using
+[`module.createRequireFromPath()`][].
+
+An equivalent for `__filename` and `__dirname` is [`import.meta.url`][].
+
+### No require.extensions
`require.extensions` is not used by `import`. The expectation is that loader
hooks can provide this workflow in the future.
-### No `require.cache`
+### No require.cache
`require.cache` is not used by `import`. It has a separate cache.
-### URL based paths
+### URL-based paths
-ESM 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.
+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.
Modules will be loaded multiple times if the `import` specifier used to resolve
them have a different query or fragment.
```js
-import './foo?query=1'; // loads ./foo with query of "?query=1"
-import './foo?query=2'; // loads ./foo with query of "?query=2"
+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"
```
For now, only modules using the `file:` protocol can be loaded.
-## Interop with existing modules
+## Interoperability with CommonJS
+
+### require
+
+`require` always treats the files it references as CommonJS. This applies
+whether `require` is used the traditional way within a CommonJS environment, or
+in an ES module environment using [`module.createRequireFromPath()`][].
+
+To include an ES module into CommonJS, use [`import()`][].
+
+### import statements
+
+An `import` statement can reference either ES module or CommonJS JavaScript.
+Other file types such as JSON and Native modules are not supported. For those,
+use [`module.createRequireFromPath()`][].
+
+`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
+the package name.
+
+```js
+import { sin, cos } from 'geometry/trigonometry-functions.mjs';
+```
+
+> Currently only the “default export” is supported for CommonJS files or
+> packages:
+>
+>
+> ```js
+> import packageMain from 'commonjs-package'; // Works
+>
+> import { method } from 'commonjs-package'; // Errors
+> ```
+>
+> There are ongoing efforts to make the latter code possible.
+
+### import() expressions
-All CommonJS, JSON, and C++ modules can be used with `import`.
+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');
+})();
+```
-Modules loaded this way will only be loaded once, even if their query
-or fragment string differs between `import` statements.
+## CommonJS, JSON, and Native Modules
-When loaded via `import` these modules will provide a single `default` export
-representing the value of `module.exports` at the time they finished evaluating.
+CommonJS, JSON, and Native modules can be used with [`module.createRequireFromPath()`][].
```js
-// foo.js
-module.exports = { one: 1 };
+// cjs.js
+module.exports = 'cjs';
-// bar.mjs
-import foo from './foo.js';
-foo.one === 1; // true
+// esm.mjs
+import { createRequireFromPath as createRequire } from 'module';
+import { fileURLToPath as fromURL } from 'url';
+
+const require = createRequire(fromURL(import.meta.url));
+
+const cjs = require('./cjs');
+cjs === 'cjs'; // true
```
+## Builtin modules
+
Builtin modules will provide named exports of their public API, as well as a
default export which can be used for, among other things, modifying the named
exports. Named exports of builtin modules are updated when the corresponding
@@ -132,7 +384,9 @@ fs.readFileSync = () => Buffer.from('Hello, ESM');
fs.readFileSync === readFileSync;
```
-## Loader hooks
+## Experimental Loader hooks
+
+**Note: This API is currently being redesigned and will still change.**.
@@ -173,11 +427,9 @@ module. This can be one of the following:
| `format` | Description |
| --- | --- |
-| `'esm'` | Load a standard JavaScript module |
-| `'cjs'` | Load a node-style CommonJS module |
-| `'builtin'` | Load a node builtin CommonJS module |
-| `'json'` | Load a JSON file |
-| `'addon'` | Load a [C++ Addon][addons] |
+| `'module'` | Load a standard JavaScript module |
+| `'commonjs'` | Load a Node.js CommonJS module |
+| `'builtin'` | Load a Node.js builtin module |
| `'dynamic'` | Use a [dynamic instantiate hook][] |
For example, a dummy loader to load JavaScript restricted to browser resolution
@@ -253,6 +505,165 @@ With the list of module exports provided upfront, the `execute` function will
then be called at the exact point of module evaluation order for that module
in the import tree.
+## 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
+CommonJS loader. Additional formats such as _"wasm"_ or _"addon"_ can be
+extended in future updates.
+
+In the following algorithms, all subroutine errors are propogated as errors
+of these top-level routines.
+
+_isMain_ is **true** when resolving the Node.js application entry point.
+
+If the top-level `--type` is _"commonjs"_, then the ESM resolver is skipped
+entirely for the CommonJS loader.
+
+If the top-level `--type` is _"module"_, then the ESM resolver is used
+as described here, with the conditional `--type` check in **ESM_FORMAT**.
+
+
+Resolver algorithm psuedocode
+
+**ESM_RESOLVE(_specifier_, _parentURL_, _isMain_)**
+> 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_).
+> 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_.
+> 1. Let _format_ be the result of **ESM_FORMAT**(_resolvedURL_, _isMain_).
+> 1. Load _resolvedURL_ as module format, _format_.
+
+PACKAGE_RESOLVE(_packageSpecifier_, _parentURL_)
+> 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. If _packageSpecifier_ does not start with _"@"_, then
+> 1. Set _packageName_ to the substring of _packageSpecifier_ until the
+> first _"/"_ separator or the end of the string.
+> 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.
+> 1. Let _packageSubpath_ be the substring of _packageSpecifier_ from the
+> position at the length of _packageName_ plus one, if any.
+> 1. Assert: _packageName_ is a valid package name or scoped package name.
+> 1. Assert: _packageSubpath_ is either empty, or a path without a leading
+> separator.
+> 1. If _packageSubpath_ contains any _"."_ or _".."_ segments or percent
+> encoded strings for _"/"_ or _"\"_ then,
+> 1. Throw an _Invalid Specifier_ error.
+> 1. If _packageSubpath_ is empty and _packageName_ is a Node.js builtin
+> module, then
+> 1. Return the string _"node:"_ concatenated with _packageSpecifier_.
+> 1. While _parentURL_ is not the file system root,
+> 1. Set _parentURL_ to the parent folder URL of _parentURL_.
+> 1. Let _packageURL_ be the URL resolution of the string concatenation of
+> _parentURL_, _"/node_modules/"_ and _packageSpecifier_.
+> 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_).
+> 1. If _packageSubpath_ is empty, then
+> 1. Return the result of **PACKAGE_MAIN_RESOLVE**(_packageURL_,
+> _pjson_).
+> 1. Otherwise,
+> 1. Return the URL resolution of _packageSubpath_ in _packageURL_.
+> 1. Throw a _Module Not Found_ error.
+
+PACKAGE_MAIN_RESOLVE(_packageURL_, _pjson_)
+> 1. If _pjson_ is **null**, then
+> 1. Throw a _Module Not Found_ error.
+> 1. If _pjson.main_ is a String, then
+> 1. Let _resolvedMain_ be the concatenation of _packageURL_, "/", and
+> _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. If _legacyMainURL_ does not end in _".js"_ then,
+> 1. Throw an _Unsupported File Extension_ error.
+> 1. Return _legacyMainURL_.
+
+**ESM_FORMAT(_url_, _isMain_)**
+> 1. Assert: _url_ corresponds to an existing file.
+> 1. If _isMain_ is **true** and the `--type` flag is _"module"_, then
+> 1. If _url_ ends with _".cjs"_, then
+> 1. Throw a _Type Mismatch_ error.
+> 1. Return _"module"_.
+> 1. Let _pjson_ be the result of **READ_PACKAGE_SCOPE**(_url_).
+> 1. If _pjson_ is **null** and _isMain_ is **true**, then
+> 1. If _url_ ends in _".mjs"_, then
+> 1. Return _"module"_.
+> 1. Return _"commonjs"_.
+> 1. If _pjson.type_ exists and is _"module"_, then
+> 1. If _url_ ends in _".cjs"_, then
+> 1. Return _"commonjs"_.
+> 1. Return _"module"_.
+> 1. Otherwise,
+> 1. If _url_ ends in _".mjs"_, then
+> 1. Return _"module"_.
+> 1. If _url_ does not end in _".js"_, then
+> 1. Throw an _Unsupported File Extension_ error.
+> 1. Return _"commonjs"_.
+
+READ_PACKAGE_SCOPE(_url_)
+> 1. Let _scopeURL_ be _url_.
+> 1. While _scopeURL_ is not the file system root,
+> 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**.
+
+READ_PACKAGE_JSON(_packageURL_)
+> 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_.
+
+
+
[Node.js EP for ES Modules]: https://github.com/nodejs/node-eps/blob/master/002-es-modules.md
-[addons]: addons.html
[dynamic instantiate hook]: #esm_dynamic_instantiate_hook
+[`module.createRequireFromPath()`]: modules.html#modules_module_createrequirefrompath_filename
+[`import.meta.url`]: #esm_import_meta
+[`import()`]: #esm_import-expressions
+[ecmascript-modules implementation]: https://github.com/nodejs/modules/blob/master/doc/plan-for-new-modules-implementation.md
diff --git a/doc/node.1 b/doc/node.1
index 6945654689..525c8b4806 100644
--- a/doc/node.1
+++ b/doc/node.1
@@ -270,6 +270,9 @@ Print stack traces for process warnings (including deprecations).
.It Fl -track-heap-objects
Track heap object allocations for heap snapshots.
.
+.It Fl -type Ns = Ns Ar type
+Set the top-level module resolution type.
+.
.It Fl -use-bundled-ca , Fl -use-openssl-ca
Use bundled Mozilla CA store as supplied by current Node.js version or use OpenSSL's default CA store.
The default store is selectable at build-time.
diff --git a/lib/internal/errors.js b/lib/internal/errors.js
index a76153297e..d2bc107c6f 100644
--- a/lib/internal/errors.js
+++ b/lib/internal/errors.js
@@ -774,6 +774,8 @@ E('ERR_INVALID_OPT_VALUE', (name, value) =>
RangeError);
E('ERR_INVALID_OPT_VALUE_ENCODING',
'The value "%s" is invalid for option "encoding"', TypeError);
+E('ERR_INVALID_PACKAGE_CONFIG',
+ 'Invalid package config in \'%s\' imported from %s', Error);
E('ERR_INVALID_PERFORMANCE_MARK',
'The "%s" performance mark has not been set', Error);
E('ERR_INVALID_PROTOCOL',
@@ -781,6 +783,8 @@ E('ERR_INVALID_PROTOCOL',
TypeError);
E('ERR_INVALID_REPL_EVAL_CONFIG',
'Cannot specify both "breakEvalOnSigint" and "eval" for REPL', TypeError);
+E('ERR_INVALID_REPL_TYPE',
+ 'Cannot specify --type for REPL', TypeError);
E('ERR_INVALID_RETURN_PROPERTY', (input, name, prop, value) => {
return `Expected a valid ${input} to be returned for the "${prop}" from the` +
` "${name}" function but got ${value}.`;
@@ -811,6 +815,9 @@ E('ERR_INVALID_SYNC_FORK_INPUT',
TypeError);
E('ERR_INVALID_THIS', 'Value of "this" must be of type %s', TypeError);
E('ERR_INVALID_TUPLE', '%s must be an iterable %s tuple', TypeError);
+E('ERR_INVALID_TYPE_FLAG',
+ 'Type flag must be one of "module", "commonjs". Received --type=%s',
+ TypeError);
E('ERR_INVALID_URI', 'URI malformed', URIError);
E('ERR_INVALID_URL', 'Invalid URL: %s', TypeError);
E('ERR_INVALID_URL_SCHEME',
@@ -865,11 +872,6 @@ E('ERR_MISSING_ARGS',
E('ERR_MISSING_DYNAMIC_INSTANTIATE_HOOK',
'The ES Module loader may not return a format of \'dynamic\' when no ' +
'dynamicInstantiate function was provided', Error);
-E('ERR_MISSING_MODULE', 'Cannot find module %s', Error);
-E('ERR_MODULE_RESOLUTION_LEGACY',
- '%s not found by import in %s.' +
- ' Legacy behavior in require() would have found it at %s',
- Error);
E('ERR_MULTIPLE_CALLBACK', 'Callback called multiple times', Error);
E('ERR_NAPI_CONS_FUNCTION', 'Constructor must be a function', TypeError);
E('ERR_NAPI_INVALID_DATAVIEW_ARGS',
@@ -954,6 +956,20 @@ E('ERR_TRANSFORM_ALREADY_TRANSFORMING',
E('ERR_TRANSFORM_WITH_LENGTH_0',
'Calling transform done when writableState.length != 0', Error);
E('ERR_TTY_INIT_FAILED', 'TTY initialization failed', SystemError);
+E('ERR_TYPE_MISMATCH', (filename, ext, typeFlag, conflict) => {
+ const typeString =
+ typeFlag === 'module' ? '--type=module or -m' : '--type=commonjs';
+ // --type mismatches file extension
+ if (conflict === 'extension')
+ return `Extension ${ext} is not supported for ` +
+ `${typeString} loading ${filename}`;
+ // --type mismatches package.json "type"
+ else if (conflict === 'scope')
+ return `Cannot use ${typeString} because nearest parent package.json ` +
+ ((typeFlag === 'module') ?
+ 'includes "type": "commonjs"' : 'includes "type": "module",') +
+ ` which controls the type to use for ${filename}`;
+}, TypeError);
E('ERR_UNCAUGHT_EXCEPTION_CAPTURE_ALREADY_SET',
'`process.setupUncaughtExceptionCapture()` was called while a capture ' +
'callback was already active',
@@ -967,11 +983,11 @@ E('ERR_UNHANDLED_ERROR',
if (err === undefined) return msg;
return `${msg} (${err})`;
}, Error);
+// This should probably be a `TypeError`.
E('ERR_UNKNOWN_CREDENTIAL', '%s identifier does not exist: %s', Error);
E('ERR_UNKNOWN_ENCODING', 'Unknown encoding: %s', TypeError);
-
+E('ERR_UNKNOWN_FILE_EXTENSION', 'Unknown file extension: %s', TypeError);
// This should probably be a `TypeError`.
-E('ERR_UNKNOWN_FILE_EXTENSION', 'Unknown file extension: %s', Error);
E('ERR_UNKNOWN_MODULE_FORMAT', 'Unknown module format: %s', RangeError);
E('ERR_UNKNOWN_SIGNAL', 'Unknown signal: %s', TypeError);
diff --git a/lib/internal/main/check_syntax.js b/lib/internal/main/check_syntax.js
index 7df70b2720..2795f5766e 100644
--- a/lib/internal/main/check_syntax.js
+++ b/lib/internal/main/check_syntax.js
@@ -11,12 +11,18 @@ const {
readStdin
} = require('internal/process/execution');
-const CJSModule = require('internal/modules/cjs/loader');
+const { pathToFileURL } = require('url');
+
const vm = require('vm');
const {
stripShebang, stripBOM
} = require('internal/modules/cjs/helpers');
+let CJSModule;
+function CJSModuleInit() {
+ if (!CJSModule)
+ CJSModule = require('internal/modules/cjs/loader');
+}
if (process.argv[1] && process.argv[1] !== '-') {
// Expand process.argv[1] into a full path.
@@ -25,7 +31,7 @@ if (process.argv[1] && process.argv[1] !== '-') {
// TODO(joyeecheung): not every one of these are necessary
prepareMainThreadExecution();
-
+ CJSModuleInit();
// Read the source.
const filename = CJSModule._resolveFilename(process.argv[1]);
@@ -34,20 +40,40 @@ if (process.argv[1] && process.argv[1] !== '-') {
markBootstrapComplete();
- checkScriptSyntax(source, filename);
+ checkSyntax(source, filename);
} else {
// TODO(joyeecheung): not every one of these are necessary
prepareMainThreadExecution();
+ CJSModuleInit();
markBootstrapComplete();
readStdin((code) => {
- checkScriptSyntax(code, '[stdin]');
+ checkSyntax(code, '[stdin]');
});
}
-function checkScriptSyntax(source, filename) {
+function checkSyntax(source, filename) {
// Remove Shebang.
source = stripShebang(source);
+
+ const experimentalModules =
+ require('internal/options').getOptionValue('--experimental-modules');
+ if (experimentalModules) {
+ let isModule = false;
+ if (filename === '[stdin]' || filename === '[eval]') {
+ isModule = require('internal/process/esm_loader').typeFlag === 'module';
+ } else {
+ const resolve = require('internal/modules/esm/default_resolve');
+ const { format } = resolve(pathToFileURL(filename).toString());
+ isModule = format === 'module';
+ }
+ if (isModule) {
+ const { ModuleWrap } = internalBinding('module_wrap');
+ new ModuleWrap(source, filename);
+ return;
+ }
+ }
+
// Remove BOM.
source = stripBOM(source);
// Wrap it.
diff --git a/lib/internal/main/eval_stdin.js b/lib/internal/main/eval_stdin.js
index 2a2ef6d38a..869a3675b6 100644
--- a/lib/internal/main/eval_stdin.js
+++ b/lib/internal/main/eval_stdin.js
@@ -7,6 +7,7 @@ const {
} = require('internal/bootstrap/pre_execution');
const {
+ evalModule,
evalScript,
readStdin
} = require('internal/process/execution');
@@ -16,5 +17,8 @@ markBootstrapComplete();
readStdin((code) => {
process._eval = code;
- evalScript('[stdin]', process._eval, process._breakFirstLine);
+ if (require('internal/process/esm_loader').typeFlag === 'module')
+ evalModule(process._eval);
+ else
+ evalScript('[stdin]', process._eval, process._breakFirstLine);
});
diff --git a/lib/internal/main/eval_string.js b/lib/internal/main/eval_string.js
index 953fab386d..9328a114aa 100644
--- a/lib/internal/main/eval_string.js
+++ b/lib/internal/main/eval_string.js
@@ -6,11 +6,14 @@
const {
prepareMainThreadExecution
} = require('internal/bootstrap/pre_execution');
-const { evalScript } = require('internal/process/execution');
+const { evalModule, evalScript } = require('internal/process/execution');
const { addBuiltinLibsToObject } = require('internal/modules/cjs/helpers');
const source = require('internal/options').getOptionValue('--eval');
prepareMainThreadExecution();
addBuiltinLibsToObject(global);
markBootstrapComplete();
-evalScript('[eval]', source, process._breakFirstLine);
+if (require('internal/process/esm_loader').typeFlag === 'module')
+ evalModule(source);
+else
+ evalScript('[eval]', source, process._breakFirstLine);
diff --git a/lib/internal/main/repl.js b/lib/internal/main/repl.js
index e6b9885351..7656af46a3 100644
--- a/lib/internal/main/repl.js
+++ b/lib/internal/main/repl.js
@@ -11,8 +11,15 @@ const {
evalScript
} = require('internal/process/execution');
+const { ERR_INVALID_REPL_TYPE } = require('internal/errors').codes;
+
prepareMainThreadExecution();
+// --type flag not supported in REPL
+if (require('internal/process/esm_loader').typeFlag) {
+ throw ERR_INVALID_REPL_TYPE();
+}
+
const cliRepl = require('internal/repl');
cliRepl.createInternalRepl(process.env, (err, repl) => {
if (err) {
diff --git a/lib/internal/modules/cjs/loader.js b/lib/internal/modules/cjs/loader.js
index 6bd7f77535..533e65385a 100644
--- a/lib/internal/modules/cjs/loader.js
+++ b/lib/internal/modules/cjs/loader.js
@@ -866,15 +866,19 @@ Module.runMain = function() {
// Load the main module--the command line argument.
if (experimentalModules) {
if (asyncESM === undefined) lazyLoadESM();
- asyncESM.loaderPromise.then((loader) => {
- return loader.import(pathToFileURL(process.argv[1]).pathname);
- })
- .catch((e) => {
- internalBinding('task_queue').triggerFatalException(e);
- });
- } else {
- Module._load(process.argv[1], null, true);
+ if (asyncESM.typeFlag !== 'commonjs') {
+ asyncESM.loaderPromise.then((loader) => {
+ return loader.import(pathToFileURL(process.argv[1]).pathname);
+ })
+ .catch((e) => {
+ internalBinding('task_queue').triggerFatalException(e);
+ });
+ // Handle any nextTicks added in the first tick of the program
+ process._tickCallback();
+ return;
+ }
}
+ Module._load(process.argv[1], null, true);
// Handle any nextTicks added in the first tick of the program
process._tickCallback();
};
diff --git a/lib/internal/modules/esm/default_resolve.js b/lib/internal/modules/esm/default_resolve.js
index 33366f0069..ef1e1a54fe 100644
--- a/lib/internal/modules/esm/default_resolve.js
+++ b/lib/internal/modules/esm/default_resolve.js
@@ -1,56 +1,118 @@
'use strict';
-const { URL } = require('url');
-const CJSmodule = require('internal/modules/cjs/loader');
const internalFS = require('internal/fs/utils');
const { NativeModule } = require('internal/bootstrap/loaders');
const { extname } = require('path');
-const { realpathSync } = require('fs');
+const { realpathSync, readFileSync } = require('fs');
const { getOptionValue } = require('internal/options');
const preserveSymlinks = getOptionValue('--preserve-symlinks');
const preserveSymlinksMain = getOptionValue('--preserve-symlinks-main');
-const {
- ERR_MISSING_MODULE,
- ERR_MODULE_RESOLUTION_LEGACY,
- ERR_UNKNOWN_FILE_EXTENSION
-} = require('internal/errors').codes;
+const { ERR_INVALID_PACKAGE_CONFIG,
+ ERR_TYPE_MISMATCH,
+ ERR_UNKNOWN_FILE_EXTENSION } = require('internal/errors').codes;
+const experimentalJsonModules = getOptionValue('--experimental-json-modules');
const { resolve: moduleWrapResolve } = internalBinding('module_wrap');
-const StringStartsWith = Function.call.bind(String.prototype.startsWith);
-const { pathToFileURL, fileURLToPath } = require('internal/url');
+const { pathToFileURL, fileURLToPath, URL } = require('internal/url');
+const asyncESM = require('internal/process/esm_loader');
const realpathCache = new Map();
+// TOOD(@guybedford): Shared cache with C++
+const pjsonCache = new Map();
-function search(target, base) {
- if (base === undefined) {
- // We cannot search without a base.
- throw new ERR_MISSING_MODULE(target);
- }
+const extensionFormatMap = {
+ '__proto__': null,
+ '.cjs': 'commonjs',
+ '.js': 'module',
+ '.mjs': 'module'
+};
+
+const legacyExtensionFormatMap = {
+ '__proto__': null,
+ '.cjs': 'commonjs',
+ '.js': 'commonjs',
+ '.json': 'commonjs',
+ '.mjs': 'module',
+ '.node': 'commonjs'
+};
+
+if (experimentalJsonModules) {
+ // This is a total hack
+ Object.assign(extensionFormatMap, {
+ '.json': 'json'
+ });
+ Object.assign(legacyExtensionFormatMap, {
+ '.json': 'json'
+ });
+}
+
+function readPackageConfig(path, parentURL) {
+ const existing = pjsonCache.get(path);
+ if (existing !== undefined)
+ return existing;
try {
- return moduleWrapResolve(target, base);
+ return JSON.parse(readFileSync(path).toString());
} catch (e) {
- e.stack; // cause V8 to generate stack before rethrow
- let error = e;
- try {
- const questionedBase = new URL(base);
- const tmpMod = new CJSmodule(questionedBase.pathname, null);
- tmpMod.paths = CJSmodule._nodeModulePaths(
- new URL('./', questionedBase).pathname);
- const found = CJSmodule._resolveFilename(target, tmpMod);
- error = new ERR_MODULE_RESOLUTION_LEGACY(target, base, found);
- } catch {
- // ignore
+ if (e.code === 'ENOENT') {
+ pjsonCache.set(path, null);
+ return null;
+ } else if (e instanceof SyntaxError) {
+ throw new ERR_INVALID_PACKAGE_CONFIG(path, fileURLToPath(parentURL));
}
- throw error;
+ throw e;
}
}
-const extensionFormatMap = {
- '__proto__': null,
- '.mjs': 'esm',
- '.json': 'json',
- '.node': 'addon',
- '.js': 'cjs'
-};
+function getPackageBoundaryConfig(url, parentURL) {
+ let pjsonURL = new URL('package.json', url);
+ while (true) {
+ const pcfg = readPackageConfig(fileURLToPath(pjsonURL), parentURL);
+ if (pcfg)
+ return pcfg;
+
+ const lastPjsonURL = pjsonURL;
+ pjsonURL = new URL('../package.json', pjsonURL);
+
+ // Terminates at root where ../package.json equals ../../package.json
+ // (can't just check "/package.json" for Windows support).
+ if (pjsonURL.pathname === lastPjsonURL.pathname)
+ return;
+ }
+}
+
+function getModuleFormat(url, isMain, parentURL) {
+ const pcfg = getPackageBoundaryConfig(url, parentURL);
+
+ const legacy = !pcfg || pcfg.type !== 'module';
+
+ const ext = extname(url.pathname);
+
+ let format = (legacy ? legacyExtensionFormatMap : extensionFormatMap)[ext];
+
+ if (!format) {
+ if (isMain)
+ format = legacy ? 'commonjs' : 'module';
+ else
+ throw new ERR_UNKNOWN_FILE_EXTENSION(fileURLToPath(url),
+ fileURLToPath(parentURL));
+ }
+
+ // Check for mismatch between --type and file extension,
+ // and between --type and the "type" field in package.json.
+ if (isMain && format !== 'module' && asyncESM.typeFlag === 'module') {
+ // Conflict between package scope type and --type
+ if (ext === '.js') {
+ if (pcfg && pcfg.type)
+ throw new ERR_TYPE_MISMATCH(
+ fileURLToPath(url), ext, asyncESM.typeFlag, 'scope');
+ // Conflict between explicit extension (.mjs, .cjs) and --type
+ } else {
+ throw new ERR_TYPE_MISMATCH(
+ fileURLToPath(url), ext, asyncESM.typeFlag, 'extension');
+ }
+ }
+
+ return format;
+}
function resolve(specifier, parentURL) {
if (NativeModule.canBeRequiredByUsers(specifier)) {
@@ -60,21 +122,11 @@ function resolve(specifier, parentURL) {
};
}
- let url;
- try {
- url = search(specifier,
- parentURL || pathToFileURL(`${process.cwd()}/`).href);
- } catch (e) {
- if (typeof e.message === 'string' &&
- StringStartsWith(e.message, 'Cannot find module')) {
- e.code = 'MODULE_NOT_FOUND';
- // TODO: also add e.requireStack to match behavior with CJS
- // MODULE_NOT_FOUND.
- }
- throw e;
- }
-
const isMain = parentURL === undefined;
+ if (isMain)
+ parentURL = pathToFileURL(`${process.cwd()}/`).href;
+
+ let url = moduleWrapResolve(specifier, parentURL);
if (isMain ? !preserveSymlinksMain : !preserveSymlinks) {
const real = realpathSync(fileURLToPath(url), {
@@ -86,19 +138,9 @@ function resolve(specifier, parentURL) {
url.hash = old.hash;
}
- const ext = extname(url.pathname);
-
- let format = extensionFormatMap[ext];
- if (!format) {
- if (isMain)
- format = 'cjs';
- else
- throw new ERR_UNKNOWN_FILE_EXTENSION(url.pathname);
- }
+ const format = getModuleFormat(url, isMain, parentURL);
return { url: `${url}`, format };
}
module.exports = resolve;
-// exported for tests
-module.exports.search = search;
diff --git a/lib/internal/modules/esm/loader.js b/lib/internal/modules/esm/loader.js
index a1a1621909..dced45f7a0 100644
--- a/lib/internal/modules/esm/loader.js
+++ b/lib/internal/modules/esm/loader.js
@@ -11,10 +11,12 @@ const { URL } = require('url');
const { validateString } = require('internal/validators');
const ModuleMap = require('internal/modules/esm/module_map');
const ModuleJob = require('internal/modules/esm/module_job');
+
const defaultResolve = require('internal/modules/esm/default_resolve');
const createDynamicModule = require(
'internal/modules/esm/create_dynamic_module');
-const translators = require('internal/modules/esm/translators');
+const { translators } = require('internal/modules/esm/translators');
+const { ModuleWrap } = internalBinding('module_wrap');
const FunctionBind = Function.call.bind(Function.prototype.bind);
@@ -32,6 +34,9 @@ class Loader {
// Registry of loaded modules, akin to `require.cache`
this.moduleMap = new ModuleMap();
+ // Map of already-loaded CJS modules to use
+ this.cjsCache = new Map();
+
// The resolver has the signature
// (specifier : string, parentURL : string, defaultResolve)
// -> Promise<{ url : string, format: string }>
@@ -48,6 +53,8 @@ class Loader {
// an object with the same keys as `exports`, whose values are get/set
// functions for the actual exported values.
this._dynamicInstantiate = undefined;
+ // The index for assigning unique URLs to anonymous module evaluation
+ this.evalIndex = 0;
}
async resolve(specifier, parentURL) {
@@ -95,9 +102,25 @@ class Loader {
return { url, format };
}
+ async eval(source, url = `eval:${++this.evalIndex}`) {
+ const evalInstance = async (url) => {
+ return {
+ module: new ModuleWrap(source, url),
+ reflect: undefined
+ };
+ };
+ const job = new ModuleJob(this, url, evalInstance, false);
+ this.moduleMap.set(url, job);
+ const { module, result } = await job.run();
+ return {
+ namespace: module.namespace(),
+ result
+ };
+ }
+
async import(specifier, parent) {
const job = await this.getModuleJob(specifier, parent);
- const module = await job.run();
+ const { module } = await job.run();
return module.namespace();
}
@@ -143,4 +166,4 @@ class Loader {
Object.setPrototypeOf(Loader.prototype, null);
-module.exports = Loader;
+exports.Loader = Loader;
diff --git a/lib/internal/modules/esm/module_job.js b/lib/internal/modules/esm/module_job.js
index 016495096c..8e0c7b7be9 100644
--- a/lib/internal/modules/esm/module_job.js
+++ b/lib/internal/modules/esm/module_job.js
@@ -23,7 +23,7 @@ class ModuleJob {
// This is a Promise<{ module, reflect }>, whose fields will be copied
// onto `this` by `link()` below once it has been resolved.
- this.modulePromise = moduleProvider(url, isMain);
+ this.modulePromise = moduleProvider.call(loader, url, isMain);
this.module = undefined;
this.reflect = undefined;
@@ -101,8 +101,7 @@ class ModuleJob {
async run() {
const module = await this.instantiate();
- module.evaluate(-1, false);
- return module;
+ return { module, result: module.evaluate(-1, false) };
}
}
Object.setPrototypeOf(ModuleJob.prototype, null);
diff --git a/lib/internal/modules/esm/translators.js b/lib/internal/modules/esm/translators.js
index 25552cff0e..243d33e7e8 100644
--- a/lib/internal/modules/esm/translators.js
+++ b/lib/internal/modules/esm/translators.js
@@ -11,26 +11,21 @@ const internalURLModule = require('internal/url');
const createDynamicModule = require(
'internal/modules/esm/create_dynamic_module');
const fs = require('fs');
-const { _makeLong } = require('path');
const {
SafeMap,
- JSON,
- FunctionPrototype,
- StringPrototype
} = primordials;
-const { URL } = require('url');
+const { fileURLToPath, URL } = require('url');
const { debuglog, promisify } = require('util');
const esmLoader = require('internal/process/esm_loader');
const readFileAsync = promisify(fs.readFile);
-const readFileSync = fs.readFileSync;
-const StringReplace = FunctionPrototype.call.bind(StringPrototype.replace);
+const StringReplace = Function.call.bind(String.prototype.replace);
const JsonParse = JSON.parse;
const debug = debuglog('esm');
const translators = new SafeMap();
-module.exports = translators;
+exports.translators = translators;
function initializeImportMeta(meta, { url }) {
meta.url = url;
@@ -42,7 +37,7 @@ async function importModuleDynamically(specifier, { url }) {
}
// Strategy for loading a standard JavaScript module
-translators.set('esm', async (url) => {
+translators.set('module', async function(url) {
const source = `${await readFileAsync(new URL(url))}`;
debug(`Translating StandardModule ${url}`);
const module = new ModuleWrap(stripShebang(source), url);
@@ -59,9 +54,14 @@ translators.set('esm', async (url) => {
// Strategy for loading a node-style CommonJS module
const isWindows = process.platform === 'win32';
const winSepRegEx = /\//g;
-translators.set('cjs', async (url, isMain) => {
+translators.set('commonjs', async function(url, isMain) {
debug(`Translating CJSModule ${url}`);
const pathname = internalURLModule.fileURLToPath(new URL(url));
+ const cached = this.cjsCache.get(url);
+ if (cached) {
+ this.cjsCache.delete(url);
+ return cached;
+ }
const module = CJSModule._cache[
isWindows ? StringReplace(pathname, winSepRegEx, '\\') : pathname];
if (module && module.loaded) {
@@ -81,7 +81,7 @@ translators.set('cjs', async (url, isMain) => {
// Strategy for loading a node builtin CommonJS module that isn't
// through normal resolution
-translators.set('builtin', async (url) => {
+translators.set('builtin', async function(url) {
debug(`Translating BuiltinModule ${url}`);
// slice 'node:' scheme
const id = url.slice(5);
@@ -97,31 +97,34 @@ translators.set('builtin', async (url) => {
});
});
-// Strategy for loading a node native module
-translators.set('addon', async (url) => {
- debug(`Translating NativeModule ${url}`);
- return createDynamicModule(['default'], url, (reflect) => {
- debug(`Loading NativeModule ${url}`);
- const module = { exports: {} };
- const pathname = internalURLModule.fileURLToPath(new URL(url));
- process.dlopen(module, _makeLong(pathname));
- reflect.exports.default.set(module.exports);
- });
-});
-
// Strategy for loading a JSON file
translators.set('json', async (url) => {
debug(`Translating JSONModule ${url}`);
- return createDynamicModule(['default'], url, (reflect) => {
- debug(`Loading JSONModule ${url}`);
- const pathname = internalURLModule.fileURLToPath(new URL(url));
- const content = readFileSync(pathname, 'utf8');
- try {
- const exports = JsonParse(stripBOM(content));
+ debug(`Loading JSONModule ${url}`);
+ const pathname = fileURLToPath(url);
+ const modulePath = isWindows ?
+ StringReplace(pathname, winSepRegEx, '\\') : pathname;
+ let module = CJSModule._cache[modulePath];
+ if (module && module.loaded) {
+ const exports = module.exports;
+ return createDynamicModule(['default'], url, (reflect) => {
reflect.exports.default.set(exports);
- } catch (err) {
- err.message = pathname + ': ' + err.message;
- throw err;
- }
+ });
+ }
+ const content = await readFileAsync(pathname, 'utf-8');
+ try {
+ const exports = JsonParse(stripBOM(content));
+ module = {
+ exports,
+ loaded: true
+ };
+ } catch (err) {
+ err.message = pathname + ': ' + err.message;
+ throw err;
+ }
+ CJSModule._cache[modulePath] = module;
+ return createDynamicModule(['default'], url, (reflect) => {
+ debug(`Parsing JSONModule ${url}`);
+ reflect.exports.default.set(module.exports);
});
});
diff --git a/lib/internal/process/esm_loader.js b/lib/internal/process/esm_loader.js
index 0b7f1be6ff..803c854d9a 100644
--- a/lib/internal/process/esm_loader.js
+++ b/lib/internal/process/esm_loader.js
@@ -3,15 +3,22 @@
const {
callbackMap,
} = internalBinding('module_wrap');
+const {
+ ERR_INVALID_TYPE_FLAG,
+ ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING,
+} = require('internal/errors').codes;
+const { emitExperimentalWarning } = require('internal/util');
+
+const type = require('internal/options').getOptionValue('--type');
+if (type && type !== 'commonjs' && type !== 'module')
+ throw new ERR_INVALID_TYPE_FLAG(type);
+exports.typeFlag = type;
+const { Loader } = require('internal/modules/esm/loader');
const { pathToFileURL } = require('internal/url');
-const Loader = require('internal/modules/esm/loader');
const {
wrapToModuleMap,
} = require('internal/vm/source_text_module');
-const {
- ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING,
-} = require('internal/errors').codes;
exports.initializeImportMetaObject = function(wrap, meta) {
if (callbackMap.has(wrap)) {
@@ -34,9 +41,7 @@ exports.importModuleDynamicallyCallback = async function(wrap, specifier) {
};
let loaderResolve;
-exports.loaderPromise = new Promise((resolve, reject) => {
- loaderResolve = resolve;
-});
+exports.loaderPromise = new Promise((resolve) => loaderResolve = resolve);
exports.ESMLoader = undefined;
@@ -44,6 +49,7 @@ exports.initializeLoader = function(cwd, userLoader) {
let ESMLoader = new Loader();
const loaderPromise = (async () => {
if (userLoader) {
+ emitExperimentalWarning('--loader');
const hooks = await ESMLoader.import(
userLoader, pathToFileURL(`${cwd}/`).href);
ESMLoader = new Loader();
diff --git a/lib/internal/process/execution.js b/lib/internal/process/execution.js
index 7118dbf3ad..070410ef6f 100644
--- a/lib/internal/process/execution.js
+++ b/lib/internal/process/execution.js
@@ -33,6 +33,24 @@ function tryGetCwd() {
}
}
+function evalModule(source) {
+ const { decorateErrorStack } = require('internal/util');
+ const asyncESM = require('internal/process/esm_loader');
+ asyncESM.loaderPromise.then(async (loader) => {
+ const { result } = await loader.eval(source);
+ if (require('internal/options').getOptionValue('--print')) {
+ console.log(result);
+ }
+ })
+ .catch((e) => {
+ decorateErrorStack(e);
+ console.error(e);
+ process.exit(1);
+ });
+ // Handle any nextTicks added in the first tick of the program.
+ process._tickCallback();
+}
+
function evalScript(name, body, breakFirstLine) {
const CJSModule = require('internal/modules/cjs/loader');
const { kVmBreakFirstLineSymbol } = require('internal/util');
@@ -176,6 +194,7 @@ function readStdin(callback) {
module.exports = {
readStdin,
tryGetCwd,
+ evalModule,
evalScript,
fatalException: createFatalException(),
setUncaughtExceptionCaptureCallback,
diff --git a/src/env.h b/src/env.h
index b269982a0b..f30d75fa18 100644
--- a/src/env.h
+++ b/src/env.h
@@ -73,14 +73,23 @@ namespace loader {
class ModuleWrap;
struct PackageConfig {
- enum class Exists { Yes, No };
- enum class IsValid { Yes, No };
- enum class HasMain { Yes, No };
-
- Exists exists;
- IsValid is_valid;
- HasMain has_main;
- std::string main;
+ struct Exists {
+ enum Bool { No, Yes };
+ };
+ struct IsValid {
+ enum Bool { No, Yes };
+ };
+ struct HasMain {
+ enum Bool { No, Yes };
+ };
+ struct IsESM {
+ enum Bool { No, Yes };
+ };
+ const Exists::Bool exists;
+ const IsValid::Bool is_valid;
+ const HasMain::Bool has_main;
+ const std::string main;
+ const IsESM::Bool esm;
};
} // namespace loader
@@ -207,6 +216,7 @@ constexpr size_t kFsStatsBufferLength = kFsStatsFieldsNumber * 2;
V(kill_signal_string, "killSignal") \
V(kind_string, "kind") \
V(library_string, "library") \
+ V(legacy_string, "legacy") \
V(mac_string, "mac") \
V(main_string, "main") \
V(max_buffer_string, "maxBuffer") \
diff --git a/src/module_wrap.cc b/src/module_wrap.cc
index 3d55d12cc3..f505c09cec 100644
--- a/src/module_wrap.cc
+++ b/src/module_wrap.cc
@@ -27,7 +27,6 @@ using v8::HandleScope;
using v8::Integer;
using v8::IntegrityLevel;
using v8::Isolate;
-using v8::JSON;
using v8::Just;
using v8::Local;
using v8::Maybe;
@@ -44,7 +43,13 @@ using v8::String;
using v8::Undefined;
using v8::Value;
-static const char* const EXTENSIONS[] = {".mjs", ".js", ".json", ".node"};
+static const char* const EXTENSIONS[] = {
+ ".mjs",
+ ".cjs",
+ ".js",
+ ".json",
+ ".node"
+};
ModuleWrap::ModuleWrap(Environment* env,
Local