Skip to content

Commit c319dd7

Browse files
esm: refactor DefaultModuleLoader
Fixes nodejs#48515 Fixes nodejs#48439
1 parent 339eb10 commit c319dd7

11 files changed

+245
-154
lines changed

doc/api/errors.md

-17
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

-5
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

+24-27
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ const {
3232
ERR_INVALID_RETURN_VALUE,
3333
ERR_LOADER_CHAIN_INCOMPLETE,
3434
ERR_UNKNOWN_BUILTIN_MODULE,
35+
ERR_METHOD_NOT_IMPLEMENTED,
3536
ERR_WORKER_UNSERIALIZABLE_ERROR,
3637
} = require('internal/errors').codes;
3738
const { exitCodes: { kUnfinishedTopLevelAwait } } = internalBinding('errors');
@@ -82,7 +83,6 @@ let debug = require('internal/util/debuglog').debuglog('esm', (fn) => {
8283

8384
// [2] `validate...()`s throw the wrong error
8485

85-
8686
class Hooks {
8787
#chains = {
8888
/**
@@ -121,20 +121,20 @@ class Hooks {
121121
// Cache URLs we've already validated to avoid repeated validation
122122
#validatedUrls = new SafeSet();
123123

124+
allowImportMetaResolve = false;
125+
124126
/**
125127
* Import and register custom/user-defined module loader hook(s).
126128
* @param {string} urlOrSpecifier
127129
* @param {string} parentURL
128130
*/
129131
async register(urlOrSpecifier, parentURL) {
130132
const moduleLoader = require('internal/process/esm_loader').esmLoader;
131-
132133
const keyedExports = await moduleLoader.import(
133134
urlOrSpecifier,
134135
parentURL,
135136
kEmptyObject,
136137
);
137-
138138
this.addCustomLoader(urlOrSpecifier, keyedExports);
139139
}
140140

@@ -152,13 +152,16 @@ class Hooks {
152152
} = pluckHooks(exports);
153153

154154
if (globalPreload) {
155-
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 });
156157
}
157158
if (resolve) {
158-
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 });
159161
}
160162
if (load) {
161-
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 });
162165
}
163166
}
164167

@@ -235,7 +238,6 @@ class Hooks {
235238
chainFinished: null,
236239
context,
237240
hookErrIdentifier: '',
238-
hookIndex: chain.length - 1,
239241
hookName: 'resolve',
240242
shortCircuited: false,
241243
};
@@ -258,7 +260,7 @@ class Hooks {
258260
}
259261
};
260262

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

263265
const resolution = await nextResolve(originalSpecifier, context);
264266
const { hookErrIdentifier } = meta; // Retrieve the value after all settled
@@ -335,6 +337,10 @@ class Hooks {
335337
};
336338
}
337339

340+
resolveSync(_originalSpecifier, _parentURL, _importAssertions) {
341+
throw new ERR_METHOD_NOT_IMPLEMENTED('resolveSync()');
342+
}
343+
338344
/**
339345
* Provide source that is understood by one of Node's translators.
340346
*
@@ -351,7 +357,6 @@ class Hooks {
351357
chainFinished: null,
352358
context,
353359
hookErrIdentifier: '',
354-
hookIndex: chain.length - 1,
355360
hookName: 'load',
356361
shortCircuited: false,
357362
};
@@ -393,7 +398,7 @@ class Hooks {
393398
}
394399
};
395400

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

398403
const loaded = await nextLoad(url, context);
399404
const { hookErrIdentifier } = meta; // Retrieve the value after all settled
@@ -717,46 +722,39 @@ function pluckHooks({
717722
* A utility function to iterate through a hook chain, track advancement in the
718723
* chain, and generate and supply the `next<HookName>` argument to the custom
719724
* hook.
720-
* @param {KeyedHook[]} chain The whole hook chain.
725+
* @param {Hook} current The (currently) first hook in the chain (this shifts
726+
* on every call).
721727
* @param {object} meta Properties that change as the current hook advances
722728
* along the chain.
723729
* @param {boolean} meta.chainFinished Whether the end of the chain has been
724730
* reached AND invoked.
725731
* @param {string} meta.hookErrIdentifier A user-facing identifier to help
726732
* pinpoint where an error occurred. Ex "file:///foo.mjs 'resolve'".
727-
* @param {number} meta.hookIndex A non-negative integer tracking the current
728-
* position in the hook chain.
729733
* @param {string} meta.hookName The kind of hook the chain is (ex 'resolve')
730734
* @param {boolean} meta.shortCircuited Whether a hook signaled a short-circuit.
731735
* @param {(hookErrIdentifier, hookArgs) => void} validate A wrapper function
732736
* containing all validation of a custom loader hook's intermediary output. Any
733737
* validation within MUST throw.
734738
* @returns {function next<HookName>(...hookArgs)} The next hook in the chain.
735739
*/
736-
function nextHookFactory(chain, meta, { validateArgs, validateOutput }) {
740+
function nextHookFactory(current, meta, { validateArgs, validateOutput }) {
737741
// First, prepare the current
738742
const { hookName } = meta;
739743
const {
740744
fn: hook,
741745
url: hookFilePath,
742-
} = chain[meta.hookIndex];
746+
next,
747+
} = current;
743748

744749
// ex 'nextResolve'
745750
const nextHookName = `next${
746751
StringPrototypeToUpperCase(hookName[0]) +
747752
StringPrototypeSlice(hookName, 1)
748753
}`;
749754

750-
// When hookIndex is 0, it's reached the default, which does not call next()
751-
// so feed it a noop that blows up if called, so the problem is obvious.
752-
const generatedHookIndex = meta.hookIndex;
753755
let nextNextHook;
754-
if (meta.hookIndex > 0) {
755-
// Now, prepare the next: decrement the pointer so the next call to the
756-
// factory generates the next link in the chain.
757-
meta.hookIndex--;
758-
759-
nextNextHook = nextHookFactory(chain, meta, { validateArgs, validateOutput });
756+
if (next) {
757+
nextNextHook = nextHookFactory(next, meta, { validateArgs, validateOutput });
760758
} else {
761759
// eslint-disable-next-line func-name-matching
762760
nextNextHook = function chainAdvancedTooFar() {
@@ -773,17 +771,16 @@ function nextHookFactory(chain, meta, { validateArgs, validateOutput }) {
773771

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

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

778776
// Set when next<HookName> is actually called, not just generated.
779-
if (generatedHookIndex === 0) { meta.chainFinished = true; }
777+
if (!next) { meta.chainFinished = true; }
780778

781779
if (context) { // `context` has already been validated, so no fancy check needed.
782780
ObjectAssign(meta.context, context);
783781
}
784782

785783
const output = await hook(arg0, meta.context, nextNextHook);
786-
787784
validateOutput(outputErrIdentifier, output);
788785

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

lib/internal/modules/esm/initialize_import_meta.js

+2-2
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)