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

// 步骤 1
//
// 通过构造新的 `vm.SourceTextModule` 对象来创建模块。
// 这会解析提供的源文本,如果出现任何问题,则抛出 `SyntaxError`。
// 默认情况下,在顶层上下文中创建了模块。
// 但在这里,我们指定 `contextifiedObject` 作为该模块所属的上下文。
//
// 这里,我们尝试从模块 "foo" 中获取默认导出,
// 并将其放入本地绑定的 "secret" 中。

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

// 步骤 2
//
// "Link" 此模块的导入依赖项。
//
// 提供的链接回调(“链接器”)接受两个参数:
// 父模块(在本例中为 `bar`)和作为导入模块的说明符的字符串。 
// 回调应该返回
// 与提供的说明符相对应的模块,
// 在`module.link()` 中记录了某些要求。
//
// 如果返回的模块尚未开始链接,
// 则将在返回的模块上调用相同的链接器回调。
//
// 即使是没有依赖关系的顶级模块也必须明确链接。
// 然而,提供的回调永远不会被调用。
//
// link() 方法返回 Promise,
// 当链接器返回的所有 Promise 都解决时,则该 Promise 将被解决。
//
// 注意:这是一个人为的示例,
// 每次调用链接器函数时都会创建新的 "foo"模块。
// 在成熟的模块系统中,可能会使用缓存来避免重复的模块。

async function linker(specifier, referencingModule) {
  if (specifier === 'foo') {
    return new vm.SourceTextModule(`
      // "secret" 变量指的是我们在创建上下文时
      // 添加到 "contextifiedObject" 中的全局变量。
      export default secret;
    `, { context: referencingModule.context });

    // 在这里使用 `contextifiedObject` 
    // 而不是 `referencingModule.context` 也可以。
  }
  throw new Error(`Unable to resolve dependency: ${specifier}`);
}
await bar.link(linker);

// 步骤 3
//
// 评估模块。
// evaluate() 方法返回 promise,其将在模块完成评估后解决。

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

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

(async () => {
  // 步骤 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 });

  // 步骤 2
  //
  // "Link" 此模块的导入依赖项。
  //
  // 提供的链接回调(“链接器”)接受两个参数: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.
  //
  // link() 方法返回 Promise,
  // 当链接器返回的所有 Promise 都解决时,则该 Promise 将被解决。
  //
  // 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);

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

  // 打印 42。
  await bar.evaluate();
})();

Stability: 1 - Experimental

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

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.

Unlike vm.Script however, every vm.Module object is bound to a context from its creation. Operations on vm.Module objects are intrinsically asynchronous, in contrast with the synchronous nature of vm.Script objects. The use of 'async' functions can help with manipulating vm.Module objects.

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

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