diff --git a/lib/internal/modules/esm/get_format.js b/lib/internal/modules/esm/get_format.js index 4ac9c011d153f4..2d747d8bfeb1f3 100644 --- a/lib/internal/modules/esm/get_format.js +++ b/lib/internal/modules/esm/get_format.js @@ -16,6 +16,7 @@ const { const experimentalNetworkImports = getOptionValue('--experimental-network-imports'); +const isESModule = !!getOptionValue('--module'); const { getPackageType, getPackageScopeConfig } = require('internal/modules/esm/resolve'); const { fileURLToPath } = require('internal/url'); const { ERR_UNKNOWN_FILE_EXTENSION } = require('internal/errors').codes; @@ -73,6 +74,9 @@ function extname(url) { * @returns {string} */ function getFileProtocolModuleFormat(url, context, ignoreErrors) { + if (isESModule && context.parentURL === undefined) + return 'module'; + const ext = extname(url); if (ext === '.js') { return getPackageType(url) === 'module' ? 'module' : 'commonjs'; diff --git a/lib/internal/modules/run_main.js b/lib/internal/modules/run_main.js index 0bfe7b11241416..4f93e5ecdff441 100644 --- a/lib/internal/modules/run_main.js +++ b/lib/internal/modules/run_main.js @@ -46,12 +46,12 @@ function shouldUseESMLoader(mainPath) { return pkg && pkg.data.type === 'module'; } -function runMainESM(mainPath) { +function runMainESM(mainPath, isPath = true) { const { loadESM } = require('internal/process/esm_loader'); const { pathToFileURL } = require('internal/url'); handleMainPromise(loadESM((esmLoader) => { - const main = path.isAbsolute(mainPath) ? + const main = isPath && path.isAbsolute(mainPath) ? pathToFileURL(mainPath).href : mainPath; return esmLoader.import(main, undefined, { __proto__: null }); })); @@ -73,6 +73,13 @@ async function handleMainPromise(promise) { // monkey-patchable code that belongs to the CJS loader (exposed by // `require('module')`) even when the entry point is ESM. function executeUserEntryPoint(main = process.argv[1]) { + const mainModule = getOptionValue('--module'); + if (mainModule) { + const { pathToFileURL, URL } = require('internal/url'); + const mainModuleURL = new URL(mainModule, pathToFileURL(process.cwd() + path.sep)).href; + return void runMainESM(mainModuleURL, false); + } + const resolvedMain = resolveMainPath(main); const useESMLoader = shouldUseESMLoader(resolvedMain); if (useESMLoader) { diff --git a/src/node.cc b/src/node.cc index e6be00eeb3c185..9395a79b11199c 100644 --- a/src/node.cc +++ b/src/node.cc @@ -360,7 +360,8 @@ MaybeLocal StartExecution(Environment* env, StartExecutionCallback cb) { return StartExecution(env, "internal/main/watch_mode"); } - if (!first_argv.empty() && first_argv != "-") { + if ((!first_argv.empty() && first_argv != "-") || + env->options()->main_esm_module != "") { return StartExecution(env, "internal/main/run_main_module"); } diff --git a/src/node_options.cc b/src/node_options.cc index 6ea85e3399be69..f64f3788ff457c 100644 --- a/src/node_options.cc +++ b/src/node_options.cc @@ -701,6 +701,9 @@ EnvironmentOptionsParser::EnvironmentOptionsParser() { "ES module to preload (option can be repeated)", &EnvironmentOptions::preload_esm_modules, kAllowedInEnvvar); + AddOption("--module", + "Main ES module to load by absolute or relative URL", + &EnvironmentOptions::main_esm_module); AddOption("--interactive", "always enter the REPL even if stdin does not appear " "to be a terminal", diff --git a/src/node_options.h b/src/node_options.h index bc18a45e681a3c..d193f7946688f0 100644 --- a/src/node_options.h +++ b/src/node_options.h @@ -211,6 +211,8 @@ class EnvironmentOptions : public Options { std::vector preload_esm_modules; + std::string main_esm_module; + std::vector user_argv; inline DebugOptions* get_debug_options() { return &debug_options_; }