Node.js v24.13.0 文档


虚拟机(执行 JavaScript)#>

【VM (executing JavaScript)】

源代码: lib/vm.js

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

【The node:vm module enables compiling and running code within V8 Virtual Machine contexts.】

node:vm 模块不是一种安全机制。不要用它来运行不可信的代码。

JavaScript 代码可以立即编译并运行,也可以先编译、保存,然后再运行。

【JavaScript code can be compiled and run immediately or compiled, saved, and run later.】

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

【A common use case is to run the code in a different V8 Context. This means invoked code has a different global object than the invoking code.】

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

【One can provide the context by contextifying an object. The invoked code treats any property in the context like a global variable. Any changes to global variables caused by the invoked code are reflected in the context object.】

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#>

【Class: vm.Script

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

【Instances of the vm.Script class contain precompiled scripts that can be executed in specific contexts.】

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> 提供一个可选的 BufferTypedArrayDataView,用于包含所提供源代码的 V8 代码缓存数据。提供时,cachedDataRejected 的值将根据 V8 对数据的接受情况被设置为 truefalse
    • 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 是字符串,那么它指定文件名。

【If options is a string, then it specifies the filename.】

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

【Creating a new vm.Script object compiles code but does not run it. The compiled vm.Script can be run later multiple times. The code is not bound to any global object; rather, it is bound before each run, just for that run.】

script.cachedDataRejected#>

当向 vm.Script 提供 cachedData 时,该值将根据 V8 是否接受数据被设置为 truefalse。否则,该值为 undefined

【When cachedData is supplied to create the vm.Script, this value will be set to either true or false depending on acceptance of the data by V8. Otherwise the value is undefined.】

script.createCachedData()#>

创建一个代码缓存,可用于 Script 构造函数的 cachedData 选项。返回一个 Buffer。此方法可以在任何时间、调用任意次数。

【Creates a code cache that can be used with the Script constructor's cachedData option. Returns a Buffer. This method may be called at any time and any number of times.】

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

【The code cache of the Script doesn't contain any JavaScript observable states. The code cache is safe to be saved along side the script source and used to construct new Script instances multiple times.】

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

【Functions in the Script source can be marked as lazily compiled and they are not compiled at construction of the Script. These functions are going to be compiled when they are invoked the first time. The code cache serializes the metadata that V8 currently knows about the Script that it can use to speed up future compilations.】

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,接收到 SIGINTCtrl+C)将终止执行并抛出一个 Error。在脚本执行期间,通过 process.on('SIGINT') 附加的现有事件处理程序将被禁用,但在脚本执行后仍然继续工作。默认值: false
  • 返回:<any> 脚本中最后执行的语句的结果。

在给定的 contextifiedObject 中运行 vm.Script 对象包含的已编译代码并返回结果。运行的代码无法访问局部作用域。

【Runs the compiled code contained by the vm.Script object within the given contextifiedObject and returns the result. Running code does not have access to local scope.】

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

【The following example compiles code that increments a global variable, sets the value of another global variable, then execute the code multiple times. The globals are contained in the context object.】

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 选项会导致启动新的事件循环和相应的线程,这会带来一定的性能开销。

【Using the timeout or breakOnSigint options will result in new event loops and corresponding threads being started, which have a non-zero performance overhead.】

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

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

此方法是 script.runInContext(vm.createContext(options), options) 的快捷方式。它一次性完成了几件事:

【This method is a shortcut to script.runInContext(vm.createContext(options), options). It does several things at once:】

  1. 创建一个新的上下文。
  2. 如果 contextObject 是一个对象,用新的上下文 使具上下文 它。如果 contextObject 未定义,则创建一个新对象并 使具上下文 它。如果 contextObjectvm.constants.DONT_CONTEXTIFY,则不要 使具上下文 任何东西。
  3. 在创建的上下文中运行 vm.Script 对象中包含的已编译代码。该代码无法访问调用此方法时的作用域。
  4. 返回结果。

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

【The following example compiles code that sets a global variable, then executes the code multiple times in different contexts. The globals are set on and contained within each individual 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' }]

// This would throw if the context is created from a contextified object.
// vm.constants.DONT_CONTEXTIFY allows creating contexts with ordinary
// global objects that can be frozen.
const freezeScript = new vm.Script('Object.freeze(globalThis); globalThis;');
const frozenContext = freezeScript.runInNewContext(vm.constants.DONT_CONTEXTIFY); 

script.runInThisContext([options])#>

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

在当前 global 对象的上下文中运行 vm.Script 所包含的已编译代码。运行的代码无法访问局部作用域,但 可以 访问当前的 global 对象。

【Runs the compiled code contained by the vm.Script within the context of the current global object. Running code does not have access to local scope, but does have access to the current global object.】

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

【The following example compiles code that increments a global variable then executes that code multiple times:】

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。

【When the script is compiled from a source that contains a source map magic comment, this property will be set to the URL of the source map.】

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#>

【Class: vm.Module

稳定性: 1 - 实验性

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

【This feature is only available with the --experimental-vm-modules command flag enabled.】

vm.Module 类提供了在 VM 上下文中使用 ECMAScript 模块的底层接口。它是 vm.Script 类的对应物,紧密对应于 ECMAScript 规范中定义的 模块记录s。

【The vm.Module class provides a low-level interface for using ECMAScript modules in VM contexts. It is the counterpart of the vm.Script class that closely mirrors Module Records as defined in the ECMAScript specification.】

然而,与 vm.Script 不同,每个 vm.Module 对象从创建之时起就绑定到一个特定的上下文。

【Unlike vm.Script however, every vm.Module object is bound to a context from its creation.】

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

【Using a vm.Module object requires three distinct steps: creation/parsing, linking, and evaluation. These three steps are illustrated in the following example.】

