VM(执行 JavaScript)
History
稳定性:2 - 稳定
node:vm 模块允许在 V8 虚拟机上下文中编译和运行代码。
node:vm 模块不是安全机制。不要用它运行不可信代码。
JavaScript 代码可以立即编译和运行,也可以编译、保存并在以后运行。
一个常见的用例是在不同的 V8 上下文中运行代码。这意味着被调用的代码拥有与调用代码不同的全局对象。
可以通过[上下文化的][contextified] 对象来提供上下文。被调用的代码将上下文中的任何属性视为全局变量。被调用代码引起的全局变量的任何更改都会反映在上下文对象中。
import { createContext, runInContext } from 'node:vm';
const x = 1;
const context = { x: 2 };
createContext(context); // 将对象上下文化。
const code = 'x += 40; var y = 17;';
// `x` 和 `y` 是上下文中的全局变量。
// 最初,x 的值为 2,因为那是 context.x 的值。
runInContext(code, context);
console.log(context.x); // 42
console.log(context.y); // 17
console.log(x); // 1; y 未定义类:vm.Script
History
vm.Script 类的实例包含可以在特定上下文中执行的预编译脚本。
vm.Script Constructor
History
Added support forvm.constants.USE_MAIN_CONTEXT_DEFAULT_LOADER.
Added support for import attributes to theimportModuleDynamically parameter.
The produceCachedData is deprecated in favour ofscript.createCachedData().
The cachedData and produceCachedData options aresupported now.
new vm.Script(code, options?): void<string><string>'evalmachine.<anonymous>'
。<number>0
。<number>0
。<TypedArray>
|
<DataView>Buffer
或
TypedArray
,或
DataView
,其中包含所提供源代码的 V8 代码缓存数据。当提供时,
cachedDataRejected
值将根据 V8 是否接受数据设置为
true
或
false
。<boolean>true
且不存在
cachedData
时,V8 将尝试为
code
生成代码缓存数据。成功后,将生成一个包含 V8 代码缓存数据的
Buffer
并存储在返回的
vm.Script
实例的
cachedData
属性中。
cachedDataProduced
值将根据是否成功生成代码缓存数据设置为
true
或
false
。此选项已
弃用
,推荐使用
script.createCachedData()
。
默认值:
false
。<Function>
|
<vm.constants.USE_MAIN_CONTEXT_DEFAULT_LOADER>import()
时应如何加载模块。此选项是实验性模块 API 的一部分。我们不建议在生产环境中使用它。详细信息请参阅
编译 API 中对动态 import() 的支持
。如果 options 是字符串,则它指定文件名。
创建新的 vm.Script 对象会编译 code 但不会运行它。编译后的 vm.Script 可以稍后多次运行。code 不绑定到任何全局对象;相反,它在每次运行之前绑定,仅针对该次运行。
- 类型:
<boolean>|<undefined>
当提供 cachedData 创建 vm.Script 时,此值将根据 V8 是否接受数据设置为 true 或 false。否则值为 undefined。
script.createCachedData(): void- 返回:{Buffer}
创建一个代码缓存,可与 Script 构造函数的 cachedData 选项一起使用。返回一个 Buffer。此方法可以随时调用任意次数。
Script 的代码缓存不包含任何 JavaScript 可观察状态。代码缓存可以安全地与脚本源一起保存,并用于多次构造新的 Script 实例。
Script 源中的函数可以标记为延迟编译,它们在 Script 构造时不会编译。这些函数将在第一次被调用时编译。代码缓存序列化 V8 当前知道的关于 Script 的元数据,它可用于加速未来的编译。
const script = new vm.Script(`
function add(a, b) {
return a + b;
}
const x = add(1, 2);
`);
const cacheWithoutAdd = script.createCachedData();
// 在 `cacheWithoutAdd` 中,函数 `add()` 被标记为在调用时进行完整编译。
script.runInThisContext();
const cacheWithAdd = script.createCachedData();
// `cacheWithAdd` 包含完全编译的函数 `add()`。script.runInContext(contextifiedObject, options?): void在给定的 contextifiedObject 中运行 vm.Script 对象包含的编译代码并返回结果。运行的代码无法访问局部作用域。
以下示例编译了递增全局变量、设置另一个全局变量值的代码,然后多次执行该代码。全局变量包含在 context 对象中。
import { createContext, Script } from 'node:vm';
const context = {
animal: 'cat',
count: 2,
};
const script = new Script('count += 1; name = "kitty";');
createContext(context);
for (let i = 0; i < 10; ++i) {
script.runInContext(context);
}
console.log(context);
// 打印:{ animal: 'cat', count: 12, name: 'kitty' }使用 timeout 或 breakOnSigint 选项将导致新的事件循环和相应的线程启动,这会产生非零的性能开销。
script.runInNewContext(contextObject?, options?): void<Object>
|
<vm.constants.DONT_CONTEXTIFY>
|
<undefined><Object><boolean>true
,接收
SIGINT
(
Ctrl
+
C
) 将终止执行并抛出
Error
。在此期间,通过
process.on('SIGINT')
附加的现有事件处理程序将被禁用,但在之后继续工作。
默认值:
false
。<string>'VM Context i'
,其中
i
是创建上下文的递增数字索引。<string>URL
对象的
url.origin
属性的值一样。最值得注意的是,此字符串应省略尾随斜杠,因为它表示路径。
默认值:
''
。<Object><string>afterEvaluate
,微任务(通过
Promise
和
async function
调度的任务)将在脚本运行后立即运行。在这种情况下,它们包含在
timeout
和
breakOnSigint
作用域中。<any>
脚本中执行的最后一条语句的结果。此方法是 script.runInContext(vm.createContext(options), options) 的快捷方式。它同时执行以下几件事:
- 创建新上下文。
- 如果
contextObject是对象,则使用新上下文对其进行 [上下文化][contextified]。如果contextObject是 undefined,则创建新对象并对其进行 [上下文化][contextified]。如果contextObject是vm.constants.DONT_CONTEXTIFY,则不进行任何 [上下文化][contextified]。 - 在创建的上下文中运行
vm.Script对象包含的编译代码。代码无法访问调用此方法的作用域。 - 返回结果。
以下示例编译了设置全局变量的代码,然后在不同上下文中多次执行该代码。全局变量设置在每个独立的 context 上并包含在其中。
import { constants, Script } from 'node:vm';
const script = new Script('globalVar = "set"');
const contexts = [{}, {}, {}];
contexts.forEach((context) => {
script.runInNewContext(context);
});
console.log(contexts);
// 打印:[{ globalVar: 'set' }, { globalVar: 'set' }, { globalVar: 'set' }]
// 如果上下文是从上下文化对象创建的,这将抛出错误。
// constants.DONT_CONTEXTIFY 允许创建具有可冻结的普通
// 全局对象的上下文。
const freezeScript = new Script('Object.freeze(globalThis); globalThis;');
const frozenContext = freezeScript.runInNewContext(constants.DONT_CONTEXTIFY);script.runInThisContext
History
The breakOnSigint option is supported now.
script.runInThisContext(options?): void在当前 global 对象的上下文中运行 vm.Script 包含的编译代码。运行的代码无法访问局部作用域,但_可以_ 访问当前 global 对象。
以下示例编译了递增 global 变量的代码,然后多次执行该代码:
import { Script } from 'node:vm';
global.globalVar = 0;
const script = new Script('globalVar += 1', { filename: 'myfile.vm' });
for (let i = 0; i < 1000; ++i) {
script.runInThisContext();
}
console.log(globalVar);
// 1000- 类型:
<string>|<undefined>
当脚本从包含 source map 魔法注释的源编译时,此属性将设置为 source map 的 URL。
import vm from 'node:vm';
const script = new vm.Script(`
function myFunc() {}
//# sourceMappingURL=sourcemap.json
`);
console.log(script.sourceMapURL);
// 打印:sourcemap.json类:vm.Module
History
稳定性:1 - 实验性
此功能仅在启用 --experimental-vm-modules 命令行标志时可用。
vm.Module 类提供了一个底层接口,用于在 VM 上下文中使用 ECMAScript 模块。它是 vm.Script 类的对应类,紧密镜像了 ECMAScript 规范中定义的 模块记录。
然而,与 vm.Script 不同的是,每个 vm.Module 对象在创建时就绑定到一个上下文。
使用 vm.Module 对象需要三个不同的步骤:创建/解析、链接和求值。这三个步骤在下面的示例中说明。
此实现位于 ECMAScript 模块加载器 的更低层级。目前也没有方法与加载器交互,尽管计划支持。
import vm from 'node:vm';
const contextifiedObject = vm.createContext({
secret: 42,
print: console.log,
});
// 步骤 1
//
// 通过构造一个新的 `vm.SourceTextModule` 对象来创建一个模块。这会
// 解析提供的源代码文本,如果出错则抛出 `SyntaxError`。默认情况下,模块是在顶层上下文中创建的。
// 但在这里,我们指定 `contextifiedObject` 作为此模块所属的上下文。
//
// 在这里,我们尝试从模块 "foo" 获取默认导出,
// 并将其放入本地绑定 "secret" 中。
const rootModule = new vm.SourceTextModule(`
import s from 'foo';
s;
print(s);
`, { context: contextifiedObject });
// 步骤 2
//
// 将此模块的导入依赖“链接”到它。
//
// 通过 `sourceTextModule.moduleRequests` 获取 SourceTextModule 请求的依赖
// 并解析它们。
//
// 即使是没有任何依赖的顶层模块也必须显式链接。然而,
// 传递给 `sourceTextModule.linkRequests(modules)` 的数组
// 可以为空。
//
// 注意:这是一个人为的例子,因为 resolveAndLinkDependencies
// 每次被调用时都会创建一个新的 "foo" 模块。在一个成熟的
// 模块系统中,可能会使用缓存来避免重复的模块。
const moduleMap = new Map([
['root', rootModule],
]);
function resolveAndLinkDependencies(module) {
const requestedModules = module.moduleRequests.map((request) => {
// 在一个成熟的模块系统中,resolveAndLinkDependencies 会
// 使用模块缓存键 `[specifier, attributes]` 来解析模块。
// 在这个例子中,我们只使用说明符作为键。
const specifier = request.specifier;
let requestedModule = moduleMap.get(specifier);
if (requestedModule === undefined) {
requestedModule = new vm.SourceTextModule(`
// "secret" 变量指的是我们在创建上下文时添加到
// "contextifiedObject" 的全局变量。
export default secret;
`, { context: module.context });
moduleMap.set(specifier, requestedModule);
// 同样解析新模块的依赖。
resolveAndLinkDependencies(requestedModule);
}
return requestedModule;
});
module.linkRequests(requestedModules);
}
resolveAndLinkDependencies(rootModule);
rootModule.instantiate();
// 步骤 3
//
// 求值模块。evaluate() 方法返回一个 promise,该 promise 将在
// 模块完成求值后兑现。
// 打印 42。
await rootModule.evaluate();- 类型:
<any>
如果 module.status 是 'errored',此属性包含模块在求值期间抛出的异常。如果状态是其他任何值,
访问此属性将导致抛出异常。
对于没有抛出异常的情况,不能使用值 undefined,因为可能与 throw undefined; 产生歧义。
对应于 ECMAScript 规范中 循环模块记录 的 [[EvaluationError]] 字段。
module.evaluate(options?): void求值模块及其依赖。对应于 ECMAScript 规范中 循环模块记录 的 Evaluate() 具体方法 字段。
如果模块是 vm.SourceTextModule,则必须在模块实例化后调用 evaluate();
否则 evaluate() 将返回一个被拒绝的 promise。
对于 vm.SourceTextModule,evaluate() 返回的 promise 可以
同步或异步地兑现:
- 如果
vm.SourceTextModule本身或其任何依赖中没有顶层await,则 promise 将在 模块及其所有依赖求值后_同步_兑现。- 如果求值成功,promise 将_同步_解决为
undefined。 - 如果求值导致异常,promise 将_同步_拒绝,拒绝原因是导致求值失败的异常,
这与
module.error相同。
- 如果求值成功,promise 将_同步_解决为
- 如果
vm.SourceTextModule本身或其任何依赖中有顶层await,则 promise 将在 模块及其所有依赖求值后_异步_兑现。- 如果求值成功,promise 将_异步_解决为
undefined。 - 如果求值导致异常,promise 将_异步_拒绝,拒绝原因是导致求值失败的异常。
- 如果求值成功,promise 将_异步_解决为
如果模块是 vm.SyntheticModule,evaluate() 总是返回一个同步兑现的 promise,参见
合成模块记录的 Evaluate() 规范:
- 如果传递给其构造函数的
evaluateCallback同步抛出异常,evaluate()返回 一个将同步拒绝该异常的 promise。 - 如果
evaluateCallback没有抛出异常,evaluate()返回一个将 同步解决为undefined的 promise。
vm.SyntheticModule 的 evaluateCallback 在 evaluate() 调用内同步执行,其
返回值被丢弃。这意味着如果 evaluateCallback 是一个异步函数,evaluate() 返回的 promise 将
不会反映其异步行为,并且来自异步
evaluateCallback 的任何拒绝都将丢失。
evaluate() 也可以在模块已经求值后再次调用,在这种情况下:
- 如果初始求值成功结束(
module.status是'evaluated'),它将什么都不做 并返回一个解决为undefined的 promise。 - 如果初始求值导致异常(
module.status是'errored'),它将重新拒绝 初始求值导致的异常。
当模块正在求值时(module.status 是 'evaluating'),不能调用此方法。
- 类型:
<string>
当前模块的标识符,如在构造函数中设置的那样。
module.link(linker): void<Function><string><vm.Module>link()
的
Module
对象。<vm.Module>
|
<Promise><Promise>链接模块依赖。此方法必须在求值之前调用,并且 每个模块只能调用一次。
使用 sourceTextModule.linkRequests(modules) 和
sourceTextModule.instantiate() 来同步或异步地链接模块。
该函数预计返回一个 Module 对象或一个最终
解决为 Module 对象的 Promise。返回的 Module 必须满足以下
两个不变量:
- 它必须属于与父
Module相同的上下文。 - 其
status不能是'errored'。
如果返回的 Module 的 status 是 'unlinked',此方法将
使用提供的相同 linker 函数递归调用返回的 Module。
link() 返回一个 Promise,当所有链接实例解决为有效的 Module 时,该 promise 将得到解决,
或者如果链接器函数抛出异常或返回无效的 Module,则该 promise 将被拒绝。
链接器函数大致对应于 ECMAScript 规范中实现定义的 HostResolveImportedModule 抽象操作,但有一些关键区别:
- 链接器函数允许是异步的,而 HostResolveImportedModule 是同步的。
模块链接期间使用的实际 HostResolveImportedModule 实现是返回链接期间链接的模块的那个。因为到 那时所有模块都已经完全链接,所以 HostResolveImportedModule 实现根据规范是完全同步的。
对应于 ECMAScript 规范中 循环模块记录 的 Link() 具体方法 字段。
- 类型:
<Object>
模块的命名空间对象。这仅在链接
(module.link())完成后可用。
对应于 ECMAScript 规范中的 GetModuleNamespace 抽象操作。
- 类型:
<string>
模块的当前状态。将是以下之一:
-
'unlinked':尚未调用module.link()。 -
'linking':已调用module.link(),但链接器函数返回的所有 Promise 尚未 解决。 -
'linked':模块已成功链接,并且其所有 依赖已链接,但尚未调用module.evaluate()。 -
'evaluating':模块正在通过其自身或父模块上的module.evaluate()进行求值。 -
'evaluated':模块已成功求值。 -
'errored':模块已求值,但抛出了异常。
除了 'errored' 之外,此状态字符串对应于规范的
循环模块记录 的 [[Status]] 字段。'errored' 对应于
规范中的 'evaluated',但 [[EvaluationError]] 设置为
不为 undefined 的值。
类:vm.SourceTextModule
History
稳定性:1 - 实验性
此功能仅在启用 --experimental-vm-modules 命令行标志时可用。
- 继承自:
<vm.Module>
vm.SourceTextModule 类提供了 ECMAScript 规范中定义的 [源代码模块记录][]。
new vm.SourceTextModule(code, options?): void<string><string>'vm:module(i)'
,其中
i
是特定于上下文的递增索引。<TypedArray>
|
<DataView>Buffer
或
TypedArray
或
DataView
,包含 V8 针对所提供源代码的代码缓存数据。
code
必须与创建此
cachedData
的模块相同。<integer>Module
生成的堆栈轨迹中显示的行号偏移量。
默认值:
0
。<integer>Module
生成的堆栈轨迹中显示的第一行列号偏移量。
默认值:
0
。<Function>Module
求值期间调用,以初始化
import.meta
。<import.meta><vm.SourceTextModule><Function>import()
时应如何加载模块。此选项是实验性模块 API 的一部分。我们不建议在生产环境中使用它。详细信息请参阅
编译 API 中对动态 import() 的支持
。创建一个新的 SourceTextModule 实例。
分配给 import.meta 对象的属性如果是对象,可能会允许模块访问指定 context 之外的信息。使用 vm.runInContext() 在特定上下文中创建对象。
import vm from 'node:vm';
const contextifiedObject = vm.createContext({ secret: 42 });
const module = new vm.SourceTextModule(
'Object.getPrototypeOf(import.meta.prop).secret = secret;',
{
initializeImportMeta(meta) {
// 注意:此对象是在顶层上下文中创建的。因此,
// Object.getPrototypeOf(import.meta.prop) 指向
// 顶层上下文中的 Object.prototype,而不是
// 上下文化对象中的那个。
meta.prop = {};
},
});
// 该模块有一个空的 `moduleRequests` 数组。
module.linkRequests([]);
module.instantiate();
await module.evaluate();
// 现在,Object.prototype.secret 将等于 42。
//
// 要解决此问题,请将
// meta.prop = {};
// 上方代码替换为
// meta.prop = vm.runInContext('{}', contextifiedObject);sourceTextModule.createCachedData(): void- 返回:{Buffer}
创建一个代码缓存,可与 SourceTextModule 构造函数的 cachedData 选项一起使用。返回一个 Buffer。此方法可以在模块求值之前调用任意次数。
SourceTextModule 的代码缓存不包含任何 JavaScript 可观察状态。代码缓存可以安全地与脚本源代码一起保存,并用于多次构造新的 SourceTextModule 实例。
SourceTextModule 源代码中的函数可以标记为延迟编译,它们在 SourceTextModule 构造时不会编译。这些函数将在首次调用时编译。代码缓存序列化 V8 当前了解的关于 SourceTextModule 的元数据,可用于加速未来的编译。
// 创建一个初始模块
const module = new vm.SourceTextModule('const a = 1;');
// 从此模块创建缓存数据
const cachedData = module.createCachedData();
// 使用缓存数据创建一个新模块。代码必须相同。
const module2 = new vm.SourceTextModule('const a = 1;', { cachedData });稳定性:0 - 已弃用:请改用
sourceTextModule.moduleRequests。
- 类型:
<string[]>
此模块所有依赖项的说明符。返回的数组被冻结,不允许对其进行任何更改。
对应于 ECMAScript 规范中 循环模块记录 的 [[RequestedModules]] 字段。
sourceTextModule.hasAsyncGraph(): void- 返回:
<boolean>
遍历依赖图,如果其依赖项中的任何模块或此模块本身包含顶层 await 表达式,则返回 true,否则返回 false。
如果图足够大,搜索可能会很慢。
这要求模块首先被实例化。如果模块尚未实例化,将抛出错误。
sourceTextModule.hasTopLevelAwait(): void- 返回:
<boolean>
返回模块本身是否包含任何顶层 await 表达式。
这对应于 ECMAScript 规范中 循环模块记录 的 [[HasTLA]] 字段。
sourceTextModule.instantiate(): void- 返回:
<undefined>
使用链接的请求模块实例化模块。
这会解析模块的导入绑定,包括重新导出的绑定名称。当存在任何无法解析的绑定时,将同步抛出错误。
如果请求的模块包括循环依赖,则在调用此方法之前,必须在循环中的所有模块上调用 sourceTextModule.linkRequests(modules) 方法。
sourceTextModule.linkRequests(modules): void<vm.Module[]>vm.Module
对象数组。
数组中模块的顺序是
sourceTextModule.moduleRequests
的顺序。<undefined>链接模块依赖项。此方法必须在求值之前调用,且每个模块只能调用一次。
modules 数组中模块实例的顺序应与 sourceTextModule.moduleRequests 被解析的顺序相对应。如果两个模块请求具有相同的说明符和导入属性,它们必须解析为同一个模块实例,否则将抛出 ERR_MODULE_LINK_MISMATCH。例如,当链接此模块的请求时:
import foo from 'foo';
import source Foo from 'foo';modules 数组必须包含对同一个实例的两个引用,因为这两个模块请求是相同的,但处于两个阶段中。
如果模块没有依赖项,modules 数组可以为空。
用户可以使用 sourceTextModule.moduleRequests 实现 ECMAScript 规范中宿主定义的 HostLoadImportedModule 抽象操作,并使用 sourceTextModule.linkRequests() 在模块上批量调用规范定义的 FinishLoadingImportedModule。
依赖项的解析是同步还是异步,由 SourceTextModule 的创建者决定。
在 modules 数组中的每个模块链接后,调用 sourceTextModule.instantiate()。
- 类型:{ModuleRequest[]} 此模块的依赖项。
此模块请求的导入依赖项。返回的数组被冻结,不允许对其进行任何更改。
例如,给定源代码文本:
import foo from 'foo';
import fooAlias from 'foo';
import bar from './bar.js';
import withAttrs from '../with-attrs.ts' with { arbitraryAttr: 'attr-val' };
import source Module from 'wasm-mod.wasm';sourceTextModule.moduleRequests 的值将是:
[
{
specifier: 'foo',
attributes: {},
phase: 'evaluation',
},
{
specifier: 'foo',
attributes: {},
phase: 'evaluation',
},
{
specifier: './bar.js',
attributes: {},
phase: 'evaluation',
},
{
specifier: '../with-attrs.ts',
attributes: { arbitraryAttr: 'attr-val' },
phase: 'evaluation',
},
{
specifier: 'wasm-mod.wasm',
attributes: {},
phase: 'source',
},
];类:vm.SyntheticModule
History
稳定性:1 - 实验性
此功能仅在启用 --experimental-vm-modules 命令行标志时可用。
- 继承自:
<vm.Module>
vm.SyntheticModule 类提供了 WebIDL 规范中定义的 合成模块记录。合成模块的目的是提供一个通用接口,用于将非 JavaScript 源暴露给 ECMAScript 模块图。
import { SyntheticModule } from 'node:vm';
const source = '{ "a": 1 }';
const syntheticModule = new SyntheticModule(['default'], function() {
const obj = JSON.parse(source);
this.setExport('default', obj);
});
// 在链接中使用 `syntheticModule`
(async () => {
await syntheticModule.link(() => {});
await syntheticModule.evaluate();
console.log('默认导出:', syntheticModule.namespace.default);
})();new vm.SyntheticModule(exportNames, evaluateCallback, options?): void<string[]><Function>创建一个新的 SyntheticModule 实例。
分配给此实例导出的对象可能会允许模块的导入者访问指定 context 之外的信息。使用 vm.runInContext() 在特定上下文中创建对象。
syntheticModule.setExport
History
调用此方法前不再需要调用 syntheticModule.link()。
syntheticModule.setExport(name, value): void此方法使用给定值设置模块导出绑定槽。
import vm from 'node:vm';
const m = new vm.SyntheticModule(['x'], () => {
m.setExport('x', 1);
});
await m.evaluate();
assert.strictEqual(m.namespace.x, 1);类型:ModuleRequest
History
- 类型:
<Object>Attributes
ModuleRequest 表示使用给定导入属性和阶段导入模块的请求。
vm.compileFunction
History
添加了对 vm.constants.USE_MAIN_CONTEXT_DEFAULT_LOADER 的支持。
如果传入了 cachedData 选项,返回值现在包含 cachedDataRejected,其语义与 vm.Script 版本相同。
为 importModuleDynamically 参数添加了导入属性支持。
再次添加了 importModuleDynamically 选项。
由于兼容性问题移除了 importModuleDynamically。
现在支持 importModuleDynamically 选项。
vm.compileFunction(code, params?, options?): void<string><string[]><Object><string>''
。<number>0
。<number>0
。<TypedArray>
|
<DataView>Buffer
或
TypedArray
或
DataView
,包含 V8 对提供的源代码的代码缓存数据。
这必须由先前使用相同
code
和
params
调用
vm.compileFunction()
生成。<boolean>false
。<Object><Object[]>[]
。<Function>
|
<vm.constants.USE_MAIN_CONTEXT_DEFAULT_LOADER>import()
时应如何加载模块。此选项是实验性模块 API 的一部分。
我们不建议在生产环境中使用它。详细信息请参阅
编译 API 中对动态 import() 的支持
。<Function>将给定代码编译到提供的上下文中(如果未提供上下文,则使用当前上下文),
并将其包装在具有给定 params 的函数内返回。
- 类型:
<Object>
返回一个包含 VM 操作常用常量的对象。
稳定性:1.1 - 积极开发中
一个常量,可用作 vm.Script 和 vm.compileFunction() 的 importModuleDynamically 选项,
以便 Node.js 使用主上下文的默认 ESM 加载器来加载请求的模块。
详细信息请参阅
编译 API 中对动态 import() 的支持。
vm.createContext
History
The contextObject argument now accepts vm.constants.DONT_CONTEXTIFY.
Added support forvm.constants.USE_MAIN_CONTEXT_DEFAULT_LOADER.
The importModuleDynamically option is supported now.
The microtaskMode option is supported now.
The first argument can no longer be a function.
The codeGeneration option is supported now.
vm.createContext(contextObject?, options?): void<Object>
|
<vm.constants.DONT_CONTEXTIFY>
|
<undefined>vm.constants.DONT_CONTEXTIFY
,要么是将要 [上下文化][contextified] 的对象。
如果为
undefined
,则将创建一个空的上下文化对象以保持向后兼容性。<Object><string>'VM Context i'
,其中
i
是
所创建上下文的递增数字索引。<string>URL
对象的
url.origin
属性的值一样。
最值得注意的是,此字符串应省略尾随斜杠,因为它表示路径。
默认值:
''
。<Object><string>afterEvaluate
,微任务(通过
Promise
和
async function
调度的任务)
将在脚本通过
script.runInContext()
运行后立即运行。
在这种情况下,它们包含在
timeout
和
breakOnSigint
作用域中。<Function>
|
<vm.constants.USE_MAIN_CONTEXT_DEFAULT_LOADER>import()
且没有引用者脚本或模块时应如何加载模块。此选项是
实验性模块 API 的一部分。我们不建议在生产环境中使用它。详细信息请参阅
编译 API 中对动态 import() 的支持
。<Object>
上下文化对象。如果给定的 contextObject 是一个对象,vm.createContext() 方法将 [准备该对象][contextified]
并返回对它的引用,以便它可用于 vm.runInContext() 或 script.runInContext() 的调用。
在此类脚本内部,全局对象将被 contextObject 包装,保留其所有现有属性,
同时还拥有任何标准 全局对象 具有的内置对象和函数。在由 vm 模块运行的脚本之外,
全局变量将保持不变。
import { createContext, runInContext } from 'node:vm';
global.globalVar = 3;
const context = { globalVar: 1 };
createContext(context);
runInContext('globalVar *= 2;', context);
console.log(context);
// 打印:{ globalVar: 2 }
console.log(global.globalVar);
// 打印:3如果省略 contextObject(或显式传递为 undefined),将返回一个新的、
空的 [上下文化][contextified] 对象。
当新创建上下文中的全局对象被 [上下文化][contextified] 时,与普通全局对象相比,它有一些怪癖。
例如,它不能被冻结。要创建没有上下文化怪癖的上下文,请将 vm.constants.DONT_CONTEXTIFY 作为 contextObject
参数传递。有关详细信息,请参阅 vm.constants.DONT_CONTEXTIFY 的文档。
vm.createContext() 方法主要用于创建单个上下文,可用于运行多个脚本。
例如,如果模拟 Web 浏览器,该方法可用于创建表示窗口全局对象的单个上下文,
然后在该上下文中一起运行所有 <script> 标签。
提供的上下文的 name 和 origin 通过 Inspector API 可见。
vm.isContext(object): void如果给定的 object 对象已使用 vm.createContext() 进行了 [上下文化][contextified],
或者它是使用 vm.constants.DONT_CONTEXTIFY 创建的上下文的全局对象,则返回 true。
vm.measureMemory(options?): void稳定性:1 - 实验性
测量 V8 已知且当前 V8 隔离区已知的所有上下文或主上下文使用的内存。
返回的 Promise 可能解析的对象格式特定于 V8 引擎,并且可能随 V8 版本的变化而变化。
返回的结果与 v8.getHeapSpaceStatistics() 返回的统计信息不同,vm.measureMemory() 测量
当前 V8 引擎实例中每个 V8 特定上下文可到达的内存,而 v8.getHeapSpaceStatistics() 的结果测量
当前 V8 实例中每个堆空间占用的内存。
import { createContext, measureMemory } from 'node:vm';
// 测量主上下文使用的内存。
measureMemory({ mode: 'summary' })
// 这与 vm.measureMemory() 相同
.then((result) => {
// 当前格式为:
// {
// total: { jsMemoryEstimate: 1601828, jsMemoryRange: [1601828, 5275288] },
// WebAssembly: { code: 0, metadata: 33962 },
// }
console.log(result);
});
const context = createContext({ a: 1 });
measureMemory({ mode: 'detailed', execution: 'eager' }).then((result) => {
// 在此处引用上下文,以便它不会被垃圾回收
// 直到测量完成。
console.log('Context:', context.a);
// {
// total: { jsMemoryEstimate: 1767100, jsMemoryRange: [1767100, 5440560] },
// WebAssembly: { code: 0, metadata: 33962 },
// current: { jsMemoryEstimate: 1601828, jsMemoryRange: [1601828, 5275288] },
// other: [{ jsMemoryEstimate: 165272, jsMemoryRange: [Array] }],
// }
console.log(result);
});vm.runInContext(code, contextifiedObject, options?): void<string><Object>code
时将用作
global
的 [上下文化][contextified] 对象。<string>'evalmachine.<anonymous>'
。<number>0
。<number>0
。<boolean>true
,接收
SIGINT
(
Ctrl
+
C
) 将终止执行并抛出
Error
。通过
process.on('SIGINT')
附加的现有事件处理程序在脚本执行期间被禁用,但在那之后继续工作。
默认值:
false
。<TypedArray>
|
<DataView>Buffer
或
TypedArray
,或
DataView
,包含 V8 针对所提供源代码的代码缓存数据。<Function>
|
<vm.constants.USE_MAIN_CONTEXT_DEFAULT_LOADER>import()
时,在此脚本评估期间应如何加载模块。此选项是实验性模块 API 的一部分。我们不建议在生产环境中使用它。详细信息请参阅
[编译 API 中对动态
import()
的支持][Support of dynamic
import()
in compilation APIs]。vm.runInContext() 方法编译 code,在 contextifiedObject 的上下文中运行它,然后返回结果。运行的代码无法访问本地作用域。contextifiedObject 对象 必须 之前已使用 vm.createContext() 方法进行了 [上下文化][contextified]。
如果 options 是字符串,则它指定文件名。
以下示例使用单个 [上下文化][contextified] 对象编译和执行不同的脚本:
import { createContext, runInContext } from 'node:vm';
const contextObject = { globalVar: 1 };
createContext(contextObject);
for (let i = 0; i < 10; ++i) {
runInContext('globalVar *= 2;', contextObject);
}
console.log(contextObject);
// 输出:{ globalVar: 1024 }vm.runInNewContext
History
contextObject 参数现在接受 vm.constants.DONT_CONTEXTIFY。
添加了对vm.constants.USE_MAIN_CONTEXT_DEFAULT_LOADER 的支持。
添加了对导入属性到importModuleDynamically 参数的支持。
现在支持 microtaskMode 选项。
现在支持 contextCodeGeneration 选项。
现在支持 breakOnSigint 选项。
vm.runInNewContext(code, contextObject?, options?): void<string><Object>
|
<vm.constants.DONT_CONTEXTIFY>
|
<undefined>vm.constants.DONT_CONTEXTIFY
,要么是将被 [上下文化][contextified] 的对象。
如果为
undefined
,将为向后兼容性创建一个空的上下文化对象。<string>'evalmachine.<anonymous>'
。<number>0
。<number>0
。<boolean>true
,接收
SIGINT
(
Ctrl
+
C
) 将终止执行并抛出
Error
。通过
process.on('SIGINT')
附加的现有事件处理程序在脚本执行期间被禁用,但在那之后继续工作。
默认值:
false
。<string>'VM Context i'
,其中
i
是创建上下文的递增数字索引。<string>URL
对象的
url.origin
属性的值一样。最值得注意的是,
此字符串应省略尾部斜杠,因为它表示路径。
默认值:
''
。<Object><TypedArray>
|
<DataView>Buffer
或
TypedArray
,或
DataView
,包含 V8 针对所提供源代码的代码缓存数据。<Function>
|
<vm.constants.USE_MAIN_CONTEXT_DEFAULT_LOADER>import()
时,在此脚本评估期间应如何加载模块。此选项是实验性模块 API 的一部分。我们不建议在生产环境中使用它。详细信息请参阅
[编译 API 中对动态
import()
的支持][Support of dynamic
import()
in compilation APIs]。<string>afterEvaluate
,微任务(通过
Promise
和
async function
调度的任务)将在脚本运行后立即运行。在这种情况下,它们包含在
timeout
和
breakOnSigint
作用域中。<any>
脚本中执行的最后一条语句的结果。此方法是
(new vm.Script(code, options)).runInContext(vm.createContext(options), options) 的快捷方式。
如果 options 是字符串,则它指定文件名。
它同时执行以下几件事:
- 创建新上下文。
- 如果
contextObject是对象,则使用新上下文对其进行 [上下文化][contextified]。 如果contextObject是 undefined,则创建新对象并对其进行 [上下文化][contextified]。 如果contextObject是vm.constants.DONT_CONTEXTIFY,则不对任何内容进行 [上下文化][contextified]。 - 将代码编译为
vm.Script - 在创建的上下文中运行编译后的代码。代码无法访问调用此方法的作用域。
- 返回结果。
以下示例编译并执行递增全局变量并设置新变量的代码。这些全局变量包含在 contextObject 中。
import { runInNewContext, constants } from 'node:vm';
const contextObject = {
animal: 'cat',
count: 2,
};
runInNewContext('count += 1; name = "kitty"', contextObject);
console.log(contextObject);
// 输出:{ animal: 'cat', count: 3, name: 'kitty' }
// 如果上下文是从上下文化对象创建的,这将抛出错误。
// vm.constants.DONT_CONTEXTIFY 允许使用普通全局对象创建上下文,这些对象
// 可以被冻结。
const frozenContext = runInNewContext(
'Object.freeze(globalThis); globalThis;',
constants.DONT_CONTEXTIFY,
);vm.runInThisContext(code, options?): void<string><string>'evalmachine.<anonymous>'
。<number>0
。<number>0
。<boolean>true
,接收
SIGINT
(
Ctrl
+
C
) 将终止执行并抛出
Error
。通过
process.on('SIGINT')
附加的现有事件处理程序在脚本执行期间被禁用,但在那之后继续工作。
默认值:
false
。<TypedArray>
|
<DataView>Buffer
或
TypedArray
,或
DataView
,包含所提供源代码的 V8 代码缓存数据。<Function>
|
<vm.constants.USE_MAIN_CONTEXT_DEFAULT_LOADER>import()
时应如何加载模块。此选项是实验性模块 API 的一部分。我们不建议在生产环境中使用它。详细信息,请参阅
编译 API 中对动态 import() 的支持
。<any>
脚本中执行的最后一条语句的结果。vm.runInThisContext() 编译 code,在当前 global 的上下文中运行它并返回结果。运行的代码无法访问局部作用域,但可以访问当前 global 对象。
如果 options 是字符串,则它指定文件名。
以下示例说明了同时使用 vm.runInThisContext() 和
JavaScript eval() 函数来运行相同的代码:
import { runInThisContext } from 'node:vm';
let localVar = 'initial value';
const vmResult = runInThisContext('localVar = "vm";');
console.log(`vmResult: '${vmResult}', localVar: '${localVar}'`);
// 打印:vmResult: 'vm', localVar: 'initial value'
const evalResult = eval('localVar = "eval";');
console.log(`evalResult: '${evalResult}', localVar: '${localVar}'`);
// 打印:evalResult: 'eval', localVar: 'eval'因为 vm.runInThisContext() 无法访问局部作用域,
所以 localVar 不变。相比之下,直接调用 eval() 确实 可以访问
局部作用域,因此 localVar 的值被更改。通过这种方式
vm.runInThisContext() 很像 间接 eval() 调用,例如
(0,eval)('code')。
当使用 script.runInThisContext() 或
vm.runInThisContext() 时,代码在当前 V8 全局
上下文中执行。传递给此 VM 上下文的代码将拥有自己隔离的作用域。
为了使用 node:http 模块运行一个简单的 Web 服务器,传递给
上下文的代码必须要么自行调用 require('node:http'),要么拥有
对 node:http 模块的引用传递给它。例如:
import { runInThisContext } from 'node:vm';
import { createRequire } from 'node:module';
const require = createRequire(import.meta.url);
const code = `
((require) => {
const { createServer } = require('node:http');
createServer((request, response) => {
response.writeHead(200, { 'Content-Type': 'text/plain' });
response.end('Hello World\\n');
}).listen(8124);
console.log('Server running at http://127.0.0.1:8124/');
})`;
runInThisContext(code)(require);上述情况中的 require() 与传入它的上下文共享状态。当执行不受信任的代码时,这可能会引入风险,例如以不希望的方式更改上下文中的对象。
在 Node.js 内执行的所有 JavaScript 都在“上下文”的作用域内运行。 根据 V8 嵌入者指南:
在 V8 中,上下文是一个执行环境,允许单独、不相关的 JavaScript 应用程序在单个 V8 实例中运行。你必须明确 指定希望任何 JavaScript 代码运行的上下文。
当调用方法 vm.createContext() 并传入一个对象时,contextObject 参数
将用于包装新的 V8 上下文实例的全局对象
(如果 contextObject 是 undefined,则在上下文化之前将从当前上下文
创建一个新对象)。此 V8 上下文为使用 node:vm
模块的方法运行的 code 提供了一个隔离的全局环境,使其可以在其中操作。
创建 V8 上下文并将其与外部上下文中的 contextObject
关联的过程,就是本文档所称的对象的“上下文化”。
上下文化会给上下文中的 globalThis 值引入一些怪癖。
例如,它不能被冻结,并且它与外部上下文中的 contextObject
引用不相等。
import { createContext, runInContext } from 'node:vm';
// 未定义的 `contextObject` 选项会使全局对象被上下文化。
const context = createContext();
console.log(runInContext('globalThis', context) === context); // false
// 上下文化的全局对象不能被冻结。
try {
runInContext('Object.freeze(globalThis);', context);
} catch (e) {
console.log(`${e.constructor.name}: ${e.message}`); // TypeError: Cannot freeze
}
console.log(runInContext('globalThis.foo = 1; foo;', context)); // 1要创建一个具有普通全局对象的上下文,并在外部上下文中访问具有较少怪癖的全局代理,请将 vm.constants.DONT_CONTEXTIFY 指定为
contextObject 参数。
此常量当用作 vm API 中的 contextObject 参数时,指示 Node.js 创建一个
上下文,而不以 Node.js 特定的方式用另一个对象包装其全局对象。
因此,新上下文内的 globalThis 值的行为将更接近普通对象。
import { createContext, runInContext, constants } from 'node:vm';
// 使用 vm.constants.DONT_CONTEXTIFY 来冻结全局对象。
const context = createContext(constants.DONT_CONTEXTIFY);
runInContext('Object.freeze(globalThis);', context);
try {
runInContext('bar = 1; bar;', context);
} catch (e) {
console.log(`${e.constructor.name}: ${e.message}`); // ReferenceError: bar is not defined
}当 vm.constants.DONT_CONTEXTIFY 用作 vm.createContext() 的 contextObject 参数时,
返回的对象是新创建上下文中全局对象的类似代理的对象,具有较少的 Node.js 特定怪癖。
它与新上下文中的 globalThis 值引用相等,
可以从上下文外部修改,并可用于直接访问新上下文中的内置对象。
import { createContext, runInContext, constants } from 'node:vm';
const context = createContext(constants.DONT_CONTEXTIFY);
// 返回的对象与新上下文中的 globalThis 引用相等。
console.log(runInContext('globalThis', context) === context); // true
// 可用于直接访问新上下文中的全局变量。
console.log(context.Array); // [Function: Array]
runInContext('foo = 1;', context);
console.log(context.foo); // 1
context.bar = 1;
console.log(runInContext('bar;', context)); // 1
// 可以被冻结,且会影响内部上下文。
Object.freeze(context);
try {
runInContext('baz = 1; baz;', context);
} catch (e) {
console.log(`${e.constructor.name}: ${e.message}`); // ReferenceError: baz is not defined
}Promise 和 async function 可以调度由 JavaScript
引擎异步运行的任务。默认情况下,这些任务在当前栈上的所有 JavaScript
函数执行完成后运行。
这使得可以绕过 timeout 和
breakOnSigint 选项的功能。
例如,以下由 vm.runInNewContext() 执行的代码设置了 5 毫秒的
超时,调度了一个无限循环在 promise
兑现后运行。调度的循环永远不会被超时中断:
import { runInNewContext } from 'node:vm';
function loop() {
console.log('entering loop');
while (1) console.log(Date.now());
}
runInNewContext(
'Promise.resolve().then(() => loop());',
{ loop, console },
{ timeout: 5 },
);
// 这行会在 'entering infinite loop' *之前* 打印 (!)
console.log('done executing');可以通过向创建 Context 的代码传递 microtaskMode: 'afterEvaluate' 来解决此问题:
import { runInNewContext } from 'node:vm';
function loop() {
while (1) console.log(Date.now());
}
runInNewContext(
'Promise.resolve().then(() => loop());',
{ loop, console },
{ timeout: 5, microtaskMode: 'afterEvaluate' },
);在这种情况下,通过 promise.then() 调度的微任务将在从 vm.runInNewContext() 返回之前运行,并将被
timeout 功能中断。这仅适用于在
vm.Context 中运行的代码,因此例如 vm.runInThisContext() 不接受此选项。
Promise 回调被输入到它们被创建时所在上下文的微任务队列中。例如,如果在上面的例子中 () => loop() 被替换为 loop,那么 loop 将被推入全局微任务
队列,因为它是来自外层(主)上下文的函数,因此也将
能够绕过超时。
如果异步调度函数如 process.nextTick()、
queueMicrotask()、setTimeout()、setImmediate() 等在
vm.Context 内部可用,传递给它们的函数将被添加到全局队列,
这些队列由所有上下文共享。因此,传递给这些函数的回调
也无法通过超时控制。
在 'afterEvaluate' 模式下,Context 拥有自己的微任务队列,与
外层(主)上下文使用的全局微任务队列分开。虽然此
模式对于强制执行 timeout 和启用 breakOnSigint 与
异步任务是必要的,但它也使得在上下文之间共享 promise 变得具有挑战性。
在下面的例子中,一个 promise 在内层上下文中创建并与
外层上下文共享。当外层上下文 await 该 promise 时,外层上下文的执行
流程以令人惊讶的方式被破坏:日志语句
永远不会执行。
import { createContext, runInContext } from 'node:vm';
const inner_context = createContext({}, { microtaskMode: 'afterEvaluate' });
// runInContext() 返回一个在内层上下文中创建的 Promise。
const inner_promise = runInContext('Promise.resolve()', inner_context);
// 作为执行 `await` 的一部分,JavaScript 运行时必须在创建 `inner_promise` 的
// 上下文的微任务队列上入队一个任务。
// 一个任务被添加到内层微任务队列中,但**它不会自动运行**:此任务将无限期保持挂起状态。
//
// 由于外层微任务队列为空,外层模块中的执行流程直接穿过,下方的日志语句永远不会执行。
await inner_promise;
console.log('this will NOT be printed');要在具有不同微任务队列的上下文之间成功共享 promise, 必须确保每当外层上下文在内层微任务队列上入队任务时,内层微任务队列上的任务都会运行。
给定上下文的微任务队列上的任务会在对该上下文使用的脚本或
模块调用 runInContext() 或 SourceTextModule.evaluate() 时运行。在我们的例子中,正常的执行流程可以通过在 await inner_promise 之前 调度第二次调用 runInContext() 来恢复。
// 调度 `runInContext()` 以手动排空内层上下文微任务
// 队列;它将在下面的 `await` 语句之后运行。
setImmediate(() => {
vm.runInContext('', context);
});
await inner_promise;
console.log('OK');注意: 严格来说,在此模式下,node:vm 偏离了 入队任务 的 ECMAScript 规范字面意思,允许来自不同上下文的异步任务以不同于它们入队的顺序运行。
以下 API 支持 importModuleDynamically 选项以启用由 vm 模块编译的代码中的动态
import()。
new vm.Scriptvm.compileFunction()new vm.SourceTextModulevm.runInThisContext()vm.runInContext()vm.runInNewContext()vm.createContext()
此选项仍然是实验性模块 API 的一部分。我们不建议 在生产环境中使用它。
如果未指定此选项,或者它为 undefined,包含
import() 的代码仍然可以由 vm API 编译,但当编译后的代码执行并实际调用 import() 时,结果将拒绝并抛出
ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING。
此选项目前不支持 vm.SourceTextModule。
使用此选项,当在编译后的代码中发起 import() 时,Node.js
将使用主上下文的默认 ESM 加载器来加载请求的
模块并将其返回给正在执行的代码。
这使得正在编译的代码可以访问 Node.js 内置模块,如 fs 或 http。如果代码在不同的上下文中执行,
请注意,从主上下文加载的模块创建的对象
仍然来自主上下文,并且不是新上下文中的内置类的
instanceof 实例。
const { Script, constants } = require('node:vm');
const script = new Script(
'import("node:fs").then(({readFile}) => readFile instanceof Function)',
{ importModuleDynamically: constants.USE_MAIN_CONTEXT_DEFAULT_LOADER });
// false: 从主上下文加载的 URL 不是新上下文中 Function 类的实例。
script.runInNewContext().then(console.log);此选项还允许脚本或函数加载用户模块:
import { Script, constants } from 'node:vm';
import { resolve } from 'node:path';
import { writeFileSync } from 'node:fs';
// 将 test.js 和 test.txt 写入当前运行脚本所在的目录。
writeFileSync(resolve(import.meta.dirname, 'test.mjs'),
'export const filename = "./test.json";');
writeFileSync(resolve(import.meta.dirname, 'test.json'),
'{"hello": "world"}');
// 编译一个脚本,加载 test.mjs 然后加载 test.json,就像脚本放在同一目录一样。
const script = new Script(
`(async function() {
const { filename } = await import('./test.mjs');
return import(filename, { with: { type: 'json' } })
})();`,
{
filename: resolve(import.meta.dirname, 'test-with-default.js'),
importModuleDynamically: constants.USE_MAIN_CONTEXT_DEFAULT_LOADER,
});
// { default: { hello: 'world' } }
script.runInThisContext().then(console.log);使用主上下文的默认加载器加载用户模块有一些注意事项:
- 被解析的模块将相对于传递给
vm.Script或vm.compileFunction()的filename选项。解析可以使用绝对路径或 URL 字符串的filename。如果filename是既不是绝对路径也不是 URL 的字符串,或者它是 undefined, 解析将相对于进程的当前工作目录。在vm.createContext()的情况下,解析总是 相对于当前工作目录,因为此选项仅在没有引用者脚本或模块时使用。 - 对于任何解析为特定路径的
filename,一旦进程 成功从该路径加载特定模块,结果可能会被缓存, 随后从同一路径加载同一模块将返回相同的内容。如果filename是 URL 字符串,如果它具有不同的搜索参数,则不会命中缓存。对于不是 URL 字符串的filename,目前无法绕过缓存行为。
当 importModuleDynamically 是一个函数时,当编译后的代码中调用 import() 时它将被调用,以便用户自定义请求的模块应如何编译和求值。目前,Node.js 实例必须
使用 --experimental-vm-modules 标志启动才能使此选项工作。如果
未设置该标志,此回调将被忽略。如果求值的代码
实际调用 import(),结果将拒绝并抛出
ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING_FLAG。
回调 importModuleDynamically(specifier, referrer, importAttributes)
具有以下签名:
<string>import()
的标识符<vm.Script>
|
<Function>
|
<vm.SourceTextModule>
|
<Object>new vm.Script
、
vm.runInThisContext
、
vm.runInContext
和
vm.runInNewContext
,引用者是编译后的
vm.Script
。对于
vm.compileFunction
,它是编译后的
Function
,对于
new vm.SourceTextModule
,它是编译后的
vm.SourceTextModule
,对于
vm.createContext()
,它是上下文
Object
。<Object>optionsExpression
可选参数的
"with"
值,如果未提供值则为空对象。<string>"source"
或
"evaluation"
)。// 此脚本必须使用 --experimental-vm-modules 运行。
import { Script, SyntheticModule } from 'node:vm';
const script = new Script('import("foo.json", { with: { type: "json" } })', {
async importModuleDynamically(specifier, referrer, importAttributes) {
console.log(specifier); // 'foo.json'
console.log(referrer); // 编译后的脚本
console.log(importAttributes); // { type: 'json' }
const m = new SyntheticModule(['bar'], () => { });
await m.link(() => { });
m.setExport('bar', { hello: 'world' });
return m;
},
});
const result = await script.runInThisContext();
console.log(result); // { bar: { hello: 'world' } }