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

Commit a4955e2

Browse files
guybedfordnodejs-ci
authored andcommitted
esm: implement top-level --type flag, type:esm -> type:module
1 parent a5b7679 commit a4955e2

22 files changed

+267
-71
lines changed

.eslintrc.js

+1
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ module.exports = {
3838
{
3939
files: [
4040
'doc/api/esm.md',
41+
'test/es-module/test-esm-type-flag.js',
4142
'*.mjs',
4243
],
4344
parserOptions: { sourceType: 'module' },

doc/api/errors.md

+21-1
Original file line numberDiff line numberDiff line change
@@ -2201,6 +2201,27 @@ A non-specific HTTP/2 error has occurred.
22012201
Used in the `repl` in case the old history file is used and an error occurred
22022202
while trying to read and parse it.
22032203

2204+
<a id="ERR_INVALID_REPL_TYPE"></a>
2205+
### ERR_INVALID_REPL_TYPE
2206+
2207+
> Stability: 1 - Experimental
2208+
2209+
The `--type=...` flag is not compatible with the Node.js REPL.
2210+
2211+
<a id="ERR_INVALID_TYPE_EXTENSION"></a>
2212+
### ERR_INVALID_TYPE_EXTENSION
2213+
2214+
> Stability: 1 - Experimental
2215+
2216+
Attempted to execute a `.cjs` module with the `--type=module` flag.
2217+
2218+
<a id="ERR_INVALID_TYPE_FLAG"></a>
2219+
### ERR_INVALID_TYPE_FLAG
2220+
2221+
> Stability: 1 - Experimental
2222+
2223+
An invalid `--type=...` flag value was provided.
2224+
22042225
<a id="ERR_MISSING_DYNAMIC_INSTANTIATE_HOOK"></a>
22052226
#### ERR_MISSING_DYNAMIC_INSTANTIATE_HOOK
22062227

@@ -2231,7 +2252,6 @@ size.
22312252
This `Error` is thrown when a read is attempted on a TTY `WriteStream`,
22322253
such as `process.stdout.on('data')`.
22332254

2234-
22352255
[`'uncaughtException'`]: process.html#process_event_uncaughtexception
22362256
[`--force-fips`]: cli.html#cli_force_fips
22372257
[`Class: assert.AssertionError`]: assert.html#assert_class_assert_assertionerror

lib/internal/errors.js

+8-3
Original file line numberDiff line numberDiff line change
@@ -780,6 +780,8 @@ E('ERR_INVALID_PROTOCOL',
780780
TypeError);
781781
E('ERR_INVALID_REPL_EVAL_CONFIG',
782782
'Cannot specify both "breakEvalOnSigint" and "eval" for REPL', TypeError);
783+
E('ERR_INVALID_REPL_TYPE',
784+
'Cannot specify --type for REPL', TypeError);
783785
E('ERR_INVALID_RETURN_PROPERTY', (input, name, prop, value) => {
784786
return `Expected a valid ${input} to be returned for the "${prop}" from the` +
785787
` "${name}" function but got ${value}.`;
@@ -810,6 +812,11 @@ E('ERR_INVALID_SYNC_FORK_INPUT',
810812
TypeError);
811813
E('ERR_INVALID_THIS', 'Value of "this" must be of type %s', TypeError);
812814
E('ERR_INVALID_TUPLE', '%s must be an iterable %s tuple', TypeError);
815+
E('ERR_INVALID_TYPE_EXTENSION', '%s extension is not supported for --type=%s',
816+
TypeError);
817+
E('ERR_INVALID_TYPE_FLAG',
818+
'Type flag must be one of "esm", "commonjs". Received --type=%s',
819+
TypeError);
813820
E('ERR_INVALID_URI', 'URI malformed', URIError);
814821
E('ERR_INVALID_URL', 'Invalid URL: %s', TypeError);
815822
E('ERR_INVALID_URL_SCHEME',
@@ -964,12 +971,10 @@ E('ERR_UNHANDLED_ERROR',
964971
// This should probably be a `TypeError`.
965972
E('ERR_UNKNOWN_CREDENTIAL', '%s identifier does not exist: %s', Error);
966973
E('ERR_UNKNOWN_ENCODING', 'Unknown encoding: %s', TypeError);
967-
974+
E('ERR_UNKNOWN_FILE_EXTENSION', 'Unknown file extension: %s', TypeError);
968975
// This should probably be a `TypeError`.
969976
E('ERR_UNKNOWN_MODULE_FORMAT', 'Unknown module format: %s', RangeError);
970977
E('ERR_UNKNOWN_SIGNAL', 'Unknown signal: %s', TypeError);
971-
E('ERR_UNSUPPORTED_FILE_EXTENSION', 'Unsupported file extension: \'%s\' ' +
972-
'imported from %s', Error);
973978

974979
E('ERR_V8BREAKITERATOR',
975980
'Full ICU data not installed. See https://github.com/nodejs/node/wiki/Intl',

lib/internal/main/check_syntax.js

+24-3
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ const {
1111
readStdin
1212
} = require('internal/process/execution');
1313

14+
const { pathToFileURL } = require('url');
15+
1416
const CJSModule = require('internal/modules/cjs/loader');
1517
const vm = require('vm');
1618
const {
@@ -34,20 +36,39 @@ if (process.argv[1] && process.argv[1] !== '-') {
3436

3537
markBootstrapComplete();
3638

37-
checkScriptSyntax(source, filename);
39+
checkSyntax(source, filename);
3840
} else {
3941
// TODO(joyeecheung): not every one of these are necessary
4042
prepareMainThreadExecution();
4143
markBootstrapComplete();
4244

4345
readStdin((code) => {
44-
checkScriptSyntax(code, '[stdin]');
46+
checkSyntax(code, '[stdin]');
4547
});
4648
}
4749

48-
function checkScriptSyntax(source, filename) {
50+
function checkSyntax(source, filename) {
4951
// Remove Shebang.
5052
source = stripShebang(source);
53+
54+
const experimentalModules =
55+
require('internal/options').getOptionValue('--experimental-modules');
56+
if (experimentalModules) {
57+
let isModule = false;
58+
if (filename === '[stdin]' || filename === '[eval]') {
59+
isModule = require('internal/process/esm_loader').typeFlag === 'module';
60+
} else {
61+
const resolve = require('internal/modules/esm/default_resolve');
62+
const { format } = resolve(pathToFileURL(filename).toString());
63+
isModule = format === 'esm';
64+
}
65+
if (isModule) {
66+
const { ModuleWrap } = internalBinding('module_wrap');
67+
new ModuleWrap(source, filename);
68+
return;
69+
}
70+
}
71+
5172
// Remove BOM.
5273
source = stripBOM(source);
5374
// Wrap it.

lib/internal/main/eval_stdin.js

+5-1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ const {
77
} = require('internal/bootstrap/pre_execution');
88

99
const {
10+
evalModule,
1011
evalScript,
1112
readStdin
1213
} = require('internal/process/execution');
@@ -16,5 +17,8 @@ markBootstrapComplete();
1617

1718
readStdin((code) => {
1819
process._eval = code;
19-
evalScript('[stdin]', process._eval, process._breakFirstLine);
20+
if (require('internal/process/esm_loader').typeFlag === 'module')
21+
evalModule(process._eval);
22+
else
23+
evalScript('[stdin]', process._eval, process._breakFirstLine);
2024
});

lib/internal/main/eval_string.js

+5-2
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,14 @@
66
const {
77
prepareMainThreadExecution
88
} = require('internal/bootstrap/pre_execution');
9-
const { evalScript } = require('internal/process/execution');
9+
const { evalModule, evalScript } = require('internal/process/execution');
1010
const { addBuiltinLibsToObject } = require('internal/modules/cjs/helpers');
1111

1212
const source = require('internal/options').getOptionValue('--eval');
1313
prepareMainThreadExecution();
1414
addBuiltinLibsToObject(global);
1515
markBootstrapComplete();
16-
evalScript('[eval]', source, process._breakFirstLine);
16+
if (require('internal/process/esm_loader').typeFlag === 'module')
17+
evalModule(source);
18+
else
19+
evalScript('[eval]', source, process._breakFirstLine);

lib/internal/main/repl.js

+7
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,15 @@ const {
1111
evalScript
1212
} = require('internal/process/execution');
1313

14+
const { ERR_INVALID_REPL_TYPE } = require('internal/errors').codes;
15+
1416
prepareMainThreadExecution();
1517

18+
// --type flag not supported in REPL
19+
if (require('internal/process/esm_loader').typeFlag) {
20+
throw ERR_INVALID_REPL_TYPE();
21+
}
22+
1623
const cliRepl = require('internal/repl');
1724
cliRepl.createInternalRepl(process.env, (err, repl) => {
1825
if (err) {

lib/internal/modules/cjs/loader.js

+13-13
Original file line numberDiff line numberDiff line change
@@ -857,21 +857,21 @@ if (experimentalModules) {
857857
// bootstrap main module.
858858
Module.runMain = function() {
859859
// Load the main module--the command line argument.
860-
const base = path.basename(process.argv[1]);
861-
const ext = path.extname(base);
862-
const isESM = ext === '.mjs';
863-
864-
if (experimentalModules && isESM) {
860+
if (experimentalModules) {
865861
if (asyncESM === undefined) lazyLoadESM();
866-
asyncESM.loaderPromise.then((loader) => {
867-
return loader.import(pathToFileURL(process.argv[1]).pathname);
868-
})
869-
.catch((e) => {
870-
internalBinding('util').triggerFatalException(e);
871-
});
872-
} else {
873-
Module._load(process.argv[1], null, true);
862+
if (asyncESM.typeFlag !== 'commonjs') {
863+
asyncESM.loaderPromise.then((loader) => {
864+
return loader.import(pathToFileURL(process.argv[1]).pathname);
865+
})
866+
.catch((e) => {
867+
internalBinding('util').triggerFatalException(e);
868+
});
869+
// Handle any nextTicks added in the first tick of the program
870+
process._tickCallback();
871+
return;
872+
}
874873
}
874+
Module._load(process.argv[1], null, true);
875875
// Handle any nextTicks added in the first tick of the program
876876
process._tickCallback();
877877
};

lib/internal/modules/esm/default_resolve.js

+12-6
Original file line numberDiff line numberDiff line change
@@ -7,20 +7,23 @@ const { realpathSync } = require('fs');
77
const { getOptionValue } = require('internal/options');
88
const preserveSymlinks = getOptionValue('--preserve-symlinks');
99
const preserveSymlinksMain = getOptionValue('--preserve-symlinks-main');
10-
const { ERR_UNSUPPORTED_FILE_EXTENSION } = require('internal/errors').codes;
10+
const { ERR_UNKNOWN_FILE_EXTENSION } = require('internal/errors').codes;
1111
const { resolve: moduleWrapResolve } = internalBinding('module_wrap');
1212
const { pathToFileURL, fileURLToPath } = require('internal/url');
13+
const { typeFlag } = require('internal/process/esm_loader');
1314

1415
const realpathCache = new Map();
1516

1617
const extensionFormatMap = {
1718
'__proto__': null,
18-
'.mjs': 'esm',
19-
'.js': 'esm'
19+
'.cjs': 'cjs',
20+
'.js': 'esm',
21+
'.mjs': 'esm'
2022
};
2123

2224
const legacyExtensionFormatMap = {
2325
'__proto__': null,
26+
'.cjs': 'cjs',
2427
'.js': 'cjs',
2528
'.json': 'cjs',
2629
'.mjs': 'esm',
@@ -39,7 +42,10 @@ function resolve(specifier, parentURL) {
3942
if (isMain)
4043
parentURL = pathToFileURL(`${process.cwd()}/`).href;
4144

42-
const resolved = moduleWrapResolve(specifier, parentURL, isMain);
45+
const resolved = moduleWrapResolve(specifier,
46+
parentURL,
47+
isMain,
48+
typeFlag === 'module');
4349

4450
let url = resolved.url;
4551
const legacy = resolved.legacy;
@@ -61,8 +67,8 @@ function resolve(specifier, parentURL) {
6167
if (isMain)
6268
format = legacy ? 'cjs' : 'esm';
6369
else
64-
throw new ERR_UNSUPPORTED_FILE_EXTENSION(fileURLToPath(url),
65-
fileURLToPath(parentURL));
70+
throw new ERR_UNKNOWN_FILE_EXTENSION(fileURLToPath(url),
71+
fileURLToPath(parentURL));
6672
}
6773

6874
return { url: `${url}`, format };

lib/internal/modules/esm/loader.js

+22-2
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,12 @@ const { URL } = require('url');
1111
const { validateString } = require('internal/validators');
1212
const ModuleMap = require('internal/modules/esm/module_map');
1313
const ModuleJob = require('internal/modules/esm/module_job');
14+
1415
const defaultResolve = require('internal/modules/esm/default_resolve');
1516
const createDynamicModule = require(
1617
'internal/modules/esm/create_dynamic_module');
1718
const { translators } = require('internal/modules/esm/translators');
19+
const { ModuleWrap } = internalBinding('module_wrap');
1820

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

@@ -51,6 +53,8 @@ class Loader {
5153
// an object with the same keys as `exports`, whose values are get/set
5254
// functions for the actual exported values.
5355
this._dynamicInstantiate = undefined;
56+
// The index for assigning unique URLs to anonymous module evaluation
57+
this.evalIndex = 0;
5458
}
5559

5660
async resolve(specifier, parentURL) {
@@ -98,9 +102,25 @@ class Loader {
98102
return { url, format };
99103
}
100104

105+
async eval(source, url = `eval:${++this.evalIndex}`) {
106+
const evalInstance = async (url) => {
107+
return {
108+
module: new ModuleWrap(source, url),
109+
reflect: undefined
110+
};
111+
};
112+
const job = new ModuleJob(this, url, evalInstance, false);
113+
this.moduleMap.set(url, job);
114+
const { module, result } = await job.run();
115+
return {
116+
namespace: module.namespace(),
117+
result
118+
};
119+
}
120+
101121
async import(specifier, parent) {
102122
const job = await this.getModuleJob(specifier, parent);
103-
const module = await job.run();
123+
const { module } = await job.run();
104124
return module.namespace();
105125
}
106126

@@ -146,4 +166,4 @@ class Loader {
146166

147167
Object.setPrototypeOf(Loader.prototype, null);
148168

149-
module.exports = Loader;
169+
exports.Loader = Loader;

lib/internal/modules/esm/module_job.js

+1-2
Original file line numberDiff line numberDiff line change
@@ -101,8 +101,7 @@ class ModuleJob {
101101

102102
async run() {
103103
const module = await this.instantiate();
104-
module.evaluate(-1, false);
105-
return module;
104+
return { module, result: module.evaluate(-1, false) };
106105
}
107106
}
108107
Object.setPrototypeOf(ModuleJob.prototype, null);

lib/internal/process/esm_loader.js

+10-4
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,20 @@
33
const {
44
callbackMap,
55
} = internalBinding('module_wrap');
6+
const {
7+
ERR_INVALID_TYPE_FLAG,
8+
ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING,
9+
} = require('internal/errors').codes;
10+
11+
const type = require('internal/options').getOptionValue('--type');
12+
if (type && type !== 'commonjs' && type !== 'module')
13+
throw new ERR_INVALID_TYPE_FLAG(type);
14+
exports.typeFlag = type;
615

7-
const Loader = require('internal/modules/esm/loader');
16+
const { Loader } = require('internal/modules/esm/loader');
817
const {
918
wrapToModuleMap,
1019
} = require('internal/vm/source_text_module');
11-
const {
12-
ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING,
13-
} = require('internal/errors').codes;
1420

1521
exports.initializeImportMetaObject = function(wrap, meta) {
1622
if (callbackMap.has(wrap)) {

lib/internal/process/execution.js

+19
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,24 @@ function tryGetCwd() {
3333
}
3434
}
3535

36+
function evalModule(source) {
37+
const { decorateErrorStack } = require('internal/util');
38+
const asyncESM = require('internal/process/esm_loader');
39+
asyncESM.loaderPromise.then(async (loader) => {
40+
const { result } = await loader.eval(source);
41+
if (require('internal/options').getOptionValue('--print')) {
42+
console.log(result);
43+
}
44+
})
45+
.catch((e) => {
46+
decorateErrorStack(e);
47+
console.error(e);
48+
process.exit(1);
49+
});
50+
// Handle any nextTicks added in the first tick of the program.
51+
process._tickCallback();
52+
}
53+
3654
function evalScript(name, body, breakFirstLine) {
3755
const CJSModule = require('internal/modules/cjs/loader');
3856
const { kVmBreakFirstLineSymbol } = require('internal/util');
@@ -180,6 +198,7 @@ function readStdin(callback) {
180198
module.exports = {
181199
readStdin,
182200
tryGetCwd,
201+
evalModule,
183202
evalScript,
184203
fatalException: createFatalException(),
185204
setUncaughtExceptionCaptureCallback,

src/env.h

-1
Original file line numberDiff line numberDiff line change
@@ -178,7 +178,6 @@ constexpr size_t kFsStatsBufferLength = kFsStatsFieldsNumber * 2;
178178
V(env_var_settings_string, "envVarSettings") \
179179
V(errno_string, "errno") \
180180
V(error_string, "error") \
181-
V(esm_string, "esm") \
182181
V(exchange_string, "exchange") \
183182
V(exit_code_string, "exitCode") \
184183
V(expire_string, "expire") \

0 commit comments

Comments
 (0)