Skip to content

Commit 705e623

Browse files
esm: remove globalPreload hook (superseded by initialize)
PR-URL: #49144 Reviewed-By: Geoffrey Booth <[email protected]> Reviewed-By: Antoine du Hamel <[email protected]>
1 parent ce30b4e commit 705e623

9 files changed

+31
-429
lines changed

doc/api/module.md

+3-75
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,9 @@ import('node:fs').then((esmFS) => {
149149
<!-- YAML
150150
added: v8.8.0
151151
changes:
152+
- version: REPLACEME
153+
pr-url: https://github.com/nodejs/node/pull/49144
154+
description: Removed `globalPreload`.
152155
- version: v20.6.0
153156
pr-url: https://github.com/nodejs/node/pull/48842
154157
description: Added `initialize` hook to replace `globalPreload`.
@@ -674,79 +677,6 @@ export async function load(url, context, nextLoad) {
674677
In a more advanced scenario, this can also be used to transform an unsupported
675678
source to a supported one (see [Examples](#examples) below).
676679
677-
#### `globalPreload()`
678-
679-
<!-- YAML
680-
changes:
681-
- version:
682-
- v18.6.0
683-
- v16.17.0
684-
pr-url: https://github.com/nodejs/node/pull/42623
685-
description: Add support for chaining globalPreload hooks.
686-
-->
687-
688-
> Stability: 1.0 - Early development
689-
690-
> **Warning:** This hook will be removed in a future version. Use
691-
> [`initialize`][] instead. When a hooks module has an `initialize` export,
692-
> `globalPreload` will be ignored.
693-
694-
* `context` {Object} Information to assist the preload code
695-
* `port` {MessagePort}
696-
* Returns: {string} Code to run before application startup
697-
698-
Sometimes it might be necessary to run some code inside of the same global
699-
scope that the application runs in. This hook allows the return of a string
700-
that is run as a sloppy-mode script on startup.
701-
702-
Similar to how CommonJS wrappers work, the code runs in an implicit function
703-
scope. The only argument is a `require`-like function that can be used to load
704-
builtins like "fs": `getBuiltin(request: string)`.
705-
706-
If the code needs more advanced `require` features, it has to construct
707-
its own `require` using `module.createRequire()`.
708-
709-
```mjs
710-
export function globalPreload(context) {
711-
return `\
712-
globalThis.someInjectedProperty = 42;
713-
console.log('I just set some globals!');
714-
715-
const { createRequire } = getBuiltin('module');
716-
const { cwd } = getBuiltin('process');
717-
718-
const require = createRequire(cwd() + '/<preload>');
719-
// [...]
720-
`;
721-
}
722-
```
723-
724-
Another argument is provided to the preload code: `port`. This is available as a
725-
parameter to the hook and inside of the source text returned by the hook. This
726-
functionality has been moved to the `initialize` hook.
727-
728-
Care must be taken in order to properly call [`port.ref()`][] and
729-
[`port.unref()`][] to prevent a process from being in a state where it won't
730-
close normally.
731-
732-
```mjs
733-
/**
734-
* This example has the application context send a message to the hook
735-
* and sends the message back to the application context
736-
*/
737-
export function globalPreload({ port }) {
738-
port.onmessage = (evt) => {
739-
port.postMessage(evt.data);
740-
};
741-
return `\
742-
port.postMessage('console.log("I went to the hook and back");');
743-
port.onmessage = (evt) => {
744-
eval(evt.data);
745-
};
746-
`;
747-
}
748-
```
749-
750680
### Examples
751681
752682
The various module customization hooks can be used together to accomplish
@@ -1105,8 +1035,6 @@ returned object contains the following keys:
11051035
[`Uint8Array`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array
11061036
[`initialize`]: #initialize
11071037
[`module`]: modules.md#the-module-object
1108-
[`port.ref()`]: worker_threads.md#portref
1109-
[`port.unref()`]: worker_threads.md#portunref
11101038
[`register`]: #moduleregisterspecifier-parenturl-options
11111039
[`string`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String
11121040
[`util.TextDecoder`]: util.md#class-utiltextdecoder

lib/internal/modules/esm/hooks.js

+12-126
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,13 @@
33
const {
44
ArrayPrototypePush,
55
ArrayPrototypePushApply,
6-
FunctionPrototypeCall,
76
Int32Array,
87
ObjectAssign,
98
ObjectDefineProperty,
109
ObjectSetPrototypeOf,
1110
Promise,
1211
SafeSet,
1312
StringPrototypeSlice,
14-
StringPrototypeStartsWith,
1513
StringPrototypeToUpperCase,
1614
globalThis,
1715
} = primordials;
@@ -33,7 +31,6 @@ const {
3331
ERR_INVALID_RETURN_VALUE,
3432
ERR_LOADER_CHAIN_INCOMPLETE,
3533
ERR_METHOD_NOT_IMPLEMENTED,
36-
ERR_UNKNOWN_BUILTIN_MODULE,
3734
ERR_WORKER_UNSERIALIZABLE_ERROR,
3835
} = require('internal/errors').codes;
3936
const { exitCodes: { kUnfinishedTopLevelAwait } } = internalBinding('errors');
@@ -49,7 +46,6 @@ const {
4946
validateString,
5047
} = require('internal/validators');
5148
const {
52-
emitExperimentalWarning,
5349
kEmptyObject,
5450
} = require('internal/util');
5551

@@ -73,8 +69,6 @@ let importMetaInitializer;
7369

7470
/**
7571
* @typedef {object} ExportedHooks
76-
* @property {Function} initialize Customizations setup hook.
77-
* @property {Function} globalPreload Global preload hook.
7872
* @property {Function} resolve Resolve hook.
7973
* @property {Function} load Load hook.
8074
*/
@@ -89,13 +83,6 @@ let importMetaInitializer;
8983

9084
class Hooks {
9185
#chains = {
92-
/**
93-
* Prior to ESM loading. These are called once before any modules are started.
94-
* @private
95-
* @property {KeyedHook[]} globalPreload Last-in-first-out list of preload hooks.
96-
*/
97-
globalPreload: [],
98-
9986
/**
10087
* Phase 1 of 2 in ESM loading.
10188
* The output of the `resolve` chain of hooks is passed into the `load` chain of hooks.
@@ -146,7 +133,6 @@ class Hooks {
146133

147134
/**
148135
* Collect custom/user-defined module loader hook(s).
149-
* After all hooks have been collected, the global preload hook(s) must be initialized.
150136
* @param {string} url Custom loader specifier
151137
* @param {Record<string, unknown>} exports
152138
* @param {any} [data] Arbitrary data to be passed from the custom loader (user-land)
@@ -155,18 +141,11 @@ class Hooks {
155141
*/
156142
addCustomLoader(url, exports, data) {
157143
const {
158-
globalPreload,
159144
initialize,
160145
resolve,
161146
load,
162147
} = pluckHooks(exports);
163148

164-
if (globalPreload && !initialize) {
165-
emitExperimentalWarning(
166-
'`globalPreload` is planned for removal in favor of `initialize`. `globalPreload`',
167-
);
168-
ArrayPrototypePush(this.#chains.globalPreload, { __proto__: null, fn: globalPreload, url });
169-
}
170149
if (resolve) {
171150
const next = this.#chains.resolve[this.#chains.resolve.length - 1];
172151
ArrayPrototypePush(this.#chains.resolve, { __proto__: null, fn: resolve, url, next });
@@ -178,49 +157,6 @@ class Hooks {
178157
return initialize?.(data);
179158
}
180159

181-
/**
182-
* Initialize `globalPreload` hooks.
183-
*/
184-
initializeGlobalPreload() {
185-
const preloadScripts = [];
186-
for (let i = this.#chains.globalPreload.length - 1; i >= 0; i--) {
187-
const { MessageChannel } = require('internal/worker/io');
188-
const channel = new MessageChannel();
189-
const {
190-
port1: insidePreload,
191-
port2: insideLoader,
192-
} = channel;
193-
194-
insidePreload.unref();
195-
insideLoader.unref();
196-
197-
const {
198-
fn: preload,
199-
url: specifier,
200-
} = this.#chains.globalPreload[i];
201-
202-
const preloaded = preload({
203-
port: insideLoader,
204-
});
205-
206-
if (preloaded == null) { continue; }
207-
208-
if (typeof preloaded !== 'string') { // [2]
209-
throw new ERR_INVALID_RETURN_VALUE(
210-
'a string',
211-
`${specifier} globalPreload`,
212-
preload,
213-
);
214-
}
215-
216-
ArrayPrototypePush(preloadScripts, {
217-
code: preloaded,
218-
port: insidePreload,
219-
});
220-
}
221-
return preloadScripts;
222-
}
223-
224160
/**
225161
* Resolve the location of the module.
226162
*
@@ -559,8 +495,9 @@ class HooksProxy {
559495
AtomicsWait(this.#lock, WORKER_TO_MAIN_THREAD_NOTIFICATION, 0);
560496
const response = this.#worker.receiveMessageSync();
561497
if (response == null || response.message.status === 'exit') { return; }
562-
const { preloadScripts } = this.#unwrapMessage(response);
563-
this.#executePreloadScripts(preloadScripts);
498+
499+
// ! This line catches initialization errors in the worker thread.
500+
this.#unwrapMessage(response);
564501
}
565502

566503
this.#isReady = true;
@@ -677,66 +614,12 @@ class HooksProxy {
677614
importMetaInitialize(meta, context, loader) {
678615
this.#importMetaInitializer(meta, context, loader);
679616
}
680-
681-
#executePreloadScripts(preloadScripts) {
682-
for (let i = 0; i < preloadScripts.length; i++) {
683-
const { code, port } = preloadScripts[i];
684-
const { compileFunction } = require('vm');
685-
const preloadInit = compileFunction(
686-
code,
687-
['getBuiltin', 'port', 'setImportMetaCallback'],
688-
{
689-
filename: '<preload>',
690-
},
691-
);
692-
let finished = false;
693-
let replacedImportMetaInitializer = false;
694-
let next = this.#importMetaInitializer;
695-
const { BuiltinModule } = require('internal/bootstrap/realm');
696-
// Calls the compiled preload source text gotten from the hook
697-
// Since the parameters are named we use positional parameters
698-
// see compileFunction above to cross reference the names
699-
try {
700-
FunctionPrototypeCall(
701-
preloadInit,
702-
globalThis,
703-
// Param getBuiltin
704-
(builtinName) => {
705-
if (StringPrototypeStartsWith(builtinName, 'node:')) {
706-
builtinName = StringPrototypeSlice(builtinName, 5);
707-
} else if (!BuiltinModule.canBeRequiredWithoutScheme(builtinName)) {
708-
throw new ERR_UNKNOWN_BUILTIN_MODULE(builtinName);
709-
}
710-
if (BuiltinModule.canBeRequiredByUsers(builtinName)) {
711-
return require(builtinName);
712-
}
713-
throw new ERR_UNKNOWN_BUILTIN_MODULE(builtinName);
714-
},
715-
// Param port
716-
port,
717-
// setImportMetaCallback
718-
(fn) => {
719-
if (finished || typeof fn !== 'function') {
720-
throw new ERR_INVALID_ARG_TYPE('fn', fn);
721-
}
722-
replacedImportMetaInitializer = true;
723-
const parent = next;
724-
next = (meta, context) => {
725-
return fn(meta, context, parent);
726-
};
727-
},
728-
);
729-
} finally {
730-
finished = true;
731-
if (replacedImportMetaInitializer) {
732-
this.#importMetaInitializer = next;
733-
}
734-
}
735-
}
736-
}
737617
}
738618
ObjectSetPrototypeOf(HooksProxy.prototype, null);
739619

620+
// TODO(JakobJingleheimer): Remove this when loaders go "stable".
621+
let globalPreloadWarningWasEmitted = false;
622+
740623
/**
741624
* A utility function to pluck the hooks from a user-defined loader.
742625
* @param {import('./loader.js).ModuleExports} exports
@@ -750,9 +633,6 @@ function pluckHooks({
750633
}) {
751634
const acceptedHooks = { __proto__: null };
752635

753-
if (globalPreload) {
754-
acceptedHooks.globalPreload = globalPreload;
755-
}
756636
if (resolve) {
757637
acceptedHooks.resolve = resolve;
758638
}
@@ -762,6 +642,12 @@ function pluckHooks({
762642

763643
if (initialize) {
764644
acceptedHooks.initialize = initialize;
645+
} else if (globalPreload && !globalPreloadWarningWasEmitted) {
646+
process.emitWarning(
647+
'`globalPreload` has been removed; use `initialize` instead.',
648+
'UnsupportedWarning',
649+
);
650+
globalPreloadWarningWasEmitted = true;
765651
}
766652

767653
return acceptedHooks;

lib/internal/modules/esm/utils.js

+1-3
Original file line numberDiff line numberDiff line change
@@ -145,9 +145,7 @@ async function initializeHooks() {
145145
);
146146
}
147147

148-
const preloadScripts = hooks.initializeGlobalPreload();
149-
150-
return { __proto__: null, hooks, preloadScripts };
148+
return hooks;
151149
}
152150

153151
module.exports = {

lib/internal/modules/esm/worker.js

+4-5
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,8 @@ function wrapMessage(status, body) {
7474
}
7575

7676
async function customizedModuleWorker(lock, syncCommPort, errorHandler) {
77-
let hooks, preloadScripts, initializationError;
77+
let hooks;
78+
let initializationError;
7879
let hasInitializationError = false;
7980

8081
{
@@ -91,9 +92,7 @@ async function customizedModuleWorker(lock, syncCommPort, errorHandler) {
9192

9293

9394
try {
94-
const initResult = await initializeHooks();
95-
hooks = initResult.hooks;
96-
preloadScripts = initResult.preloadScripts;
95+
hooks = await initializeHooks();
9796
} catch (exception) {
9897
// If there was an error while parsing and executing a user loader, for example if because a
9998
// loader contained a syntax error, then we need to send the error to the main thread so it can
@@ -107,7 +106,7 @@ async function customizedModuleWorker(lock, syncCommPort, errorHandler) {
107106
if (hasInitializationError) {
108107
syncCommPort.postMessage(wrapMessage('error', initializationError));
109108
} else {
110-
syncCommPort.postMessage(wrapMessage('success', { preloadScripts }), preloadScripts.map(({ port }) => port));
109+
syncCommPort.postMessage(wrapMessage('success'));
111110
}
112111

113112
// We're ready, so unlock the main thread.

0 commit comments

Comments
 (0)