From 565a5e374b374630710278f81e019f99e14bef4e Mon Sep 17 00:00:00 2001 From: Geoff Goodman Date: Wed, 4 Oct 2023 15:39:58 -0400 Subject: [PATCH] module: initialize hook returns load, resolve This commit allows the `initialize()` hook to optionally return an object having the `resolve()` and `load()` hooks as properties. This allows state passed into `initialize()` to be shared with the `resolve()` and `load()` hooks either via closure or class instance. In addition to developer ergonomics, supporting this model will make it easier to write tests against a loader module. The existing design forces state to be shared at the module level which puts the burden of invalidating the ESM module cache on anyone hoping to write isolated tests against a loader module. Fixes: https://github.com/nodejs/node/issues/50042 --- lib/internal/modules/esm/hooks.js | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/lib/internal/modules/esm/hooks.js b/lib/internal/modules/esm/hooks.js index 8dd81ca52c5318..00c1ab75aaff87 100644 --- a/lib/internal/modules/esm/hooks.js +++ b/lib/internal/modules/esm/hooks.js @@ -3,6 +3,7 @@ const { ArrayPrototypePush, ArrayPrototypePushApply, + FunctionPrototypeBind, Int32Array, ObjectAssign, ObjectDefineProperty, @@ -139,7 +140,7 @@ class Hooks { * to the worker. * @returns {any | Promise} User data, ignored unless it's a promise, in which case it will be awaited. */ - addCustomLoader(url, exports, data) { + async addCustomLoader(url, exports, data) { const { initialize, resolve, @@ -154,7 +155,26 @@ class Hooks { const next = this.#chains.load[this.#chains.load.length - 1]; ArrayPrototypePush(this.#chains.load, { __proto__: null, fn: load, url, next }); } - return initialize?.(data); + + const hooks = await initialize?.(data); + + if (hooks?.resolve) { + if (resolve) { + throw new Error('Invariant violation: both a module-level and returned resolve hook found'); + } + const next = this.#chains.resolve[this.#chains.resolve.length - 1]; + const boundResolve = FunctionPrototypeBind(hooks.resolve, hooks); + ArrayPrototypePush(this.#chains.resolve, { __proto__: null, fn: boundResolve, url, next }); + } + + if (hooks?.load) { + if (load) { + throw new Error('Invariant violation: both a module-level and returned load hook found'); + } + const next = this.#chains.load[this.#chains.load.length - 1]; + const boundLoad = FunctionPrototypeBind(hooks.load, hooks); + ArrayPrototypePush(this.#chains.load, { __proto__: null, fn: boundLoad, url, next }); + } } /**