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

Entry points proposal spec and implementation #32

Merged
merged 2 commits into from
Feb 14, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ module.exports = {
{
files: [
'doc/api/esm.md',
'test/es-module/test-esm-type-flag.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 @@ -485,6 +485,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
22 changes: 21 additions & 1 deletion doc/api/errors.md
Original file line number Diff line number Diff line change
Expand Up @@ -2196,6 +2196,27 @@ 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_INVALID_TYPE_EXTENSION"></a>
### ERR_INVALID_TYPE_EXTENSION

> Stability: 1 - Experimental

Attempted to execute a `.cjs` module with the `--type=module` flag.

<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 @@ -2226,7 +2247,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
28 changes: 20 additions & 8 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 Down Expand Up @@ -234,7 +240,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 +251,24 @@ 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 an _Invalid File Extension_ 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. Return _"commonjs"_.
> 1. If _pjson.type_ exists and is _"module"_, then
> 1. If _url_ ends in _".cjs"_, then
> 1. Return _"commonjs"_.
> 1. If _url_ does not end in _".js"_ or _".mjs"_, then
> 1. Throw an _Unsupported File Extension_ error.
> 1. Return _"esm"_.
> 1. Return _"module"_.
> 1. Otherwise,
> 1. If _url_ ends in _".mjs"_, then
> 1. Return _"esm"_.
> 1. Return _"module"_.
> 1. Otherwise,
> 1. Return _"legacy"_.
> 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 @@ -272,6 +272,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
11 changes: 8 additions & 3 deletions lib/internal/errors.js
Original file line number Diff line number Diff line change
Expand Up @@ -780,6 +780,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}.`;
Expand Down Expand Up @@ -810,6 +812,11 @@ 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_EXTENSION', '%s extension is not supported for --type=%s',
TypeError);
E('ERR_INVALID_TYPE_FLAG',
'Type flag must be one of "esm", "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 @@ -964,12 +971,10 @@ 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_MODULE_FORMAT', 'Unknown module format: %s', RangeError);
E('ERR_UNKNOWN_SIGNAL', 'Unknown signal: %s', TypeError);
E('ERR_UNSUPPORTED_FILE_EXTENSION', 'Unsupported file extension: \'%s\' ' +
'imported from %s', Error);

E('ERR_V8BREAKITERATOR',
'Full ICU data not installed. See https://github.com/nodejs/node/wiki/Intl',
Expand Down
27 changes: 24 additions & 3 deletions lib/internal/main/check_syntax.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ const {
readStdin
} = require('internal/process/execution');

const { pathToFileURL } = require('url');

const CJSModule = require('internal/modules/cjs/loader');
const vm = require('vm');
const {
Expand All @@ -32,20 +34,39 @@ if (process.argv[1] && process.argv[1] !== '-') {
prepareMainThreadExecution();
markBootstrapComplete();

checkScriptSyntax(source, filename);
checkSyntax(source, filename);
} else {
// TODO(joyeecheung): not every one of these are necessary
prepareMainThreadExecution();
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 === 'esm';
}
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
26 changes: 13 additions & 13 deletions lib/internal/modules/cjs/loader.js
Original file line number Diff line number Diff line change
Expand Up @@ -786,21 +786,21 @@ if (experimentalModules) {
// bootstrap main module.
Module.runMain = function() {
// Load the main module--the command line argument.
const base = path.basename(process.argv[1]);
const ext = path.extname(base);
const isESM = ext === '.mjs';

if (experimentalModules && isESM) {
if (experimentalModules) {
if (asyncESM === undefined) lazyLoadESM();
asyncESM.loaderPromise.then((loader) => {
return loader.import(pathToFileURL(process.argv[1]).pathname);
})
.catch((e) => {
internalBinding('util').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('util').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();
};
Expand Down
18 changes: 12 additions & 6 deletions lib/internal/modules/esm/default_resolve.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,23 @@ const { realpathSync } = require('fs');
const { getOptionValue } = require('internal/options');
const preserveSymlinks = getOptionValue('--preserve-symlinks');
const preserveSymlinksMain = getOptionValue('--preserve-symlinks-main');
const { ERR_UNSUPPORTED_FILE_EXTENSION } = require('internal/errors').codes;
const { ERR_UNKNOWN_FILE_EXTENSION } = require('internal/errors').codes;
const { resolve: moduleWrapResolve } = internalBinding('module_wrap');
const { pathToFileURL, fileURLToPath } = require('internal/url');
const { typeFlag } = require('internal/process/esm_loader');

const realpathCache = new Map();

const extensionFormatMap = {
'__proto__': null,
'.mjs': 'esm',
'.js': 'esm'
'.cjs': 'cjs',
'.js': 'esm',
'.mjs': 'esm'
};

const legacyExtensionFormatMap = {
'__proto__': null,
'.cjs': 'cjs',
'.js': 'cjs',
'.json': 'cjs',
'.mjs': 'esm',
Expand All @@ -39,7 +42,10 @@ function resolve(specifier, parentURL) {
if (isMain)
parentURL = pathToFileURL(`${process.cwd()}/`).href;

const resolved = moduleWrapResolve(specifier, parentURL, isMain);
const resolved = moduleWrapResolve(specifier,
parentURL,
isMain,
typeFlag === 'module');

let url = resolved.url;
const legacy = resolved.legacy;
Expand All @@ -61,8 +67,8 @@ function resolve(specifier, parentURL) {
if (isMain)
format = legacy ? 'cjs' : 'esm';
else
throw new ERR_UNSUPPORTED_FILE_EXTENSION(fileURLToPath(url),
fileURLToPath(parentURL));
throw new ERR_UNKNOWN_FILE_EXTENSION(fileURLToPath(url),
fileURLToPath(parentURL));
}

return { url: `${url}`, format };
Expand Down
Loading