这个实现比 ECMAScript 模块加载器 处于更底层。目前也无法与 Loader 交互,但计划未来支持。

【This implementation lies at a lower level than the ECMAScript Module loader. There is also no way to interact with the Loader yet, though support is planned.】

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 rootModule = new vm.SourceTextModule(`
  import s from 'foo';
  s;
  print(s);
`, { context: contextifiedObject });

// Step 2
//
// "Link" the imported dependencies of this Module to it.
//
// Obtain the requested dependencies of a SourceTextModule by
// `sourceTextModule.moduleRequests` and resolve them.
//
// Even top-level Modules without dependencies must be explicitly linked. The
// array passed to `sourceTextModule.linkRequests(modules)` can be
// empty, however.
//
// Note: This is a contrived example in that the resolveAndLinkDependencies
// 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.

const moduleMap = new Map([
  ['root', rootModule],
]);

function resolveAndLinkDependencies(module) {
  const requestedModules = module.moduleRequests.map((request) => {
    // In a full-fledged module system, the resolveAndLinkDependencies would
    // resolve the module with the module cache key `[specifier, attributes]`.
    // In this example, we just use the specifier as the key.
    const specifier = request.specifier;

    let requestedModule = moduleMap.get(specifier);
    if (requestedModule === undefined) {
      requestedModule = new vm.SourceTextModule(`
        // The "secret" variable refers to the global variable we added to
        // "contextifiedObject" when creating the context.
        export default secret;
      `, { context: module.context });
      moduleMap.set(specifier, requestedModule);
      // Resolve the dependencies of the new module as well.
      resolveAndLinkDependencies(requestedModule);
    }

    return requestedModule;
  });

  module.linkRequests(requestedModules);
}

resolveAndLinkDependencies(rootModule);
rootModule.instantiate();

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

// Prints 42.
await rootModule.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 rootModule = new vm.SourceTextModule(`
    import s from 'foo';
    s;
    print(s);
  `, { context: contextifiedObject });

  // Step 2
  //
  // "Link" the imported dependencies of this Module to it.
  //
  // Obtain the requested dependencies of a SourceTextModule by
  // `sourceTextModule.moduleRequests` and resolve them.
  //
  // Even top-level Modules without dependencies must be explicitly linked. The
  // array passed to `sourceTextModule.linkRequests(modules)` can be
  // empty, however.
  //
  // Note: This is a contrived example in that the resolveAndLinkDependencies
  // 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.

  const moduleMap = new Map([
    ['root', rootModule],
  ]);

  function resolveAndLinkDependencies(module) {
    const requestedModules = module.moduleRequests.map((request) => {
      // In a full-fledged module system, the resolveAndLinkDependencies would
      // resolve the module with the module cache key `[specifier, attributes]`.
      // In this example, we just use the specifier as the key.
      const specifier = request.specifier;

      let requestedModule = moduleMap.get(specifier);
      if (requestedModule === undefined) {
        requestedModule = new vm.SourceTextModule(`
          // The "secret" variable refers to the global variable we added to
          // "contextifiedObject" when creating the context.
          export default secret;
        `, { context: module.context });
        moduleMap.set(specifier, requestedModule);
        // Resolve the dependencies of the new module as well.
        resolveAndLinkDependencies(requestedModule);
      }

      return requestedModule;
    });

    module.linkRequests(requestedModules);
  }

  resolveAndLinkDependencies(rootModule);
  rootModule.instantiate();

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

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

module.error#>

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

【If the module.status is 'errored', this property contains the exception thrown by the module during evaluation. If the status is anything else, accessing this property will result in a thrown exception.】

undefined 不能用于没有抛出异常的情况,因为可能会与 throw undefined; 混淆。

【The value undefined cannot be used for cases where there is not a thrown exception due to possible ambiguity with throw undefined;.】

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

【Corresponds to the [[EvaluationError]] field of Cyclic Module Records in the ECMAScript specification.】

module.evaluate([options])#>

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

评估模块及其依赖。对应 ECMAScript 规范中 循环模块记录Evaluate() 具体方法 字段。

【Evaluate the module and its depenendencies. Corresponds to the Evaluate() concrete method field of Cyclic Module Records in the ECMAScript specification.】

如果模块是 vm.SourceTextModule,必须在模块实例化后调用 evaluate();否则 evaluate() 将返回一个被拒绝的 Promise。

【If the module is a vm.SourceTextModule, evaluate() must be called after the module has been instantiated; otherwise evaluate() will return a rejected promise.】

对于 vm.SourceTextModuleevaluate() 返回的 Promise 可能会同步或异步地被完成:

【For a vm.SourceTextModule, the promise returned by evaluate() may be fulfilled either synchronously or asynchronously:】

  1. 如果 vm.SourceTextModule 本身或其任何依赖中没有顶层 await,则在模块及其所有依赖被评估后,Promise 将会 同步 地被完成。
    1. 如果评估成功,该 Promise 将会同步解析为 undefined
    2. 如果评估结果出现异常,该 promise 将会被 同步 拒绝,并携带导致评估失败的异常,这与 module.error 相同。
  2. 如果 vm.SourceTextModule 本身或其任何依赖中包含顶层 await,则该 Promise 将在模块及其所有依赖被评估后 异步 完成。
    1. 如果评估成功,该 Promise 将被_异步_解析为 undefined
    2. 如果评估结果导致异常,该承诺将被 异步 拒绝,并伴随导致评估失败的异常。

如果该模块是 vm.SyntheticModuleevaluate() 总是返回一个同步完成的 promise,参见 评估()一个合成模块记录 的规范:

【If the module is a vm.SyntheticModule, evaluate() always returns a promise that fulfills synchronously, see the specification of Evaluate() of a Synthetic Module Record:】

  1. 如果传递给其构造函数的 evaluateCallback 同步抛出异常,则 evaluate() 会返回一个会被同步拒绝并带有该异常的 promise。
  2. 如果 evaluateCallback 没有抛出异常,evaluate() 会返回一个将会同步解析为 undefined 的 Promise。

