@@ -54,7 +54,6 @@ const {
5454} = require ( 'internal/modules/esm/resolve' ) ;
5555const {
5656 getDefaultConditions,
57- loaderWorkerId,
5857} = require ( 'internal/modules/esm/utils' ) ;
5958const { deserializeError } = require ( 'internal/error_serdes' ) ;
6059const {
@@ -105,7 +104,39 @@ function defineImportAssertionAlias(context) {
105104
106105// [2] `validate...()`s throw the wrong error
107106
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 {
109140 #chains = {
110141 /**
111142 * Phase 1 of 2 in ESM loading.
@@ -452,7 +483,7 @@ class Hooks {
452483 } ;
453484 }
454485
455- forceLoadHooks ( ) {
486+ waitForLoaderHookInitialization ( ) {
456487 // No-op
457488 }
458489
@@ -462,14 +493,20 @@ class Hooks {
462493 return meta ;
463494 }
464495}
465- ObjectSetPrototypeOf ( Hooks . prototype , null ) ;
496+ ObjectSetPrototypeOf ( AsyncLoaderHooksOnLoaderHookWorker . prototype , null ) ;
466497
467498/**
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.
470502 */
471503let 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 {
473510 /**
474511 * Shared memory. Always use Atomics method to read or write to it.
475512 * @type {Int32Array }
@@ -503,7 +540,7 @@ class HooksProxy {
503540 const lock = new SharedArrayBuffer ( SHARED_MEMORY_BYTE_LENGTH ) ;
504541 this . #lock = new Int32Array ( lock ) ;
505542
506- this . #worker = new InternalWorker ( loaderWorkerId , {
543+ this . #worker = new InternalWorker ( 'internal/modules/esm/worker' , {
507544 stderr : false ,
508545 stdin : false ,
509546 stdout : false ,
@@ -644,7 +681,7 @@ class HooksProxy {
644681 this . #importMetaInitializer( meta , context , loader ) ;
645682 }
646683}
647- ObjectSetPrototypeOf ( HooksProxy . prototype , null ) ;
684+ ObjectSetPrototypeOf ( AsyncLoaderHookWorker . prototype , null ) ;
648685
649686// TODO(JakobJingleheimer): Remove this when loaders go "stable".
650687let globalPreloadWarningWasEmitted = false ;
@@ -757,6 +794,95 @@ function nextHookFactory(current, meta, { validateArgs, validateOutput }) {
757794 ) ;
758795}
759796
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+ }
760885
761- exports . Hooks = Hooks ;
762- exports . HooksProxy = HooksProxy ;
886+ exports . AsyncLoaderHooksProxiedToLoaderHookWorker = AsyncLoaderHooksProxiedToLoaderHookWorker ;
887+ exports . AsyncLoaderHooksOnLoaderHookWorker = AsyncLoaderHooksOnLoaderHookWorker ;
888+ exports . AsyncLoaderHookWorker = AsyncLoaderHookWorker ;
0 commit comments