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 d12d0c9a48..e748beeb99 100644
--- a/doc/api/errors.md
+++ b/doc/api/errors.md
@@ -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.
+
+### 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
@@ -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
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 acdde6c58e..bea5cd9a5c 100644
--- a/lib/internal/modules/cjs/loader.js
+++ b/lib/internal/modules/cjs/loader.js
@@ -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();
};
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 e618347231..46b62af7c8 100644
--- a/src/env.h
+++ b/src/env.h
@@ -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") \
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 f5e6eef94e..a611a1482c 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 f0888a0538..b0630ae284 100644
--- a/src/node_options.cc
+++ b/src/node_options.cc
@@ -209,10 +209,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);
@@ -239,6 +241,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 c915b79b6b..8d34e6181c 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, '');