vm.SyntheticModuleevaluateCallback 会在 evaluate() 调用中同步执行,其返回值会被丢弃。这意味着如果 evaluateCallback 是一个异步函数,evaluate() 返回的 Promise 不会反映其异步行为,并且异步 evaluateCallback 中的任何拒绝都会丢失。

【The evaluateCallback of a vm.SyntheticModule is executed synchronously within the evaluate() call, and its return value is discarded. This means if evaluateCallback is an asynchronous function, the promise returned by evaluate() will not reflect its asynchronous behavior, and any rejections from an asynchronous evaluateCallback will be lost.】

evaluate() 在模块已经被评估之后也可以再次调用,在这种情况下:

  1. 如果初始评估成功结束(module.status'evaluated'),它将什么也不做,并返回一个解析为 undefined 的 Promise。
  2. 如果初始评估导致了异常(module.status'errored'),它将重新拒绝初始评估产生的异常。

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

【This method cannot be called while the module is being evaluated (module.status is 'evaluating').】

module.identifier#>

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

【The identifier of the current module, as set in the constructor.】

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 

        Per ECMA-262, hosts are expected to trigger an error if an unsupported attribute is present.

      • assert <Object> Alias for extra.attributes.

    • 返回:<vm.Module> | <Promise>

  • 返回:<Promise>

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

【Link module dependencies. This method must be called before evaluation, and can only be called once per module.】

使用 sourceTextModule.linkRequests(modules)sourceTextModule.instantiate() 以同步或异步方式连接模块。

【Use sourceTextModule.linkRequests(modules) and sourceTextModule.instantiate() to link modules either synchronously or asynchronously.】

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

【The function is expected to return a Module object or a Promise that eventually resolves to a Module object. The returned Module must satisfy the following two invariants:】

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

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

【If the returned Module's status is 'unlinked', this method will be recursively called on the returned Module with the same provided linker function.】

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

链接器函数大致对应于 ECMAScript 规范中实现定义的 主机解析导入模块 抽象操作,但有一些关键差异:

【The linker function roughly corresponds to the implementation-defined HostResolveImportedModule abstract operation in the ECMAScript specification, with a few key differences:】

在模块链接期间使用的实际 主机解析导入模块 实现会返回在链接过程中被链接的模块。由于在那时所有模块都已经完全链接,因此 主机解析导入模块 实现根据规范是完全同步的。

【The actual HostResolveImportedModule implementation used during module linking is one that returns the modules linked during linking. Since at that point all modules would have been fully linked already, the HostResolveImportedModule implementation is fully synchronous per specification.】

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

【Corresponds to the Link() concrete method field of Cyclic Module Records in the ECMAScript specification.】

module.namespace#>

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

【The namespace object of the module. This is only available after linking (module.link()) has completed.】

对应于 ECMAScript 规范中的 获取模块命名空间 抽象操作。

【Corresponds to the GetModuleNamespace abstract operation in the ECMAScript specification.】

module.status#>

模块的当前状态。可能为以下之一:

【The current status of the module. Will be one of:】

  • '未链接':尚未调用 module.link()
  • 'linking'module.link() 已被调用,但链接器函数返回的所有 Promise 尚未全部解决。
  • 'linked':模块已成功链接,且其所有依赖也已链接,但尚未调用 module.evaluate()
  • 'evaluating':该模块正在通过对自身或父模块调用 module.evaluate() 进行评估。
  • 'evaluated':模块已成功评估。
  • 'errored':该模块已被评估,但抛出了一个异常。

除了 'errored',这个状态字符串对应规范中 循环模块记录[[Status]] 字段。'errored' 对应规范中的 'evaluated',但 [[EvaluationError]] 被设置为一个不为 undefined 的值。

【Other than 'errored', this status string corresponds to the specification's Cyclic Module Record's [[Status]] field. 'errored' corresponds to 'evaluated' in the specification, but with [[EvaluationError]] set to a value that is not undefined.】

类:vm.SourceTextModule#>

【Class: vm.SourceTextModule

稳定性: 1 - 实验性

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

【This feature is only available with the --experimental-vm-modules command flag enabled.】

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

【The vm.SourceTextModule class provides the Source Text Module Record as defined in the ECMAScript specification.】

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

  • code <string> 解析 JavaScript 模块的代码
  • options
    • identifier <string> 用于堆栈跟踪的字符串。 默认值: 'vm:module(i)',其中 i 是上下文特定的递增索引。
    • cachedData <Buffer> | <TypedArray> | <DataView> 提供可选的 BufferTypedArray,或带有 V8 代码缓存数据的 DataView,用于提供的源代码。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 实例。

【Creates a new SourceTextModule instance.】

分配给 import.meta 对象的属性如果是对象,可能允许模块访问指定 context 之外的信息。使用 vm.runInContext() 可以在特定上下文中创建对象。

【Properties assigned to the import.meta object that are objects may allow the module to access information outside the specified context. Use vm.runInContext() to create objects in a specific context.】

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 = {};
    },
  });
// The module has an empty `moduleRequests` array.
module.linkRequests([]);
module.instantiate();
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 = {};
      },
    });
  // The module has an empty `moduleRequests` array.
  module.linkRequests([]);
  module.instantiate();
  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。在模块被执行之前,此方法可以调用任意次。

【Creates a code cache that can be used with the SourceTextModule constructor's cachedData option. Returns a Buffer. This method may be called any number of times before the module has been evaluated.】

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

【The code cache of the SourceTextModule doesn't contain any JavaScript observable states. The code cache is safe to be saved along side the script source and used to construct new SourceTextModule instances multiple times.】

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

