VM(执行 JavaScript)#

稳定性:2 - 稳定

源代码: lib/vm.js

node:vm 模块允许在 V8 虚拟机上下文中编译和运行代码。

node:vm 模块不是安全机制。不要使用它来运行不受信任的代码。

JavaScript 代码可以立即编译和运行,也可以编译、保存和稍后运行。

一个常见的用例是在不同的 V8 上下文中运行代码。这意味着调用的代码与调用代码具有不同的全局对象。

可以通过 上下文化 一个对象来提供上下文。调用的代码将上下文中的任何属性视为全局变量。调用代码对全局变量的任何更改都会反映在上下文对象中。

const vm = require('node:vm');

const x = 1;

const context = { x: 2 };
vm.createContext(context); // Contextify the object.

const code = 'x += 40; var y = 17;';
// `x` and `y` are global variables in the context.
// Initially, x has the value 2 because that is the value of context.x.
vm.runInContext(code, context);

console.log(context.x); // 42
console.log(context.y); // 17

console.log(x); // 1; y is not defined. 

类:vm.Script#

vm.Script 类实例包含预编译的脚本,可以在特定上下文中执行。

new vm.Script(code[, options])#

  • code <string> 要编译的 JavaScript 代码。
  • options <Object> | <string>
    • filename <string> 指定此脚本生成的堆栈跟踪中使用的文件名。默认值: 'evalmachine.<anonymous>'
    • lineOffset <number> 指定此脚本生成的堆栈跟踪中显示的行号偏移量。默认值: 0
    • columnOffset <number> 指定此脚本生成的堆栈跟踪中显示的第一行列号偏移量。默认值: 0
    • cachedData <Buffer> | <TypedArray> | <DataView> 提供一个可选的 BufferTypedArray,或 DataView,其中包含 V8 的代码缓存数据,用于提供的源代码。当提供时,cachedDataRejected 值将设置为 truefalse,具体取决于 V8 是否接受数据。
    • produceCachedData <boolean>true 且没有 cachedData 时,V8 将尝试为 code 生成代码缓存数据。成功后,将生成一个包含 V8 代码缓存数据的 Buffer,并将其存储在返回的 vm.Script 实例的 cachedData 属性中。cachedDataProduced 值将设置为 truefalse,具体取决于是否成功生成代码缓存数据。此选项已弃用,建议使用 script.createCachedData()默认值: false
    • importModuleDynamically <Function> | <vm.constants.USE_MAIN_CONTEXT_DEFAULT_LOADER> 用于指定在调用 import() 时,此脚本评估期间模块应如何加载。此选项是实验性模块 API 的一部分。我们不建议在生产环境中使用它。有关详细信息,请参阅 编译 API 中动态 import() 的支持

如果 options 是一个字符串,则它指定文件名。

创建一个新的 vm.Script 对象会编译 code 但不会运行它。编译后的 vm.Script 可以稍后多次运行。code 不绑定到任何全局对象;相反,它在每次运行之前绑定,仅针对该运行。

script.cachedDataRejected#

cachedData 被提供用于创建 vm.Script 时,此值将被设置为 truefalse,具体取决于 V8 是否接受数据。否则,该值为 undefined

script.createCachedData()#

创建一个代码缓存,可用于 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();
// In `cacheWithoutAdd` the function `add()` is marked for full compilation
// upon invocation.

script.runInThisContext();

const cacheWithAdd = script.createCachedData();
// `cacheWithAdd` contains fully compiled function `add()`. 

script.runInContext(contextifiedObject[, options])#

  • contextifiedObject <Object>vm.createContext() 方法返回的 上下文化 对象。
  • options <Object>
    • displayErrors <boolean> 当为 true 时,如果在编译 code 时发生 Error,则导致错误的代码行将附加到堆栈跟踪。默认值:true
    • timeout <integer> 指定在终止执行之前执行 code 的毫秒数。如果执行被终止,将抛出一个 Error。此值必须为严格的正整数。
    • breakOnSigint <boolean> 如果为 true,则接收 SIGINT (Ctrl+C) 将终止执行并抛出一个 Error。通过 process.on('SIGINT') 附加的事件的现有处理程序在脚本执行期间被禁用,但在之后继续工作。默认值: false
  • 返回值:<any> 脚本中执行的最后一个语句的结果。

在给定的 contextifiedObject 中运行 vm.Script 对象包含的已编译代码,并返回结果。运行代码无法访问本地范围。

以下示例编译代码,该代码递增全局变量,设置另一个全局变量的值,然后多次执行代码。全局变量包含在 context 对象中。

const vm = require('node:vm');

const context = {
  animal: 'cat',
  count: 2,
};

const script = new vm.Script('count += 1; name = "kitty";');

vm.createContext(context);
for (let i = 0; i < 10; ++i) {
  script.runInContext(context);
}

console.log(context);
// Prints: { animal: 'cat', count: 12, name: 'kitty' } 

使用 timeoutbreakOnSigint 选项将导致启动新的事件循环和相应的线程,这些线程具有非零的性能开销。

script.runInNewContext([contextObject[, options]])#

  • contextObject <Object> 将被 上下文化 的对象。如果为 undefined,将创建一个新对象。
  • options <Object>
    • displayErrors <boolean> 当为 true 时,如果在编译 code 时发生 Error,则导致错误的代码行将附加到堆栈跟踪。默认值:true
    • timeout <integer> 指定在终止执行之前执行 code 的毫秒数。如果执行被终止,将抛出一个 Error。此值必须为严格的正整数。
    • breakOnSigint <boolean> 如果为 true,则接收 SIGINT (Ctrl+C) 将终止执行并抛出一个 Error。通过 process.on('SIGINT') 附加的事件的现有处理程序在脚本执行期间被禁用,但在之后继续工作。默认值: false
    • contextName <string> 新创建上下文的易于理解的名称。默认值: 'VM Context i',其中 i 是创建上下文的递增数字索引。
    • contextOrigin <string> 来源 对应于新创建的上下文,用于显示目的。来源应像 URL 一样格式化,但仅包含方案、主机和端口(如果需要),类似于 url.origin 属性的值 URL 对象。最值得注意的是,此字符串应省略尾部斜杠,因为这表示路径。默认值: ''
    • contextCodeGeneration <对象>
      • strings <布尔值> 如果设置为 false,任何对 eval 或函数构造函数(FunctionGeneratorFunction 等)的调用都会抛出 EvalError默认值: true
      • wasm <布尔值> 如果设置为 false,任何尝试编译 WebAssembly 模块都会抛出 WebAssembly.CompileError默认值: true
    • microtaskMode <字符串> 如果设置为 afterEvaluate,microtasks(通过 Promiseasync function 调度的任务)将在脚本运行后立即运行。在这种情况下,它们包含在 timeoutbreakOnSigint 范围内。
  • 返回值:<any> 脚本中执行的最后一个语句的结果。

