From d6689125d87219b6e16349b60c7a4ad3a1099749 Mon Sep 17 00:00:00 2001 From: Gus Caplan Date: Tue, 15 May 2018 14:08:34 -0500 Subject: [PATCH 1/2] loader: introduce hook contexts --- doc/api/esm.md | 24 +++++++++++++++---- lib/internal/modules/esm/loader.js | 11 +++++++-- lib/internal/vm/module.js | 2 +- .../builtin-named-exports-loader.mjs | 4 ++-- .../es-module-loaders/loader-shared-dep.mjs | 2 +- .../es-module-loaders/loader-with-dep.mjs | 2 +- .../not-found-assert-loader.mjs | 2 +- 7 files changed, 35 insertions(+), 12 deletions(-) diff --git a/doc/api/esm.md b/doc/api/esm.md index 459f87771815cc..be47ca1371ccd2 100644 --- a/doc/api/esm.md +++ b/doc/api/esm.md @@ -142,6 +142,22 @@ provided via a `--loader ./loader-name.mjs` argument to Node.js. When hooks are used they only apply to ES module loading and not to any CommonJS modules loaded. +### Hook Context + +This context contains access to functions that are scoped to the current load +process. + +A hook context consists of the following properties: + +- `defaultResolve` {Function} A shortcut to the resolve algorithm that ships + with Node.js. + - `specifier` {string} The specifier of the module to import. + - `parentURL` {string} The URL of the module that requested the specifier. +- `resolve` {Function} A shortcut to the current resolve function. Especially + useful if resolve is not hooked. + - `specifier` {string} The specifier of the module to import. + - `parentURL` {string} The URL of the module that requested the specifier. + ### Resolve hook The resolve hook returns the resolved file URL and module format for a @@ -153,7 +169,7 @@ baseURL.pathname = `${process.cwd()}/`; export async function resolve(specifier, parentModuleURL = baseURL, - defaultResolver) { + hookContext) { return { url: new URL(specifier, parentModuleURL).href, format: 'esm' @@ -195,7 +211,7 @@ const JS_EXTENSIONS = new Set(['.js', '.mjs']); const baseURL = new URL('file://'); baseURL.pathname = `${process.cwd()}/`; -export function resolve(specifier, parentModuleURL = baseURL, defaultResolve) { +export function resolve(specifier, parentModuleURL = baseURL, hookContext) { if (builtins.includes(specifier)) { return { url: specifier, @@ -204,7 +220,7 @@ export function resolve(specifier, parentModuleURL = baseURL, defaultResolve) { } if (/^\.{0,2}[/]/.test(specifier) !== true && !specifier.startsWith('file:')) { // For node_modules support: - // return defaultResolve(specifier, parentModuleURL); + // return hookContext.defaultResolve(specifier, parentModuleURL); throw new Error( `imports must begin with '/', './', or '../'; '${specifier}' does not`); } @@ -238,7 +254,7 @@ This hook is called only for modules that return `format: 'dynamic'` from the `resolve` hook. ```js -export async function dynamicInstantiate(url) { +export async function dynamicInstantiate(url, hookContext) { return { exports: ['customExportName'], execute: (exports) => { diff --git a/lib/internal/modules/esm/loader.js b/lib/internal/modules/esm/loader.js index 18b0827eee0f68..def42346ac1195 100644 --- a/lib/internal/modules/esm/loader.js +++ b/lib/internal/modules/esm/loader.js @@ -45,6 +45,12 @@ class Loader { // an object with the same keys as `exports`, whose values are get/set // functions for the actual exported values. this._dynamicInstantiate = undefined; + + // Set up context passed to hooks + this.hookContext = Object.assign(Object.create(null), { + defaultResolve, + resolve: (specifier, parentURL) => this.resolve(specifier, parentURL), + }); } async resolve(specifier, parentURL) { @@ -53,7 +59,7 @@ class Loader { throw new ERR_INVALID_ARG_TYPE('parentURL', 'string', parentURL); const { url, format } = - await this._resolve(specifier, parentURL, defaultResolve); + await this._resolve(specifier, parentURL, this.hookContext); if (typeof url !== 'string') throw new ERR_INVALID_ARG_TYPE('url', 'string', url); @@ -97,7 +103,8 @@ class Loader { loaderInstance = async (url) => { debug(`Translating dynamic ${url}`); - const { exports, execute } = await this._dynamicInstantiate(url); + const { exports, execute } = + await this._dynamicInstantiate(url, this.hookContext); return createDynamicModule(exports, url, (reflect) => { debug(`Loading dynamic ${url}`); execute(reflect.exports); diff --git a/lib/internal/vm/module.js b/lib/internal/vm/module.js index 5c8cc27c6042d8..04d41f67d08ff3 100644 --- a/lib/internal/vm/module.js +++ b/lib/internal/vm/module.js @@ -245,5 +245,5 @@ class Module { module.exports = { Module, initImportMetaMap, - wrapToModuleMap + wrapToModuleMap, }; diff --git a/test/fixtures/es-module-loaders/builtin-named-exports-loader.mjs b/test/fixtures/es-module-loaders/builtin-named-exports-loader.mjs index 28ccd6ecf2076f..541d6c61335f96 100644 --- a/test/fixtures/es-module-loaders/builtin-named-exports-loader.mjs +++ b/test/fixtures/es-module-loaders/builtin-named-exports-loader.mjs @@ -18,12 +18,12 @@ export function dynamicInstantiate(url) { }; } -export function resolve(specifier, base, defaultResolver) { +export function resolve(specifier, base, { defaultResolve }) { if (builtins.has(specifier)) { return { url: `node:${specifier}`, format: 'dynamic' }; } - return defaultResolver(specifier, base); + return defaultResolve(specifier, base); } diff --git a/test/fixtures/es-module-loaders/loader-shared-dep.mjs b/test/fixtures/es-module-loaders/loader-shared-dep.mjs index 1a19e4c8927527..34a3303d73ec9e 100644 --- a/test/fixtures/es-module-loaders/loader-shared-dep.mjs +++ b/test/fixtures/es-module-loaders/loader-shared-dep.mjs @@ -1,7 +1,7 @@ import dep from './loader-dep.js'; import assert from 'assert'; -export function resolve(specifier, base, defaultResolve) { +export function resolve(specifier, base, { defaultResolve }) { assert.strictEqual(dep.format, 'esm'); return defaultResolve(specifier, base); } diff --git a/test/fixtures/es-module-loaders/loader-with-dep.mjs b/test/fixtures/es-module-loaders/loader-with-dep.mjs index 944e6e438c5cbd..db927f4050991d 100644 --- a/test/fixtures/es-module-loaders/loader-with-dep.mjs +++ b/test/fixtures/es-module-loaders/loader-with-dep.mjs @@ -1,5 +1,5 @@ import dep from './loader-dep.js'; -export function resolve (specifier, base, defaultResolve) { +export function resolve (specifier, base, { defaultResolve }) { return { url: defaultResolve(specifier, base).url, format: dep.format diff --git a/test/fixtures/es-module-loaders/not-found-assert-loader.mjs b/test/fixtures/es-module-loaders/not-found-assert-loader.mjs index d15f294fe674bb..53f460bd6e21ab 100644 --- a/test/fixtures/es-module-loaders/not-found-assert-loader.mjs +++ b/test/fixtures/es-module-loaders/not-found-assert-loader.mjs @@ -3,7 +3,7 @@ import assert from 'assert'; // a loader that asserts that the defaultResolve will throw "not found" // (skipping the top-level main of course) let mainLoad = true; -export async function resolve (specifier, base, defaultResolve) { +export async function resolve (specifier, base, { defaultResolve }) { if (mainLoad) { mainLoad = false; return defaultResolve(specifier, base); From e93d419d40fe9e02240db191b27ed4023c9baa00 Mon Sep 17 00:00:00 2001 From: Gus Caplan Date: Tue, 15 May 2018 19:09:22 -0500 Subject: [PATCH 2/2] add vm.Module linker hook to hookContext --- doc/api/esm.md | 5 +++++ doc/api/vm.md | 3 +++ lib/internal/modules/esm/loader.js | 5 +++++ lib/internal/vm/module.js | 15 +++++++++++++++ 4 files changed, 28 insertions(+) diff --git a/doc/api/esm.md b/doc/api/esm.md index be47ca1371ccd2..b587c233cf3785 100644 --- a/doc/api/esm.md +++ b/doc/api/esm.md @@ -157,6 +157,9 @@ A hook context consists of the following properties: useful if resolve is not hooked. - `specifier` {string} The specifier of the module to import. - `parentURL` {string} The URL of the module that requested the specifier. +- `vmModuleLinkHook` {object} This value can be passed to [`module.link`][] to + allow linking the import requests of a [`vm.Module`][] instance to the + loader. ### Resolve hook @@ -269,6 +272,8 @@ With the list of module exports provided upfront, the `execute` function will then be called at the exact point of module evaluation order for that module in the import tree. +[`module.link`]: vm.html#vm_module_link_linker +[`vm.Module`]: vm.html#vm_class_vm_module [Node.js EP for ES Modules]: https://github.com/nodejs/node-eps/blob/master/002-es-modules.md [addons]: addons.html [dynamic instantiate hook]: #esm_dynamic_instantiate_hook diff --git a/doc/api/vm.md b/doc/api/vm.md index cc9b3135381dad..f434f457c7bb20 100644 --- a/doc/api/vm.md +++ b/doc/api/vm.md @@ -395,6 +395,8 @@ that point all modules would have been fully linked already, the [HostResolveImportedModule][] implementation is fully synchronous per specification. +The linker may also be passed from [`hookContext.vmModuleLinkhook`][]. + ## Class: vm.Script