Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

esm: allow --import to define main entry #49946

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 17 additions & 4 deletions lib/internal/modules/run_main.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
'use strict';

const {
ArrayPrototypeAt,
StringPrototypeEndsWith,
} = primordials;

Expand Down Expand Up @@ -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 });
}));
}
Expand Down Expand Up @@ -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) {
Expand Down
4 changes: 3 additions & 1 deletion src/node.cc
Original file line number Diff line number Diff line change
Expand Up @@ -360,7 +360,9 @@ MaybeLocal<Value> 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");
}

Expand Down
45 changes: 44 additions & 1 deletion test/es-module/test-esm-import-flag.mjs
Original file line number Diff line number Diff line change
@@ -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';

Expand Down Expand Up @@ -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,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We would want a test with several --import and some way to validate that there's a "main" one

]
);

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,
Expand Down