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: prevent erroneous re-initialization of ESMLoader #43763

Merged
Show file tree
Hide file tree
Changes from 2 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
10 changes: 9 additions & 1 deletion lib/internal/process/esm_loader.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,14 +40,20 @@ async function importModuleDynamicallyCallback(wrap, specifier, assertions) {
};

const esmLoader = new ESMLoader();

exports.esmLoader = esmLoader;

// Module.runMain() causes loadESM() to re-run (which it should do); however, this should NOT cause
// ESM to be re-initialised; doing so causes duplicate custom loaders to be added to the public
// esmLoader.
let isESMInitialized = false;

/**
* Causes side-effects: user-defined loader hooks are added to esmLoader.
* @returns {void}
*/
async function initializeLoader() {
if (isESMInitialized) { return; }

const { getOptionValue } = require('internal/options');
const customLoaders = getOptionValue('--experimental-loader');

Expand Down Expand Up @@ -75,6 +81,8 @@ async function initializeLoader() {
// Hooks must then be added to external/public loader
// (so they're triggered in userland)
await esmLoader.addCustomLoaders(keyedExportsList);

isESMInitialized = true;
}

exports.loadESM = async function loadESM(callback) {
Expand Down
30 changes: 30 additions & 0 deletions test/es-module/test-esm-initialization.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import '../common/index.mjs';
import * as fixtures from '../common/fixtures.mjs';
import assert from 'node:assert';
import { spawnSync } from 'node:child_process';


{ // Verify unadulterated source is loaded when there are no loaders
const { status, stderr, stdout } = spawnSync(
process.execPath,
[
'--loader',
fixtures.fileURL('es-module-loaders', 'loader-resolve-passthru.mjs'),
'--no-warnings',
fixtures.path('es-modules', 'runmain.mjs'),
],
{ encoding: 'utf8' },
);

const resolveHookRunCount = [...(stdout.matchAll(/resolve passthru/g) ?? new Array())]
.length - 1; // less 1 because the first is the needle
JakobJingleheimer marked this conversation as resolved.
Show resolved Hide resolved

assert.strictEqual(stderr, '');
/**
* resolveHookRunCount = 2:
* 1. fixtures/…/runmain.mjs
* 2. node:module (imported by fixtures/…/runmain.mjs)
*/
assert.strictEqual(resolveHookRunCount, 2);
assert.strictEqual(status, 0);
}
7 changes: 7 additions & 0 deletions test/fixtures/es-modules/runmain.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { runMain } from 'node:module';

try { await import.meta.resolve('doesnt-matter.mjs') } catch {}

runMain();

try { await import.meta.resolve('doesnt-matter.mjs') } catch {}