Skip to content
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

vm: add support for import.meta to Module #19277

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions doc/api/vm.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i think people are already used to this just from using vm, might not need this example

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I would prefer to keep the example for now.

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[]}
Expand Down
11 changes: 7 additions & 4 deletions lib/internal/bootstrap/node.js
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}

Expand Down
17 changes: 16 additions & 1 deletion lib/internal/process/esm_loader.js
Original file line number Diff line number Diff line change
Expand Up @@ -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)) {
Expand All @@ -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;
Expand Down
19 changes: 18 additions & 1 deletion lib/internal/vm/module.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {}) {
Expand Down Expand Up @@ -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,
Expand All @@ -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 },
Expand Down Expand Up @@ -206,5 +221,7 @@ class Module {
}

module.exports = {
Module
Module,
initImportMetaMap,
wrapToModuleMap
};
45 changes: 45 additions & 0 deletions test/parallel/test-vm-module-import-meta.js
Original file line number Diff line number Diff line change
@@ -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();
})();