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 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
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' },
);

// Length minus 1 because the first match is the needle.
const resolveHookRunCount = (stdout.match(/resolve passthru/g)?.length ?? 0) - 1;

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 {}