首先对给定的 contextObject 进行上下文化,在创建的上下文中运行 vm.Script 对象包含的已编译代码,并返回结果。运行代码无法访问本地范围。

以下示例编译设置全局变量的代码,然后在不同的上下文中多次执行该代码。全局变量在每个单独的 context 中设置和包含。

const vm = require('node:vm');

const script = new vm.Script('globalVar = "set"');

const contexts = [{}, {}, {}];
contexts.forEach((context) => {
  script.runInNewContext(context);
});

console.log(contexts);
// Prints: [{ globalVar: 'set' }, { globalVar: 'set' }, { globalVar: 'set' }] 

script.runInThisContext([options])#

  • options <Object>
    • displayErrors <boolean> 当为 true 时,如果在编译 code 时发生 Error,则导致错误的代码行将附加到堆栈跟踪。默认值:true
    • timeout <integer> 指定在终止执行之前执行 code 的毫秒数。如果执行被终止,将抛出一个 Error。此值必须为严格的正整数。
    • breakOnSigint <boolean> 如果为 true,则接收 SIGINT (Ctrl+C) 将终止执行并抛出一个 Error。通过 process.on('SIGINT') 附加的事件的现有处理程序在脚本执行期间被禁用,但在之后继续工作。默认值: false
  • 返回值:<any> 脚本中执行的最后一个语句的结果。

在当前 global 对象的上下文中运行 vm.Script 包含的已编译代码。运行代码无法访问本地范围,但可以访问当前 global 对象。

以下示例编译递增 global 变量的代码,然后多次执行该代码

const vm = require('node:vm');

global.globalVar = 0;

const script = new vm.Script('globalVar += 1', { filename: 'myfile.vm' });

for (let i = 0; i < 1000; ++i) {
  script.runInThisContext();
}

console.log(globalVar);

// 1000 

script.sourceMapURL#

当脚本从包含源映射魔法注释的源代码编译时,此属性将设置为源映射的 URL。

import vm from 'node:vm';

const script = new vm.Script(`
function myFunc() {}
//# sourceMappingURL=sourcemap.json
`);

console.log(script.sourceMapURL);
// Prints: sourcemap.jsonconst vm = require('node:vm');

const script = new vm.Script(`
function myFunc() {}
//# sourceMappingURL=sourcemap.json
`);

console.log(script.sourceMapURL);
// Prints: sourcemap.json

类:vm.Module#

稳定性:1 - 实验性

此功能仅在启用 --experimental-vm-modules 命令标志时可用。

vm.Module 类提供了一个低级接口,用于在 VM 上下文中使用 ECMAScript 模块。它是 vm.Script 类的对应类,它密切反映了 ECMAScript 规范中定义的 模块记录

vm.Script 不同,每个 vm.Module 对象从创建时起就绑定到一个上下文。与 vm.Script 对象的同步特性相反,vm.Module 对象的操作本质上是异步的。使用 'async' 函数可以帮助操作 vm.Module 对象。

使用 vm.Module 对象需要三个不同的步骤:创建/解析、链接和评估。以下示例说明了这三个步骤。

此实现位于比 ECMAScript 模块加载器 更低的级别。目前还没有与加载器交互的方法,但计划支持。

import vm from 'node:vm';

const contextifiedObject = vm.createContext({
  secret: 42,
  print: console.log,
});

// Step 1
//
// Create a Module by constructing a new `vm.SourceTextModule` object. This
// parses the provided source text, throwing a `SyntaxError` if anything goes
// wrong. By default, a Module is created in the top context. But here, we
// specify `contextifiedObject` as the context this Module belongs to.
//
// Here, we attempt to obtain the default export from the module "foo", and
// put it into local binding "secret".

const bar = new vm.SourceTextModule(`
  import s from 'foo';
  s;
  print(s);
`, { context: contextifiedObject });

// Step 2
//
// "Link" the imported dependencies of this Module to it.
//
// The provided linking callback (the "linker") accepts two arguments: the
// parent module (`bar` in this case) and the string that is the specifier of
// the imported module. The callback is expected to return a Module that
// corresponds to the provided specifier, with certain requirements documented
// in `module.link()`.
//
// If linking has not started for the returned Module, the same linker
// callback will be called on the returned Module.
//
// Even top-level Modules without dependencies must be explicitly linked. The
// callback provided would never be called, however.
//
// The link() method returns a Promise that will be resolved when all the
// Promises returned by the linker resolve.
//
// Note: This is a contrived example in that the linker function creates a new
// "foo" module every time it is called. In a full-fledged module system, a
// cache would probably be used to avoid duplicated modules.

async function linker(specifier, referencingModule) {
  if (specifier === 'foo') {
    return new vm.SourceTextModule(`
      // The "secret" variable refers to the global variable we added to
      // "contextifiedObject" when creating the context.
      export default secret;
    `, { context: referencingModule.context });

    // Using `contextifiedObject` instead of `referencingModule.context`
    // here would work as well.
  }
  throw new Error(`Unable to resolve dependency: ${specifier}`);
}
await bar.link(linker);

// Step 3
//
// Evaluate the Module. The evaluate() method returns a promise which will
// resolve after the module has finished evaluating.

