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.
|
||||
* `columnOffset` {integer} Spcifies the column number offset that is displayed
|
||||
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.
|
||||
|
||||
*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
|
||||
|
||||
* {string[]}
|
||||
|
@ -108,10 +108,13 @@
|
||||
'DeprecationWarning', 'DEP0062', startup, true);
|
||||
}
|
||||
|
||||
if (process.binding('config').experimentalModules) {
|
||||
process.emitWarning(
|
||||
'The ESM module loader is experimental.',
|
||||
'ExperimentalWarning', undefined);
|
||||
if (process.binding('config').experimentalModules ||
|
||||
process.binding('config').experimentalVMModules) {
|
||||
if (process.binding('config').experimentalModules) {
|
||||
process.emitWarning(
|
||||
'The ESM module loader is experimental.',
|
||||
'ExperimentalWarning', undefined);
|
||||
}
|
||||
NativeModule.require('internal/process/esm_loader').setup();
|
||||
}
|
||||
|
||||
|
@ -10,6 +10,10 @@ const { getURLFromFilePath } = require('internal/url');
|
||||
const Loader = require('internal/modules/esm/loader');
|
||||
const path = require('path');
|
||||
const { URL } = require('url');
|
||||
const {
|
||||
initImportMetaMap,
|
||||
wrapToModuleMap
|
||||
} = require('internal/vm/module');
|
||||
|
||||
function normalizeReferrerURL(referrer) {
|
||||
if (typeof referrer === 'string' && path.isAbsolute(referrer)) {
|
||||
@ -19,7 +23,18 @@ function normalizeReferrerURL(referrer) {
|
||||
}
|
||||
|
||||
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;
|
||||
|
@ -43,6 +43,10 @@ const perContextModuleId = new WeakMap();
|
||||
const wrapMap = new WeakMap();
|
||||
const dependencyCacheMap = new WeakMap();
|
||||
const linkingStatusMap = new WeakMap();
|
||||
// vm.Module -> function
|
||||
const initImportMetaMap = new WeakMap();
|
||||
// ModuleWrap -> vm.Module
|
||||
const wrapToModuleMap = new WeakMap();
|
||||
|
||||
class Module {
|
||||
constructor(src, options = {}) {
|
||||
@ -80,6 +84,16 @@ class Module {
|
||||
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, {
|
||||
[kParsingContext]: context,
|
||||
lineOffset: options.lineOffset,
|
||||
@ -88,6 +102,7 @@ class Module {
|
||||
|
||||
wrapMap.set(this, wrap);
|
||||
linkingStatusMap.set(this, 'unlinked');
|
||||
wrapToModuleMap.set(wrap, this);
|
||||
|
||||
Object.defineProperties(this, {
|
||||
url: { value: url, enumerable: true },
|
||||
@ -206,5 +221,7 @@ class Module {
|
||||
}
|
||||
|
||||
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