【Functions in the SourceTextModule source can be marked as lazily compiled and they are not compiled at construction of the SourceTextModule. These functions are going to be compiled when they are invoked the first time. The code cache serializes the metadata that V8 currently knows about the SourceTextModule that it can use to speed up future compilations.】

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

sourceTextModule.dependencySpecifiers#>

该模块所有依赖的说明符。返回的数组是冻结的,不能对其进行任何更改。

【The specifiers of all dependencies of this module. The returned array is frozen to disallow any changes to it.】

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

【Corresponds to the [[RequestedModules]] field of Cyclic Module Records in the ECMAScript specification.】

sourceTextModule.hasAsyncGraph()#>

遍历依赖图,如果其依赖中的任何模块或该模块本身包含顶层 await 表达式,则返回 true,否则返回 false

【Iterates over the dependency graph and returns true if any module in its dependencies or this module itself contains top-level await expressions, otherwise returns false.】

如果图足够大,搜索速度可能会很慢。

【The search may be slow if the graph is big enough.】

这要求先实例化该模块。如果模块尚未实例化,将会抛出错误。

【This requires the module to be instantiated first. If the module is not instantiated yet, an error will be thrown.】

sourceTextModule.hasTopLevelAwait()#>

返回模块本身是否包含任何顶层 await 表达式。

【Returns whether the module itself contains any top-level await expressions.】

这对应于 ECMAScript 规范中 循环模块记录 的字段 [[HasTLA]]

【This corresponds to the field [[HasTLA]] in Cyclic Module Record in the ECMAScript specification.】

sourceTextModule.instantiate()#>

使用链接的请求模块实例化模块。

【Instantiate the module with the linked requested modules.】

这会解析模块的导入绑定,包括重新导出的绑定名称。当存在无法解析的绑定时,将会同步抛出错误。

【This resolves the imported bindings of the module, including re-exported binding names. When there are any bindings that cannot be resolved, an error would be thrown synchronously.】

如果请求的模块包含循环依赖,则必须在调用此方法之前,对循环中的所有模块调用 sourceTextModule.linkRequests(modules) 方法。

【If the requested modules include cyclic dependencies, the sourceTextModule.linkRequests(modules) method must be called on all modules in the cycle before calling this method.】

sourceTextModule.linkRequests(modules)#>

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

【Link module dependencies. This method must be called before evaluation, and can only be called once per module.】

modules 数组中模块实例的顺序应对应于 sourceTextModule.moduleRequests 被解析的顺序。如果两个模块请求具有相同的说明符和导入属性,它们必须使用相同的模块实例进行解析,否则将抛出 ERR_MODULE_LINK_MISMATCH 错误。例如,在链接此模块的请求时:

【The order of the module instances in the modules array should correspond to the order of sourceTextModule.moduleRequests being resolved. If two module requests have the same specifier and import attributes, they must be resolved with the same module instance or an ERR_MODULE_LINK_MISMATCH would be thrown. For example, when linking requests for this module:】

import foo from 'foo';
import source Foo from 'foo'; 

modules 数组必须包含对同一个实例的两个引用,因为这两个模块请求是相同的,但处于两个不同的阶段。

【The modules array must contain two references to the same instance, because the two module requests are identical but in two phases.】

如果该模块没有依赖,modules 数组可以为空。

【If the module has no dependencies, the modules array can be empty.】

用户可以使用 sourceTextModule.moduleRequests 来实现 ECMAScript 规范中主机定义的 主机加载导入模块 抽象操作,并使用 sourceTextModule.linkRequests() 调用规范中定义的 完成加载导入的模块,以批量处理包含所有依赖的模块。

【Users can use sourceTextModule.moduleRequests to implement the host-defined HostLoadImportedModule abstract operation in the ECMAScript specification, and using sourceTextModule.linkRequests() to invoke specification defined FinishLoadingImportedModule, on the module with all dependencies in a batch.】

SourceTextModule 的创建者决定依赖的解析是同步还是异步。

【It's up to the creator of the SourceTextModule to determine if the resolution of the dependencies is synchronous or asynchronous.】

在链接 modules 数组中的每个模块后,调用 sourceTextModule.instantiate()

【After each module in the modules array is linked, call sourceTextModule.instantiate().】

sourceTextModule.moduleRequests#>

  • 类型:ModuleRequest[] 该模块的依赖。

此模块所需的导入依赖。返回的数组是冻结的,不允许对其进行任何更改。

【The requested import dependencies of this module. The returned array is frozen to disallow any changes to it.】

例如,给定一个源文本:

【For example, given a source text:】

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 的值将是:

【The value of the sourceTextModule.moduleRequests will be:】

[
  {
    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#>

【Class: vm.SyntheticModule

稳定性: 1 - 实验性

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

【This feature is only available with the --experimental-vm-modules command flag enabled.】

vm.SyntheticModule 类提供了 WebIDL 规范中定义的 合成模块记录。合成模块的目的是为将非 JavaScript 源暴露给 ECMAScript 模块图提供通用接口。

【The vm.SyntheticModule class provides the Synthetic Module Record as defined in the WebIDL specification. The purpose of synthetic modules is to provide a generic interface for exposing non-JavaScript sources to ECMAScript module graphs.】

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 实例。

【Creates a new SyntheticModule instance.】

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

【Objects assigned to the exports of this instance may allow importers of the module to access information outside the specified context. Use vm.runInContext() to create objects in a specific context.】

syntheticModule.setExport(name, value)#>

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

此方法使用给定值设置模块的导出绑定槽。

【This method sets the module export binding slots with the given value.】

import vm from 'node:vm';

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

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.evaluate();
  assert.strictEqual(m.namespace.x, 1);
})();

类型:ModuleRequest#>

【Type: ModuleRequest

ModuleRequest 表示带有特定导入属性和阶段的模块导入请求。

