@@ -31,6 +31,7 @@ const { |
31 | 31 | ERR_INVALID_RETURN_PROPERTY_VALUE, |
32 | 32 | ERR_INVALID_RETURN_VALUE, |
33 | 33 | ERR_LOADER_CHAIN_INCOMPLETE, |
| 34 | + ERR_METHOD_NOT_IMPLEMENTED, |
34 | 35 | ERR_UNKNOWN_BUILTIN_MODULE, |
35 | 36 | ERR_WORKER_UNSERIALIZABLE_ERROR, |
36 | 37 | } = require('internal/errors').codes; |
@@ -64,7 +65,7 @@ const { |
64 | 65 | let debug = require('internal/util/debuglog').debuglog('esm', (fn) => { |
65 | 66 | debug = fn; |
66 | 67 | }); |
67 | | - |
| 68 | +let importMetaInitializer; |
68 | 69 |
|
69 | 70 | /** |
70 | 71 | * @typedef {object} ExportedHooks |
@@ -81,7 +82,6 @@ let debug = require('internal/util/debuglog').debuglog('esm', (fn) => { |
81 | 82 |
|
82 | 83 | // [2] `validate...()`s throw the wrong error |
83 | 84 |
|
84 | | - |
85 | 85 | class Hooks { |
86 | 86 | #chains = { |
87 | 87 | /** |
@@ -120,20 +120,20 @@ class Hooks { |
120 | 120 | // Cache URLs we've already validated to avoid repeated validation |
121 | 121 | #validatedUrls = new SafeSet(); |
122 | 122 |
|
| 123 | + allowImportMetaResolve = false; |
| 124 | + |
123 | 125 | /** |
124 | 126 | * Import and register custom/user-defined module loader hook(s). |
125 | 127 | * @param {string} urlOrSpecifier |
126 | 128 | * @param {string} parentURL |
127 | 129 | */ |
128 | 130 | async register(urlOrSpecifier, parentURL) { |
129 | 131 | const moduleLoader = require('internal/process/esm_loader').esmLoader; |
130 | | - |
131 | 132 | const keyedExports = await moduleLoader.import( |
132 | 133 | urlOrSpecifier, |
133 | 134 | parentURL, |
134 | 135 | kEmptyObject, |
135 | 136 | ); |
136 | | - |
137 | 137 | this.addCustomLoader(urlOrSpecifier, keyedExports); |
138 | 138 | } |
139 | 139 |
|
@@ -151,13 +151,15 @@ class Hooks { |
151 | 151 | } = pluckHooks(exports); |
152 | 152 |
|
153 | 153 | if (globalPreload) { |
154 | | - ArrayPrototypePush(this.#chains.globalPreload, { fn: globalPreload, url }); |
| 154 | + ArrayPrototypePush(this.#chains.globalPreload, { __proto__: null, fn: globalPreload, url }); |
155 | 155 | } |
156 | 156 | if (resolve) { |
157 | | - ArrayPrototypePush(this.#chains.resolve, { fn: resolve, url }); |
| 157 | + const next = this.#chains.resolve[this.#chains.resolve.length - 1]; |
| 158 | + ArrayPrototypePush(this.#chains.resolve, { __proto__: null, fn: resolve, url, next }); |
158 | 159 | } |
159 | 160 | if (load) { |
160 | | - ArrayPrototypePush(this.#chains.load, { fn: load, url }); |
| 161 | + const next = this.#chains.load[this.#chains.load.length - 1]; |
| 162 | + ArrayPrototypePush(this.#chains.load, { __proto__: null, fn: load, url, next }); |
161 | 163 | } |
162 | 164 | } |
163 | 165 |
|
@@ -234,7 +236,6 @@ class Hooks { |
234 | 236 | chainFinished: null, |
235 | 237 | context, |
236 | 238 | hookErrIdentifier: '', |
237 | | - hookIndex: chain.length - 1, |
238 | 239 | hookName: 'resolve', |
239 | 240 | shortCircuited: false, |
240 | 241 | }; |
@@ -257,7 +258,7 @@ class Hooks { |
257 | 258 | } |
258 | 259 | }; |
259 | 260 |
|
260 | | - const nextResolve = nextHookFactory(chain, meta, { validateArgs, validateOutput }); |
| 261 | + const nextResolve = nextHookFactory(chain[chain.length - 1], meta, { validateArgs, validateOutput }); |
261 | 262 |
|
262 | 263 | const resolution = await nextResolve(originalSpecifier, context); |
263 | 264 | const { hookErrIdentifier } = meta; // Retrieve the value after all settled |
@@ -334,6 +335,10 @@ class Hooks { |
334 | 335 | }; |
335 | 336 | } |
336 | 337 |
|
| 338 | + resolveSync(_originalSpecifier, _parentURL, _importAssertions) { |
| 339 | + throw new ERR_METHOD_NOT_IMPLEMENTED('resolveSync()'); |
| 340 | + } |
| 341 | + |
337 | 342 | /** |
338 | 343 | * Provide source that is understood by one of Node's translators. |
339 | 344 | * |
@@ -350,7 +355,6 @@ class Hooks { |
350 | 355 | chainFinished: null, |
351 | 356 | context, |
352 | 357 | hookErrIdentifier: '', |
353 | | - hookIndex: chain.length - 1, |
354 | 358 | hookName: 'load', |
355 | 359 | shortCircuited: false, |
356 | 360 | }; |
@@ -392,7 +396,7 @@ class Hooks { |
392 | 396 | } |
393 | 397 | }; |
394 | 398 |
|
395 | | - const nextLoad = nextHookFactory(chain, meta, { validateArgs, validateOutput }); |
| 399 | + const nextLoad = nextHookFactory(chain[chain.length - 1], meta, { validateArgs, validateOutput }); |
396 | 400 |
|
397 | 401 | const loaded = await nextLoad(url, context); |
398 | 402 | const { hookErrIdentifier } = meta; // Retrieve the value after all settled |
@@ -467,6 +471,16 @@ class Hooks { |
467 | 471 | source, |
468 | 472 | }; |
469 | 473 | } |
| 474 | + |
| 475 | + forceLoadHooks() { |
| 476 | + // No-op |
| 477 | + } |
| 478 | + |
| 479 | + importMetaInitialize(meta, context, loader) { |
| 480 | + importMetaInitializer ??= require('internal/modules/esm/initialize_import_meta').initializeImportMeta; |
| 481 | + meta = importMetaInitializer(meta, context, loader); |
| 482 | + return meta; |
| 483 | + } |
470 | 484 | } |
471 | 485 | ObjectSetPrototypeOf(Hooks.prototype, null); |
472 | 486 |
|
@@ -716,46 +730,39 @@ function pluckHooks({ |
716 | 730 | * A utility function to iterate through a hook chain, track advancement in the |
717 | 731 | * chain, and generate and supply the `next<HookName>` argument to the custom |
718 | 732 | * hook. |
719 | | - * @param {KeyedHook[]} chain The whole hook chain. |
| 733 | + * @param {Hook} current The (currently) first hook in the chain (this shifts |
| 734 | + * on every call). |
720 | 735 | * @param {object} meta Properties that change as the current hook advances |
721 | 736 | * along the chain. |
722 | 737 | * @param {boolean} meta.chainFinished Whether the end of the chain has been |
723 | 738 | * reached AND invoked. |
724 | 739 | * @param {string} meta.hookErrIdentifier A user-facing identifier to help |
725 | 740 | * pinpoint where an error occurred. Ex "file:///foo.mjs 'resolve'". |
726 | | - * @param {number} meta.hookIndex A non-negative integer tracking the current |
727 | | - * position in the hook chain. |
728 | 741 | * @param {string} meta.hookName The kind of hook the chain is (ex 'resolve') |
729 | 742 | * @param {boolean} meta.shortCircuited Whether a hook signaled a short-circuit. |
730 | 743 | * @param {(hookErrIdentifier, hookArgs) => void} validate A wrapper function |
731 | 744 | * containing all validation of a custom loader hook's intermediary output. Any |
732 | 745 | * validation within MUST throw. |
733 | 746 | * @returns {function next<HookName>(...hookArgs)} The next hook in the chain. |
734 | 747 | */ |
735 | | -function nextHookFactory(chain, meta, { validateArgs, validateOutput }) { |
| 748 | +function nextHookFactory(current, meta, { validateArgs, validateOutput }) { |
736 | 749 | // First, prepare the current |
737 | 750 | const { hookName } = meta; |
738 | 751 | const { |
739 | 752 | fn: hook, |
740 | 753 | url: hookFilePath, |
741 | | - } = chain[meta.hookIndex]; |
| 754 | + next, |
| 755 | + } = current; |
742 | 756 |
|
743 | 757 | // ex 'nextResolve' |
744 | 758 | const nextHookName = `next${ |
745 | 759 | StringPrototypeToUpperCase(hookName[0]) + |
746 | 760 | StringPrototypeSlice(hookName, 1) |
747 | 761 | }`; |
748 | 762 |
|
749 | | - // When hookIndex is 0, it's reached the default, which does not call next() |
750 | | - // so feed it a noop that blows up if called, so the problem is obvious. |
751 | | - const generatedHookIndex = meta.hookIndex; |
752 | 763 | let nextNextHook; |
753 | | - if (meta.hookIndex > 0) { |
754 | | - // Now, prepare the next: decrement the pointer so the next call to the |
755 | | - // factory generates the next link in the chain. |
756 | | - meta.hookIndex--; |
757 | | - |
758 | | - nextNextHook = nextHookFactory(chain, meta, { validateArgs, validateOutput }); |
| 764 | + if (next) { |
| 765 | + nextNextHook = nextHookFactory(next, meta, { validateArgs, validateOutput }); |
759 | 766 | } else { |
760 | 767 | // eslint-disable-next-line func-name-matching |
761 | 768 | nextNextHook = function chainAdvancedTooFar() { |
@@ -772,17 +779,16 @@ function nextHookFactory(chain, meta, { validateArgs, validateOutput }) { |
772 | 779 |
|
773 | 780 | validateArgs(`${meta.hookErrIdentifier} hook's ${nextHookName}()`, arg0, context); |
774 | 781 |
|
775 | | - const outputErrIdentifier = `${chain[generatedHookIndex].url} '${hookName}' hook's ${nextHookName}()`; |
| 782 | + const outputErrIdentifier = `${hookFilePath} '${hookName}' hook's ${nextHookName}()`; |
776 | 783 |
|
777 | 784 | // Set when next<HookName> is actually called, not just generated. |
778 | | - if (generatedHookIndex === 0) { meta.chainFinished = true; } |
| 785 | + if (!next) { meta.chainFinished = true; } |
779 | 786 |
|
780 | 787 | if (context) { // `context` has already been validated, so no fancy check needed. |
781 | 788 | ObjectAssign(meta.context, context); |
782 | 789 | } |
783 | 790 |
|
784 | 791 | const output = await hook(arg0, meta.context, nextNextHook); |
785 | | - |
786 | 792 | validateOutput(outputErrIdentifier, output); |
787 | 793 |
|
788 | 794 | if (output?.shortCircuit === true) { meta.shortCircuited = true; } |
|