| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1372,7 +1372,7 @@ E('ERR_IPC_ONE_PIPE', 'Child process can have only one IPC pipe', Error); | |||
| 1372 | 1372 | E('ERR_IPC_SYNC_FORK', 'IPC cannot be used with synchronous forks', Error); | |
| 1373 | 1373 | E( | |
| 1374 | 1374 | 'ERR_LOADER_CHAIN_INCOMPLETE', | |
| 1375 | - 'The "%s" hook from %s did not call the next hook in its chain and did not' + | ||
| 1375 | + '"%s" did not call the next hook in its chain and did not' + | ||
| 1376 | 1376 | ' explicitly signal a short circuit. If this is intentional, include' + | |
| 1377 | 1377 | ' `shortCircuit: true` in the hook\'s return.', | |
| 1378 | 1378 | Error | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -12,17 +12,22 @@ const { | |||
| 12 | 12 | FunctionPrototypeCall, | |
| 13 | 13 | ObjectAssign, | |
| 14 | 14 | ObjectCreate, | |
| 15 | + ObjectDefineProperty, | ||
| 15 | 16 | ObjectSetPrototypeOf, | |
| 16 | 17 | PromiseAll, | |
| 18 | + ReflectApply, | ||
| 17 | 19 | RegExpPrototypeExec, | |
| 18 | 20 | SafeArrayIterator, | |
| 19 | 21 | SafeWeakMap, | |
| 22 | + StringPrototypeSlice, | ||
| 23 | + StringPrototypeToUpperCase, | ||
| 20 | 24 | globalThis, | |
| 21 | 25 | } = primordials; | |
| 22 | 26 | const { MessageChannel } = require('internal/worker/io'); | |
| 23 | 27 | ||
| 24 | 28 | const { | |
| 25 | 29 | ERR_LOADER_CHAIN_INCOMPLETE, | |
| 30 | + ERR_INTERNAL_ASSERTION, | ||
| 26 | 31 | ERR_INVALID_ARG_TYPE, | |
| 27 | 32 | ERR_INVALID_ARG_VALUE, | |
| 28 | 33 | ERR_INVALID_RETURN_PROPERTY_VALUE, | |
@@ -89,6 +94,81 @@ const { getOptionValue } = require('internal/options'); | |||
| 89 | 94 | ||
| 90 | 95 | let emittedSpecifierResolutionWarning = false; | |
| 91 | 96 | ||
| 97 | + /** | ||
| 98 | + * A utility function to iterate through a hook chain, track advancement in the | ||
| 99 | + * chain, and generate and supply the `next<HookName>` argument to the custom | ||
| 100 | + * hook. | ||
| 101 | + * @param {KeyedHook[]} chain The whole hook chain. | ||
| 102 | + * @param {object} meta Properties that change as the current hook advances | ||
| 103 | + * along the chain. | ||
| 104 | + * @param {boolean} meta.chainFinished Whether the end of the chain has been | ||
| 105 | + * reached AND invoked. | ||
| 106 | + * @param {string} meta.hookErrIdentifier A user-facing identifier to help | ||
| 107 | + * pinpoint where an error occurred. Ex "file:///foo.mjs 'resolve'". | ||
| 108 | + * @param {number} meta.hookIndex A non-negative integer tracking the current | ||
| 109 | + * position in the hook chain. | ||
| 110 | + * @param {string} meta.hookName The kind of hook the chain is (ex 'resolve') | ||
| 111 | + * @param {boolean} meta.shortCircuited Whether a hook signaled a short-circuit. | ||
| 112 | + * @param {(hookErrIdentifier, hookArgs) => void} validate A wrapper function | ||
| 113 | + * containing all validation of a custom loader hook's intermediary output. Any | ||
| 114 | + * validation within MUST throw. | ||
| 115 | + * @returns {function next<HookName>(...hookArgs)} The next hook in the chain. | ||
| 116 | + */ | ||
| 117 | + function nextHookFactory(chain, meta, validate) { | ||
| 118 | + // First, prepare the current | ||
| 119 | + const { hookName } = meta; | ||
| 120 | + const { | ||
| 121 | + fn: hook, | ||
| 122 | + url: hookFilePath, | ||
| 123 | + } = chain[meta.hookIndex]; | ||
| 124 | + | ||
| 125 | + // ex 'nextResolve' | ||
| 126 | + const nextHookName = `next${ | ||
| 127 | + StringPrototypeToUpperCase(hookName[0]) + | ||
| 128 | + StringPrototypeSlice(hookName, 1) | ||
| 129 | + }`; | ||
| 130 | + | ||
| 131 | + // When hookIndex is 0, it's reached the default, which does not call next() | ||
| 132 | + // so feed it a noop that blows up if called, so the problem is obvious. | ||
| 133 | + const generatedHookIndex = meta.hookIndex; | ||
| 134 | + let nextNextHook; | ||
| 135 | + if (meta.hookIndex > 0) { | ||
| 136 | + // Now, prepare the next: decrement the pointer so the next call to the | ||
| 137 | + // factory generates the next link in the chain. | ||
| 138 | + meta.hookIndex--; | ||
| 139 | + | ||
| 140 | + nextNextHook = nextHookFactory(chain, meta, validate); | ||
| 141 | + } else { | ||
| 142 | + // eslint-disable-next-line func-name-matching | ||
| 143 | + nextNextHook = function chainAdvancedTooFar() { | ||
| 144 | + throw new ERR_INTERNAL_ASSERTION( | ||
| 145 | + `ESM custom loader '${hookName}' advanced beyond the end of the chain.` | ||
| 146 | + ); | ||
| 147 | + }; | ||
| 148 | + } | ||
| 149 | + | ||
| 150 | + return ObjectDefineProperty( | ||
| 151 | + async (...args) => { | ||
| 152 | + // Update only when hook is invoked to avoid fingering the wrong filePath | ||
| 153 | + meta.hookErrIdentifier = `${hookFilePath} '${hookName}'`; | ||
| 154 | + | ||
| 155 | + validate(`${meta.hookErrIdentifier} hook's ${nextHookName}()`, args); | ||
| 156 | + | ||
| 157 | + // Set when next<HookName> is actually called, not just generated. | ||
| 158 | + if (generatedHookIndex === 0) { meta.chainFinished = true; } | ||
| 159 | + | ||
| 160 | + ArrayPrototypePush(args, nextNextHook); | ||
| 161 | + const output = await ReflectApply(hook, undefined, args); | ||
| 162 | + | ||
| 163 | + if (output?.shortCircuit === true) { meta.shortCircuited = true; } | ||
| 164 | + return output; | ||
| 165 | + | ||
| 166 | + }, | ||
| 167 | + 'name', | ||
| 168 | + { __proto__: null, value: nextHookName }, | ||
| 169 | + ); | ||
| 170 | + } | ||
| 171 | + | ||
| 92 | 172 | /** | |
| 93 | 173 | * An ESMLoader instance is used as the main entry point for loading ES modules. | |
| 94 | 174 | * Currently, this is a singleton -- there is only one used for loading | |
@@ -471,32 +551,21 @@ class ESMLoader { | |||
| 471 | 551 | * @returns {{ format: ModuleFormat, source: ModuleSource }} | |
| 472 | 552 | */ | |
| 473 | 553 | async load(url, context = {}) { | |
| 474 | - const loaders = this.#loaders; | ||
| 475 | - let hookIndex = loaders.length - 1; | ||
| 476 | - let { | ||
| 477 | - fn: loader, | ||
| 478 | - url: loaderFilePath, | ||
| 479 | - } = loaders[hookIndex]; | ||
| 480 | - let chainFinished = hookIndex === 0; | ||
| 481 | - let shortCircuited = false; | ||
| 482 | - | ||
| 483 | - const nextLoad = async (nextUrl, ctx = context) => { | ||
| 484 | - --hookIndex; // `nextLoad` has been called, so decrement our pointer. | ||
| 485 | - | ||
| 486 | - ({ | ||
| 487 | - fn: loader, | ||
| 488 | - url: loaderFilePath, | ||
| 489 | - } = loaders[hookIndex]); | ||
| 490 | - | ||
| 491 | - if (hookIndex === 0) { chainFinished = true; } | ||
| 492 | - | ||
| 493 | - const hookErrIdentifier = `${loaderFilePath} "load"`; | ||
| 554 | + const chain = this.#loaders; | ||
| 555 | + const meta = { | ||
| 556 | + chainFinished: null, | ||
| 557 | + hookErrIdentifier: '', | ||
| 558 | + hookIndex: chain.length - 1, | ||
| 559 | + hookName: 'load', | ||
| 560 | + shortCircuited: false, | ||
| 561 | + }; | ||
| 494 | 562 | ||
| 563 | + const validate = (hookErrIdentifier, { 0: nextUrl, 1: ctx }) => { | ||
| 495 | 564 | if (typeof nextUrl !== 'string') { | |
| 496 | 565 | // non-strings can be coerced to a url string | |
| 497 | 566 | // validateString() throws a less-specific error | |
| 498 | 567 | throw new ERR_INVALID_ARG_TYPE( | |
| 499 | - `${hookErrIdentifier} nextLoad(url)`, | ||
| 568 | + `${hookErrIdentifier} url`, | ||
| 500 | 569 | 'a url string', | |
| 501 | 570 | nextUrl, | |
| 502 | 571 | ); | |
@@ -508,29 +577,20 @@ class ESMLoader { | |||
| 508 | 577 | new URL(nextUrl); | |
| 509 | 578 | } catch { | |
| 510 | 579 | throw new ERR_INVALID_ARG_VALUE( | |
| 511 | - `${hookErrIdentifier} nextLoad(url)`, | ||
| 580 | + `${hookErrIdentifier} url`, | ||
| 512 | 581 | nextUrl, | |
| 513 | 582 | 'should be a url string', | |
| 514 | 583 | ); | |
| 515 | 584 | } | |
| 516 | 585 | } | |
| 517 | 586 | ||
| 518 | - validateObject(ctx, `${hookErrIdentifier} nextLoad(, context)`); | ||
| 519 | - | ||
| 520 | - const output = await loader(nextUrl, ctx, nextLoad); | ||
| 521 | - | ||
| 522 | - if (output?.shortCircuit === true) { shortCircuited = true; } | ||
| 523 | - | ||
| 524 | - return output; | ||
| 587 | + validateObject(ctx, `${hookErrIdentifier} context`); | ||
| 525 | 588 | }; | |
| 526 | 589 | ||
| 527 | - const loaded = await loader( | ||
| 528 | - url, | ||
| 529 | - context, | ||
| 530 | - nextLoad, | ||
| 531 | - ); | ||
| 590 | + const nextLoad = nextHookFactory(chain, meta, validate); | ||
| 532 | 591 | ||
| 533 | - const hookErrIdentifier = `${loaderFilePath} load`; | ||
| 592 | + const loaded = await nextLoad(url, context); | ||
| 593 | + const { hookErrIdentifier } = meta; // Retrieve the value after all settled | ||
| 534 | 594 | ||
| 535 | 595 | if (typeof loaded !== 'object') { // [2] | |
| 536 | 596 | throw new ERR_INVALID_RETURN_VALUE( | |
@@ -540,10 +600,10 @@ class ESMLoader { | |||
| 540 | 600 | ); | |
| 541 | 601 | } | |
| 542 | 602 | ||
| 543 | - if (loaded?.shortCircuit === true) { shortCircuited = true; } | ||
| 603 | + if (loaded?.shortCircuit === true) { meta.shortCircuited = true; } | ||
| 544 | 604 | ||
| 545 | - if (!chainFinished && !shortCircuited) { | ||
| 546 | - throw new ERR_LOADER_CHAIN_INCOMPLETE('load', loaderFilePath); | ||
| 605 | + if (!meta.chainFinished && !meta.shortCircuited) { | ||
| 606 | + throw new ERR_LOADER_CHAIN_INCOMPLETE(hookErrIdentifier); | ||
| 547 | 607 | } | |
| 548 | 608 | ||
| 549 | 609 | const { | |
@@ -736,55 +796,34 @@ class ESMLoader { | |||
| 736 | 796 | parentURL, | |
| 737 | 797 | ); | |
| 738 | 798 | } | |
| 739 | - const resolvers = this.#resolvers; | ||
| 740 | - | ||
| 741 | - let hookIndex = resolvers.length - 1; | ||
| 742 | - let { | ||
| 743 | - fn: resolver, | ||
| 744 | - url: resolverFilePath, | ||
| 745 | - } = resolvers[hookIndex]; | ||
| 746 | - let chainFinished = hookIndex === 0; | ||
| 747 | - let shortCircuited = false; | ||
| 799 | + const chain = this.#resolvers; | ||
| 800 | + const meta = { | ||
| 801 | + chainFinished: null, | ||
| 802 | + hookErrIdentifier: '', | ||
| 803 | + hookIndex: chain.length - 1, | ||
| 804 | + hookName: 'resolve', | ||
| 805 | + shortCircuited: false, | ||
| 806 | + }; | ||
| 748 | 807 | ||
| 749 | 808 | const context = { | |
| 750 | 809 | conditions: DEFAULT_CONDITIONS, | |
| 751 | 810 | importAssertions, | |
| 752 | 811 | parentURL, | |
| 753 | 812 | }; | |
| 754 | - | ||
| 755 | - const nextResolve = async (suppliedSpecifier, ctx = context) => { | ||
| 756 | - --hookIndex; // `nextResolve` has been called, so decrement our pointer. | ||
| 757 | - | ||
| 758 | - ({ | ||
| 759 | - fn: resolver, | ||
| 760 | - url: resolverFilePath, | ||
| 761 | - } = resolvers[hookIndex]); | ||
| 762 | - | ||
| 763 | - if (hookIndex === 0) { chainFinished = true; } | ||
| 764 | - | ||
| 765 | - const hookErrIdentifier = `${resolverFilePath} "resolve"`; | ||
| 813 | + const validate = (hookErrIdentifier, { 0: suppliedSpecifier, 1: ctx }) => { | ||
| 766 | 814 | ||
| 767 | 815 | validateString( | |
| 768 | 816 | suppliedSpecifier, | |
| 769 | - `${hookErrIdentifier} nextResolve(specifier)`, | ||
| 817 | + `${hookErrIdentifier} specifier`, | ||
| 770 | 818 | ); // non-strings can be coerced to a url string | |
| 771 | 819 | ||
| 772 | - validateObject(ctx, `${hookErrIdentifier} nextResolve(, context)`); | ||
| 773 | - | ||
| 774 | - const output = await resolver(suppliedSpecifier, ctx, nextResolve); | ||
| 775 | - | ||
| 776 | - if (output?.shortCircuit === true) { shortCircuited = true; } | ||
| 777 | - | ||
| 778 | - return output; | ||
| 820 | + validateObject(ctx, `${hookErrIdentifier} context`); | ||
| 779 | 821 | }; | |
| 780 | 822 | ||
| 781 | - const resolution = await resolver( | ||
| 782 | - originalSpecifier, | ||
| 783 | - context, | ||
| 784 | - nextResolve, | ||
| 785 | - ); | ||
| 823 | + const nextResolve = nextHookFactory(chain, meta, validate); | ||
| 786 | 824 | ||
| 787 | - const hookErrIdentifier = `${resolverFilePath} resolve`; | ||
| 825 | + const resolution = await nextResolve(originalSpecifier, context); | ||
| 826 | + const { hookErrIdentifier } = meta; // Retrieve the value after all settled | ||
| 788 | 827 | ||
| 789 | 828 | if (typeof resolution !== 'object') { // [2] | |
| 790 | 829 | throw new ERR_INVALID_RETURN_VALUE( | |
@@ -794,10 +833,10 @@ class ESMLoader { | |||
| 794 | 833 | ); | |
| 795 | 834 | } | |
| 796 | 835 | ||
| 797 | - if (resolution?.shortCircuit === true) { shortCircuited = true; } | ||
| 836 | + if (resolution?.shortCircuit === true) { meta.shortCircuited = true; } | ||
| 798 | 837 | ||
| 799 | - if (!chainFinished && !shortCircuited) { | ||
| 800 | - throw new ERR_LOADER_CHAIN_INCOMPLETE('resolve', resolverFilePath); | ||
| 838 | + if (!meta.chainFinished && !meta.shortCircuited) { | ||
| 839 | + throw new ERR_LOADER_CHAIN_INCOMPLETE(hookErrIdentifier); | ||
| 801 | 840 | } | |
| 802 | 841 | ||
| 803 | 842 | const { | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1081,7 +1081,7 @@ function throwIfUnsupportedURLScheme(parsed, experimentalNetworkImports) { | |||
| 1081 | 1081 | } | |
| 1082 | 1082 | } | |
| 1083 | 1083 | ||
| 1084 | - async function defaultResolve(specifier, context = {}, defaultResolveUnused) { | ||
| 1084 | + async function defaultResolve(specifier, context = {}) { | ||
| 1085 | 1085 | let { parentURL, conditions } = context; | |
| 1086 | 1086 | if (parentURL && policy?.manifest) { | |
| 1087 | 1087 | const redirects = policy.manifest.getDependencyMapper(parentURL); | |
| Back | FazBrowse Home | New Git URL |
0 commit comments