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

Commit

Permalink
esm: implement top-level --type flag, type:esm -> type:module
Browse files Browse the repository at this point in the history
  • Loading branch information
guybedford authored and nodejs-ci committed Feb 15, 2019
1 parent fb26cd9 commit 4514848
Show file tree
Hide file tree
Showing 22 changed files with 267 additions and 71 deletions.
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
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
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
24 changes: 22 additions & 2 deletions lib/internal/modules/esm/loader.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 { ModuleWrap } = internalBinding('module_wrap');

const FunctionBind = Function.call.bind(Function.prototype.bind);

Expand Down Expand Up @@ -51,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) {
Expand Down Expand Up @@ -98,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();
}

Expand Down Expand Up @@ -146,4 +166,4 @@ class Loader {

Object.setPrototypeOf(Loader.prototype, null);

module.exports = Loader;
exports.Loader = Loader;
3 changes: 1 addition & 2 deletions lib/internal/modules/esm/module_job.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
14 changes: 10 additions & 4 deletions lib/internal/process/esm_loader.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,20 @@
const {
callbackMap,
} = internalBinding('module_wrap');
const {
ERR_INVALID_TYPE_FLAG,
ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING,
} = require('internal/errors').codes;

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 { 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)) {
Expand Down
19 changes: 19 additions & 0 deletions lib/internal/process/execution.js
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand Down Expand Up @@ -180,6 +198,7 @@ function readStdin(callback) {
module.exports = {
readStdin,
tryGetCwd,
evalModule,
evalScript,
fatalException: createFatalException(),
setUncaughtExceptionCaptureCallback,
Expand Down
1 change: 0 additions & 1 deletion src/env.h
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,6 @@ constexpr size_t kFsStatsBufferLength = kFsStatsFieldsNumber * 2;
V(env_var_settings_string, "envVarSettings") \
V(errno_string, "errno") \
V(error_string, "error") \
V(esm_string, "esm") \
V(exchange_string, "exchange") \
V(exit_code_string, "exitCode") \
V(expire_string, "expire") \
Expand Down
Loading

0 comments on commit 4514848

Please sign in to comment.