【A ModuleRequest represents the request to import a module with given import attributes and phase.】

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

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

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

【Compiles the given code into the provided context (if no context is supplied, the current context is used), and returns it wrapped inside a function with the given params.】

vm.constants#>

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

【Returns an object containing commonly used constants for VM operations.】

vm.constants.USE_MAIN_CONTEXT_DEFAULT_LOADER#>

稳定性: 1.1 - 处于活跃开发中

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

【A constant that can be used as the importModuleDynamically option to vm.Script and vm.compileFunction() so that Node.js uses the default ESM loader from the main context to load the requested module.】

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

【For detailed information, see Support of dynamic import() in compilation APIs.】

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

  • contextObject <Object> | <undefined> 可以是 vm.constants.DONT_CONTEXTIFY 或者将被 情境化 的对象。如果为 undefined,将会创建一个空的 contextified 对象以保证向后兼容性。
  • options <Object>
    • name <string> 新创建上下文的人类可读名称。 默认值: 'VM Context i',其中 i 是已创建上下文的递增数字索引。
    • origin <string> 起源 对应于新创建的用于显示的上下文。origin 应该像 URL 一样格式化,但只包含方案、主机和端口(如有必要),类似于 URL 对象的 url.origin 属性的值。最重要的是,该字符串应省略末尾的斜杠,因为斜杠表示路径。默认值: ''
    • codeGeneration <Object>
      • strings <boolean> 如果设置为 false,则对 eval 或函数构造器(FunctionGeneratorFunction 等)的任何调用都会抛出 EvalError默认值: true
      • wasm <boolean> 如果设置为 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> 已上下文化的对象。

如果给定的 contextObject 是一个对象,vm.createContext() 方法将 准备那个对象 并返回它的引用,以便可以在调用 vm.runInContext()script.runInContext() 时使用。在这些脚本内部,全局对象将被 contextObject 封装,保留其所有现有属性,同时还拥有任何标准 全局对象 的内置对象和函数。在由 vm 模块运行的脚本之外,全局变量将保持不变。

【If the given contextObject is an object, the vm.createContext() method will prepare that object and return a reference to it so that it can be used in calls to vm.runInContext() or script.runInContext(). Inside such scripts, the global object will be wrapped by the contextObject, retaining all of its existing properties but also having the built-in objects and functions any standard global object has. Outside of scripts run by the vm module, global variables will remain unchanged.】

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),将返回一个新的、空的 情境化 对象。

【If contextObject is omitted (or passed explicitly as undefined), a new, empty contextified object will be returned.】

当新创建的上下文中的全局对象是 情境化 时,与普通全局对象相比,它有一些特殊情况。例如,它无法被冻结。要创建没有这些上下文特殊情况的上下文,请将 vm.constants.DONT_CONTEXTIFY 作为 contextObject 参数传入。详情请参阅 vm.constants.DONT_CONTEXTIFY 的文档。

【When the global object in the newly created context is contextified, it has some quirks compared to ordinary global objects. For example, it cannot be frozen. To create a context without the contextifying quirks, pass vm.constants.DONT_CONTEXTIFY as the contextObject argument. See the documentation of vm.constants.DONT_CONTEXTIFY for details.】

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

【The vm.createContext() method is primarily useful for creating a single context that can be used to run multiple scripts. For instance, if emulating a web browser, the method can be used to create a single context representing a window's global object, then run all <script> tags together within that context.】

通过检查器 API,可以看到所提供的上下文的 nameorigin

【The provided name and origin of the context are made visible through the Inspector API.】

vm.isContext(object)#>

如果给定的 object 对象已使用 vm.createContext() 进行过 情境化,或者它是使用 vm.constants.DONT_CONTEXTIFY 创建的上下文的全局对象,则返回 true

【Returns true if the given object object has been contextified using vm.createContext(), or if it's the global object of a context created using vm.constants.DONT_CONTEXTIFY.】

vm.measureMemory([options])#>

稳定性: 1 - 实验性

测量 V8 所知的内存,以及当前 V8 isolate 或主上下文中所有已知上下文使用的内存。

【Measure the memory known to V8 and used by all contexts known to the current V8 isolate, or the main context.】

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

返回的 Promise 可能解析的对象格式是 V8 引擎特有的,并且可能会随着 V8 的不同版本而变化。

【The format of the object that the returned Promise may resolve with is specific to the V8 engine and may change from one version of V8 to the next.】

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

【The returned result is different from the statistics returned by v8.getHeapSpaceStatistics() in that vm.measureMemory() measure the memory reachable by each V8 specific contexts in the current instance of the V8 engine, while the result of v8.getHeapSpaceStatistics() measure the memory occupied by each heap space in the current V8 instance.】

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,接收到 SIGINTCtrl+C)将终止执行并抛出一个 Error。在脚本执行期间,通过 process.on('SIGINT') 附加的现有事件处理程序将被禁用,但在脚本执行后仍然继续工作。默认值: false
    • cachedData <Buffer> | <TypedArray> | <DataView> 提供了一个可选的 BufferTypedArrayDataView,其中包含所提供源代码的 V8 代码缓存数据。
    • importModuleDynamically <Function> | <vm.constants.USE_MAIN_CONTEXT_DEFAULT_LOADER> 用于指定在调用 import() 时,该脚本在评估过程中模块应如何加载。此选项是实验性模块 API 的一部分。我们不建议在生产环境中使用它。有关详细信息,请参见 在编译 API 中支持动态 import()

vm.runInContext() 方法会编译 code,在 contextifiedObject 的上下文中运行它,然后返回结果。运行的代码无法访问本地作用域。contextifiedObject 对象必须之前使用 vm.createContext() 方法被 情境化

【The vm.runInContext() method compiles code, runs it within the context of the contextifiedObject, then returns the result. Running code does not have access to the local scope. The contextifiedObject object must have been previously contextified using the vm.createContext() method.】