// Prints 42.
await bar.evaluate();const vm = require('node:vm');

const contextifiedObject = vm.createContext({
  secret: 42,
  print: console.log,
});

(async () => {
  // Step 1
  //
  // Create a Module by constructing a new `vm.SourceTextModule` object. This
  // parses the provided source text, throwing a `SyntaxError` if anything goes
  // wrong. By default, a Module is created in the top context. But here, we
  // specify `contextifiedObject` as the context this Module belongs to.
  //
  // Here, we attempt to obtain the default export from the module "foo", and
  // put it into local binding "secret".

  const bar = new vm.SourceTextModule(`
    import s from 'foo';
    s;
    print(s);
  `, { context: contextifiedObject });

  // Step 2
  //
  // "Link" the imported dependencies of this Module to it.
  //
  // The provided linking callback (the "linker") accepts two arguments: the
  // parent module (`bar` in this case) and the string that is the specifier of
  // the imported module. The callback is expected to return a Module that
  // corresponds to the provided specifier, with certain requirements documented
  // in `module.link()`.
  //
  // If linking has not started for the returned Module, the same linker
  // callback will be called on the returned Module.
  //
  // Even top-level Modules without dependencies must be explicitly linked. The
  // callback provided would never be called, however.
  //
  // The link() method returns a Promise that will be resolved when all the
  // Promises returned by the linker resolve.
  //
  // Note: This is a contrived example in that the linker function creates a new
  // "foo" module every time it is called. In a full-fledged module system, a
  // cache would probably be used to avoid duplicated modules.

  async function linker(specifier, referencingModule) {
    if (specifier === 'foo') {
      return new vm.SourceTextModule(`
        // The "secret" variable refers to the global variable we added to
        // "contextifiedObject" when creating the context.
        export default secret;
      `, { context: referencingModule.context });

      // Using `contextifiedObject` instead of `referencingModule.context`
      // here would work as well.
    }
    throw new Error(`Unable to resolve dependency: ${specifier}`);
  }
  await bar.link(linker);

  // Step 3
  //
  // Evaluate the Module. The evaluate() method returns a promise which will
  // resolve after the module has finished evaluating.

  // Prints 42.
  await bar.evaluate();
})();

module.dependencySpecifiers#

此模块所有依赖项的说明符。返回的数组被冻结,不允许对其进行任何更改。

对应于 ECMAScript 规范中 循环模块记录[[RequestedModules]] 字段。

module.error#

如果 module.status'errored',则此属性包含模块在评估期间抛出的异常。如果状态为其他任何值,访问此属性将导致抛出异常。

由于可能与 throw undefined; 模糊不清,因此在没有抛出异常的情况下,不能使用 undefined 值。

对应于 ECMAScript 规范中 循环模块记录[[EvaluationError]] 字段。

module.evaluate([options])#

  • options <Object>
    • timeout <integer> 指定在终止执行之前评估的毫秒数。如果执行被中断,将抛出 Error。此值必须是严格的正整数。
    • breakOnSigint <boolean> 如果为 true,则接收 SIGINT (Ctrl+C) 将终止执行并抛出一个 Error。通过 process.on('SIGINT') 附加的事件的现有处理程序在脚本执行期间被禁用,但在之后继续工作。默认值: false
  • 返回值:<Promise> 成功时以 undefined 完成。

评估模块。

此方法必须在模块链接后调用;否则它将拒绝。它也可以在模块已经被评估后调用,在这种情况下,如果初始评估成功结束(module.status'evaluated'),它将不做任何事情,或者它将重新抛出初始评估导致的异常(module.status'errored')。

在模块正在评估时(module.status'evaluating'),不能调用此方法。

对应于 ECMAScript 规范中 Evaluate() 具体方法 字段的 循环模块记录

module.identifier#

当前模块的标识符,在构造函数中设置。

module.link(linker)#

  • linker <Function>
    • specifier <string> 请求的模块的说明符

      import foo from 'foo';
      //              ^^^^^ the module specifier 
    • referencingModule <vm.Module> 调用 link()Module 对象。

    • extra <Object>

      • attributes <Object> 属性中的数据
        import foo from 'foo' with { name: 'value' };
        //                         ^^^^^^^^^^^^^^^^^ the attribute 
        根据 ECMA-262,如果存在不支持的属性,主机应触发错误。
      • assert <Object> extra.attributes 的别名。
    • 返回值: <vm.Module> | <Promise>

  • 返回值: <Promise>

链接模块依赖项。此方法必须在评估之前调用,并且每个模块只能调用一次。

该函数预计将返回一个 Module 对象或一个最终解析为 Module 对象的 Promise。返回的 Module 必须满足以下两个不变式

  • 它必须属于与父 Module 相同的上下文。
  • 它的 status 必须不是 'errored'

如果返回的 Modulestatus'unlinked',则此方法将使用相同的提供的 linker 函数递归调用返回的 Module

link() 返回一个 Promise,当所有链接实例都解析为有效的 Module 时,该 Promise 将被解决,如果链接器函数抛出异常或返回无效的 Module,则该 Promise 将被拒绝。

链接器函数大致对应于 ECMAScript 规范中定义的 HostResolveImportedModule 抽象操作,但有一些关键区别。

在模块链接期间使用的实际 HostResolveImportedModule 实现是返回在链接期间链接的模块的实现。由于此时所有模块都已完全链接,因此 HostResolveImportedModule 实现根据规范是完全同步的。

对应于 ECMAScript 规范中 Link() 具体方法 字段的 循环模块记录

module.namespace#

模块的命名空间对象。这只有在链接(module.link())完成后才可用。

对应于 ECMAScript 规范中的 GetModuleNamespace 抽象操作。

module.status#

模块的当前状态。将是以下之一:

  • 'unlinked':尚未调用 module.link()

  • 'linking':已调用 module.link(),但链接器函数返回的所有 Promise 尚未全部解决。

  • 'linked':模块已成功链接,并且其所有依赖项都已链接,但尚未调用 module.evaluate()

  • 'evaluating':模块正在通过对自身或父模块的 module.evaluate() 进行评估。

  • 'evaluated':模块已成功评估。

  • 'errored':模块已评估,但抛出了异常。

