GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — module: refactor and clarify async loader hook customizations · nodejs/node@2d48d17

File tree

Expand file treeCollapse file tree
Open diff view settings
Filter options
  • lib/internal
    • main
      • worker_thread.js
    • modules
      • esm
        • hooks.js
        • loader.js
        • utils.js
        • worker.js
      • run_main.js
    • process
      • pre_execution.js
  • test
    • es-module
      • test-esm-loader-modulemap.js
    • parallel
      • test-bootstrap-modules.js
Expand file treeCollapse file tree
Open diff view settings
Collapse file

‎lib/internal/main/worker_thread.js‎

Copy file name to clipboardExpand all lines: lib/internal/main/worker_thread.js
+12-10Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,14 @@ const {
1111
ObjectDefineProperty,
1212
PromisePrototypeThen,
1313
RegExpPrototypeExec,
14-
SafeWeakMap,
1514
globalThis: {
1615
SharedArrayBuffer,
1716
},
1817
} = primordials;
1918

2019
const {
2120
prepareWorkerThreadExecution,
22-
setupUserModules,
21+
initializeModuleLoaders,
2322
markBootstrapComplete,
2423
} = require('internal/process/pre_execution');
2524

@@ -138,11 +137,13 @@ port.on('message', (message) => {
138137
workerIo.sharedCwdCounter = cwdCounter;
139138
}
140139

141-
const isLoaderWorker =
142-
doEval === 'internal' &&
143-
filename === require('internal/modules/esm/utils').loaderWorkerId;
144-
// Disable custom loaders in loader worker.
145-
setupUserModules(isLoaderWorker);
140+
const isLoaderHookWorker = (filename === 'internal/modules/esm/worker' && doEval === 'internal');
141+
if (!isLoaderHookWorker) {
142+
// If we are in the loader hook worker, delay the module loader initializations until
143+
// initializeAsyncLoaderHooksOnLoaderHookWorker() which needs to run preloads
144+
// after the asynchronous loader hooks are registered.
145+
initializeModuleLoaders({ shouldSpawnLoaderHookWorker: true, shouldPreloadModules: true });
146+
}
146147

147148
if (!hasStdin)
148149
process.stdin.push(null);
@@ -152,9 +153,10 @@ port.on('message', (message) => {
152153
port.postMessage({ type: UP_AND_RUNNING });
153154
switch (doEval) {
154155
case 'internal': {
155-
// Create this WeakMap in js-land because V8 has no C++ API for WeakMap.
156-
internalBinding('module_wrap').callbackMap = new SafeWeakMap();
157-
require(filename)(workerData, publicPort);
156+
// Currently the only user of internal eval is the async loader hook thread.
157+
assert(isLoaderHookWorker, `Unexpected internal eval ${filename}`);
158+
const setupModuleWorker = require('internal/modules/esm/worker');
159+
setupModuleWorker(workerData, publicPort);
158160
break;
159161
}
160162

Collapse file

‎lib/internal/modules/esm/hooks.js‎

Copy file name to clipboardExpand all lines: lib/internal/modules/esm/hooks.js
+137-11Lines changed: 137 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,6 @@ const {
5454
} = require('internal/modules/esm/resolve');
5555
const {
5656
getDefaultConditions,
57-
loaderWorkerId,
5857
} = require('internal/modules/esm/utils');
5958
const { deserializeError } = require('internal/error_serdes');
6059
const {
@@ -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
*/
471503
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 {
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".
650687
let 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

Comments
0 (0)
You are visitor 01402503 — GeezerVison does not store the pages you look up.
Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.