如果 options 是字符串,那么它指定文件名。

【If options is a string, then it specifies the filename.】

以下示例使用单个 情境化 对象编译并执行不同的脚本:

【The following example compiles and executes different scripts using a single contextified object:】

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> 可以是 vm.constants.DONT_CONTEXTIFY 或者将被 情境化 的对象。如果为 undefined,将会创建一个空的 contextified 对象以保证向后兼容性。
  • 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,接收到 SIGINTCtrl+C)将终止执行并抛出一个 Error。在脚本执行期间,通过 process.on('SIGINT') 附加的现有事件处理程序将被禁用,但在脚本执行后仍然继续工作。默认值: false
    • contextName <string> 新创建上下文的人类可读名称。 默认值: 'VM Context i',其中 i 是创建的上下文的递增数字索引。
    • contextOrigin <string> 起源 对应于新创建的上下文以用于显示目的。该来源应格式化为类似 URL 的形式,但仅包含方案、主机和端口(如有必要),类似于 URL 对象的 url.origin 属性的值。最重要的是,该字符串应省略尾部的斜杠,因为斜杠表示路径。默认值: ''
    • contextCodeGeneration <Object>
      • strings <boolean> 如果设置为 false,则对 eval 或函数构造器(FunctionGeneratorFunction 等)的任何调用都会抛出 EvalError默认值: true
      • wasm <boolean> 如果设置为 false,任何尝试编译 WebAssembly 模块的操作都会抛出 WebAssembly.CompileError默认值: true
    • cachedData <Buffer> | <TypedArray> | <DataView> 提供了一个可选的 BufferTypedArrayDataView,其中包含所提供源代码的 V8 代码缓存数据。
    • importModuleDynamically <Function> | <vm.constants.USE_MAIN_CONTEXT_DEFAULT_LOADER> 用于指定在调用 import() 时,该脚本在评估过程中模块应如何加载。此选项是实验性模块 API 的一部分。我们不建议在生产环境中使用它。有关详细信息,请参见 在编译 API 中支持动态 import()
    • microtaskMode <string> 如果设置为 afterEvaluate,微任务(通过 Promiseasync function 调度的任务)将在脚本运行后立即执行。在这种情况下,它们会包含在 timeoutbreakOnSigint 的作用域中。
  • 返回:<any> 脚本中最后执行的语句的结果。

此方法是 (new vm.Script(code, options)).runInContext(vm.createContext(options), options) 的快捷方式。如果 options 是字符串,则它指定文件名。

【This method is a shortcut to (new vm.Script(code, options)).runInContext(vm.createContext(options), options). If options is a string, then it specifies the filename.】

它同时做几件事:

【It does several things at once:】

  1. 创建一个新的上下文。
  2. 如果 contextObject 是一个对象,用新的上下文 使具上下文 它。如果 contextObject 未定义,则创建一个新对象并 使具上下文 它。如果 contextObjectvm.constants.DONT_CONTEXTIFY,则不要 使具上下文 任何东西。
  3. 将代码编译为 vm.Script
  4. 在创建的上下文中运行编译后的代码。代码无法访问调用此方法的作用域。
  5. 返回结果。

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

【The following example compiles and executes code that increments a global variable and sets a new one. These globals are contained in the 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' }

// This would throw if the context is created from a contextified object.
// vm.constants.DONT_CONTEXTIFY allows creating contexts with ordinary global objects that
// can be frozen.
const frozenContext = vm.runInNewContext('Object.freeze(globalThis); globalThis;', vm.constants.DONT_CONTEXTIFY); 

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,接收到 SIGINTCtrl+C)将终止执行并抛出一个 Error。在脚本执行期间,通过 process.on('SIGINT') 附加的现有事件处理程序将被禁用,但在脚本执行后仍然继续工作。默认值: false
    • cachedData <Buffer> | <TypedArray> | <DataView> 提供了一个可选的 BufferTypedArrayDataView,其中包含所提供源代码的 V8 代码缓存数据。
    • importModuleDynamically <Function> | <vm.constants.USE_MAIN_CONTEXT_DEFAULT_LOADER> 用于指定在调用 import() 时,该脚本在评估过程中模块应如何加载。此选项是实验性模块 API 的一部分。我们不建议在生产环境中使用它。有关详细信息,请参见 在编译 API 中支持动态 import()
  • 返回:<any> 脚本中最后执行的语句的结果。

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

如果 options 是字符串,那么它指定文件名。

【If options is a string, then it specifies the filename.】

下面的示例说明了如何同时使用 vm.runInThisContext() 和 JavaScript 的 eval() 函数来运行相同的代码:

【The following example illustrates using both vm.runInThisContext() and the JavaScript eval() function to run the same code:】

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')

【Because vm.runInThisContext() does not have access to the local scope, localVar is unchanged. In contrast, a direct eval() call does have access to the local scope, so the value localVar is changed. In this way vm.runInThisContext() is much like an indirect eval() call, e.g. (0,eval)('code').】

示例:在虚拟机中运行 HTTP 服务器#>

【Example: Running an HTTP server within a VM】

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

【When using either script.runInThisContext() or vm.runInThisContext(), the code is executed within the current V8 global context. The code passed to this VM context will have its own isolated scope.】

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

【In order to run a simple web server using the node:http module the code passed to the context must either call require('node:http') on its own, or have a reference to the node:http module passed to it. For instance:】

'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() 与传入它的上下文共享状态。当执行不受信任的代码时,这可能会引入风险,例如以不希望的方式修改上下文中的对象。

【The require() in the above case shares the state with the context it is passed from. This may introduce risks when untrusted code is executed, e.g. altering objects in the context in unwanted ways.】

给一个对象“上下文化”是什么意思?#>

【What does it mean to "contextify" an object?】