除了 'errored' 之外,此状态字符串对应于规范的 循环模块记录[[Status]] 字段。'errored' 对应于规范中的 'evaluated',但 [[EvaluationError]] 设置为非 undefined 的值。

类:vm.SourceTextModule#

稳定性:1 - 实验性

此功能仅在启用 --experimental-vm-modules 命令标志时可用。

vm.SourceTextModule 类提供了 ECMAScript 规范中定义的 源文本模块记录

new vm.SourceTextModule(code[, options])#

  • code <string> 要解析的 JavaScript 模块代码
  • options
    • identifier <string> 在堆栈跟踪中使用的字符串。默认值:'vm:module(i)',其中 i 是特定于上下文的递增索引。
    • cachedData <Buffer> | <TypedArray> | <DataView> 提供可选的 BufferTypedArray,或 DataView,其中包含 V8 为提供的源代码生成的代码缓存数据。code 必须与创建此 cachedData 的模块相同。
    • context <Object>vm.createContext() 方法返回的 上下文化 对象,用于编译和评估此 Module。如果未指定上下文,则模块将在当前执行上下文中进行评估。
    • lineOffset <integer> 指定此 Module 生成的堆栈跟踪中显示的行号偏移量。默认值:0
    • columnOffset <integer> 指定此 Module 生成的堆栈跟踪中显示的第一行列号偏移量。默认值:0
    • initializeImportMeta <Function> 在评估此 Module 期间调用,以初始化 import.meta
    • importModuleDynamically <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) {
      // 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 contextified object.
      meta.prop = {};
    },
  });
// Since module has no dependencies, the linker function will never be called.
await module.link(() => {});
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('{}', contextifiedObject);const vm = require('node:vm');
const contextifiedObject = vm.createContext({ secret: 42 });
(async () => {
  const module = new vm.SourceTextModule(
    '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 contextified object.
        meta.prop = {};
      },
    });
  // Since module has no dependencies, the linker function will never be called.
  await module.link(() => {});
  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('{}', contextifiedObject);
})();

sourceTextModule.createCachedData()#

创建一个代码缓存,可与SourceTextModule构造函数的cachedData选项一起使用。返回一个Buffer。此方法可以在模块被评估之前被调用任意次数。

SourceTextModule的代码缓存不包含任何 JavaScript 可观察状态。代码缓存可以安全地与脚本源一起保存,并用于多次构造新的SourceTextModule实例。

SourceTextModule源中的函数可以标记为延迟编译,它们不会在SourceTextModule构造时被编译。这些函数将在第一次调用时被编译。代码缓存序列化 V8 当前知道的关于SourceTextModule的元数据,这些元数据可以用来加速未来的编译。

// Create an initial module
const module = new vm.SourceTextModule('const a = 1;');

// Create cached data from this module
const cachedData = module.createCachedData();

// Create a new module using the cached data. The code must be the same.
const module2 = new vm.SourceTextModule('const a = 1;', { cachedData }); 

类:vm.SyntheticModule#

稳定性:1 - 实验性

此功能仅在启用 --experimental-vm-modules 命令标志时可用。

vm.SyntheticModule类提供了合成模块记录,如 WebIDL 规范中所定义。合成模块的目的是提供一个通用接口,用于将非 JavaScript 源公开到 ECMAScript 模块图。

const vm = require('node:vm');

const source = '{ "a": 1 }';
const module = new vm.SyntheticModule(['default'], function() {
  const obj = JSON.parse(source);
  this.setExport('default', obj);
});

// Use `module` in linking... 

new vm.SyntheticModule(exportNames, evaluateCallback[, options])#

  • exportNames <string[]> 将从模块中导出的名称数组。
  • evaluateCallback <Function> 模块被评估时调用。
  • options
    • identifier <string> 在堆栈跟踪中使用的字符串。默认值:'vm:module(i)',其中 i 是特定于上下文的递增索引。
    • context <Object>vm.createContext()方法返回的上下文化对象,用于在此Module中进行编译和评估。

创建一个新的SyntheticModule实例。

分配给此实例导出的对象可能允许模块导入者访问指定context之外的信息。使用vm.runInContext()在特定上下文中创建对象。

syntheticModule.setExport(name, value)#

  • name <string> 要设置的导出的名称。
  • value <any> 要设置的导出值。

此方法在模块链接后用于设置导出的值。如果在模块链接之前调用它,将抛出ERR_VM_MODULE_STATUS错误。

import vm from 'node:vm';

const m = new vm.SyntheticModule(['x'], () => {
  m.setExport('x', 1);
});

await m.link(() => {});
await m.evaluate();

assert.strictEqual(m.namespace.x, 1);const vm = require('node:vm');
(async () => {
  const m = new vm.SyntheticModule(['x'], () => {
    m.setExport('x', 1);
  });
  await m.link(() => {});
  await m.evaluate();
  assert.strictEqual(m.namespace.x, 1);
})();

vm.compileFunction(code[, params[, options]])#

  • code <string> 要编译的函数主体。
  • params <string[]> 包含函数所有参数的字符串数组。
  • options <Object>
    • filename <string> 指定此脚本生成的堆栈跟踪中使用的文件名。默认值:''
    • lineOffset <number> 指定此脚本生成的堆栈跟踪中显示的行号偏移量。默认值: 0
    • columnOffset <number> 指定此脚本生成的堆栈跟踪中显示的第一行列号偏移量。默认值: 0
    • cachedData <Buffer> | <TypedArray> | <DataView> 提供可选的BufferTypedArray,或使用 V8 代码缓存数据的DataView。这必须由先前对vm.compileFunction()的调用使用相同的codeparams生成。
    • produceCachedData <boolean> 指定是否生成新的缓存数据。默认值:false
    • parsingContext <Object> 所述函数应在其中编译的上下文化对象。
    • contextExtensions <Object[]> 包含一系列上下文扩展(封装当前作用域的对象)的数组,在编译时应用。默认值: []
  • importModuleDynamically <Function> | <vm.constants.USE_MAIN_CONTEXT_DEFAULT_LOADER> 用于指定在调用 import() 时,此函数评估期间模块的加载方式。此选项是实验性模块 API 的一部分。我们不建议在生产环境中使用它。有关详细信息,请参阅 编译 API 中动态 import() 的支持
  • 返回值:<Function>

