Skip to content

Commit 76f775e

Browse files
esm: refactor DefaultModuleLoader
Fixes nodejs#48515 Fixes nodejs#48439
1 parent 951da52 commit 76f775e

9 files changed

+280
-147
lines changed

lib/internal/modules/esm/hooks.js

+59-10
Original file line numberDiff line numberDiff line change
@@ -81,8 +81,8 @@ let debug = require('internal/util/debuglog').debuglog('esm', (fn) => {
8181
// [2] `validate...()`s throw the wrong error
8282

8383

84-
class Hooks {
85-
#chains = {
84+
function getDefaultChains() {
85+
return {
8686
/**
8787
* Prior to ESM loading. These are called once before any modules are started.
8888
* @private
@@ -115,27 +115,46 @@ class Hooks {
115115
},
116116
],
117117
};
118+
}
119+
120+
class Hooks {
121+
#chains;
118122

119123
// Cache URLs we've already validated to avoid repeated validation
120-
#validatedUrls = new SafeSet();
124+
#validatedUrls;
125+
126+
constructor(chains = getDefaultChains(), validatedUrls = new SafeSet()) {
127+
this.#chains = chains;
128+
this.#validatedUrls = validatedUrls;
129+
}
121130

122131
/**
123132
* Import and register custom/user-defined module loader hook(s).
124133
* @param {string} urlOrSpecifier
125134
* @param {string} parentURL
126135
*/
127136
async register(urlOrSpecifier, parentURL) {
128-
const moduleLoader = require('internal/process/esm_loader').esmLoader;
129-
130-
const keyedExports = await moduleLoader.import(
137+
const esmLoader = require('internal/process/esm_loader').esmLoader;
138+
const keyedExports = await esmLoader.import(
131139
urlOrSpecifier,
132140
parentURL,
133141
kEmptyObject,
134142
);
135-
136143
this.addCustomLoader(urlOrSpecifier, keyedExports);
137144
}
138145

146+
allowImportMetaResolve() {
147+
return false;
148+
}
149+
150+
getChains() {
151+
return this.#chains;
152+
}
153+
154+
getValidatedUrls() {
155+
return this.#validatedUrls;
156+
}
157+
139158
/**
140159
* Collect custom/user-defined module loader hook(s).
141160
* After all hooks have been collected, the global preload hook(s) must be initialized.
@@ -221,15 +240,16 @@ class Hooks {
221240
parentURL,
222241
importAssertions = { __proto__: null },
223242
) {
243+
const chain = this.#chains.resolve;
224244
throwIfInvalidParentURL(parentURL);
225245

226-
const chain = this.#chains.resolve;
227246
const context = {
228247
conditions: getDefaultConditions(),
229248
importAssertions,
230249
parentURL,
231250
};
232251
const meta = {
252+
hooks: this,
233253
chainFinished: null,
234254
context,
235255
hookErrIdentifier: '',
@@ -346,6 +366,7 @@ class Hooks {
346366
async load(url, context = {}) {
347367
const chain = this.#chains.load;
348368
const meta = {
369+
hooks: this,
349370
chainFinished: null,
350371
context,
351372
hookErrIdentifier: '',
@@ -528,7 +549,17 @@ class HooksProxy {
528549
debug('wait for signal from worker');
529550
AtomicsWait(this.#lock, WORKER_TO_MAIN_THREAD_NOTIFICATION, 0);
530551
const response = this.#worker.receiveMessageSync();
531-
if (response.message.status === 'exit') { return; }
552+
if (response.message.status === 'exit') {
553+
// TODO: I do not understand why this is necessary.
554+
// node \
555+
// --no-warnings --experimental-loader 'data:text/javascript,process.exit(42)'
556+
// ./test/fixtures/empty.js
557+
// Does not trigger `this.#worker.on('exit', process.exit);`.
558+
// I think it is because `makeSyncRequest` keeps waiting to see another
559+
// message and blocks the thread from ANY other activity including the exit.
560+
process.exit(response.message.body);
561+
return;
562+
}
532563
const { preloadScripts } = this.#unwrapMessage(response);
533564
this.#executePreloadScripts(preloadScripts);
534565
}
@@ -749,7 +780,25 @@ function nextHookFactory(chain, meta, { validateArgs, validateOutput }) {
749780
ObjectAssign(meta.context, context);
750781
}
751782

752-
const output = await hook(arg0, meta.context, nextNextHook);
783+
const esmLoader = require('internal/process/esm_loader').esmLoader;
784+
785+
const chains = meta.hooks.getChains();
786+
const load = chain === chains.load ? chains.load.slice(0, generatedHookIndex) : chains.load;
787+
const resolve = chain === chains.resolve ? chains.resolve.slice(0, generatedHookIndex) : chains.resolve;
788+
let output;
789+
if (load.length > 0 && resolve.length > 0) {
790+
const nextChains = {
791+
load,
792+
resolve,
793+
globalPreload: chains.globalPreload,
794+
};
795+
const delegate = new Hooks(nextChains, meta.hooks.getValidatedUrls());
796+
output = await esmLoader.withDelegate(delegate, () => {
797+
return hook(arg0, meta.context, nextNextHook);
798+
});
799+
} else {
800+
output = await hook(arg0, meta.context, nextNextHook);
801+
}
753802

754803
validateOutput(outputErrIdentifier, output);
755804

lib/internal/modules/esm/initialize_import_meta.js

+10-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,14 @@
11
'use strict';
22

3+
const { Symbol } = primordials;
4+
35
const { getOptionValue } = require('internal/options');
46
const experimentalImportMetaResolve = getOptionValue('--experimental-import-meta-resolve');
7+
const kResolveSync = Symbol('sync');
8+
9+
const importAssertions = {
10+
[kResolveSync]: true,
11+
};
512

613
/**
714
* Generate a function to be used as import.meta.resolve for a particular module.
@@ -14,7 +21,7 @@ function createImportMetaResolve(defaultParentUrl, loader) {
1421
let url;
1522

1623
try {
17-
({ url } = loader.resolve(specifier, parentUrl));
24+
({ url } = loader.resolve(specifier, parentUrl, importAssertions));
1825
} catch (error) {
1926
if (error?.code === 'ERR_UNSUPPORTED_DIR_IMPORT') {
2027
({ url } = error);
@@ -38,7 +45,7 @@ function initializeImportMeta(meta, context, loader) {
3845
const { url } = context;
3946

4047
// Alphabetical
41-
if (experimentalImportMetaResolve && loader.loaderType !== 'internal') {
48+
if (experimentalImportMetaResolve && loader.allowImportMetaResolve()) {
4249
meta.resolve = createImportMetaResolve(url, loader);
4350
}
4451

@@ -49,4 +56,5 @@ function initializeImportMeta(meta, context, loader) {
4956

5057
module.exports = {
5158
initializeImportMeta,
59+
kResolveSync,
5260
};

0 commit comments

Comments
 (0)