-
Notifications
You must be signed in to change notification settings - Fork 30.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
introduce loader hook context #20761
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -142,6 +142,25 @@ 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. | ||
- `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 | ||
|
||
The resolve hook returns the resolved file URL and module format for a | ||
|
@@ -153,7 +172,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 +214,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 +223,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 +257,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) => { | ||
|
@@ -253,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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
[`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 |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -12,6 +12,7 @@ const defaultResolve = require('internal/modules/esm/default_resolve'); | |
const createDynamicModule = require( | ||
'internal/modules/esm/create_dynamic_module'); | ||
const translators = require('internal/modules/esm/translators'); | ||
const { vmModuleLinkHookMap } = require('internal/vm/module'); | ||
|
||
const FunctionBind = Function.call.bind(Function.prototype.bind); | ||
|
||
|
@@ -45,6 +46,16 @@ 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 | ||
const k = Object.freeze(Object.create(null)); | ||
vmModuleLinkHookMap.set(k, this); | ||
|
||
this.hookContext = Object.assign(Object.create(null), { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd prefer a different name since this has relations to This also seems to have some conflict with #18914 that I need to think on. |
||
defaultResolve, | ||
resolve: (specifier, parentURL) => this.resolve(specifier, parentURL), | ||
vmModuleLinkHook: k, | ||
}); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If you want to deny the hook context access to outside world, you are not – the [[Prototype]] of these functions are the %FunctionPrototype% of the outside world. It would be better to create these functions in the context directly, if possible, rather than using |
||
} | ||
|
||
async resolve(specifier, parentURL) { | ||
|
@@ -53,7 +64,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 +108,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); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -20,6 +20,8 @@ const { | |
const { SafePromise } = require('internal/safe_globals'); | ||
const { validateInt32, validateUint32 } = require('internal/validators'); | ||
|
||
const vmModuleLinkHookMap = new WeakMap(); | ||
|
||
const { | ||
ModuleWrap, | ||
kUninstantiated, | ||
|
@@ -151,6 +153,15 @@ class Module { | |
} | ||
|
||
async link(linker) { | ||
const linkingFromLoader = vmModuleLinkHookMap.has(linker); | ||
if (linkingFromLoader) { | ||
const loader = vmModuleLinkHookMap.get(linker); | ||
linker = (specifier, parent) => | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. any reason not to use an async function here? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. habits are hard to break :) nice catch |
||
loader.getModuleJob(specifier, parent.url) | ||
.then((j) => j.modulePromise) | ||
.then((r) => r.module); | ||
} | ||
|
||
if (typeof linker !== 'function') | ||
throw new ERR_INVALID_ARG_TYPE('linker', 'function', linker); | ||
if (linkingStatusMap.get(this) !== 'unlinked') | ||
|
@@ -163,6 +174,9 @@ class Module { | |
|
||
const promises = wrap.link(async (specifier) => { | ||
const m = await linker(specifier, this); | ||
if (linkingFromLoader) { | ||
return m; | ||
} | ||
if (!m || !wrapMap.has(m)) | ||
throw new ERR_VM_MODULE_NOT_MODULE(); | ||
if (m.context !== this.context) | ||
|
@@ -245,5 +259,6 @@ class Module { | |
module.exports = { | ||
Module, | ||
initImportMetaMap, | ||
wrapToModuleMap | ||
wrapToModuleMap, | ||
vmModuleLinkHookMap, | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
`module.link`
->`module.link()`
?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
{object}
->{Object}
.