将给定代码编译到提供的上下文中(如果未提供上下文,则使用当前上下文),并将其包装在具有给定 params 的函数中返回。

vm.constants#

返回一个包含 VM 操作常用常量的对象。

vm.constants.USE_MAIN_CONTEXT_DEFAULT_LOADER#

稳定性:1.1 - 积极开发

一个常量,可以用作 vm.Scriptvm.compileFunction()importModuleDynamically 选项,以便 Node.js 使用主上下文中的默认 ESM 加载器来加载请求的模块。

有关详细信息,请参阅 编译 API 中动态 import() 的支持

vm.createContext([contextObject[, options]])#

  • contextObject <Object>
  • options <Object>
    • name <string> 新创建上下文的易读名称。默认值: 'VM Context i',其中 i 是创建上下文的递增数字索引。
    • origin <string> Origin 用于显示目的,对应于新创建的上下文。该 origin 应像 URL 一样格式化,但仅包含方案、主机和端口(如果需要),类似于 url.origin 属性的 URL 对象的值。最值得注意的是,此字符串应省略尾部斜杠,因为该斜杠表示路径。默认值: ''
    • codeGeneration <Object>
      • strings <布尔值> 如果设置为 false,任何对 eval 或函数构造函数(FunctionGeneratorFunction 等)的调用都会抛出 EvalError默认值: true
      • wasm <布尔值> 如果设置为 false,任何尝试编译 WebAssembly 模块都会抛出 WebAssembly.CompileError默认值: true
    • microtaskMode <string> 如果设置为 afterEvaluate,微任务(通过 Promiseasync function 调度的任务)将在脚本通过 script.runInContext() 运行后立即运行。在这种情况下,它们包含在 timeoutbreakOnSigint 范围内。
    • importModuleDynamically <Function> | <vm.constants.USE_MAIN_CONTEXT_DEFAULT_LOADER> 用于指定在没有引用脚本或模块的情况下,在该上下文中调用 import() 时,模块应如何加载。此选项是实验性模块 API 的一部分。我们不建议在生产环境中使用它。有关详细信息,请参阅 编译 API 中对动态 import() 的支持
  • 返回值:<Object> 上下文化对象。

如果给定 contextObjectvm.createContext() 方法将 准备该对象,以便它可以在调用 vm.runInContext()script.runInContext() 时使用。在这些脚本内部,contextObject 将是全局对象,保留其所有现有属性,但也具有任何标准 全局对象 拥有的内置对象和函数。在 vm 模块运行的脚本之外,全局变量将保持不变。

const vm = require('node:vm');

global.globalVar = 3;

const context = { globalVar: 1 };
vm.createContext(context);

vm.runInContext('globalVar *= 2;', context);

console.log(context);
// Prints: { globalVar: 2 }

console.log(global.globalVar);
// Prints: 3 

如果省略 contextObject(或显式地将其传递为 undefined),则将返回一个新的、空的 上下文化 对象。

vm.createContext() 方法主要用于创建单个上下文,该上下文可用于运行多个脚本。例如,如果模拟 Web 浏览器,该方法可用于创建表示窗口全局对象的单个上下文,然后在该上下文内一起运行所有 <script> 标签。

提供的上下文的 nameorigin 通过 Inspector API 可见。

vm.isContext(object)#

如果给定的 object 对象已使用 上下文化(使用 vm.createContext()),则返回 true

vm.measureMemory([options])#

稳定性:1 - 实验性

测量 V8 已知并由当前 V8 隔离区中所有已知上下文或主上下文使用的内存。

  • options <Object> 可选。
    • mode <string> 可以是 'summary''detailed'。在摘要模式下,只会返回为主上下文测量的内存。在详细模式下,将返回为当前 V8 隔离区中所有已知上下文测量的内存。默认值: 'summary'
    • execution <string> 可以是 'default''eager'。使用默认执行,promise 不会在下次计划的垃圾回收开始之前解析,这可能需要一段时间(或者如果程序在下次 GC 之前退出,则永远不会解析)。使用急切执行,GC 将立即启动以测量内存。默认值: 'default'
  • 返回值:<Promise> 如果内存测量成功,promise 将解析为包含有关内存使用情况信息的 object。否则,它将被 ERR_CONTEXT_NOT_INITIALIZED 错误拒绝。

返回的 Promise 可能解析为的 object 的格式特定于 V8 引擎,并且可能在 V8 的不同版本之间发生变化。

返回的结果与v8.getHeapSpaceStatistics()返回的统计信息不同,因为vm.measureMemory()测量的是当前 V8 引擎实例中每个 V8 特定上下文可访问的内存,而v8.getHeapSpaceStatistics()的结果测量的是当前 V8 实例中每个堆空间占用的内存。

const vm = require('node:vm');
// Measure the memory used by the main context.
vm.measureMemory({ mode: 'summary' })
  // This is the same as vm.measureMemory()
  .then((result) => {
    // The current format is:
    // {
    //   total: {
    //      jsMemoryEstimate: 2418479, jsMemoryRange: [ 2418479, 2745799 ]
    //    }
    // }
    console.log(result);
  });

const context = vm.createContext({ a: 1 });
vm.measureMemory({ mode: 'detailed', execution: 'eager' })
  .then((result) => {
    // Reference the context here so that it won't be GC'ed
    // until the measurement is complete.
    console.log(context.a);
    // {
    //   total: {
    //     jsMemoryEstimate: 2574732,
    //     jsMemoryRange: [ 2574732, 2904372 ]
    //   },
    //   current: {
    //     jsMemoryEstimate: 2438996,
    //     jsMemoryRange: [ 2438996, 2768636 ]
    //   },
    //   other: [
    //     {
    //       jsMemoryEstimate: 135736,
    //       jsMemoryRange: [ 135736, 465376 ]
    //     }
    //   ]
    // }
    console.log(result);
  }); 