在 Node.js 中执行的所有 JavaScript 都运行在一个“上下文”的范围内。 根据 V8 嵌入指南

【All JavaScript executed within Node.js runs within the scope of a "context". According to the V8 Embedder's Guide:】

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

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

【When the method vm.createContext() is called with an object, the contextObject argument will be used to wrap the global object of a new instance of a V8 Context (if contextObject is undefined, a new object will be created from the current context before its contextified). This V8 Context provides the code run using the node:vm module's methods with an isolated global environment within which it can operate. The process of creating the V8 Context and associating it with the contextObject in the outer context is what this document refers to as "contextifying" the object.】

上下文化会给上下文中的 globalThis 值引入一些特点。例如,它不能被冻结,并且与外部上下文中的 contextObject 在引用上不相等。

【The contextifying would introduce some quirks to the globalThis value in the context. For example, it cannot be frozen, and it is not reference equal to the contextObject in the outer context.】

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

// An undefined `contextObject` option makes the global object contextified.
const context = vm.createContext();
console.log(vm.runInContext('globalThis', context) === context);  // false
// A contextified global object cannot be frozen.
try {
  vm.runInContext('Object.freeze(globalThis);', context);
} catch (e) {
  console.log(e); // TypeError: Cannot freeze
}
console.log(vm.runInContext('globalThis.foo = 1; foo;', context));  // 1 

要创建一个带有普通全局对象的上下文,并在外部上下文中以更少的怪癖访问全局代理,请将 vm.constants.DONT_CONTEXTIFY 指定为 contextObject 参数。

【To create a context with an ordinary global object and get access to a global proxy in the outer context with fewer quirks, specify vm.constants.DONT_CONTEXTIFY as the contextObject argument.】

vm.constants.DONT_CONTEXTIFY#>

当在 vm API 中将此常量作为 contextObject 参数使用时,会指示 Node.js 创建一个上下文,而不会以 Node.js 特有的方式将其全局对象封装在另一个对象中。因此,新上下文中的 globalThis 值的行为将更接近普通对象。

【This constant, when used as the contextObject argument in vm APIs, instructs Node.js to create a context without wrapping its global object with another object in a Node.js-specific manner. As a result, the globalThis value inside the new context would behave more closely to an ordinary one.】

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

// Use vm.constants.DONT_CONTEXTIFY to freeze the global object.
const context = vm.createContext(vm.constants.DONT_CONTEXTIFY);
vm.runInContext('Object.freeze(globalThis);', context);
try {
  vm.runInContext('bar = 1; bar;', context);
} catch (e) {
  console.log(e); // Uncaught ReferenceError: bar is not defined
} 

vm.constants.DONT_CONTEXTIFY 作为 contextObject 参数传递给 vm.createContext() 时,返回的对象是新创建上下文中全局对象的类似代理的对象,具有更少的 Node.js 特有问题。它与新上下文中的 globalThis 值相同,可以从上下文外部修改,并且可以直接用于访问新上下文中的内置对象。

【When vm.constants.DONT_CONTEXTIFY is used as the contextObject argument to vm.createContext(), the returned object is a proxy-like object to the global object in the newly created context with fewer Node.js-specific quirks. It is reference equal to the globalThis value in the new context, can be modified from outside the context, and can be used to access built-ins in the new context directly.】

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

const context = vm.createContext(vm.constants.DONT_CONTEXTIFY);

// Returned object is reference equal to globalThis in the new context.
console.log(vm.runInContext('globalThis', context) === context);  // true

// Can be used to access globals in the new context directly.
console.log(context.Array);  // [Function: Array]
vm.runInContext('foo = 1;', context);
console.log(context.foo);  // 1
context.bar = 1;
console.log(vm.runInContext('bar;', context));  // 1

// Can be frozen and it affects the inner context.
Object.freeze(context);
try {
  vm.runInContext('baz = 1; baz;', context);
} catch (e) {
  console.log(e); // Uncaught ReferenceError: baz is not defined
} 

与异步任务和 Promises 的超时交互#>

【Timeout interactions with asynchronous tasks and Promises】

Promiseasync function 可以让 JavaScript 引擎异步调度任务。默认情况下,这些任务会在当前调用栈上的所有 JavaScript 函数执行完毕后运行。这使得可以绕过 timeoutbreakOnSigint 选项的功能。

例如,以下由 vm.runInNewContext() 执行的代码,在 5 毫秒的超时时间内,会在一个 promise 解决后安排一个无限循环运行。这个被安排的循环永远不会被超时中断:

【For example, the following code executed by vm.runInNewContext() with a timeout of 5 milliseconds schedules an infinite loop to run after a promise resolves. The scheduled loop is never interrupted by the timeout:】

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'); 

可以通过向创建 Context 的代码传递 microtaskMode: 'afterEvaluate' 来解决这个问题:

【This can be addressed by passing microtaskMode: 'afterEvaluate' to the code that creates the 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() 不支持此选项。

【In this case, the microtask scheduled through promise.then() will be run before returning from vm.runInNewContext(), and will be interrupted by the timeout functionality. This applies only to code running in a vm.Context, so e.g. vm.runInThisContext() does not take this option.】

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

【Promise callbacks are entered into the microtask queue of the context in which they were created. For example, if () => loop() is replaced with just loop in the above example, then loop will be pushed into the global microtask queue, because it is a function from the outer (main) context, and thus will also be able to escape the timeout.】

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

【If asynchronous scheduling functions such as process.nextTick(), queueMicrotask(), setTimeout(), setImmediate(), etc. are made available inside a vm.Context, functions passed to them will be added to global queues, which are shared by all contexts. Therefore, callbacks passed to those functions are not controllable through the timeout either.】

microtaskMode'afterEvaluate' 时,注意不要在不同上下文之间共享 Promise#>

