From 6ef8d53ab653fa07524a4abc9211476101ac59c4 Mon Sep 17 00:00:00 2001 From: Geoffrey Booth Date: Thu, 28 Sep 2023 21:31:49 -0700 Subject: [PATCH] esm: allow --import to define main entry --- lib/internal/modules/run_main.js | 21 +++++++++--- src/node.cc | 4 ++- test/es-module/test-esm-import-flag.mjs | 45 ++++++++++++++++++++++++- 3 files changed, 64 insertions(+), 6 deletions(-) diff --git a/lib/internal/modules/run_main.js b/lib/internal/modules/run_main.js index ac1ffef0412b17..1c54f9c0649e69 100644 --- a/lib/internal/modules/run_main.js +++ b/lib/internal/modules/run_main.js @@ -1,6 +1,7 @@ 'use strict'; const { + ArrayPrototypeAt, StringPrototypeEndsWith, } = primordials; @@ -53,15 +54,15 @@ function shouldUseESMLoader(mainPath) { /** * Run the main entry point through the ESM Loader. - * @param {string} mainPath Absolute path to the main entry point + * @param {string} mainEntry - Absolute path to the main entry point, or a specifier to be resolved by the ESM Loader */ -function runMainESM(mainPath) { +function runMainESM(mainEntry) { const { loadESM } = require('internal/process/esm_loader'); const { pathToFileURL } = require('internal/url'); handleMainPromise(loadESM((esmLoader) => { - const main = path.isAbsolute(mainPath) ? - pathToFileURL(mainPath).href : mainPath; + const main = path.isAbsolute(mainEntry) ? + pathToFileURL(mainEntry).href : mainEntry; return esmLoader.import(main, undefined, { __proto__: null }); })); } @@ -89,6 +90,18 @@ async function handleMainPromise(promise) { * @param {string} main CLI main entry point string */ function executeUserEntryPoint(main = process.argv[1]) { + if (!main) { + const importFlagValues = getOptionValue('--import'); + if (importFlagValues.length > 0) { + // No main entry point was specified, but --import was used, so we'll use the last --import value as the entry. + // Because --import takes specifiers that get resolved by the ESM resolution algorithm, we pass it through to be + // resolved later. + const entry = ArrayPrototypeAt(importFlagValues, -1); + runMainESM(entry); + return; + } + } + const resolvedMain = resolveMainPath(main); const useESMLoader = shouldUseESMLoader(resolvedMain); if (useESMLoader) { diff --git a/src/node.cc b/src/node.cc index 89e0e5524c2102..c4d1f0dde210f5 100644 --- a/src/node.cc +++ b/src/node.cc @@ -360,7 +360,9 @@ 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 != "-") || + (first_argv.empty() && !env->options()->force_repl && + !env->options()->preload_esm_modules.empty())) { return StartExecution(env, "internal/main/run_main_module"); } diff --git a/test/es-module/test-esm-import-flag.mjs b/test/es-module/test-esm-import-flag.mjs index 2f6f4dd48b49a8..71cfc231bcfcac 100644 --- a/test/es-module/test-esm-import-flag.mjs +++ b/test/es-module/test-esm-import-flag.mjs @@ -1,6 +1,7 @@ -import { spawnPromisified } from '../common/index.mjs'; +import { mustCall, spawnPromisified } from '../common/index.mjs'; import fixtures from '../common/fixtures.js'; import assert from 'node:assert'; +import { spawn } from 'node:child_process'; import { execPath } from 'node:process'; import { describe, it } from 'node:test'; @@ -42,6 +43,48 @@ describe('import modules using --import', { concurrency: true }, () => { assert.strictEqual(signal, null); }); + it('should treat the last import as the main entry point when no argument is passed', async () => { + const { code, signal, stderr, stdout } = await spawnPromisified( + execPath, + [ + '--import', mjsImport, + ] + ); + + assert.strictEqual(stderr, ''); + assert.match(stdout, /^\.mjs file\r?\n$/); + assert.strictEqual(code, 0); + assert.strictEqual(signal, null); + }); + + it('should still launch the REPL when --interactive is passed', async () => { + const child = spawn(execPath, [ + '--import', mjsImport, + '--interactive', + ]); + + child.on('exit', mustCall((code, signal) => { + assert.strictEqual(code, 0); + assert.strictEqual(signal, null); + })); + + child.stdin.write('.exit\n'); + }); + + it('should still launch the REPL when -i is passed', async () => { + const child = spawn(execPath, [ + '--import', mjsImport, + '-i', + ]); + + child.on('exit', mustCall((code, signal) => { + assert.strictEqual(code, 0); + assert.strictEqual(signal, null); + })); + + child.stdin.write('.exit\n'); + }); + it('should import when main entrypoint is a cjs file', async () => { const { code, signal, stderr, stdout } = await spawnPromisified( execPath,