vm.runInContext(code, contextifiedObject[, options])#

  • code <string> 要编译和运行的 JavaScript 代码。
  • contextifiedObject <Object> 将在编译和运行code时用作global上下文化对象。
  • options <Object> | <string>
    • filename <string> 指定此脚本生成的堆栈跟踪中使用的文件名。默认值: 'evalmachine.<anonymous>'
    • lineOffset <number> 指定此脚本生成的堆栈跟踪中显示的行号偏移量。默认值: 0
    • columnOffset <number> 指定此脚本生成的堆栈跟踪中显示的第一行列号偏移量。默认值: 0
    • displayErrors <boolean> 当为 true 时,如果在编译 code 时发生 Error,则导致错误的代码行将附加到堆栈跟踪。默认值:true
    • timeout <integer> 指定在终止执行之前执行 code 的毫秒数。如果执行被终止,将抛出一个 Error。此值必须为严格的正整数。
    • breakOnSigint <boolean> 如果为 true,则接收 SIGINT (Ctrl+C) 将终止执行并抛出一个 Error。通过 process.on('SIGINT') 附加的事件的现有处理程序在脚本执行期间被禁用,但在之后继续工作。默认值: false
    • cachedData <Buffer> | <TypedArray> | <DataView> 提供可选的BufferTypedArray,或DataView,其中包含 V8 的代码缓存数据,用于提供的源代码。
    • importModuleDynamically <Function> | <vm.constants.USE_MAIN_CONTEXT_DEFAULT_LOADER> 用于指定在调用import()时,在评估此脚本期间如何加载模块。此选项是实验性模块 API 的一部分。我们不建议在生产环境中使用它。有关详细信息,请参阅编译 API 中对动态import()的支持

vm.runInContext()方法编译code,在contextifiedObject的上下文中运行它,然后返回结果。运行代码无法访问本地范围。contextifiedObject对象必须使用vm.createContext()方法事先进行上下文化

如果 options 是一个字符串,则它指定文件名。

以下示例使用单个上下文化对象编译和执行不同的脚本

const vm = require('node:vm');

const contextObject = { globalVar: 1 };
vm.createContext(contextObject);

for (let i = 0; i < 10; ++i) {
  vm.runInContext('globalVar *= 2;', contextObject);
}
console.log(contextObject);
// Prints: { globalVar: 1024 } 

vm.runInNewContext(code[, contextObject[, options]])#

  • code <string> 要编译和运行的 JavaScript 代码。
  • contextObject <Object> 将被 上下文化 的对象。如果为 undefined,将创建一个新对象。
  • options <Object> | <string>
    • filename <string> 指定此脚本生成的堆栈跟踪中使用的文件名。默认值: 'evalmachine.<anonymous>'
    • lineOffset <number> 指定此脚本生成的堆栈跟踪中显示的行号偏移量。默认值: 0
    • columnOffset <number> 指定此脚本生成的堆栈跟踪中显示的第一行列号偏移量。默认值: 0
    • displayErrors <boolean> 当为 true 时,如果在编译 code 时发生 Error,则导致错误的代码行将附加到堆栈跟踪。默认值:true
    • timeout <integer> 指定在终止执行之前执行 code 的毫秒数。如果执行被终止,将抛出一个 Error。此值必须为严格的正整数。
    • breakOnSigint <boolean> 如果为 true,则接收 SIGINT (Ctrl+C) 将终止执行并抛出一个 Error。通过 process.on('SIGINT') 附加的事件的现有处理程序在脚本执行期间被禁用,但在之后继续工作。默认值: false
    • contextName <string> 新创建上下文的易于理解的名称。默认值: 'VM Context i',其中 i 是创建上下文的递增数字索引。
    • contextOrigin <string> 来源 对应于新创建的上下文,用于显示目的。来源应像 URL 一样格式化,但仅包含方案、主机和端口(如果需要),类似于 url.origin 属性的值 URL 对象。最值得注意的是,此字符串应省略尾部斜杠,因为这表示路径。默认值: ''
    • contextCodeGeneration <对象>
      • strings <布尔值> 如果设置为 false,任何对 eval 或函数构造函数(FunctionGeneratorFunction 等)的调用都会抛出 EvalError默认值: true
      • wasm <布尔值> 如果设置为 false,任何尝试编译 WebAssembly 模块都会抛出 WebAssembly.CompileError默认值: true
    • cachedData <Buffer> | <TypedArray> | <DataView> 提供可选的BufferTypedArray,或DataView,其中包含 V8 的代码缓存数据,用于提供的源代码。
    • importModuleDynamically <Function> | <vm.constants.USE_MAIN_CONTEXT_DEFAULT_LOADER> 用于指定在调用import()时,在评估此脚本期间如何加载模块。此选项是实验性模块 API 的一部分。我们不建议在生产环境中使用它。有关详细信息,请参阅编译 API 中对动态import()的支持
    • microtaskMode <字符串> 如果设置为 afterEvaluate,microtasks(通过 Promiseasync function 调度的任务)将在脚本运行后立即运行。在这种情况下,它们包含在 timeoutbreakOnSigint 范围内。
  • 返回值:<any> 脚本中执行的最后一个语句的结果。

vm.runInNewContext()首先对给定的contextObject进行上下文化(如果传递的是undefined,则创建一个新的contextObject),编译code,在创建的上下文中运行它,然后返回结果。运行代码无法访问本地范围。

如果 options 是一个字符串,则它指定文件名。

以下示例编译并执行代码,该代码会递增全局变量并设置一个新的全局变量。这些全局变量包含在contextObject中。

const vm = require('node:vm');

const contextObject = {
  animal: 'cat',
  count: 2,
};

