@@ -54,7 +54,6 @@ const { |
54 | 54 | } = require('internal/modules/esm/resolve'); |
55 | 55 | const { |
56 | 56 | getDefaultConditions, |
57 | | - loaderWorkerId, |
58 | 57 | } = require('internal/modules/esm/utils'); |
59 | 58 | const { deserializeError } = require('internal/error_serdes'); |
60 | 59 | const { |
@@ -105,7 +104,39 @@ function defineImportAssertionAlias(context) { |
105 | 104 |
|
106 | 105 | // [2] `validate...()`s throw the wrong error |
107 | 106 |
|
108 | | -class Hooks { |
| 107 | +/** |
| 108 | + * @typedef {{ format: ModuleFormat, source: ModuleSource }} LoadResult |
| 109 | + */ |
| 110 | + |
| 111 | +/** |
| 112 | + * @typedef {{ format: ModuleFormat, url: string, importAttributes: Record<string, string> }} ResolveResult |
| 113 | + */ |
| 114 | + |
| 115 | +/** |
| 116 | + * Interface for classes that implement asynchronous loader hooks that can be attached to the ModuleLoader |
| 117 | + * via `ModuleLoader.#setAsyncLoaderHooks()`. |
| 118 | + * @typedef {object} AsyncLoaderHooks |
| 119 | + * @property {boolean} allowImportMetaResolve Whether to allow the use of `import.meta.resolve`. |
| 120 | + * @property {(url: string, context: object, defaultLoad: Function) => Promise<LoadResult>} load |
| 121 | + * Calling the asynchronous `load` hook asynchronously. |
| 122 | + * @property {(url: string, context: object, defaultLoad: Function) => LoadResult} loadSync |
| 123 | + * Calling the asynchronous `load` hook synchronously. |
| 124 | + * @property {(originalSpecifier: string, parentURL: string, |
| 125 | + * importAttributes: Record<string, string>) => Promise<ResolveResult>} resolve |
| 126 | + * Calling the asynchronous `resolve` hook asynchronously. |
| 127 | + * @property {(originalSpecifier: string, parentURL: string, |
| 128 | + * importAttributes: Record<string, string>) => ResolveResult} resolveSync |
| 129 | + * Calling the asynchronous `resolve` hook synchronously. |
| 130 | + * @property {(specifier: string, parentURL: string) => any} register Register asynchronous loader hooks |
| 131 | + * @property {() => void} waitForLoaderHookInitialization Force loading of hooks. |
| 132 | + */ |
| 133 | + |
| 134 | +/** |
| 135 | + * @implements {AsyncLoaderHooks} |
| 136 | + * Instances of this class run directly on the loader hook worker thread and customize the module |
| 137 | + * loading of the hooks worker itself. |
| 138 | + */ |
| 139 | +class AsyncLoaderHooksOnLoaderHookWorker { |
109 | 140 | #chains = { |
110 | 141 | /** |
111 | 142 | * Phase 1 of 2 in ESM loading. |
@@ -452,7 +483,7 @@ class Hooks { |
452 | 483 | }; |
453 | 484 | } |
454 | 485 |
|
455 | | - forceLoadHooks() { |
| 486 | + waitForLoaderHookInitialization() { |
456 | 487 | // No-op |
457 | 488 | } |
458 | 489 |
|
@@ -462,14 +493,20 @@ class Hooks { |
462 | 493 | return meta; |
463 | 494 | } |
464 | 495 | } |
465 | | -ObjectSetPrototypeOf(Hooks.prototype, null); |
| 496 | +ObjectSetPrototypeOf(AsyncLoaderHooksOnLoaderHookWorker.prototype, null); |
466 | 497 |
|
467 | 498 | /** |
468 | | - * There may be multiple instances of Hooks/HooksProxy, but there is only 1 Internal worker, so |
469 | | - * there is only 1 MessageChannel. |
| 499 | + * There is only one loader hook thread for each non-loader-hook worker thread |
| 500 | + * (i.e. the non-loader-hook thread and any worker threads that are not loader hook workers themselves), |
| 501 | + * so there is only 1 MessageChannel. |
470 | 502 | */ |
471 | 503 | let MessageChannel; |
472 | | -class HooksProxy { |
| 504 | + |
| 505 | +/** |
| 506 | + * Abstraction over a worker thread that runs the asynchronous module loader hooks. |
| 507 | + * Instances of this class run on the non-loader-hook thread and communicate with the loader hooks worker thread. |
| 508 | + */ |
| 509 | +class AsyncLoaderHookWorker { |
473 | 510 | /** |
474 | 511 | * Shared memory. Always use Atomics method to read or write to it. |
475 | 512 | * @type {Int32Array} |
@@ -503,7 +540,7 @@ class HooksProxy { |
503 | 540 | const lock = new SharedArrayBuffer(SHARED_MEMORY_BYTE_LENGTH); |
504 | 541 | this.#lock = new Int32Array(lock); |
505 | 542 |
|
506 | | - this.#worker = new InternalWorker(loaderWorkerId, { |
| 543 | + this.#worker = new InternalWorker('internal/modules/esm/worker', { |
507 | 544 | stderr: false, |
508 | 545 | stdin: false, |
509 | 546 | stdout: false, |
@@ -644,7 +681,7 @@ class HooksProxy { |
644 | 681 | this.#importMetaInitializer(meta, context, loader); |
645 | 682 | } |
646 | 683 | } |
647 | | -ObjectSetPrototypeOf(HooksProxy.prototype, null); |
| 684 | +ObjectSetPrototypeOf(AsyncLoaderHookWorker.prototype, null); |
648 | 685 |
|
649 | 686 | // TODO(JakobJingleheimer): Remove this when loaders go "stable". |
650 | 687 | let globalPreloadWarningWasEmitted = false; |
@@ -757,6 +794,95 @@ function nextHookFactory(current, meta, { validateArgs, validateOutput }) { |
757 | 794 | ); |
758 | 795 | } |
759 | 796 |
|
| 797 | +/** |
| 798 | + * @type {AsyncLoaderHookWorker} |
| 799 | + * Worker instance used to run async loader hooks in a separate thread. This is a singleton for each |
| 800 | + * non-loader-hook worker thread (i.e. the main thread and any worker threads that are not |
| 801 | + * loader hook workers themselves). |
| 802 | + */ |
| 803 | +let asyncLoaderHookWorker; |
| 804 | +/** |
| 805 | + * Get the AsyncLoaderHookWorker instance. If it is not defined, then create a new one. |
| 806 | + * @returns {AsyncLoaderHookWorker} |
| 807 | + */ |
| 808 | +function getAsyncLoaderHookWorker() { |
| 809 | + asyncLoaderHookWorker ??= new AsyncLoaderHookWorker(); |
| 810 | + return asyncLoaderHookWorker; |
| 811 | +} |
| 812 | + |
| 813 | +/** |
| 814 | + * @implements {AsyncLoaderHooks} |
| 815 | + * Instances of this class are created in the non-loader-hook thread and communicate with the worker thread |
| 816 | + * spawned to run the async loader hooks. |
| 817 | + */ |
| 818 | +class AsyncLoaderHooksProxiedToLoaderHookWorker { |
| 819 | + |
| 820 | + allowImportMetaResolve = true; |
| 821 | + |
| 822 | + /** |
| 823 | + * Instantiate a module loader that uses user-provided custom loader hooks. |
| 824 | + */ |
| 825 | + constructor() { |
| 826 | + getAsyncLoaderHookWorker(); |
| 827 | + } |
| 828 | + |
| 829 | + /** |
| 830 | + * Register some loader specifier. |
| 831 | + * @param {string} originalSpecifier The specified URL path of the loader to |
| 832 | + * be registered. |
| 833 | + * @param {string} parentURL The parent URL from where the loader will be |
| 834 | + * registered if using it package name as specifier |
| 835 | + * @param {any} [data] Arbitrary data to be passed from the custom loader |
| 836 | + * (user-land) to the worker. |
| 837 | + * @param {any[]} [transferList] Objects in `data` that are changing ownership |
| 838 | + * @param {boolean} [isInternal] For internal loaders that should not be publicly exposed. |
| 839 | + * @returns {{ format: string, url: URL['href'] }} |
| 840 | + */ |
| 841 | + register(originalSpecifier, parentURL, data, transferList, isInternal) { |
| 842 | + return asyncLoaderHookWorker.makeSyncRequest('register', transferList, originalSpecifier, parentURL, |
| 843 | + data, isInternal); |
| 844 | + } |
| 845 | + |
| 846 | + /** |
| 847 | + * Resolve the location of the module. |
| 848 | + * @param {string} originalSpecifier The specified URL path of the module to |
| 849 | + * be resolved. |
| 850 | + * @param {string} [parentURL] The URL path of the module's parent. |
| 851 | + * @param {ImportAttributes} importAttributes Attributes from the import |
| 852 | + * statement or expression. |
| 853 | + * @returns {{ format: string, url: URL['href'] }} |
| 854 | + */ |
| 855 | + resolve(originalSpecifier, parentURL, importAttributes) { |
| 856 | + return asyncLoaderHookWorker.makeAsyncRequest('resolve', undefined, originalSpecifier, parentURL, importAttributes); |
| 857 | + } |
| 858 | + |
| 859 | + resolveSync(originalSpecifier, parentURL, importAttributes) { |
| 860 | + // This happens only as a result of `import.meta.resolve` calls, which must be sync per spec. |
| 861 | + return asyncLoaderHookWorker.makeSyncRequest('resolve', undefined, originalSpecifier, parentURL, importAttributes); |
| 862 | + } |
| 863 | + |
| 864 | + /** |
| 865 | + * Provide source that is understood by one of Node's translators. |
| 866 | + * @param {URL['href']} url The URL/path of the module to be loaded |
| 867 | + * @param {object} [context] Metadata about the module |
| 868 | + * @returns {Promise<{ format: ModuleFormat, source: ModuleSource }>} |
| 869 | + */ |
| 870 | + load(url, context) { |
| 871 | + return asyncLoaderHookWorker.makeAsyncRequest('load', undefined, url, context); |
| 872 | + } |
| 873 | + loadSync(url, context) { |
| 874 | + return asyncLoaderHookWorker.makeSyncRequest('load', undefined, url, context); |
| 875 | + } |
| 876 | + |
| 877 | + importMetaInitialize(meta, context, loader) { |
| 878 | + asyncLoaderHookWorker.importMetaInitialize(meta, context, loader); |
| 879 | + } |
| 880 | + |
| 881 | + waitForLoaderHookInitialization() { |
| 882 | + asyncLoaderHookWorker.waitForWorker(); |
| 883 | + } |
| 884 | +} |
760 | 885 |
|
761 | | -exports.Hooks = Hooks; |
762 | | -exports.HooksProxy = HooksProxy; |
| 886 | +exports.AsyncLoaderHooksProxiedToLoaderHookWorker = AsyncLoaderHooksProxiedToLoaderHookWorker; |
| 887 | +exports.AsyncLoaderHooksOnLoaderHookWorker = AsyncLoaderHooksOnLoaderHookWorker; |
| 888 | +exports.AsyncLoaderHookWorker = AsyncLoaderHookWorker; |