From d89e9ee2ae0202903a7ea87e929b81e249fb3e01 Mon Sep 17 00:00:00 2001 From: Guy Bedford Date: Mon, 11 Feb 2019 02:27:18 +0200 Subject: [PATCH] esm: implement top-level --type flag, type:esm -> type:module --- .eslintrc.js | 1 + doc/api/errors.md | 22 ++++++- lib/internal/errors.js | 11 +++- lib/internal/main/check_syntax.js | 27 ++++++++- lib/internal/main/eval_stdin.js | 6 +- lib/internal/main/eval_string.js | 7 ++- lib/internal/main/repl.js | 7 +++ lib/internal/modules/cjs/loader.js | 26 ++++---- lib/internal/modules/esm/default_resolve.js | 18 ++++-- lib/internal/modules/esm/loader.js | 24 +++++++- lib/internal/modules/esm/module_job.js | 3 +- lib/internal/process/esm_loader.js | 14 +++-- lib/internal/process/execution.js | 19 ++++++ src/env.h | 1 - src/module_wrap.cc | 67 ++++++++++++++++----- src/module_wrap.h | 4 -- src/node_options.cc | 10 ++- src/node_options.h | 1 + test/es-module/test-esm-loader-modulemap.js | 2 +- test/es-module/test-esm-type-flag.js | 11 ++++ test/parallel/test-cli-syntax-piped-bad.js | 33 ++++++++-- test/parallel/test-cli-syntax-piped-good.js | 24 ++++++-- 22 files changed, 267 insertions(+), 71 deletions(-) create mode 100644 test/es-module/test-esm-type-flag.js diff --git a/.eslintrc.js b/.eslintrc.js index 777e24bba4..ee08e0206b 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -38,6 +38,7 @@ module.exports = { { files: [ 'doc/api/esm.md', + 'test/es-module/test-esm-type-flag.js', '*.mjs', ], parserOptions: { sourceType: 'module' }, diff --git a/doc/api/errors.md b/doc/api/errors.md index 4d94b3b04b..0f33cfe689 100644 --- a/doc/api/errors.md +++ b/doc/api/errors.md @@ -2201,6 +2201,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. + +### ERR_INVALID_REPL_TYPE + +> Stability: 1 - Experimental + +The `--type=...` flag is not compatible with the Node.js REPL. + + +### ERR_INVALID_TYPE_EXTENSION + +> Stability: 1 - Experimental + +Attempted to execute a `.cjs` module with the `--type=module` flag. + + +### ERR_INVALID_TYPE_FLAG + +> Stability: 1 - Experimental + +An invalid `--type=...` flag value was provided. + #### ERR_MISSING_DYNAMIC_INSTANTIATE_HOOK @@ -2231,7 +2252,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 diff --git a/lib/internal/errors.js b/lib/internal/errors.js index 4f755ebef2..32c264dbe9 100644 --- a/lib/internal/errors.js +++ b/lib/internal/errors.js @@ -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}.`; @@ -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', @@ -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', diff --git a/lib/internal/main/check_syntax.js b/lib/internal/main/check_syntax.js index 392fadb99f..1cba49f6b9 100644 --- a/lib/internal/main/check_syntax.js +++ b/lib/internal/main/check_syntax.js @@ -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 { @@ -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. 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 19ac66668c..a2500e585a 100644 --- a/lib/internal/modules/cjs/loader.js +++ b/lib/internal/modules/cjs/loader.js @@ -797,21 +797,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(); }; diff --git a/lib/internal/modules/esm/default_resolve.js b/lib/internal/modules/esm/default_resolve.js index 1e784e710a..fea151a0ff 100644 --- a/lib/internal/modules/esm/default_resolve.js +++ b/lib/internal/modules/esm/default_resolve.js @@ -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', @@ -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; @@ -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 }; diff --git a/lib/internal/modules/esm/loader.js b/lib/internal/modules/esm/loader.js index 1776d3da48..d2b4ff3d8f 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 { ModuleWrap } = internalBinding('module_wrap'); const FunctionBind = Function.call.bind(Function.prototype.bind); @@ -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) { @@ -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(); } @@ -146,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 5cbf4e3126..8e0c7b7be9 100644 --- a/lib/internal/modules/esm/module_job.js +++ b/lib/internal/modules/esm/module_job.js @@ -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/process/esm_loader.js b/lib/internal/process/esm_loader.js index 98e1f26d94..e405dc8b30 100644 --- a/lib/internal/process/esm_loader.js +++ b/lib/internal/process/esm_loader.js @@ -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)) { diff --git a/lib/internal/process/execution.js b/lib/internal/process/execution.js index a35feaacce..6d256a05e3 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'); @@ -180,6 +198,7 @@ function readStdin(callback) { module.exports = { readStdin, tryGetCwd, + evalModule, evalScript, fatalException: createFatalException(), setUncaughtExceptionCaptureCallback, diff --git a/src/env.h b/src/env.h index 7c7f3c7e0e..bf3b01454d 100644 --- a/src/env.h +++ b/src/env.h @@ -178,7 +178,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") \ diff --git a/src/module_wrap.cc b/src/module_wrap.cc index ed6795bd8e..6f527aa1aa 100644 --- a/src/module_wrap.cc +++ b/src/module_wrap.cc @@ -585,7 +585,7 @@ Maybe GetPackageConfig(Environment* env, IsESM::Bool esm = IsESM::No; Local type_v; if (pkg_json->Get(env->context(), env->type_string()).ToLocal(&type_v)) { - if (type_v->StrictEquals(env->esm_string())) { + if (type_v->StrictEquals(env->module_string())) { esm = IsESM::Yes; } } @@ -692,8 +692,7 @@ Maybe LegacyMainResolve(const URL& pjson_url, Maybe FinalizeResolution(Environment* env, const URL& resolved, const URL& base, - bool check_exists, - bool is_main) { + bool check_exists) { const std::string& path = resolved.ToFilePath(); if (check_exists && CheckDescriptorAtPath(path) != FILE) { @@ -715,6 +714,35 @@ Maybe FinalizeResolution(Environment* env, resolved, pcfg.FromJust()->esm == IsESM::No }); } +Maybe FinalizeResolutionMain(Environment* env, + const URL& resolved, + const URL& base, + bool esm_flag) { + const std::string& path = resolved.ToFilePath(); + + if (CheckDescriptorAtPath(path) != FILE) { + std::string msg = "Cannot find module '" + path + + "' imported from " + base.ToFilePath(); + node::THROW_ERR_MODULE_NOT_FOUND(env, msg.c_str()); + return Nothing(); + } + + if (esm_flag) { + return Just(ModuleResolution { resolved, false }); + } + + Maybe pcfg = + GetPackageBoundaryConfig(env, resolved, base); + if (pcfg.IsNothing()) return Nothing(); + + if (pcfg.FromJust()->exists == Exists::No) { + return Just(ModuleResolution { resolved, true }); + } + + return Just(ModuleResolution { + resolved, pcfg.FromJust()->esm == IsESM::No }); +} + Maybe PackageMainResolve(Environment* env, const URL& pjson_url, const PackageConfig& pcfg, @@ -729,13 +757,11 @@ Maybe PackageMainResolve(Environment* env, } if (pcfg.has_main == HasMain::Yes && pcfg.main.substr(pcfg.main.length() - 4, 4) == ".mjs") { - return FinalizeResolution(env, URL(pcfg.main, pjson_url), base, true, - true); + return FinalizeResolution(env, URL(pcfg.main, pjson_url), base, true); } if (pcfg.esm == IsESM::Yes && pcfg.main.substr(pcfg.main.length() - 3, 3) == ".js") { - return FinalizeResolution(env, URL(pcfg.main, pjson_url), base, true, - true); + return FinalizeResolution(env, URL(pcfg.main, pjson_url), base, true); } Maybe resolved = LegacyMainResolve(pjson_url, pcfg); @@ -743,7 +769,7 @@ Maybe PackageMainResolve(Environment* env, if (resolved.IsNothing()) { return Nothing(); } - return FinalizeResolution(env, resolved.FromJust(), base, false, true); + return FinalizeResolution(env, resolved.FromJust(), base, false); } Maybe PackageResolve(Environment* env, @@ -793,12 +819,11 @@ Maybe PackageResolve(Environment* env, if (!pkg_subpath.length()) { return PackageMainResolve(env, pjson_url, *pcfg.FromJust(), base); } else { - return FinalizeResolution(env, URL(pkg_subpath, pjson_url), - base, true, false); + return FinalizeResolution(env, URL(pkg_subpath, pjson_url), base, true); } CHECK(false); // Cross-platform root check. - } while (pjson_url.path().length() != last_path.length()); + } while (pjson_path.length() != last_path.length()); std::string msg = "Cannot find package '" + pkg_name + "' imported from " + base.ToFilePath(); @@ -811,7 +836,8 @@ Maybe PackageResolve(Environment* env, Maybe Resolve(Environment* env, const std::string& specifier, const URL& base, - bool is_main) { + bool is_main, + bool esm_flag) { // Order swapped from spec for minor perf gain. // Ok since relative URLs cannot parse as URLs. URL resolved; @@ -825,14 +851,17 @@ Maybe Resolve(Environment* env, return PackageResolve(env, specifier, base); } } - return FinalizeResolution(env, resolved, base, true, is_main); + if (is_main) + return FinalizeResolutionMain(env, resolved, base, esm_flag); + else + return FinalizeResolution(env, resolved, base, true); } void ModuleWrap::Resolve(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); - // module.resolve(specifier, url, is_main) - CHECK_EQ(args.Length(), 3); + // module.resolve(specifier, url, is_main, esm_flag) + CHECK_EQ(args.Length(), 4); CHECK(args[0]->IsString()); Utf8Value specifier_utf8(env->isolate(), args[0]); @@ -844,6 +873,8 @@ void ModuleWrap::Resolve(const FunctionCallbackInfo& args) { CHECK(args[2]->IsBoolean()); + CHECK(args[3]->IsBoolean()); + if (url.flags() & URL_FLAGS_FAILED) { return node::THROW_ERR_INVALID_ARG_TYPE( env, "second argument is not a URL string"); @@ -851,7 +882,11 @@ void ModuleWrap::Resolve(const FunctionCallbackInfo& args) { TryCatchScope try_catch(env); Maybe result = - node::loader::Resolve(env, specifier_std, url, args[2]->IsTrue()); + node::loader::Resolve(env, + specifier_std, + url, + args[2]->IsTrue(), + args[3]->IsTrue()); if (result.IsNothing()) { CHECK(try_catch.HasCaught()); try_catch.ReThrow(); diff --git a/src/module_wrap.h b/src/module_wrap.h index 51111eeecf..3c3f7d66b4 100644 --- a/src/module_wrap.h +++ b/src/module_wrap.h @@ -28,10 +28,6 @@ struct ModuleResolution { bool legacy; }; -v8::Maybe Resolve(Environment* env, - const std::string& specifier, - const url::URL& base); - class ModuleWrap : public BaseObject { public: static const std::string EXTENSIONS[]; diff --git a/src/node_options.cc b/src/node_options.cc index 4055ae6696..1aa74ea1d1 100644 --- a/src/node_options.cc +++ b/src/node_options.cc @@ -213,10 +213,12 @@ EnvironmentOptionsParser::EnvironmentOptionsParser() { kAllowedInEnvironment); AddOption("--preserve-symlinks", "preserve symbolic links when resolving", - &EnvironmentOptions::preserve_symlinks); + &EnvironmentOptions::preserve_symlinks, + kAllowedInEnvironment); AddOption("--preserve-symlinks-main", "preserve symbolic links when resolving the main module", - &EnvironmentOptions::preserve_symlinks_main); + &EnvironmentOptions::preserve_symlinks_main, + kAllowedInEnvironment); AddOption("--prof-process", "process V8 profiler output generated using --prof", &EnvironmentOptions::prof_process); @@ -243,6 +245,10 @@ EnvironmentOptionsParser::EnvironmentOptionsParser() { "show stack traces on process warnings", &EnvironmentOptions::trace_warnings, kAllowedInEnvironment); + AddOption("--type", + "top-level module type name", + &EnvironmentOptions::module_type, + kAllowedInEnvironment); AddOption("--check", "syntax check script without executing", diff --git a/src/node_options.h b/src/node_options.h index 77a679453c..0674abcaca 100644 --- a/src/node_options.h +++ b/src/node_options.h @@ -86,6 +86,7 @@ class EnvironmentOptions : public Options { public: bool abort_on_uncaught_exception = false; bool experimental_modules = false; + std::string module_type; std::string experimental_policy; bool experimental_repl_await = false; bool experimental_vm_modules = false; diff --git a/test/es-module/test-esm-loader-modulemap.js b/test/es-module/test-esm-loader-modulemap.js index 946d54ffaa..5493c6c47c 100644 --- a/test/es-module/test-esm-loader-modulemap.js +++ b/test/es-module/test-esm-loader-modulemap.js @@ -7,7 +7,7 @@ const common = require('../common'); const { URL } = require('url'); -const Loader = require('internal/modules/esm/loader'); +const { Loader } = require('internal/modules/esm/loader'); const ModuleMap = require('internal/modules/esm/module_map'); const ModuleJob = require('internal/modules/esm/module_job'); const createDynamicModule = require( diff --git a/test/es-module/test-esm-type-flag.js b/test/es-module/test-esm-type-flag.js new file mode 100644 index 0000000000..cf91580490 --- /dev/null +++ b/test/es-module/test-esm-type-flag.js @@ -0,0 +1,11 @@ +// Flags: --experimental-modules --type=module +/* eslint-disable node-core/required-modules */ +import cjs from '../fixtures/baz.js'; +import '../common/index.mjs'; +import { message } from '../fixtures/es-modules/message.mjs'; +import assert from 'assert'; + +// Assert we loaded esm dependency as ".js" in this mode +assert.strictEqual(message, 'A message'); +// Assert we loaded CommonJS dependency +assert.strictEqual(cjs, 'perhaps I work'); diff --git a/test/parallel/test-cli-syntax-piped-bad.js b/test/parallel/test-cli-syntax-piped-bad.js index 64e2d47931..4385d8c8c5 100644 --- a/test/parallel/test-cli-syntax-piped-bad.js +++ b/test/parallel/test-cli-syntax-piped-bad.js @@ -8,24 +8,45 @@ const node = process.execPath; // test both sets of arguments that check syntax const syntaxArgs = [ - ['-c'], - ['--check'] + '-c', + '--check' ]; // Match on the name of the `Error` but not the message as it is different // depending on the JavaScript engine. -const syntaxErrorRE = /^SyntaxError: \b/m; +const syntaxErrorRE = /^SyntaxError: Unexpected identifier\b/m; // Should throw if code piped from stdin with --check has bad syntax // loop each possible option, `-c` or `--check` -syntaxArgs.forEach(function(args) { +syntaxArgs.forEach(function(arg) { const stdin = 'var foo bar;'; - const c = spawnSync(node, args, { encoding: 'utf8', input: stdin }); + const c = spawnSync(node, [arg], { encoding: 'utf8', input: stdin }); // stderr should include '[stdin]' as the filename assert(c.stderr.startsWith('[stdin]'), `${c.stderr} starts with ${stdin}`); - // no stdout or stderr should be produced + // no stdout should be produced + assert.strictEqual(c.stdout, ''); + + // stderr should have a syntax error message + assert(syntaxErrorRE.test(c.stderr), `${syntaxErrorRE} === ${c.stderr}`); + + assert.strictEqual(c.status, 1); +}); + +// Check --type=module +syntaxArgs.forEach(function(arg) { + const stdin = 'export var p = 5; var foo bar;'; + const c = spawnSync( + node, + ['--experimental-modules', '--type=module', '--no-warnings', arg], + { encoding: 'utf8', input: stdin } + ); + + // stderr should include '[stdin]' as the filename + assert(c.stderr.startsWith('[stdin]'), `${c.stderr} starts with ${stdin}`); + + // no stdout should be produced assert.strictEqual(c.stdout, ''); // stderr should have a syntax error message diff --git a/test/parallel/test-cli-syntax-piped-good.js b/test/parallel/test-cli-syntax-piped-good.js index 79716fcf39..7a8c9b2c05 100644 --- a/test/parallel/test-cli-syntax-piped-good.js +++ b/test/parallel/test-cli-syntax-piped-good.js @@ -8,15 +8,31 @@ const node = process.execPath; // test both sets of arguments that check syntax const syntaxArgs = [ - ['-c'], - ['--check'] + '-c', + '--check' ]; // Should not execute code piped from stdin with --check. // Loop each possible option, `-c` or `--check`. -syntaxArgs.forEach(function(args) { +syntaxArgs.forEach(function(arg) { const stdin = 'throw new Error("should not get run");'; - const c = spawnSync(node, args, { encoding: 'utf8', input: stdin }); + const c = spawnSync(node, [arg], { encoding: 'utf8', input: stdin }); + + // no stdout or stderr should be produced + assert.strictEqual(c.stdout, ''); + assert.strictEqual(c.stderr, ''); + + assert.strictEqual(c.status, 0); +}); + +// Check --type=module +syntaxArgs.forEach(function(arg) { + const stdin = 'export var p = 5; throw new Error("should not get run");'; + const c = spawnSync( + node, + ['--experimental-modules', '--no-warnings', '--type=module', arg], + { encoding: 'utf8', input: stdin } + ); // no stdout or stderr should be produced assert.strictEqual(c.stdout, '');