Skip to content

Commit 3af3746

Browse files
committed
esm: allow --import to define main entry
1 parent 5570c29 commit 3af3746

File tree

3 files changed

+64
-6
lines changed

3 files changed

+64
-6
lines changed

lib/internal/modules/run_main.js

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
'use strict';
22

33
const {
4+
ArrayPrototypeAt,
45
StringPrototypeEndsWith,
56
} = primordials;
67

@@ -53,15 +54,15 @@ function shouldUseESMLoader(mainPath) {
5354

5455
/**
5556
* Run the main entry point through the ESM Loader.
56-
* @param {string} mainPath Absolute path to the main entry point
57+
* @param {string} mainEntry - Absolute path to the main entry point, or a specifier to be resolved by the ESM Loader
5758
*/
58-
function runMainESM(mainPath) {
59+
function runMainESM(mainEntry) {
5960
const { loadESM } = require('internal/process/esm_loader');
6061
const { pathToFileURL } = require('internal/url');
6162

6263
handleMainPromise(loadESM((esmLoader) => {
63-
const main = path.isAbsolute(mainPath) ?
64-
pathToFileURL(mainPath).href : mainPath;
64+
const main = path.isAbsolute(mainEntry) ?
65+
pathToFileURL(mainEntry).href : mainEntry;
6566
return esmLoader.import(main, undefined, { __proto__: null });
6667
}));
6768
}
@@ -89,6 +90,18 @@ async function handleMainPromise(promise) {
8990
* @param {string} main CLI main entry point string
9091
*/
9192
function executeUserEntryPoint(main = process.argv[1]) {
93+
if (!main) {
94+
const importFlagValues = getOptionValue('--import');
95+
if (importFlagValues.length > 0) {
96+
// No main entry point was specified, but --import was used, so we'll use the last --import value as the entry.
97+
// Because --import takes specifiers that get resolved by the ESM resolution algorithm, we pass it through to be
98+
// resolved later.
99+
const entry = ArrayPrototypeAt(importFlagValues, -1);
100+
runMainESM(entry);
101+
return;
102+
}
103+
}
104+
92105
const resolvedMain = resolveMainPath(main);
93106
const useESMLoader = shouldUseESMLoader(resolvedMain);
94107
if (useESMLoader) {

src/node.cc

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -360,7 +360,9 @@ MaybeLocal<Value> StartExecution(Environment* env, StartExecutionCallback cb) {
360360
return StartExecution(env, "internal/main/watch_mode");
361361
}
362362

363-
if (!first_argv.empty() && first_argv != "-") {
363+
if ((!first_argv.empty() && first_argv != "-") ||
364+
(first_argv.empty() && !env->options()->force_repl &&
365+
!env->options()->preload_esm_modules.empty())) {
364366
return StartExecution(env, "internal/main/run_main_module");
365367
}
366368

test/es-module/test-esm-import-flag.mjs

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
import { spawnPromisified } from '../common/index.mjs';
1+
import { mustCall, spawnPromisified } from '../common/index.mjs';
22
import fixtures from '../common/fixtures.js';
33
import assert from 'node:assert';
4+
import { spawn } from 'node:child_process';
45
import { execPath } from 'node:process';
56
import { describe, it } from 'node:test';
67

@@ -42,6 +43,48 @@ describe('import modules using --import', { concurrency: true }, () => {
4243
assert.strictEqual(signal, null);
4344
});
4445

46+
it('should treat the last import as the main entry point when no argument is passed', async () => {
47+
const { code, signal, stderr, stdout } = await spawnPromisified(
48+
execPath,
49+
[
50+
'--import', mjsImport,
51+
]
52+
);
53+
54+
assert.strictEqual(stderr, '');
55+
assert.match(stdout, /^\.mjs file\r?\n$/);
56+
assert.strictEqual(code, 0);
57+
assert.strictEqual(signal, null);
58+
});
59+
60+
it('should still launch the REPL when --interactive is passed', async () => {
61+
const child = spawn(execPath, [
62+
'--import', mjsImport,
63+
'--interactive',
64+
]);
65+
66+
child.on('exit', mustCall((code, signal) => {
67+
assert.strictEqual(code, 0);
68+
assert.strictEqual(signal, null);
69+
}));
70+
71+
child.stdin.write('.exit\n');
72+
});
73+
74+
it('should still launch the REPL when -i is passed', async () => {
75+
const child = spawn(execPath, [
76+
'--import', mjsImport,
77+
'-i',
78+
]);
79+
80+
child.on('exit', mustCall((code, signal) => {
81+
assert.strictEqual(code, 0);
82+
assert.strictEqual(signal, null);
83+
}));
84+
85+
child.stdin.write('.exit\n');
86+
});
87+
4588
it('should import when main entrypoint is a cjs file', async () => {
4689
const { code, signal, stderr, stdout } = await spawnPromisified(
4790
execPath,

0 commit comments

Comments
 (0)