vm.runInNewContext('count += 1; name = "kitty"', contextObject);
console.log(contextObject);
// Prints: { animal: 'cat', count: 3, name: 'kitty' } 

vm.runInThisContext(code[, options])#

  • code <string> 要编译和运行的 JavaScript 代码。
  • options <Object> | <string>
    • filename <string> 指定此脚本生成的堆栈跟踪中使用的文件名。默认值: 'evalmachine.<anonymous>'
    • lineOffset <number> 指定此脚本生成的堆栈跟踪中显示的行号偏移量。默认值: 0
    • columnOffset <number> 指定此脚本生成的堆栈跟踪中显示的第一行列号偏移量。默认值: 0
    • displayErrors <boolean> 当为 true 时,如果在编译 code 时发生 Error,则导致错误的代码行将附加到堆栈跟踪。默认值:true
    • timeout <integer> 指定在终止执行之前执行 code 的毫秒数。如果执行被终止,将抛出一个 Error。此值必须为严格的正整数。
    • breakOnSigint <boolean> 如果为 true,则接收 SIGINT (Ctrl+C) 将终止执行并抛出一个 Error。通过 process.on('SIGINT') 附加的事件的现有处理程序在脚本执行期间被禁用,但在之后继续工作。默认值: false
    • cachedData <Buffer> | <TypedArray> | <DataView> 提供可选的BufferTypedArray,或DataView,其中包含 V8 的代码缓存数据,用于提供的源代码。
    • importModuleDynamically <Function> | <vm.constants.USE_MAIN_CONTEXT_DEFAULT_LOADER> 用于指定在调用import()时,在评估此脚本期间如何加载模块。此选项是实验性模块 API 的一部分。我们不建议在生产环境中使用它。有关详细信息,请参阅编译 API 中对动态import()的支持
  • 返回值:<any> 脚本中执行的最后一个语句的结果。

vm.runInThisContext() 会编译 code,在当前 global 的上下文中运行它,并返回结果。运行的代码无法访问局部作用域,但可以访问当前的 global 对象。

如果 options 是一个字符串,则它指定文件名。

以下示例说明了使用 vm.runInThisContext() 和 JavaScript 的 eval() 函数来运行相同的代码。

const vm = require('node:vm');
let localVar = 'initial value';

const vmResult = vm.runInThisContext('localVar = "vm";');
console.log(`vmResult: '${vmResult}', localVar: '${localVar}'`);
// Prints: vmResult: 'vm', localVar: 'initial value'

const evalResult = eval('localVar = "eval";');
console.log(`evalResult: '${evalResult}', localVar: '${localVar}'`);
// Prints: evalResult: 'eval', localVar: 'eval' 

由于 vm.runInThisContext() 无法访问局部作用域,因此 localVar 不会改变。相反,eval() 可以访问局部作用域,因此 localVar 的值会改变。这样,vm.runInThisContext() 就类似于 间接 eval() 调用,例如 (0,eval)('code')

示例:在 VM 中运行 HTTP 服务器#

当使用 script.runInThisContext()vm.runInThisContext() 时,代码将在当前 V8 全局上下文中执行。传递给此 VM 上下文的代码将拥有自己的隔离作用域。

为了使用 node:http 模块运行一个简单的 Web 服务器,传递给上下文的代码必须自行调用 require('node:http'),或者拥有传递给它的 node:http 模块的引用。例如

'use strict';
const vm = require('node:vm');

const code = `
((require) => {
  const http = require('node:http');

  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/');
})`;

vm.runInThisContext(code)(require); 

上述情况下的 require() 与传递它的上下文共享状态。当执行不受信任的代码时,这可能会带来风险,例如以不希望的方式更改上下文中的对象。

将对象“上下文化”意味着什么?#

在 Node.js 中执行的所有 JavaScript 代码都在“上下文”的作用域内运行。根据 V8 嵌入器指南

在 V8 中,上下文是一个执行环境,它允许将独立的、无关的 JavaScript 应用程序运行在 V8 的单个实例中。您必须明确指定要运行任何 JavaScript 代码的上下文。

当调用 vm.createContext() 方法时,contextObject 参数(如果 contextObjectundefined,则为新创建的对象)会在内部与 V8 上下文的新的实例相关联。此 V8 上下文为使用 node:vm 模块的方法运行的 code 提供了一个隔离的全局环境,它可以在其中运行。创建 V8 上下文并将其与 contextObject 关联的过程就是本文档中所指的“上下文化”对象。

异步任务和 Promise 的超时交互#

Promiseasync function 可以异步地安排 JavaScript 引擎运行的任务。默认情况下,这些任务在当前堆栈上的所有 JavaScript 函数执行完毕后运行。这允许逃避 timeoutbreakOnSigint 选项的功能。

例如,以下由 vm.runInNewContext() 执行的代码,超时时间为 5 毫秒,安排了一个无限循环在 Promise 解决后运行。计划的循环不会被超时中断

const vm = require('node:vm');

function loop() {
  console.log('entering loop');
  while (1) console.log(Date.now());
}

vm.runInNewContext(
  'Promise.resolve().then(() => loop());',
  { loop, console },
  { timeout: 5 },
);
// This is printed *before* 'entering loop' (!)
console.log('done executing'); 

可以通过将 microtaskMode: 'afterEvaluate' 传递给创建 Context 的代码来解决此问题

const vm = require('node:vm');

function loop() {
  while (1) console.log(Date.now());
}

vm.runInNewContext(
  'Promise.resolve().then(() => loop());',
  { loop, console },
  { timeout: 5, microtaskMode: 'afterEvaluate' },
); 

在这种情况下,通过 promise.then() 调度的微任务将在从 vm.runInNewContext() 返回之前运行,并将被 timeout 功能中断。这仅适用于在 vm.Context 中运行的代码,因此例如 vm.runInThisContext() 不会使用此选项。

Promise 回调将进入创建它们的上下文的微任务队列。例如,如果在上面的示例中将 () => loop() 替换为 loop,那么 loop 将被推入全局微任务队列,因为它来自外部(主)上下文,因此也将能够逃避超时。