【When microtaskMode is 'afterEvaluate', beware sharing Promises between Contexts】

'afterEvaluate' 模式下,Context 拥有自己的微任务队列,独立于外部(主)上下文使用的全局微任务队列。虽然这种模式对于在异步任务中强制 timeout 和启用 breakOnSigint 是必要的,但它也使跨上下文共享 promise 变得具有挑战性。

【In 'afterEvaluate' mode, the Context has its own microtask queue, separate from the global microtask queue used by the outer (main) context. While this mode is necessary to enforce timeout and enable breakOnSigint with asynchronous tasks, it also makes sharing promises between contexts challenging.】

在下面的示例中,一个 promise 在内部上下文中创建,并与外部上下文共享。当外部上下文对该 promise 使用 await 时,外部上下文的执行流程以一种令人意外的方式被打断:日志语句从未被执行。

【In the example below, a promise is created in the inner context and shared with the outer context. When the outer context await on the promise, the execution flow of the outer context is disrupted in a surprising way: the log statement is never executed.】

import * as vm from 'node:vm';

const inner_context = vm.createContext({}, { microtaskMode: 'afterEvaluate' });

// runInContext() returns a Promise created in the inner context.
const inner_promise = vm.runInContext(
  'Promise.resolve()',
  context,
);

// As part of performing `await`, the JavaScript runtime must enqueue a task
// on the microtask queue of the context where `inner_promise` was created.
// A task is added on the inner microtask queue, but **it will not be run
// automatically**: this task will remain pending indefinitely.
//
// Since the outer microtask queue is empty, execution in the outer module
// falls through, and the log statement below is never executed.
await inner_promise;

console.log('this will NOT be printed'); 

要在具有不同微任务队列的上下文之间成功共享 Promise,必须确保每当外部上下文在内部微任务队列中入队一个任务时,内部微任务队列的任务都会被执行。

【To successfully share promises between contexts with different microtask queues, it is necessary to ensure that tasks on the inner microtask queue will be run whenever the outer context enqueues a task on the inner microtask queue.】

在给定上下文的微任务队列中的任务,会在对使用该上下文的脚本或模块调用 runInContext()SourceTextModule.evaluate() 时运行。在我们的示例中,可以通过在 await inner_promise 之前安排第二次调用 runInContext() 来恢复正常的执行流程。

【The tasks on the microtask queue of a given context are run whenever runInContext() or SourceTextModule.evaluate() are invoked on a script or module using this context. In our example, the normal execution flow can be restored by scheduling a second call to runInContext() before await inner_promise.】

// Schedule `runInContext()` to manually drain the inner context microtask
// queue; it will run after the `await` statement below.
setImmediate(() => {
  vm.runInContext('', context);
});

await inner_promise;

console.log('OK'); 

**注意:**严格来说,在此模式下,node:vm 与 ECMAScript 对 排队作业 的规范字面意义有所出入,它允许来自不同上下文的异步任务以与入队顺序不同的顺序执行。

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

【Support of dynamic import() in compilation APIs】

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

【The following APIs support an importModuleDynamically option to enable dynamic import() in code compiled by the vm module.】

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

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

【This option is still part of the experimental modules API. We do not recommend using it in a production environment.】

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

【When the importModuleDynamically option is not specified or undefined】

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

【If this option is not specified, or if it's undefined, code containing import() can still be compiled by the vm APIs, but when the compiled code is executed and it actually calls import(), the result will reject with ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING.】

importModuleDynamicallyvm.constants.USE_MAIN_CONTEXT_DEFAULT_LOADER#>

【When importModuleDynamically is vm.constants.USE_MAIN_CONTEXT_DEFAULT_LOADER

该选项目前不支持 vm.SourceTextModule

【This option is currently not supported for vm.SourceTextModule.】

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

【With this option, when an import() is initiated in the compiled code, Node.js would use the default ESM loader from the main context to load the requested module and return it to the code being executed.】

这使得被编译的代码可以访问 Node.js 内置模块,例如 fshttp。如果代码在不同的上下文中执行,需要注意的是,从主上下文加载的模块创建的对象仍然来自主上下文,并且在新上下文中并不是内置类的 instanceof 实例。

【This gives access to Node.js built-in modules such as fs or http to the code being compiled. If the code is executed in a different context, be aware that the objects created by modules loaded from the main context are still from the main context and not instanceof built-in classes in the new context.】

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);

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

【This option also allows the script or function to load user modules:】

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);

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

【There are a few caveats with loading user modules using the default loader from the main context:】

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

importModuleDynamically 是一个函数时#>

【When importModuleDynamically is a function】

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

【When importModuleDynamically is a function, it will be invoked when import() is called in the compiled code for users to customize how the requested module should be compiled and evaluated. Currently, the Node.js instance must be launched with the --experimental-vm-modules flag for this option to work. If the flag isn't set, this callback will be ignored. If the code evaluated actually calls to import(), the result will reject with ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING_FLAG.】

回调 importModuleDynamically(specifier, referrer, importAttributes) 的签名如下:

【The callback importModuleDynamically(specifier, referrer, importAttributes) has the following signature:】

  • specifier <string> 传递给 import() 的指定符
  • referrer <vm.Script> | <Function> | <vm.SourceTextModule> | <Object> referrer 是针对 new vm.Scriptvm.runInThisContextvm.runInContextvm.runInNewContext 编译后的 vm.Script。对于 vm.compileFunction 是编译后的 Function,对于 new vm.SourceTextModule 是编译后的 vm.SourceTextModule,对于 vm.createContext() 则是上下文 Object
  • importAttributes <Object> 传递给 optionsExpression 可选参数的 "with" 值,如果未提供值,则为一个空对象。
  • phase <string> 动态导入的阶段("source""evaluation")。
  • 返回值:<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' } }
})();
Node.js 中文网 - 粤ICP备13048890号