Skip to content
This repository has been archived by the owner on Apr 16, 2020. It is now read-only.

Commit

Permalink
esm: irp type implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
guybedford authored and nodejs-ci committed Mar 14, 2019
1 parent 4629143 commit 8cc0cf6
Show file tree
Hide file tree
Showing 52 changed files with 962 additions and 176 deletions.
2 changes: 2 additions & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -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',
],
parserOptions: { sourceType: 'module' },
Expand Down
12 changes: 12 additions & 0 deletions doc/api/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -504,6 +504,18 @@ added: v2.4.0

Track heap object allocations for heap snapshots.

### `-m`, `--type=type`

When using `--experimental-modules`, this informs the module resolution type
to interpret the top-level entry into Node.js.

Works with stdin, `--eval`, `--print` as well as standard execution.

Valid values are `"commonjs"` and `"module"`, where the default is to infer
from the file extension and package type boundary.

`-m` is an alias for `--type=module`.

### `--use-bundled-ca`, `--use-openssl-ca`
<!-- YAML
added: v6.11.0
Expand Down
32 changes: 31 additions & 1 deletion doc/api/errors.md
Original file line number Diff line number Diff line change
Expand Up @@ -1267,6 +1267,11 @@ An invalid or unexpected value was passed in an options object.

An invalid or unknown file encoding was passed.

<a id="ERR_INVALID_PACKAGE_CONFIG"></a>
### ERR_INVALID_PACKAGE_CONFIG

An invalid `package.json` file was found which failed parsing.

<a id="ERR_INVALID_PERFORMANCE_MARK"></a>
### ERR_INVALID_PERFORMANCE_MARK

Expand Down Expand Up @@ -2213,6 +2218,32 @@ A non-specific HTTP/2 error has occurred.
Used in the `repl` in case the old history file is used and an error occurred
while trying to read and parse it.

<a id="ERR_INVALID_REPL_TYPE"></a>
### ERR_INVALID_REPL_TYPE

> Stability: 1 - Experimental
The `--type=...` flag is not compatible with the Node.js REPL.

<a id="ERR_TYPE_MISMATCH"></a>
### ERR_TYPE_MISMATCH

> Stability: 1 - Experimental
The `--type=commonjs` flag was used to attempt to execute an `.mjs` file or
a `.js` file where the nearest parent `package.json` contains
`"type": "module"`; or
the `--type=module` flag was used to attempt to execute a `.cjs` file or
a `.js` file where the nearest parent `package.json` either lacks a `"type"`
field or contains `"type": "commonjs"`.

<a id="ERR_INVALID_TYPE_FLAG"></a>
### ERR_INVALID_TYPE_FLAG

> Stability: 1 - Experimental
An invalid `--type=...` flag value was provided.

<a id="ERR_MISSING_DYNAMIC_INSTANTIATE_HOOK"></a>
#### ERR_MISSING_DYNAMIC_INSTANTIATE_HOOK

Expand Down Expand Up @@ -2243,7 +2274,6 @@ size.
This `Error` is thrown when a read is attempted on a TTY `WriteStream`,
such as `process.stdout.on('data')`.


[`'uncaughtException'`]: process.html#process_event_uncaughtexception
[`--force-fips`]: cli.html#cli_force_fips
[`Class: assert.AssertionError`]: assert.html#assert_class_assert_assertionerror
Expand Down
38 changes: 26 additions & 12 deletions doc/api/esm.md
Original file line number Diff line number Diff line change
Expand Up @@ -158,8 +158,8 @@ The algorithm to load an ES module specifier is given through the
module specifier relative to a parentURL, in addition to the unique module
format for that resolved URL given by the **ESM_FORMAT** routine.
The _"esm"_ format is returned for an ECMAScript Module, while the
_"legacy"_ format is used to indicate loading through the legacy
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.
Expand All @@ -168,6 +168,12 @@ 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**.
**ESM_RESOLVE(_specifier_, _parentURL_, _isMain_)**
> 1. Let _resolvedURL_ be **undefined**.
> 1. If _specifier_ is a valid URL, then
Expand All @@ -184,7 +190,8 @@ _isMain_ is **true** when resolving the Node.js application entry point.
> **PACKAGE_RESOLVE**(_specifier_, _parentURL_).
> 1. If the file at _resolvedURL_ does not exist, then
> 1. Throw a _Module Not Found_ error.
> 1. Let _format_ be the result of **ESM_FORMAT**(_url_, _isMain_).
> 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_)
Expand Down Expand Up @@ -234,7 +241,7 @@ PACKAGE_MAIN_RESOLVE(_packageURL_, _pjson_)
> _pjson.main_.
> 1. If the file at _resolvedMain_ exists, then
> 1. Return _resolvedMain_.
> 1. If _pjson.type_ is equal to _"esm"_, then
> 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
Expand All @@ -245,18 +252,25 @@ PACKAGE_MAIN_RESOLVE(_packageURL_, _pjson_)
**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_BOUNDARY**(_url_).
> 1. If _pjson_ is **null** and _isMain_ is **true**, then
> 1. Return _"legacy"_.
> 1. If _pjson.type_ exists and is _"esm"_, then
> 1. If _url_ does not end in _".js"_ or _".mjs"_, then
> 1. Throw an _Unsupported File Extension_ error.
> 1. Return _"esm"_.
> 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 _"esm"_.
> 1. Otherwise,
> 1. Return _"legacy"_.
> 1. Return _"module"_.
> 1. If _url_ does not end in _".js"_, then
> 1. Throw an _Unsupported File Extension_ error.
> 1. Return _"commonjs"_.
READ_PACKAGE_BOUNDARY(_url_)
> 1. Let _boundaryURL_ be _url_.
Expand Down
3 changes: 3 additions & 0 deletions doc/node.1
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
32 changes: 22 additions & 10 deletions lib/internal/errors.js
Original file line number Diff line number Diff line change
Expand Up @@ -774,13 +774,17 @@ 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',
'Protocol "%s" not supported. Expected "%s"',
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}.`;
Expand Down Expand Up @@ -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',
Expand Down Expand Up @@ -865,13 +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_MODULE_NOT_FOUND', (module, base, legacyResolution) => {
let msg = `Cannot find module '${module}' imported from ${base}.`;
if (legacyResolution)
msg += ' Legacy behavior in require() would have found it at ' +
legacyResolution;
return msg;
}, 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',
Expand Down Expand Up @@ -956,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',
Expand All @@ -972,10 +986,8 @@ E('ERR_UNHANDLED_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\' imported ' +
'from %s', Error);
E('ERR_UNKNOWN_MODULE_FORMAT', 'Unknown module format: %s', RangeError);
E('ERR_UNKNOWN_SIGNAL', 'Unknown signal: %s', TypeError);

Expand Down
36 changes: 31 additions & 5 deletions lib/internal/main/check_syntax.js
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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]);

Expand All @@ -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.
Expand Down
6 changes: 5 additions & 1 deletion lib/internal/main/eval_stdin.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ const {
} = require('internal/bootstrap/pre_execution');

const {
evalModule,
evalScript,
readStdin
} = require('internal/process/execution');
Expand All @@ -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);
});
7 changes: 5 additions & 2 deletions lib/internal/main/eval_string.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
7 changes: 7 additions & 0 deletions lib/internal/main/repl.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
Loading

0 comments on commit 8cc0cf6

Please sign in to comment.