| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent f96b610 commit 10e7c3a
10 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -684,6 +684,9 @@ of Node.js applications. | |||
| 684 | 684 | <!-- YAML | |
| 685 | 685 | added: v8.8.0 | |
| 686 | 686 | changes: | |
| 687 | + - version: REPLACEME | ||
| 688 | + pr-url: https://github.com/nodejs/node/pull/48842 | ||
| 689 | + description: Added `initialize` hook to replace `globalPreload`. | ||
| 687 | 690 | - version: | |
| 688 | 691 | - v18.6.0 | |
| 689 | 692 | pr-url: https://github.com/nodejs/node/pull/42623 | |
@@ -737,6 +740,69 @@ different [realm](https://tc39.es/ecma262/#realm). The hooks thread may be | |||
| 737 | 740 | terminated by the main thread at any time, so do not depend on asynchronous | |
| 738 | 741 | operations to (like `console.log`) complete. | |
| 739 | 742 | ||
| 743 | + #### `initialize()` | ||
| 744 | + | ||
| 745 | + <!-- YAML | ||
| 746 | + added: REPLACEME | ||
| 747 | + --> | ||
| 748 | + | ||
| 749 | + > The loaders API is being redesigned. This hook may disappear or its | ||
| 750 | + > signature may change. Do not rely on the API described below. | ||
| 751 | + | ||
| 752 | + * `data` {any} The data from `register(loader, import.meta.url, { data })`. | ||
| 753 | + * Returns: {any} The data to be returned to the caller of `register`. | ||
| 754 | + | ||
| 755 | + The `initialize` hook provides a way to define a custom function that runs | ||
| 756 | + in the loader's thread when the loader is initialized. Initialization happens | ||
| 757 | + when the loader is registered via [`register`][] or registered via the | ||
| 758 | + `--loader` command line option. | ||
| 759 | + | ||
| 760 | + This hook can send and receive data from a [`register`][] invocation, including | ||
| 761 | + ports and other transferrable objects. The return value of `initialize` must be | ||
| 762 | + either: | ||
| 763 | + | ||
| 764 | + * `undefined`, | ||
| 765 | + * something that can be posted as a message between threads (e.g. the input to | ||
| 766 | + [`port.postMessage`][]), | ||
| 767 | + * a `Promise` resolving to one of the aforementioned values. | ||
| 768 | + | ||
| 769 | + Loader code: | ||
| 770 | + | ||
| 771 | + ```js | ||
| 772 | + // In the below example this file is referenced as | ||
| 773 | + // '/path-to-my-loader.js' | ||
| 774 | + | ||
| 775 | + export async function initialize({ number, port }) { | ||
| 776 | + port.postMessage(`increment: ${number + 1}`); | ||
| 777 | + return 'ok'; | ||
| 778 | + } | ||
| 779 | + ``` | ||
| 780 | + | ||
| 781 | + Caller code: | ||
| 782 | + | ||
| 783 | + ```js | ||
| 784 | + import assert from 'node:assert'; | ||
| 785 | + import { register } from 'node:module'; | ||
| 786 | + import { MessageChannel } from 'node:worker_threads'; | ||
| 787 | + | ||
| 788 | + // This example showcases how a message channel can be used to | ||
| 789 | + // communicate between the main (application) thread and the loader | ||
| 790 | + // running on the loaders thread, by sending `port2` to the loader. | ||
| 791 | + const { port1, port2 } = new MessageChannel(); | ||
| 792 | + | ||
| 793 | + port1.on('message', (msg) => { | ||
| 794 | + assert.strictEqual(msg, 'increment: 2'); | ||
| 795 | + }); | ||
| 796 | + | ||
| 797 | + const result = register('/path-to-my-loader.js', { | ||
| 798 | + parentURL: import.meta.url, | ||
| 799 | + data: { number: 1, port: port2 }, | ||
| 800 | + transferList: [port2], | ||
| 801 | + }); | ||
| 802 | + | ||
| 803 | + assert.strictEqual(result, 'ok'); | ||
| 804 | + ``` | ||
| 805 | + | ||
| 740 | 806 | #### `resolve(specifier, context, nextResolve)` | |
| 741 | 807 | ||
| 742 | 808 | <!-- YAML | |
@@ -941,8 +1007,8 @@ changes: | |||
| 941 | 1007 | description: Add support for chaining globalPreload hooks. | |
| 942 | 1008 | --> | |
| 943 | 1009 | ||
| 944 | - > The loaders API is being redesigned. This hook may disappear or its | ||
| 945 | - > signature may change. Do not rely on the API described below. | ||
| 1010 | + > This hook will be removed in a future version. Use [`initialize`][] instead. | ||
| 1011 | + > When a loader has an `initialize` export, `globalPreload` will be ignored. | ||
| 946 | 1012 | ||
| 947 | 1013 | > In a previous version of this API, this hook was named | |
| 948 | 1014 | > `getGlobalPreloadCode`. | |
@@ -1642,13 +1708,16 @@ success! | |||
| 1642 | 1708 | [`import.meta.resolve`]: #importmetaresolvespecifier-parent | |
| 1643 | 1709 | [`import.meta.url`]: #importmetaurl | |
| 1644 | 1710 | [`import`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import | |
| 1711 | + [`initialize`]: #initialize | ||
| 1645 | 1712 | [`module.createRequire()`]: module.md#modulecreaterequirefilename | |
| 1646 | 1713 | [`module.register()`]: module.md#moduleregister | |
| 1647 | 1714 | [`module.syncBuiltinESMExports()`]: module.md#modulesyncbuiltinesmexports | |
| 1648 | 1715 | [`package.json`]: packages.md#nodejs-packagejson-field-definitions | |
| 1716 | + [`port.postMessage`]: worker_threads.md#portpostmessagevalue-transferlist | ||
| 1649 | 1717 | [`port.ref()`]: https://nodejs.org/dist/latest-v17.x/docs/api/worker_threads.html#portref | |
| 1650 | 1718 | [`port.unref()`]: https://nodejs.org/dist/latest-v17.x/docs/api/worker_threads.html#portunref | |
| 1651 | 1719 | [`process.dlopen`]: process.md#processdlopenmodule-filename-flags | |
| 1720 | + [`register`]: module.md#moduleregister | ||
| 1652 | 1721 | [`string`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String | |
| 1653 | 1722 | [`util.TextDecoder`]: util.md#class-utiltextdecoder | |
| 1654 | 1723 | [cjs-module-lexer]: https://github.com/nodejs/cjs-module-lexer/tree/1.2.2 | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -173,6 +173,28 @@ globalPreload: http-to-https | |||
| 173 | 173 | globalPreload: unpkg | |
| 174 | 174 | ``` | |
| 175 | 175 | ||
| 176 | + This function can also be used to pass data to the loader's [`initialize`][] | ||
| 177 | + hook; the data passed to the hook may include transferrable objects like ports. | ||
| 178 | + | ||
| 179 | + ```mjs | ||
| 180 | + import { register } from 'node:module'; | ||
| 181 | + import { MessageChannel } from 'node:worker_threads'; | ||
| 182 | + | ||
| 183 | + // This example showcases how a message channel can be used to | ||
| 184 | + // communicate to the loader, by sending `port2` to the loader. | ||
| 185 | + const { port1, port2 } = new MessageChannel(); | ||
| 186 | + | ||
| 187 | + port1.on('message', (msg) => { | ||
| 188 | + console.log(msg); | ||
| 189 | + }); | ||
| 190 | + | ||
| 191 | + register('./my-programmatic-loader.mjs', { | ||
| 192 | + parentURL: import.meta.url, | ||
| 193 | + data: { number: 1, port: port2 }, | ||
| 194 | + transferList: [port2], | ||
| 195 | + }); | ||
| 196 | + ``` | ||
| 197 | + | ||
| 176 | 198 | ### `module.syncBuiltinESMExports()` | |
| 177 | 199 | ||
| 178 | 200 | <!-- YAML | |
@@ -358,6 +380,7 @@ returned object contains the following keys: | |||
| 358 | 380 | [`--enable-source-maps`]: cli.md#--enable-source-maps | |
| 359 | 381 | [`NODE_V8_COVERAGE=dir`]: cli.md#node_v8_coveragedir | |
| 360 | 382 | [`SourceMap`]: #class-modulesourcemap | |
| 383 | + [`initialize`]: esm.md#initialize | ||
| 361 | 384 | [`module`]: modules.md#the-module-object | |
| 362 | 385 | [module wrapper]: modules.md#the-module-wrapper | |
| 363 | 386 | [source map include directives]: https://sourcemaps.info/spec.html#h.lmz475t4mvbx | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -2,6 +2,7 @@ | |||
| 2 | 2 | ||
| 3 | 3 | const { | |
| 4 | 4 | ArrayPrototypePush, | |
| 5 | + ArrayPrototypePushApply, | ||
| 5 | 6 | FunctionPrototypeCall, | |
| 6 | 7 | Int32Array, | |
| 7 | 8 | ObjectAssign, | |
@@ -46,8 +47,10 @@ const { | |||
| 46 | 47 | validateObject, | |
| 47 | 48 | validateString, | |
| 48 | 49 | } = require('internal/validators'); | |
| 49 | - | ||
| 50 | - const { kEmptyObject } = require('internal/util'); | ||
| 50 | + const { | ||
| 51 | + emitExperimentalWarning, | ||
| 52 | + kEmptyObject, | ||
| 53 | + } = require('internal/util'); | ||
| 51 | 54 | ||
| 52 | 55 | const { | |
| 53 | 56 | defaultResolve, | |
@@ -82,6 +85,7 @@ let importMetaInitializer; | |||
| 82 | 85 | ||
| 83 | 86 | // [2] `validate...()`s throw the wrong error | |
| 84 | 87 | ||
| 88 | + let globalPreloadWarned = false; | ||
| 85 | 89 | class Hooks { | |
| 86 | 90 | #chains = { | |
| 87 | 91 | /** | |
@@ -126,31 +130,43 @@ class Hooks { | |||
| 126 | 130 | * Import and register custom/user-defined module loader hook(s). | |
| 127 | 131 | * @param {string} urlOrSpecifier | |
| 128 | 132 | * @param {string} parentURL | |
| 133 | + * @param {any} [data] Arbitrary data to be passed from the custom | ||
| 134 | + * loader (user-land) to the worker. | ||
| 129 | 135 | */ | |
| 130 | - async register(urlOrSpecifier, parentURL) { | ||
| 136 | + async register(urlOrSpecifier, parentURL, data) { | ||
| 131 | 137 | const moduleLoader = require('internal/process/esm_loader').esmLoader; | |
| 132 | 138 | const keyedExports = await moduleLoader.import( | |
| 133 | 139 | urlOrSpecifier, | |
| 134 | 140 | parentURL, | |
| 135 | 141 | kEmptyObject, | |
| 136 | 142 | ); | |
| 137 | - this.addCustomLoader(urlOrSpecifier, keyedExports); | ||
| 143 | + return this.addCustomLoader(urlOrSpecifier, keyedExports, data); | ||
| 138 | 144 | } | |
| 139 | 145 | ||
| 140 | 146 | /** | |
| 141 | 147 | * Collect custom/user-defined module loader hook(s). | |
| 142 | 148 | * After all hooks have been collected, the global preload hook(s) must be initialized. | |
| 143 | 149 | * @param {string} url Custom loader specifier | |
| 144 | 150 | * @param {Record<string, unknown>} exports | |
| 151 | + * @param {any} [data] Arbitrary data to be passed from the custom loader (user-land) | ||
| 152 | + * to the worker. | ||
| 153 | + * @returns {any} The result of the loader's `initialize` hook, if provided. | ||
| 145 | 154 | */ | |
| 146 | - addCustomLoader(url, exports) { | ||
| 155 | + addCustomLoader(url, exports, data) { | ||
| 147 | 156 | const { | |
| 148 | 157 | globalPreload, | |
| 158 | + initialize, | ||
| 149 | 159 | resolve, | |
| 150 | 160 | load, | |
| 151 | 161 | } = pluckHooks(exports); | |
| 152 | 162 | ||
| 153 | - if (globalPreload) { | ||
| 163 | + if (globalPreload && !initialize) { | ||
| 164 | + if (globalPreloadWarned === false) { | ||
| 165 | + globalPreloadWarned = true; | ||
| 166 | + emitExperimentalWarning( | ||
| 167 | + '`globalPreload` will be removed in a future version. Please use `initialize` instead.', | ||
| 168 | + ); | ||
| 169 | + } | ||
| 154 | 170 | ArrayPrototypePush(this.#chains.globalPreload, { __proto__: null, fn: globalPreload, url }); | |
| 155 | 171 | } | |
| 156 | 172 | if (resolve) { | |
@@ -161,6 +177,7 @@ class Hooks { | |||
| 161 | 177 | const next = this.#chains.load[this.#chains.load.length - 1]; | |
| 162 | 178 | ArrayPrototypePush(this.#chains.load, { __proto__: null, fn: load, url, next }); | |
| 163 | 179 | } | |
| 180 | + return initialize?.(data); | ||
| 164 | 181 | } | |
| 165 | 182 | ||
| 166 | 183 | /** | |
@@ -552,15 +569,30 @@ class HooksProxy { | |||
| 552 | 569 | } | |
| 553 | 570 | } | |
| 554 | 571 | ||
| 555 | - async makeAsyncRequest(method, ...args) { | ||
| 572 | + /** | ||
| 573 | + * Invoke a remote method asynchronously. | ||
| 574 | + * @param {string} method Method to invoke | ||
| 575 | + * @param {any[]} [transferList] Objects in `args` to be transferred | ||
| 576 | + * @param {any[]} args Arguments to pass to `method` | ||
| 577 | + * @returns {Promise<any>} | ||
| 578 | + */ | ||
| 579 | + async makeAsyncRequest(method, transferList, ...args) { | ||
| 556 | 580 | this.waitForWorker(); | |
| 557 | 581 | ||
| 558 | 582 | MessageChannel ??= require('internal/worker/io').MessageChannel; | |
| 559 | 583 | const asyncCommChannel = new MessageChannel(); | |
| 560 | 584 | ||
| 561 | 585 | // Pass work to the worker. | |
| 562 | - debug('post async message to worker', { method, args }); | ||
| 563 | - this.#worker.postMessage({ method, args, port: asyncCommChannel.port2 }, [asyncCommChannel.port2]); | ||
| 586 | + debug('post async message to worker', { method, args, transferList }); | ||
| 587 | + const finalTransferList = [asyncCommChannel.port2]; | ||
| 588 | + if (transferList) { | ||
| 589 | + ArrayPrototypePushApply(finalTransferList, transferList); | ||
| 590 | + } | ||
| 591 | + this.#worker.postMessage({ | ||
| 592 | + __proto__: null, | ||
| 593 | + method, args, | ||
| 594 | + port: asyncCommChannel.port2, | ||
| 595 | + }, finalTransferList); | ||
| 564 | 596 | ||
| 565 | 597 | if (this.#numberOfPendingAsyncResponses++ === 0) { | |
| 566 | 598 | // On the next lines, the main thread will await a response from the worker thread that might | |
@@ -592,12 +624,19 @@ class HooksProxy { | |||
| 592 | 624 | return body; | |
| 593 | 625 | } | |
| 594 | 626 | ||
| 595 | - makeSyncRequest(method, ...args) { | ||
| 627 | + /** | ||
| 628 | + * Invoke a remote method synchronously. | ||
| 629 | + * @param {string} method Method to invoke | ||
| 630 | + * @param {any[]} [transferList] Objects in `args` to be transferred | ||
| 631 | + * @param {any[]} args Arguments to pass to `method` | ||
| 632 | + * @returns {any} | ||
| 633 | + */ | ||
| 634 | + makeSyncRequest(method, transferList, ...args) { | ||
| 596 | 635 | this.waitForWorker(); | |
| 597 | 636 | ||
| 598 | 637 | // Pass work to the worker. | |
| 599 | - debug('post sync message to worker', { method, args }); | ||
| 600 | - this.#worker.postMessage({ method, args }); | ||
| 638 | + debug('post sync message to worker', { method, args, transferList }); | ||
| 639 | + this.#worker.postMessage({ __proto__: null, method, args }, transferList); | ||
| 601 | 640 | ||
| 602 | 641 | let response; | |
| 603 | 642 | do { | |
@@ -707,6 +746,7 @@ ObjectSetPrototypeOf(HooksProxy.prototype, null); | |||
| 707 | 746 | */ | |
| 708 | 747 | function pluckHooks({ | |
| 709 | 748 | globalPreload, | |
| 749 | + initialize, | ||
| 710 | 750 | resolve, | |
| 711 | 751 | load, | |
| 712 | 752 | }) { | |
@@ -722,6 +762,10 @@ function pluckHooks({ | |||
| 722 | 762 | acceptedHooks.load = load; | |
| 723 | 763 | } | |
| 724 | 764 | ||
| 765 | + if (initialize) { | ||
| 766 | + acceptedHooks.initialize = initialize; | ||
| 767 | + } | ||
| 768 | + | ||
| 725 | 769 | return acceptedHooks; | |
| 726 | 770 | } | |
| 727 | 771 | ||
| Back | FazBrowse Home | New Git URL |
0 commit comments