如果在 vm.Context 中提供了异步调度函数,例如 process.nextTick()queueMicrotask()setTimeout()setImmediate() 等,传递给它们的函数将被添加到全局队列中,这些队列由所有上下文共享。因此,传递给这些函数的回调也不能通过超时进行控制。

编译 API 中动态 import() 的支持#

以下 API 支持 importModuleDynamically 选项,以在 vm 模块编译的代码中启用动态 import()

  • new vm.Script
  • vm.compileFunction()
  • new vm.SourceTextModule
  • vm.runInThisContext()
  • vm.runInContext()
  • vm.runInNewContext()
  • vm.createContext()

此选项仍是实验性模块 API 的一部分。我们不建议在生产环境中使用它。

当未指定或未定义 importModuleDynamically 选项时#

如果未指定此选项,或其值为 undefined,包含 import() 的代码仍然可以被 vm API 编译,但当编译后的代码执行并实际调用 import() 时,结果将拒绝并抛出 ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING 错误。

importModuleDynamicallyvm.constants.USE_MAIN_CONTEXT_DEFAULT_LOADER#

此选项目前不支持 vm.SourceTextModule

使用此选项,当在编译后的代码中启动 import() 时,Node.js 将使用主上下文中的默认 ESM 加载器来加载请求的模块并将其返回给正在执行的代码。

这使编译的代码能够访问 Node.js 内置模块,例如 fshttp。如果代码在不同的上下文中执行,请注意,从主上下文加载的模块创建的对象仍然来自主上下文,而不是新上下文中的内置类实例。

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 loaded from the main context is not an instance of the Function
// class in the new context.
script.runInNewContext().then(console.log);import { Script, constants } from 'node:vm';

const script = new Script(
  'import("node:fs").then(({readFile}) => readFile instanceof Function)',
  { importModuleDynamically: constants.USE_MAIN_CONTEXT_DEFAULT_LOADER });

// false: URL loaded from the main context is not an instance of the Function
// class in the new context.
script.runInNewContext().then(console.log);

此选项还允许脚本或函数加载用户模块。

import { Script, constants } from 'node:vm';
import { resolve } from 'node:path';
import { writeFileSync } from 'node:fs';

// Write test.js and test.txt to the directory where the current script
// being run is located.
writeFileSync(resolve(import.meta.dirname, 'test.mjs'),
              'export const filename = "./test.json";');
writeFileSync(resolve(import.meta.dirname, 'test.json'),
              '{"hello": "world"}');

// Compile a script that loads test.mjs and then test.json
// as if the script is placed in the same directory.
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);const { Script, constants } = require('node:vm');
const { resolve } = require('node:path');
const { writeFileSync } = require('node:fs');

// Write test.js and test.txt to the directory where the current script
// being run is located.
writeFileSync(resolve(__dirname, 'test.mjs'),
              'export const filename = "./test.json";');
writeFileSync(resolve(__dirname, 'test.json'),
              '{"hello": "world"}');

// Compile a script that loads test.mjs and then test.json
// as if the script is placed in the same directory.
const script = new Script(
  `(async function() {
    const { filename } = await import('./test.mjs');
    return import(filename, { with: { type: 'json' } })
  })();`,
  {
    filename: resolve(__dirname, 'test-with-default.js'),
    importModuleDynamically: constants.USE_MAIN_CONTEXT_DEFAULT_LOADER,
  });

// { default: { hello: 'world' } }
script.runInThisContext().then(console.log);

使用主上下文中的默认加载器加载用户模块有一些注意事项。

  1. 要解析的模块将相对于传递给 vm.Scriptvm.compileFunction()filename 选项。解析可以处理绝对路径或 URL 字符串形式的 filename。如果 filename 是既不是绝对路径也不是 URL 的字符串,或者它未定义,则解析将相对于进程的当前工作目录。在 vm.createContext() 的情况下,解析始终相对于当前工作目录,因为此选项仅在没有引用脚本或模块时使用。
  2. 对于解析为特定路径的任何给定 filename,一旦进程成功从该路径加载特定模块,结果可能会被缓存,并且随后从同一路径加载同一模块将返回相同的内容。如果 filename 是 URL 字符串,则如果它具有不同的搜索参数,则不会命中缓存。对于不是 URL 字符串的 filename,目前无法绕过缓存行为。

importModuleDynamically 是一个函数#

importModuleDynamically 是一个函数时,它将在编译代码中调用 import() 时被调用,以便用户自定义如何编译和评估请求的模块。目前,Node.js 实例必须使用 --experimental-vm-modules 标志启动才能使此选项生效。如果未设置标志,则此回调将被忽略。如果评估的代码实际调用 import(),则结果将拒绝,并出现 ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING_FLAG 错误。

回调 importModuleDynamically(specifier, referrer, importAttributes) 具有以下签名

  • specifier <string> 传递给 import() 的说明符
  • referrer <vm.Script> | <Function> | <vm.SourceTextModule> | <Object> 引用者是 new vm.Scriptvm.runInThisContextvm.runInContextvm.runInNewContext 的编译后的 vm.Script。它是 vm.compileFunction 的编译后的 Function,是 new vm.SourceTextModule 的编译后的 vm.SourceTextModule,以及 vm.createContext() 的上下文 Object
  • importAttributes <Object> 传递给 optionsExpression 可选参数的 "with" 值,或者如果未提供值,则为空对象。
  • 返回值:<Module Namespace Object> | <vm.Module> 建议返回 vm.Module,以便利用错误跟踪,并避免包含 then 函数导出名的命名空间问题。
// This script must be run with --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);   // The compiled script
    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' } }// This script must be run with --experimental-vm-modules.
const { Script, SyntheticModule } = require('node:vm');

(async function main() {
  const script = new Script('import("foo.json", { with: { type: "json" } })', {
    async importModuleDynamically(specifier, referrer, importAttributes) {
      console.log(specifier);  // 'foo.json'
      console.log(referrer);   // The compiled script
      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' } }
})();