Skip to content

Commit 7b51341

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

11 files changed

Lines changed: 191 additions & 150 deletions

File tree

doc/api/errors.md

Lines changed: 0 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1233,23 +1233,6 @@ provided.
12331233
Encoding provided to `TextDecoder()` API was not one of the
12341234
[WHATWG Supported Encodings][].
12351235

1236-
<a id="ERR_ESM_LOADER_REGISTRATION_UNAVAILABLE"></a>
1237-
1238-
### `ERR_ESM_LOADER_REGISTRATION_UNAVAILABLE`
1239-
1240-
<!-- YAML
1241-
added: REPLACEME
1242-
-->
1243-
1244-
Programmatically registering custom ESM loaders
1245-
currently requires at least one custom loader to have been
1246-
registered via the `--experimental-loader` flag. A no-op
1247-
loader registered via CLI is sufficient
1248-
(for example: `--experimental-loader data:text/javascript,`;
1249-
do not omit the necessary trailing comma).
1250-
A future version of Node.js will support the programmatic
1251-
registration of loaders without needing to also use the flag.
1252-
12531236
<a id="ERR_EVAL_ESM_CANNOT_PRINT"></a>
12541237

12551238
### `ERR_EVAL_ESM_CANNOT_PRINT`

lib/internal/errors.js

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1036,11 +1036,6 @@ E('ERR_ENCODING_INVALID_ENCODED_DATA', function(encoding, ret) {
10361036
}, TypeError);
10371037
E('ERR_ENCODING_NOT_SUPPORTED', 'The "%s" encoding is not supported',
10381038
RangeError);
1039-
E('ERR_ESM_LOADER_REGISTRATION_UNAVAILABLE', 'Programmatically registering custom ESM loaders ' +
1040-
'currently requires at least one custom loader to have been registered via the --experimental-loader ' +
1041-
'flag. A no-op loader registered via CLI is sufficient (for example: `--experimental-loader ' +
1042-
'"data:text/javascript,"` with the necessary trailing comma). A future version of Node.js ' +
1043-
'will remove this requirement.', Error);
10441039
E('ERR_EVAL_ESM_CANNOT_PRINT', '--print cannot be used with ESM input', Error);
10451040
E('ERR_EVENT_RECURSION', 'The event "%s" is already being dispatched', Error);
10461041
E('ERR_FALSY_VALUE_REJECTION', function(reason) {

lib/internal/modules/esm/hooks.js

Lines changed: 29 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ const {
3030
ERR_INVALID_RETURN_PROPERTY_VALUE,
3131
ERR_INVALID_RETURN_VALUE,
3232
ERR_LOADER_CHAIN_INCOMPLETE,
33+
ERR_METHOD_NOT_IMPLEMENTED,
3334
ERR_WORKER_UNSERIALIZABLE_ERROR,
3435
} = require('internal/errors').codes;
3536
const { exitCodes: { kUnfinishedTopLevelAwait } } = internalBinding('errors');
@@ -80,7 +81,6 @@ let debug = require('internal/util/debuglog').debuglog('esm', (fn) => {
8081

8182
// [2] `validate...()`s throw the wrong error
8283

83-
8484
class Hooks {
8585
#chains = {
8686
/**
@@ -126,16 +126,18 @@ class Hooks {
126126
*/
127127
async register(urlOrSpecifier, parentURL) {
128128
const moduleLoader = require('internal/process/esm_loader').esmLoader;
129-
130129
const keyedExports = await moduleLoader.import(
131130
urlOrSpecifier,
132131
parentURL,
133132
kEmptyObject,
134133
);
135-
136134
this.addCustomLoader(urlOrSpecifier, keyedExports);
137135
}
138136

137+
allowImportMetaResolve() {
138+
return false;
139+
}
140+
139141
/**
140142
* Collect custom/user-defined module loader hook(s).
141143
* After all hooks have been collected, the global preload hook(s) must be initialized.
@@ -150,13 +152,16 @@ class Hooks {
150152
} = pluckHooks(exports);
151153

152154
if (globalPreload) {
153-
ArrayPrototypePush(this.#chains.globalPreload, { fn: globalPreload, url });
155+
const next = this.#chains.globalPreload[this.#chains.globalPreload.length - 1];
156+
ArrayPrototypePush(this.#chains.globalPreload, { fn: globalPreload, url, next });
154157
}
155158
if (resolve) {
156-
ArrayPrototypePush(this.#chains.resolve, { fn: resolve, url });
159+
const next = this.#chains.resolve[this.#chains.resolve.length - 1];
160+
ArrayPrototypePush(this.#chains.resolve, { fn: resolve, url, next });
157161
}
158162
if (load) {
159-
ArrayPrototypePush(this.#chains.load, { fn: load, url });
163+
const next = this.#chains.load[this.#chains.load.length - 1];
164+
ArrayPrototypePush(this.#chains.load, { fn: load, url, next });
160165
}
161166
}
162167

@@ -233,7 +238,6 @@ class Hooks {
233238
chainFinished: null,
234239
context,
235240
hookErrIdentifier: '',
236-
hookIndex: chain.length - 1,
237241
hookName: 'resolve',
238242
shortCircuited: false,
239243
};
@@ -256,7 +260,7 @@ class Hooks {
256260
}
257261
};
258262

259-
const nextResolve = nextHookFactory(chain, meta, { validateArgs, validateOutput });
263+
const nextResolve = nextHookFactory(chain[chain.length - 1], meta, { validateArgs, validateOutput });
260264

261265
const resolution = await nextResolve(originalSpecifier, context);
262266
const { hookErrIdentifier } = meta; // Retrieve the value after all settled
@@ -333,6 +337,10 @@ class Hooks {
333337
};
334338
}
335339

340+
resolveSync(_originalSpecifier, _parentURL, _importAssertions) {
341+
throw new ERR_METHOD_NOT_IMPLEMENTED('resolveSync()');
342+
}
343+
336344
/**
337345
* Provide source that is understood by one of Node's translators.
338346
*
@@ -349,7 +357,6 @@ class Hooks {
349357
chainFinished: null,
350358
context,
351359
hookErrIdentifier: '',
352-
hookIndex: chain.length - 1,
353360
hookName: 'load',
354361
shortCircuited: false,
355362
};
@@ -391,7 +398,7 @@ class Hooks {
391398
}
392399
};
393400

394-
const nextLoad = nextHookFactory(chain, meta, { validateArgs, validateOutput });
401+
const nextLoad = nextHookFactory(chain[chain.length - 1], meta, { validateArgs, validateOutput });
395402

396403
const loaded = await nextLoad(url, context);
397404
const { hookErrIdentifier } = meta; // Retrieve the value after all settled
@@ -528,7 +535,10 @@ class HooksProxy {
528535
debug('wait for signal from worker');
529536
AtomicsWait(this.#lock, WORKER_TO_MAIN_THREAD_NOTIFICATION, 0);
530537
const response = this.#worker.receiveMessageSync();
531-
if (response.message.status === 'exit') { return; }
538+
if (response.message.status === 'exit') {
539+
process.exit(response.message.body);
540+
return;
541+
}
532542
const { preloadScripts } = this.#unwrapMessage(response);
533543
this.#executePreloadScripts(preloadScripts);
534544
}
@@ -684,46 +694,38 @@ function pluckHooks({
684694
* A utility function to iterate through a hook chain, track advancement in the
685695
* chain, and generate and supply the `next<HookName>` argument to the custom
686696
* hook.
687-
* @param {KeyedHook[]} chain The whole hook chain.
697+
* @param {Hook} current The first hook in the chain.
688698
* @param {object} meta Properties that change as the current hook advances
689699
* along the chain.
690700
* @param {boolean} meta.chainFinished Whether the end of the chain has been
691701
* reached AND invoked.
692702
* @param {string} meta.hookErrIdentifier A user-facing identifier to help
693703
* pinpoint where an error occurred. Ex "file:///foo.mjs 'resolve'".
694-
* @param {number} meta.hookIndex A non-negative integer tracking the current
695-
* position in the hook chain.
696704
* @param {string} meta.hookName The kind of hook the chain is (ex 'resolve')
697705
* @param {boolean} meta.shortCircuited Whether a hook signaled a short-circuit.
698706
* @param {(hookErrIdentifier, hookArgs) => void} validate A wrapper function
699707
* containing all validation of a custom loader hook's intermediary output. Any
700708
* validation within MUST throw.
701709
* @returns {function next<HookName>(...hookArgs)} The next hook in the chain.
702710
*/
703-
function nextHookFactory(chain, meta, { validateArgs, validateOutput }) {
711+
function nextHookFactory(current, meta, { validateArgs, validateOutput }) {
704712
// First, prepare the current
705713
const { hookName } = meta;
706714
const {
707715
fn: hook,
708716
url: hookFilePath,
709-
} = chain[meta.hookIndex];
717+
next,
718+
} = current;
710719

711720
// ex 'nextResolve'
712721
const nextHookName = `next${
713722
StringPrototypeToUpperCase(hookName[0]) +
714723
StringPrototypeSlice(hookName, 1)
715724
}`;
716725

717-
// When hookIndex is 0, it's reached the default, which does not call next()
718-
// so feed it a noop that blows up if called, so the problem is obvious.
719-
const generatedHookIndex = meta.hookIndex;
720726
let nextNextHook;
721-
if (meta.hookIndex > 0) {
722-
// Now, prepare the next: decrement the pointer so the next call to the
723-
// factory generates the next link in the chain.
724-
meta.hookIndex--;
725-
726-
nextNextHook = nextHookFactory(chain, meta, { validateArgs, validateOutput });
727+
if (next) {
728+
nextNextHook = nextHookFactory(next, meta, { validateArgs, validateOutput });
727729
} else {
728730
// eslint-disable-next-line func-name-matching
729731
nextNextHook = function chainAdvancedTooFar() {
@@ -740,17 +742,16 @@ function nextHookFactory(chain, meta, { validateArgs, validateOutput }) {
740742

741743
validateArgs(`${meta.hookErrIdentifier} hook's ${nextHookName}()`, arg0, context);
742744

743-
const outputErrIdentifier = `${chain[generatedHookIndex].url} '${hookName}' hook's ${nextHookName}()`;
745+
const outputErrIdentifier = `${hookFilePath} '${hookName}' hook's ${nextHookName}()`;
744746

745747
// Set when next<HookName> is actually called, not just generated.
746-
if (generatedHookIndex === 0) { meta.chainFinished = true; }
748+
if (!next) { meta.chainFinished = true; }
747749

748750
if (context) { // `context` has already been validated, so no fancy check needed.
749751
ObjectAssign(meta.context, context);
750752
}
751753

752754
const output = await hook(arg0, meta.context, nextNextHook);
753-
754755
validateOutput(outputErrIdentifier, output);
755756

756757
if (output?.shortCircuit === true) { meta.shortCircuited = true; }

lib/internal/modules/esm/initialize_import_meta.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ function createImportMetaResolve(defaultParentUrl, loader) {
1414
let url;
1515

1616
try {
17-
({ url } = loader.resolve(specifier, parentUrl));
17+
({ url } = loader.resolveSync(specifier, parentUrl));
1818
} catch (error) {
1919
if (error?.code === 'ERR_UNSUPPORTED_DIR_IMPORT') {
2020
({ url } = error);
@@ -38,7 +38,7 @@ function initializeImportMeta(meta, context, loader) {
3838
const { url } = context;
3939

4040
// Alphabetical
41-
if (experimentalImportMetaResolve && loader.loaderType !== 'internal') {
41+
if (experimentalImportMetaResolve && loader.allowImportMetaResolve()) {
4242
meta.resolve = createImportMetaResolve(url, loader);
4343
}
4444

0 commit comments

Comments
 (0)