vm: add support for import.meta to Module
Fixes: https://github.com/nodejs/node/issues/18570 PR-URL: https://github.com/nodejs/node/pull/19277 Reviewed-By: Gus Caplan <me@gus.host> Reviewed-By: Tiancheng "Timothy" Gu <timothygu99@gmail.com>
This commit is contained in:
parent
28b622cb08
commit
07ba9141e4
@ -167,9 +167,49 @@ const contextifiedSandbox = vm.createContext({ secret: 42 });
|
|||||||
in stack traces produced by this Module.
|
in stack traces produced by this Module.
|
||||||
* `columnOffset` {integer} Spcifies the column number offset that is displayed
|
* `columnOffset` {integer} Spcifies the column number offset that is displayed
|
||||||
in stack traces produced by this Module.
|
in stack traces produced by this Module.
|
||||||
|
* `initalizeImportMeta` {Function} Called during evaluation of this Module to
|
||||||
|
initialize the `import.meta`. This function has the signature `(meta,
|
||||||
|
module)`, where `meta` is the `import.meta` object in the Module, and
|
||||||
|
`module` is this `vm.Module` object.
|
||||||
|
|
||||||
Creates a new ES `Module` object.
|
Creates a new ES `Module` object.
|
||||||
|
|
||||||
|
*Note*: Properties assigned to the `import.meta` object that are objects may
|
||||||
|
allow the Module to access information outside the specified `context`, if the
|
||||||
|
object is created in the top level context. Use `vm.runInContext()` to create
|
||||||
|
objects in a specific context.
|
||||||
|
|
||||||
|
```js
|
||||||
|
const vm = require('vm');
|
||||||
|
|
||||||
|
const contextifiedSandbox = vm.createContext({ secret: 42 });
|
||||||
|
|
||||||
|
(async () => {
|
||||||
|
const module = new vm.Module(
|
||||||
|
'Object.getPrototypeOf(import.meta.prop).secret = secret;',
|
||||||
|
{
|
||||||
|
initializeImportMeta(meta) {
|
||||||
|
// Note: this object is created in the top context. As such,
|
||||||
|
// Object.getPrototypeOf(import.meta.prop) points to the
|
||||||
|
// Object.prototype in the top context rather than that in
|
||||||
|
// the sandbox.
|
||||||
|
meta.prop = {};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// Since module has no dependencies, the linker function will never be called.
|
||||||
|
await module.link(() => {});
|
||||||
|
module.initialize();
|
||||||
|
await module.evaluate();
|
||||||
|
|
||||||
|
// Now, Object.prototype.secret will be equal to 42.
|
||||||
|
//
|
||||||
|
// To fix this problem, replace
|
||||||
|
// meta.prop = {};
|
||||||
|
// above with
|
||||||
|
// meta.prop = vm.runInContext('{}', contextifiedSandbox);
|
||||||
|
})();
|
||||||
|
```
|
||||||
|
|
||||||
### module.dependencySpecifiers
|
### module.dependencySpecifiers
|
||||||
|
|
||||||
* {string[]}
|
* {string[]}
|
||||||
|
@ -108,10 +108,13 @@
|
|||||||
'DeprecationWarning', 'DEP0062', startup, true);
|
'DeprecationWarning', 'DEP0062', startup, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (process.binding('config').experimentalModules) {
|
if (process.binding('config').experimentalModules ||
|
||||||
process.emitWarning(
|
process.binding('config').experimentalVMModules) {
|
||||||
'The ESM module loader is experimental.',
|
if (process.binding('config').experimentalModules) {
|
||||||
'ExperimentalWarning', undefined);
|
process.emitWarning(
|
||||||
|
'The ESM module loader is experimental.',
|
||||||
|
'ExperimentalWarning', undefined);
|
||||||
|
}
|
||||||
NativeModule.require('internal/process/esm_loader').setup();
|
NativeModule.require('internal/process/esm_loader').setup();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -10,6 +10,10 @@ const { getURLFromFilePath } = require('internal/url');
|
|||||||
const Loader = require('internal/modules/esm/loader');
|
const Loader = require('internal/modules/esm/loader');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const { URL } = require('url');
|
const { URL } = require('url');
|
||||||
|
const {
|
||||||
|
initImportMetaMap,
|
||||||
|
wrapToModuleMap
|
||||||
|
} = require('internal/vm/module');
|
||||||
|
|
||||||
function normalizeReferrerURL(referrer) {
|
function normalizeReferrerURL(referrer) {
|
||||||
if (typeof referrer === 'string' && path.isAbsolute(referrer)) {
|
if (typeof referrer === 'string' && path.isAbsolute(referrer)) {
|
||||||
@ -19,7 +23,18 @@ function normalizeReferrerURL(referrer) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function initializeImportMetaObject(wrap, meta) {
|
function initializeImportMetaObject(wrap, meta) {
|
||||||
meta.url = wrap.url;
|
const vmModule = wrapToModuleMap.get(wrap);
|
||||||
|
if (vmModule === undefined) {
|
||||||
|
// This ModuleWrap belongs to the Loader.
|
||||||
|
meta.url = wrap.url;
|
||||||
|
} else {
|
||||||
|
const initializeImportMeta = initImportMetaMap.get(vmModule);
|
||||||
|
if (initializeImportMeta !== undefined) {
|
||||||
|
// This ModuleWrap belongs to vm.Module, initializer callback was
|
||||||
|
// provided.
|
||||||
|
initializeImportMeta(meta, vmModule);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let loaderResolve;
|
let loaderResolve;
|
||||||
|
@ -43,6 +43,10 @@ const perContextModuleId = new WeakMap();
|
|||||||
const wrapMap = new WeakMap();
|
const wrapMap = new WeakMap();
|
||||||
const dependencyCacheMap = new WeakMap();
|
const dependencyCacheMap = new WeakMap();
|
||||||
const linkingStatusMap = new WeakMap();
|
const linkingStatusMap = new WeakMap();
|
||||||
|
// vm.Module -> function
|
||||||
|
const initImportMetaMap = new WeakMap();
|
||||||
|
// ModuleWrap -> vm.Module
|
||||||
|
const wrapToModuleMap = new WeakMap();
|
||||||
|
|
||||||
class Module {
|
class Module {
|
||||||
constructor(src, options = {}) {
|
constructor(src, options = {}) {
|
||||||
@ -80,6 +84,16 @@ class Module {
|
|||||||
perContextModuleId.set(context, 1);
|
perContextModuleId.set(context, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (options.initializeImportMeta !== undefined) {
|
||||||
|
if (typeof options.initializeImportMeta === 'function') {
|
||||||
|
initImportMetaMap.set(this, options.initializeImportMeta);
|
||||||
|
} else {
|
||||||
|
throw new ERR_INVALID_ARG_TYPE(
|
||||||
|
'options.initializeImportMeta', 'function',
|
||||||
|
options.initializeImportMeta);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const wrap = new ModuleWrap(src, url, {
|
const wrap = new ModuleWrap(src, url, {
|
||||||
[kParsingContext]: context,
|
[kParsingContext]: context,
|
||||||
lineOffset: options.lineOffset,
|
lineOffset: options.lineOffset,
|
||||||
@ -88,6 +102,7 @@ class Module {
|
|||||||
|
|
||||||
wrapMap.set(this, wrap);
|
wrapMap.set(this, wrap);
|
||||||
linkingStatusMap.set(this, 'unlinked');
|
linkingStatusMap.set(this, 'unlinked');
|
||||||
|
wrapToModuleMap.set(wrap, this);
|
||||||
|
|
||||||
Object.defineProperties(this, {
|
Object.defineProperties(this, {
|
||||||
url: { value: url, enumerable: true },
|
url: { value: url, enumerable: true },
|
||||||
@ -206,5 +221,7 @@ class Module {
|
|||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
Module
|
Module,
|
||||||
|
initImportMetaMap,
|
||||||
|
wrapToModuleMap
|
||||||
};
|
};
|
||||||
|
45
test/parallel/test-vm-module-import-meta.js
Normal file
45
test/parallel/test-vm-module-import-meta.js
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
// Flags: --experimental-vm-modules --harmony-import-meta
|
||||||
|
|
||||||
|
const common = require('../common');
|
||||||
|
const assert = require('assert');
|
||||||
|
const { Module } = require('vm');
|
||||||
|
|
||||||
|
common.crashOnUnhandledRejection();
|
||||||
|
|
||||||
|
async function testBasic() {
|
||||||
|
const m = new Module('import.meta;', {
|
||||||
|
initializeImportMeta: common.mustCall((meta, module) => {
|
||||||
|
assert.strictEqual(module, m);
|
||||||
|
meta.prop = 42;
|
||||||
|
})
|
||||||
|
});
|
||||||
|
await m.link(common.mustNotCall());
|
||||||
|
m.instantiate();
|
||||||
|
const { result } = await m.evaluate();
|
||||||
|
assert.strictEqual(typeof result, 'object');
|
||||||
|
assert.strictEqual(Object.getPrototypeOf(result), null);
|
||||||
|
assert.strictEqual(result.prop, 42);
|
||||||
|
assert.deepStrictEqual(Reflect.ownKeys(result), ['prop']);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function testInvalid() {
|
||||||
|
for (const invalidValue of [
|
||||||
|
null, {}, 0, Symbol.iterator, [], 'string', false
|
||||||
|
]) {
|
||||||
|
common.expectsError(() => {
|
||||||
|
new Module('', {
|
||||||
|
initializeImportMeta: invalidValue
|
||||||
|
});
|
||||||
|
}, {
|
||||||
|
code: 'ERR_INVALID_ARG_TYPE',
|
||||||
|
type: TypeError
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
(async () => {
|
||||||
|
await testBasic();
|
||||||
|
await testInvalid();
|
||||||
|
})();
|
Loading…
x
Reference in New Issue
Block a user