Spec-Zone.ru › Node.js

VM (выполнение JavaScript)

Исходный код: lib/vm.js

Стабильность: 2 — Стабильный

Модуль node:vm позволяет компилировать и выполнять код в контекстах виртуальной машины V8.

Модуль node:vm не является средством обеспечения безопасности. Не используйте его для выполнения недоверенного кода.

Код JavaScript можно скомпилировать и выполнить сразу либо скомпилировать, сохранить и выполнить позже.

Один из распространённых вариантов использования — выполнение кода в другом контексте V8. Это означает, что у вызываемого кода другой глобальный объект, чем у вызывающего кода.

Контекст можно задать, контекстуализировав объект. Вызываемый код рассматривает любое свойство контекста как глобальную переменную. Любые изменения глобальных переменных, вызванные вызываемым кодом, отражаются в объекте контекста.

Модули JavaScript
import { createContext, runInContext } from 'node:vm';

const x = 1;

const context = { x: 2 };
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.
runInContext(code, context);

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

console.log(x); // 1; y is not defined
CommonJS
const { createContext, runInContext } = require('node:vm');

const x = 1;

const context = { x: 2 };
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.
runInContext(code, context);

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

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

Класс: vm.Script

Добавлено в: v0.3.1

Экземпляры класса vm.Script содержат предварительно скомпилированные скрипты, которые можно выполнять в определённых контекстах.

new vm.Script(code[, options])

Добавлено в: v0.3.1
История
Версия Изменения
v21.7.0, v20.12.0

Добавлена поддержка vm.constants.USE_MAIN_CONTEXT_DEFAULT_LOADER.

v17.0.0, v16.12.0

Добавлена поддержка атрибутов импорта для параметра importModuleDynamically.

v10.6.0

Параметр produceCachedData устарел и заменён на script.createCachedData().

v5.7.0

Теперь поддерживаются параметры cachedData и produceCachedData.

  • code <string> Код JavaScript для компиляции.
  • options <Object> | <string>
    • filename <string> Задаёт имя файла, используемое в трассировках стека, создаваемых этим скриптом. По умолчанию: 'evalmachine.<anonymous>'.
    • lineOffset <number> Задаёт смещение номера строки, отображаемое в трассировках стека, создаваемых этим скриптом. По умолчанию: 0.
    • columnOffset <number> Задаёт смещение номера столбца первой строки, отображаемое в трассировках стека, создаваемых этим скриптом. По умолчанию: 0.
    • cachedData <Buffer> | <TypedArray> | <DataView> Предоставляет необязательный Buffer или TypedArray либо DataView с данными кэша кода V8 для указанного исходного кода. Если он указан, значение cachedDataRejected будет установлено в true или false в зависимости от того, приняла ли V8 эти данные.
    • produceCachedData <boolean> Если задано true и параметр cachedData отсутствует, V8 попытается создать данные кэша кода для code. В случае успеха будет создан Buffer с данными кэша кода V8 и сохранён в свойстве cachedData возвращённого экземпляра vm.Script. Значение cachedDataProduced будет установлено в true или false в зависимости от того, удалось ли создать данные кэша кода. Этот параметр устарел и заменён на script.createCachedData(). По умолчанию: false.
    • importModuleDynamically <Function> | <vm.constants.USE_MAIN_CONTEXT_DEFAULT_LOADER> Используется для указания способа загрузки модулей при вычислении этого скрипта, когда вызывается import(). Этот параметр входит в экспериментальный API модулей. Не рекомендуется использовать его в производственной среде. Подробные сведения см. в разделе Поддержка динамического import() в API компиляции.

Если options — строка, она задаёт имя файла.

Создание нового объекта vm.Script компилирует code, но не выполняет его. Скомпилированный vm.Script можно выполнить позже несколько раз. code не привязан к какому-либо глобальному объекту; вместо этого он привязывается перед каждым запуском и только на время этого запуска.

script.cachedDataRejected

Добавлено в: v5.7.0
  • Тип: <boolean> | <undefined>

Если при создании vm.Script указан параметр cachedData, это значение будет установлено в true или false в зависимости от того, приняла ли V8 эти данные. В противном случае значение равно undefined.

script.createCachedData()

Добавлено в: v10.6.0
  • Возвращает: <Buffer>

Создаёт кэш кода, который можно использовать с параметром cachedData конструктора Script. Возвращает 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()`.
jscopy

script.runInContext(contextifiedObject[, options])

Добавлено в: v0.3.1
История
Версия Изменения
v6.3.0

Теперь поддерживается параметр breakOnSigint.

  • 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> результат последнего выполненного в скрипте оператора.

Выполняет скомпилированный код, содержащийся в объекте vm.Script, в заданном contextifiedObject и возвращает результат. Выполняемый код не имеет доступа к локальной области видимости.

В следующем примере компилируется код, который увеличивает глобальную переменную, задаёт значение другой глобальной переменной, а затем выполняется несколько раз. Глобальные переменные содержатся в объекте context.

Модули JavaScript
import { createContext, Script } from 'node:vm';

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

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

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

console.log(context);
// Prints: { animal: 'cat', count: 12, name: 'kitty' }
CommonJS
const { createContext, Script } = require('node:vm');

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

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

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

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

Использование параметров timeout или breakOnSigint приведёт к запуску новых циклов событий и соответствующих потоков, что повлечёт ненулевые накладные расходы на производительность.

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

Добавлено в: v0.3.1
История
Версия Изменения
v22.8.0, v20.18.0

Аргумент contextObject теперь принимает vm.constants.DONT_CONTEXTIFY.

v14.6.0

Теперь поддерживается параметр microtaskMode.

v10.0.0

Теперь поддерживается параметр contextCodeGeneration.

v6.3.0

Теперь поддерживается параметр breakOnSigint.

  • contextObject <Object> | <vm.constants.DONT_CONTEXTIFY> | <undefined> Либо vm.constants.DONT_CONTEXTIFY, либо объект, который будет контекстуализирован. Если задано 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 <Object>
      • strings <boolean> Если задать значение false, любые вызовы eval или конструкторов функций (Function, GeneratorFunction и т. д.) приведут к выбросу EvalError. По умолчанию: true.
      • wasm <boolean> Если задать значение false, любая попытка скомпилировать модуль WebAssembly приведёт к выбросу WebAssembly.CompileError. По умолчанию: true.
    • microtaskMode <string> Если задано значение afterEvaluate, микрозадачи (задачи, запланированные через Promise и async function) будут выполняться сразу после завершения скрипта. В этом случае они включаются в области видимости timeout и breakOnSigint.
  • Возвращает: <any> результат последнего выполненного в скрипте оператора.

Этот метод является сокращённой формой вызова script.runInContext(vm.createContext(options), options). Он выполняет сразу несколько действий:

  1. Создаёт новый контекст.
  2. Если contextObject — объект, он контекстуализируется в новом контексте. Если contextObject не определён, создаётся новый объект и контекстуализируется. Если contextObject — vm.constants.DONT_CONTEXTIFY, ничего не контекстуализируется.
  3. Выполняет скомпилированный код, содержащийся в объекте vm.Script, в созданном контексте. Код не имеет доступа к области видимости, в которой вызван этот метод.
  4. Возвращает результат.

В следующем примере компилируется код, который задаёт глобальную переменную, а затем несколько раз выполняет этот код в разных контекстах. Глобальные переменные задаются в каждом отдельном объекте context и содержатся в нём.

Модули JavaScript
import { constants, Script } from 'node:vm';

const script = new 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.
// constants.DONT_CONTEXTIFY allows creating contexts with ordinary
// global objects that can be frozen.
const freezeScript = new Script('Object.freeze(globalThis); globalThis;');
const frozenContext = freezeScript.runInNewContext(constants.DONT_CONTEXTIFY);
CommonJS
const { constants, Script } = require('node:vm');

const script = new 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.
// constants.DONT_CONTEXTIFY allows creating contexts with ordinary
// global objects that can be frozen.
const freezeScript = new Script('Object.freeze(globalThis); globalThis;');
const frozenContext = freezeScript.runInNewContext(constants.DONT_CONTEXTIFY);

script.runInThisContext([options])

Добавлено в: v0.3.1
История
Версия Изменения
v6.3.0

Теперь поддерживается параметр breakOnSigint.

  • options <Object>
    • displayErrors <boolean> Если задано true и при компиляции code возникает Error, строка кода, вызвавшая ошибку, добавляется в трассировку стека. По умолчанию: true.
    • timeout <integer> Задаёт количество миллисекунд, в течение которых выполняется code до остановки. При остановке выполнения будет выброшено Error. Значение должно быть строго положительным целым числом.
    • breakOnSigint <boolean> Если задано true, получение SIGINT (Ctrl+C) остановит выполнение и приведёт к выбросу Error. Обработчики события, ранее добавленные через process.on('SIGINT'), отключаются на время выполнения скрипта, но продолжают работать после его завершения. По умолчанию: false.
  • Возвращает: <any> результат последнего выполненного в скрипте оператора.

Выполняет скомпилированный код, содержащийся в vm.Script, в контексте текущего объекта global. Выполняемый код не имеет доступа к локальной области видимости, но имеет доступ к текущему объекту global.

В следующем примере компилируется код, который увеличивает переменную global, а затем выполняет этот код несколько раз:

Модули JavaScript
import { Script } from 'node:vm';

global.globalVar = 0;

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

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

console.log(globalVar);

// 1000
CommonJS
const { Script } = require('node:vm');

global.globalVar = 0;

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

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

console.log(globalVar);

// 1000

script.sourceMapURL

Добавлено в: v19.1.0, v18.13.0
  • Тип: <string> | <undefined>

Если скрипт скомпилирован из исходного кода, содержащего специальный комментарий с картой исходного кода, этому свойству будет присвоен URL карты исходного кода.

Модули JavaScript
import vm from 'node:vm';

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

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

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

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

Класс: vm.Module

Добавлено в: v13.0.0, v12.16.0

Стабильность: 1 — Экспериментальный

Эта функция доступна только при включённом флаге команды --experimental-vm-modules.

Класс vm.Module предоставляет низкоуровневый интерфейс для использования модулей ECMAScript в контекстах VM. Он является аналогом класса vm.Script и точно повторяет структуру записей модулей, определённых в спецификации ECMAScript.

Однако, в отличие от vm.Script, каждый объект vm.Module привязан к контексту с момента его создания.

Для использования объекта vm.Module требуются три отдельные шага: создание/разбор, связывание и вычисление. Эти три шага показаны в следующем примере.

Эта реализация находится на более низком уровне, чем загрузчик модулей ECMAScript. Взаимодействовать с загрузчиком пока невозможно, однако такая поддержка запланирована.

Модули JavaScript
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();
CommonJS
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

  • Тип: <any>

Если module.status имеет значение 'errored', это свойство содержит исключение, выброшенное модулем при вычислении. При любом другом состоянии обращение к этому свойству приведёт к выбросу исключения.

Значение undefined нельзя использовать в случаях, когда исключение не выброшено, поскольку его можно спутать с throw undefined;.

Соответствует полю [[EvaluationError]] записей циклических модулей в спецификации ECMAScript.

module.evaluate([options])

  • options <Object>
    • timeout <integer> Задаёт количество миллисекунд для вычисления до остановки выполнения. Если выполнение прервано, будет выброшено Error. Значение должно быть строго положительным целым числом.
    • breakOnSigint <boolean> Если задано true, получение SIGINT (Ctrl+C) остановит выполнение и приведёт к выбросу Error. Обработчики события, ранее добавленные через process.on('SIGINT'), отключаются на время выполнения скрипта, но продолжают работать после его завершения. По умолчанию: false.
  • Возвращает: <Promise> При успешном выполнении разрешается со значением undefined.

Вычисляет модуль и его зависимости. Соответствует конкретному методу Evaluate() записей циклических модулей в спецификации ECMAScript.

Если модуль является vm.SourceTextModule, evaluate() необходимо вызвать после инстанцирования модуля; в противном случае evaluate() вернёт отклонённый промис.

Для vm.SourceTextModule промис, возвращаемый evaluate(), может быть выполнен синхронно или асинхронно:

  1. Если vm.SourceTextModule не содержит await верхнего уровня ни в самом модуле, ни в его зависимостях, промис будет выполнен синхронно после вычисления модуля и всех его зависимостей.
    1. Если вычисление завершится успешно, промис будет синхронно разрешён со значением undefined.
    2. Если вычисление приведёт к исключению, промис будет синхронно отклонён с исключением, из-за которого вычисление завершилось неудачно; это то же исключение, что и module.error.
  2. Если vm.SourceTextModule содержит await верхнего уровня в самом модуле или в любой из его зависимостей, промис будет выполнен асинхронно после вычисления модуля и всех его зависимостей.
    1. Если вычисление завершится успешно, промис будет асинхронно разрешён со значением undefined.
    2. Если вычисление приведёт к исключению, промис будет асинхронно отклонён с исключением, из-за которого вычисление завершилось неудачно.

Если модуль является vm.SyntheticModule, evaluate() всегда возвращает промис, который выполняется синхронно. См. спецификацию метода Evaluate() для записи синтетического модуля:

  1. Если evaluateCallback, переданный конструктору, синхронно выбрасывает исключение, evaluate() возвращает промис, который будет синхронно отклонён с этим исключением.
  2. Если evaluateCallback не выбрасывает исключение, evaluate() возвращает промис, который будет синхронно разрешён со значением undefined.

evaluateCallback объекта vm.SyntheticModule выполняется синхронно в рамках вызова evaluate(), а возвращаемое им значение отбрасывается. Это означает, что если evaluateCallback — асинхронная функция, промис, возвращаемый evaluate(), не будет отражать её асинхронное поведение, а любые отклонения асинхронного evaluateCallback будут потеряны.

evaluate() также можно вызвать повторно после вычисления модуля. В этом случае:

  1. Если начальное вычисление завершилось успешно (module.status имеет значение 'evaluated'), метод ничего не сделает и вернёт промис, разрешающийся со значением undefined.
  2. Если начальное вычисление привело к исключению (module.status имеет значение 'errored'), метод повторно отклонит промис с исключением, возникшим при начальном вычислении.

Этот метод нельзя вызывать во время вычисления модуля (module.status имеет значение 'evaluating').

module.identifier

  • Тип: <string>

Идентификатор текущего модуля, заданный в конструкторе.

module.link(linker)

История
Версия Изменения
v21.1.0, v20.10.0, v18.19.0

Параметр extra.assert переименован в extra.attributes. Прежнее имя по-прежнему поддерживается для обратной совместимости.

  • linker <Function>
    • specifier <string> Спецификатор запрашиваемого модуля:

      import foo from 'foo';
      //              ^^^^^ the module specifier
      mjscopy
    • referencingModule <vm.Module> Объект Module, для которого вызывается link().

    • extra <Object>

      • attributes <Object> Данные из атрибута:
        import foo from 'foo' with { name: 'value' };
        //                         ^^^^^^^^^^^^^^^^^ the attribute
        mjscopy
        Согласно ECMA-262, ожидается, что хосты вызовут ошибку, если присутствует неподдерживаемый атрибут.
      • assert <Object> Псевдоним для extra.attributes.
    • Возвращает: <vm.Module> | <Promise>

  • Возвращает: <Promise>

Связывает зависимости модуля. Этот метод необходимо вызвать до оценки, и его можно вызвать только один раз для каждого модуля.

Используйте sourceTextModule.linkRequests(modules) и sourceTextModule.instantiate(), чтобы связывать модули синхронно или асинхронно.

Ожидается, что функция вернет объект Module или Promise, который в итоге разрешится в объект Module. Возвращенный Module должен удовлетворять следующим двум инвариантам:

  • Он должен принадлежать тому же контексту, что и родительский Module.
  • Его status не должен быть 'errored'.

Если status возвращенного Module равен 'unlinked', этот метод будет рекурсивно вызван для возвращенного Module с той же предоставленной функцией linker.

link() возвращает Promise, который будет разрешен, когда все экземпляры, участвующие в связывании, разрешатся в корректный Module, или отклонен, если функция связывания выбросит исключение либо вернет некорректный Module.

Функция связывания приблизительно соответствует абстрактной операции HostResolveImportedModule, определяемой реализацией в спецификации ECMAScript, с несколькими важными отличиями:

  • Функция связывания может быть асинхронной, тогда как HostResolveImportedModule является синхронной.

Фактическая реализация HostResolveImportedModule, используемая при связывании модулей, возвращает модули, связанные в процессе связывания. Поскольку к этому моменту все модули уже полностью связаны, согласно спецификации реализация HostResolveImportedModule полностью синхронна.

Соответствует полю конкретного метода Link() записей Cyclic Module Record в спецификации ECMAScript.

module.namespace

  • Тип: <Object>

Объект пространства имен модуля. Он доступен только после завершения связывания (module.link()).

Соответствует абстрактной операции GetModuleNamespace в спецификации ECMAScript.

module.status

  • Тип: <string>

Текущий статус модуля. Может принимать одно из следующих значений:

  • 'unlinked': вызов module.link() еще не выполнялся.

  • 'linking': вызов module.link() выполнен, но еще не разрешились все промисы, возвращенные функцией связывания.

  • 'linked': модуль успешно связан, и все его зависимости связаны, но вызов module.evaluate() еще не выполнялся.

  • 'evaluating': модуль оценивается через module.evaluate() для себя или родительского модуля.

  • 'evaluated': модуль успешно оценен.

  • 'errored': модуль оценен, но было выброшено исключение.

За исключением 'errored', эта строка статуса соответствует полю [[Status]] записи Cyclic Module Record в спецификации. 'errored' соответствует 'evaluated' в спецификации, но при этом [[EvaluationError]] имеет значение, отличное от undefined.

Класс: vm.SourceTextModule

Добавлено в: v9.6.0

Стабильность: 1 — экспериментальный

Эта функция доступна только при включенном флаге командной строки --experimental-vm-modules.

  • Наследует: <vm.Module>

Класс vm.SourceTextModule предоставляет запись модуля с исходным текстом, определенную в спецификации ECMAScript.

new vm.SourceTextModule(code[, options])

История
Версия Изменения
v17.0.0, v16.12.0

Добавлена поддержка атрибутов импорта для параметра importModuleDynamically.

  • code <string> Код модуля JavaScript для разбора
  • options
    • identifier <string> Строка, используемая в трассировках стека. По умолчанию: 'vm:module(i)', где i — возрастающий индекс, зависящий от контекста.
    • cachedData <Buffer> | <TypedArray> | <DataView> Предоставляет необязательный Buffer или TypedArray либо DataView с данными кэша кода V8 для указанного исходного кода. code должен совпадать с модулем, из которого был создан этот cachedData.
    • context <Object> Контекстизированный объект, возвращенный методом vm.createContext(), в котором следует скомпилировать и оценить этот Module. Если контекст не указан, модуль оценивается в текущем контексте выполнения.
    • lineOffset <integer> Задает смещение номера строки, отображаемое в трассировках стека, создаваемых этим Module. По умолчанию: 0.
    • columnOffset <integer> Задает смещение номера столбца в первой строке, отображаемое в трассировках стека, создаваемых этим Module. По умолчанию: 0.
    • initializeImportMeta <Function> Вызывается при оценке этого Module для инициализации import.meta.
      • meta <import.meta>
      • module <vm.SourceTextModule>
    • importModuleDynamically <Function> Используется для указания способа загрузки модулей при оценке этого модуля во время вызова import(). Этот параметр относится к экспериментальному API модулей. Мы не рекомендуем использовать его в производственной среде. Подробные сведения см. в разделе Поддержка динамического import() в API компиляции.

Создает новый экземпляр SourceTextModule.

Объекты, назначенные свойствам объекта import.meta, могут позволить модулю получать доступ к данным за пределами указанного context. Используйте vm.runInContext() для создания объектов в определенном контексте.

Модули JavaScript
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);
CommonJS
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()

Добавлено в: v13.7.0, v12.17.0
  • Возвращает: <Buffer>

Создает кэш кода, который можно использовать с параметром cachedData конструктора SourceTextModule. Возвращает Buffer. Этот метод можно вызывать любое количество раз до оценки модуля.

Кэш кода SourceTextModule не содержит состояний, наблюдаемых из JavaScript. Кэш кода безопасно сохранять вместе с исходным текстом скрипта и многократно использовать для создания новых экземпляров SourceTextModule.

Функции в исходном коде SourceTextModule можно пометить для отложенной компиляции; они не компилируются при создании SourceTextModule. Эти функции будут скомпилированы при первом вызове. Кэш кода сериализует метаданные, известные V8 на данный момент о SourceTextModule, которые V8 может использовать для ускорения последующих компиляций.

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

sourceTextModule.dependencySpecifiers

История
Версия Изменения
v24.4.0, v22.20.0

Этот параметр устарел; вместо него следует использовать sourceTextModule.moduleRequests.

Стабильность: 0 — устарело: используйте вместо него sourceTextModule.moduleRequests.

  • Тип: <string[]>

Спецификаторы всех зависимостей этого модуля. Возвращенный массив заморожен, чтобы запретить любые его изменения.

Соответствует полю [[RequestedModules]] записей Cyclic Module Record в спецификации ECMAScript.

sourceTextModule.hasAsyncGraph()

Добавлено в: v24.9.0
  • Возвращает: <boolean>

Обходит граф зависимостей и возвращает true, если какой-либо модуль в зависимостях или сам этот модуль содержит выражения await верхнего уровня; в противном случае возвращает false.

Поиск может выполняться медленно, если граф достаточно велик.

Для этого модуль сначала необходимо инстанцировать. Если модуль еще не инстанцирован, будет выброшена ошибка.

sourceTextModule.hasTopLevelAwait()

Добавлено в: v24.9.0
  • Возвращает: <boolean>

Возвращает сведения о том, содержит ли сам модуль выражения await верхнего уровня.

Это соответствует полю [[HasTLA]] в Cyclic Module Record спецификации ECMAScript.

sourceTextModule.instantiate()

Добавлено в: v24.8.0, v22.21.0
  • Возвращает: <undefined>

Инстанцирует модуль с уже связанными запрашиваемыми модулями.

Этот метод разрешает импортированные привязки модуля, включая имена повторно экспортируемых привязок. Если имеются привязки, которые невозможно разрешить, синхронно выбрасывается ошибка.

Если запрашиваемые модули содержат циклические зависимости, метод sourceTextModule.linkRequests(modules) необходимо вызвать для всех модулей цикла до вызова этого метода.

sourceTextModule.linkRequests(modules)

Добавлено в: v24.8.0, v22.21.0
  • modules <vm.Module[]> Массив объектов vm.Module, от которых зависит этот модуль. Порядок модулей в массиве соответствует порядку sourceTextModule.moduleRequests.
  • Возвращает: <undefined>

Связывает зависимости модуля. Этот метод необходимо вызвать до оценки, и его можно вызвать только один раз для каждого модуля.

Порядок экземпляров модулей в массиве modules должен соответствовать порядку разрешения sourceTextModule.moduleRequests. Если два запроса модуля имеют одинаковый спецификатор и атрибуты импорта, для них необходимо использовать один и тот же экземпляр модуля, иначе будет выброшен ERR_MODULE_LINK_MISMATCH. Например, при связывании запросов для этого модуля:

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

Массив modules должен содержать две ссылки на один и тот же экземпляр, поскольку два запроса модуля идентичны, но относятся к двум фазам.

Если у модуля нет зависимостей, массив modules может быть пустым.

Пользователи могут использовать sourceTextModule.moduleRequests для реализации определяемой хостом абстрактной операции HostLoadImportedModule в спецификации ECMAScript, а также использовать sourceTextModule.linkRequests() для вызова определенной спецификацией операции FinishLoadingImportedModule для модуля со всеми зависимостями одним пакетом.

Создатель SourceTextModule сам определяет, будет ли разрешение зависимостей синхронным или асинхронным.

После связывания каждого модуля в массиве modules вызовите sourceTextModule.instantiate().

sourceTextModule.moduleRequests

Добавлено в: v24.4.0, v22.20.0
  • Тип: <ModuleRequest[]> Зависимости этого модуля.

Запрашиваемые зависимости импорта этого модуля. Возвращенный массив заморожен, чтобы запретить любые его изменения.

Например, для следующего исходного текста:

import foo from 'foo';
import fooAlias from 'foo';
import bar from './bar.js';
import withAttrs from '../with-attrs.ts' with { arbitraryAttr: 'attr-val' };
import source Module from 'wasm-mod.wasm';
mjscopy

Значение sourceTextModule.moduleRequests будет следующим:

[
  {
    specifier: 'foo',
    attributes: {},
    phase: 'evaluation',
  },
  {
    specifier: 'foo',
    attributes: {},
    phase: 'evaluation',
  },
  {
    specifier: './bar.js',
    attributes: {},
    phase: 'evaluation',
  },
  {
    specifier: '../with-attrs.ts',
    attributes: { arbitraryAttr: 'attr-val' },
    phase: 'evaluation',
  },
  {
    specifier: 'wasm-mod.wasm',
    attributes: {},
    phase: 'source',
  },
];
jscopy

Класс: vm.SyntheticModule

Добавлено в: v13.0.0, v12.16.0

Стабильность: 1 — экспериментальный

Эта функция доступна только при включенном флаге командной строки --experimental-vm-modules.

  • Наследует: <vm.Module>

Класс vm.SyntheticModule предоставляет запись синтетического модуля, определенную в спецификации WebIDL. Синтетические модули предназначены для предоставления универсального интерфейса, позволяющего включать источники, отличные от JavaScript, в графы модулей ECMAScript.

Модули JavaScript
import { SyntheticModule } from 'node:vm';

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

// Use `syntheticModule` in linking
(async () => {
  await syntheticModule.link(() => {});
  await syntheticModule.evaluate();

  console.log('Default export:', syntheticModule.namespace.default);
})();
CommonJS
const { SyntheticModule } = require('node:vm');

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

// Use `syntheticModule` in linking
(async () => {
  await syntheticModule.link(() => {});
  await syntheticModule.evaluate();

  console.log('Default export:', syntheticModule.namespace.default);
})();

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

Добавлено в: v13.0.0, v12.16.0
  • exportNames <string[]> Массив имен, которые будут экспортированы из модуля.
  • evaluateCallback <Function> Вызывается при оценке модуля.
  • options
    • identifier <string> Строка, используемая в трассировках стека. По умолчанию: 'vm:module(i)', где i — возрастающий индекс, зависящий от контекста.
    • context <Object> Контекстизированный объект, возвращенный методом vm.createContext(), в котором следует скомпилировать и оценить этот Module.

Создает новый экземпляр SyntheticModule.

Объекты, назначенные экспортам этого экземпляра, могут позволить импортирующим модулям получать доступ к данным за пределами указанного context. Используйте vm.runInContext() для создания объектов в определенном контексте.

syntheticModule.setExport(name, value)

Добавлено в: v13.0.0, v12.16.0
История
Версия Изменения
v24.8.0, v22.21.0

Больше не требуется вызывать syntheticModule.link() перед вызовом этого метода.

  • name <string> Имя экспорта, которому нужно присвоить значение.
  • value <any> Значение, присваиваемое экспорту.

Этот метод задает слоты привязок экспорта модуля, присваивая им указанное значение.

Модули JavaScript
import vm from 'node:vm';

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

await m.evaluate();

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

Добавлено в: v24.4.0, v22.20.0
  • Тип: <Object>
    • specifier <string> Спецификатор запрашиваемого модуля.
    • attributes <Object> Значение "with", переданное в WithClause в ImportDeclaration, или пустой объект, если значение не было указано.
    • phase <string> Фаза запрашиваемого модуля ("source" или "evaluation").

ModuleRequest представляет запрос на импорт модуля с указанными атрибутами импорта и фазой.

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

Добавлено в: v10.10.0
История
Версия Изменения
v21.7.0, v20.12.0

Добавлена поддержка vm.constants.USE_MAIN_CONTEXT_DEFAULT_LOADER.

v19.6.0, v18.15.0

Если передан параметр cachedData, возвращаемое значение теперь включает cachedDataRejected с той же семантикой, что и версия vm.Script.

v17.0.0, v16.12.0

Добавлена поддержка атрибутов импорта для параметра importModuleDynamically.

v15.9.0

Параметр importModuleDynamically добавлен повторно.

v14.3.0

Параметр importModuleDynamically удален из-за проблем совместимости.

v14.1.0, v13.14.0

Теперь поддерживается параметр importModuleDynamically.

  • code <string> Тело компилируемой функции.
  • params <string[]> Массив строк, содержащий все параметры функции.
  • options <Object>
    • filename <string> Задает имя файла, используемое в трассировках стека, создаваемых этим скриптом. По умолчанию: ''.
    • lineOffset <number> Задает смещение номера строки, отображаемое в трассировках стека, создаваемых этим скриптом. По умолчанию: 0.
    • columnOffset <number> Задает смещение номера столбца в первой строке, отображаемое в трассировках стека, создаваемых этим скриптом. По умолчанию: 0.
    • cachedData <Buffer> | <TypedArray> | <DataView> Предоставляет необязательный Buffer или TypedArray либо DataView с данными кэша кода V8 для указанного исходного кода. Он должен быть создан предыдущим вызовом vm.compileFunction() с теми же code и params.
    • produceCachedData <boolean> Задает, нужно ли создавать новые данные кэша. По умолчанию: false.
    • parsingContext <Object> Контекстизированный объект, в котором следует скомпилировать указанную функцию.
    • contextExtensions <Object[]> Массив, содержащий набор расширений контекста (объектов, оборачивающих текущую область видимости), которые будут применены при компиляции. По умолчанию: [].
    • importModuleDynamically <Function> | <vm.constants.USE_MAIN_CONTEXT_DEFAULT_LOADER> Используется для указания способа загрузки модулей при оценке этой функции во время вызова import(). Этот параметр относится к экспериментальному API модулей. Мы не рекомендуем использовать его в производственной среде. Подробные сведения см. в разделе Поддержка динамического import() в API компиляции.
  • Возвращает: <Function>

Компилирует указанный код в предоставленный контекст (если контекст не задан, используется текущий) и возвращает его, обернутым в функцию с указанными params.

vm.constants

Добавлено в: v21.7.0, v20.12.0
  • Тип: <Object>

Возвращает объект, содержащий часто используемые константы для операций VM.

vm.constants.USE_MAIN_CONTEXT_DEFAULT_LOADER

Добавлено в: v21.7.0, v20.12.0

Стабильность: 1.1 — активная разработка

Константа, которую можно использовать в качестве параметра importModuleDynamically для vm.Script и vm.compileFunction(), чтобы Node.js загружал запрашиваемый модуль с помощью загрузчика ESM по умолчанию из основного контекста.

Подробные сведения см. в разделе Поддержка динамического import() в API компиляции.

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

Добавлено в: v0.3.1
История
Версия Изменения
v22.8.0, v20.18.0

Аргумент contextObject теперь принимает vm.constants.DONT_CONTEXTIFY.

v21.7.0, v20.12.0

Добавлена поддержка vm.constants.USE_MAIN_CONTEXT_DEFAULT_LOADER.

v21.2.0, v20.11.0

Теперь поддерживается параметр importModuleDynamically.

v14.6.0

Теперь поддерживается параметр microtaskMode.

v10.0.0

Первый аргумент больше не может быть функцией.

v10.0.0

Теперь поддерживается параметр codeGeneration.

  • contextObject <Object> | <vm.constants.DONT_CONTEXTIFY> | <undefined> Либо vm.constants.DONT_CONTEXTIFY, либо объект, который будет контекстуализирован. Если undefined, для обеспечения обратной совместимости будет создан пустой контекстуализированный объект.
  • options <Object>
    • name <string> Понятное для человека имя вновь созданного контекста. По умолчанию: 'VM Context i', где i — возрастающий числовой индекс созданного контекста.
    • origin <string> Источник, соответствующий вновь созданному контексту и используемый для отображения. Источник должен быть отформатирован как URL, но содержать только схему, хост и порт (если необходимо), подобно значению свойства url.origin объекта URL. В частности, эта строка не должна содержать завершающую косую черту, поскольку она обозначает путь. По умолчанию: ''.
    • codeGeneration <Object>
      • strings <boolean> Если установлено значение false, любые вызовы eval или конструкторов функций (Function, GeneratorFunction и т. д.) приведут к возникновению EvalError. По умолчанию: true.
      • wasm <boolean> Если установлено значение false, любая попытка скомпилировать модуль WebAssembly приведёт к возникновению WebAssembly.CompileError. По умолчанию: true.
    • microtaskMode <string> Если установлено значение afterEvaluate, микрозадачи (задачи, запланированные через Promise и async function) будут выполняться сразу после выполнения скрипта с помощью script.runInContext(). В этом случае они включаются в области timeout и breakOnSigint.
    • importModuleDynamically <Function> | <vm.constants.USE_MAIN_CONTEXT_DEFAULT_LOADER> Используется для указания способа загрузки модулей, когда в этом контексте вызывается import() без ссылающегося скрипта или модуля. Этот параметр является частью экспериментального API модулей. Мы не рекомендуем использовать его в производственной среде. Подробную информацию см. в разделе Поддержка динамического import() в API компиляции.
  • Возвращает: контекстуализированный объект <Object>.

Если заданный contextObject является объектом, метод vm.createContext() подготовит этот объект и вернёт ссылку на него, чтобы его можно было использовать при вызове vm.runInContext() или script.runInContext(). В таких скриптах глобальный объект будет обёрнут объектом contextObject, сохраняя все существующие свойства и получая встроенные объекты и функции, имеющиеся у любого стандартного глобального объекта. За пределами скриптов, запускаемых модулем vm, глобальные переменные останутся неизменными.

Модули JavaScript
import { createContext, runInContext } from 'node:vm';

global.globalVar = 3;

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

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

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

console.log(global.globalVar);
// Prints: 3
CommonJS
const { createContext, runInContext } = require('node:vm');

global.globalVar = 3;

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

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

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

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

Если contextObject опущен (или явно передан как undefined), будет возвращён новый пустой контекстуализированный объект.

Когда глобальный объект в только что созданном контексте контекстуализирован, он имеет некоторые особенности по сравнению с обычными глобальными объектами. Например, его нельзя заморозить. Чтобы создать контекст без особенностей контекстуализации, передайте vm.constants.DONT_CONTEXTIFY в качестве аргумента contextObject. Подробности см. в документации к vm.constants.DONT_CONTEXTIFY.

Метод vm.createContext() в первую очередь полезен для создания единственного контекста, который можно использовать для запуска нескольких скриптов. Например, при эмуляции веб-браузера этот метод можно использовать для создания единственного контекста, представляющего глобальный объект окна, а затем запускать все теги <script> в этом контексте.

Указанные name и origin контекста доступны через API Inspector.

vm.isContext(object)

Добавлено в: v0.11.7
  • object <Object>
  • Возвращает: <boolean>

Возвращает true, если заданный объект object был контекстуализирован с помощью vm.createContext() или является глобальным объектом контекста, созданного с помощью vm.constants.DONT_CONTEXTIFY.

vm.measureMemory([options])

Добавлено в: v13.10.0

Стабильность: 1 — экспериментальный

Измеряет память, известную V8 и используемую всеми контекстами, известными текущей изолированной среде V8, либо основным контекстом.

  • options <Object> Необязательно.
    • mode <string> Либо 'summary', либо 'detailed'. В режиме сводки будет возвращена только память, измеренная для основного контекста. В подробном режиме будет возвращена память, измеренная для всех контекстов, известных текущей изолированной среде V8. По умолчанию: 'summary'
    • execution <string> Либо 'default', либо 'eager'. При стандартном выполнении обещание не будет выполнено, пока не начнётся следующая запланированная сборка мусора; это может занять некоторое время (или она вообще не начнётся, если программа завершится до следующей сборки мусора). При немедленном выполнении сборка мусора будет запущена сразу для измерения памяти. По умолчанию: 'default'
  • Возвращает: <Promise> Если измерение памяти выполнено успешно, обещание будет выполнено с объектом, содержащим информацию об использовании памяти. В противном случае оно будет отклонено с ошибкой ERR_CONTEXT_NOT_INITIALIZED.

Формат объекта, с которым может быть выполнено возвращённое Promise, зависит от движка V8 и может изменяться от одной версии V8 к другой.

Возвращаемый результат отличается от статистики, предоставляемой v8.getHeapSpaceStatistics(): vm.measureMemory() измеряет память, доступную из каждого специфичного для V8 контекста в текущем экземпляре движка V8, тогда как результат v8.getHeapSpaceStatistics() измеряет память, занимаемую каждой областью кучи в текущем экземпляре V8.

Модули JavaScript
import { createContext, measureMemory } from 'node:vm';
// Measure the memory used by the main context.
measureMemory({ mode: 'summary' })
  // This is the same as vm.measureMemory()
  .then((result) => {
    // The current format is:
    // {
    //   total: { jsMemoryEstimate: 1601828, jsMemoryRange: [1601828, 5275288] },
    //   WebAssembly: { code: 0, metadata: 33962 },
    // }
    console.log(result);
  });

const context = createContext({ a: 1 });
measureMemory({ mode: 'detailed', execution: 'eager' }).then((result) => {
  // Reference the context here so that it won't be GC'ed
  // until the measurement is complete.
  console.log('Context:', context.a);
  // {
  //   total: { jsMemoryEstimate: 1767100, jsMemoryRange: [1767100, 5440560] },
  //   WebAssembly: { code: 0, metadata: 33962 },
  //   current: { jsMemoryEstimate: 1601828, jsMemoryRange: [1601828, 5275288] },
  //   other: [{ jsMemoryEstimate: 165272, jsMemoryRange: [Array] }],
  // }
  console.log(result);
});
CommonJS
const { createContext, measureMemory } = require('node:vm');
// Measure the memory used by the main context.
measureMemory({ mode: 'summary' })
  // This is the same as vm.measureMemory()
  .then((result) => {
    // The current format is:
    // {
    //   total: { jsMemoryEstimate: 1601828, jsMemoryRange: [1601828, 5275288] },
    //   WebAssembly: { code: 0, metadata: 33962 },
    // }
    console.log(result);
  });

const context = createContext({ a: 1 });
measureMemory({ mode: 'detailed', execution: 'eager' }).then((result) => {
  // Reference the context here so that it won't be GC'ed
  // until the measurement is complete.
  console.log('Context:', context.a);
  // {
  //   total: { jsMemoryEstimate: 1767100, jsMemoryRange: [1767100, 5440560] },
  //   WebAssembly: { code: 0, metadata: 33962 },
  //   current: { jsMemoryEstimate: 1601828, jsMemoryRange: [1601828, 5275288] },
  //   other: [{ jsMemoryEstimate: 165272, jsMemoryRange: [Array] }],
  // }
  console.log(result);
});

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

Добавлено в: v0.3.1
История
Версия Изменения
v21.7.0, v20.12.0

Добавлена поддержка vm.constants.USE_MAIN_CONTEXT_DEFAULT_LOADER.

v17.0.0, v16.12.0

Добавлена поддержка атрибутов импорта для параметра importModuleDynamically.

v6.3.0

Теперь поддерживается параметр breakOnSigint.

  • code <string> Код JavaScript для компиляции и запуска.
  • contextifiedObject <Object> Контекстуализированный объект, который будет использоваться в качестве global при компиляции и запуске code.
  • options <Object> | <string>
    • filename <string> Указывает имя файла, используемое в трассировках стека, созданных этим скриптом. По умолчанию: 'evalmachine.<anonymous>'.
    • lineOffset <number> Указывает смещение номера строки, отображаемое в трассировках стека, созданных этим скриптом. По умолчанию: 0.
    • columnOffset <number> Указывает смещение номера столбца первой строки, отображаемое в трассировках стека, созданных этим скриптом. По умолчанию: 0.
    • displayErrors <boolean> Если true, при возникновении Error во время компиляции code строка кода, вызвавшая ошибку, добавляется в трассировку стека. По умолчанию: true.
    • timeout <integer> Указывает количество миллисекунд, в течение которых выполняется code до завершения выполнения. Если выполнение будет завершено принудительно, будет выброшена ошибка Error. Это значение должно быть строго положительным целым числом.
    • breakOnSigint <boolean> Если true, получение SIGINT (Ctrl+C) завершит выполнение и выбросит ошибку Error. Существующие обработчики события, добавленные с помощью process.on('SIGINT'), отключаются на время выполнения скрипта, но продолжают работать после его завершения. По умолчанию: false.
    • cachedData <Buffer> | <TypedArray> | <DataView> Предоставляет необязательный Buffer или TypedArray, либо DataView с данными кэша кода V8 для указанного исходного кода.
    • importModuleDynamically <Function> | <vm.constants.USE_MAIN_CONTEXT_DEFAULT_LOADER> Используется для указания способа загрузки модулей при вычислении этого скрипта, когда вызывается import(). Этот параметр является частью экспериментального API модулей. Мы не рекомендуем использовать его в производственной среде. Подробную информацию см. в разделе Поддержка динамического import() в API компиляции.

Метод vm.runInContext() компилирует code, выполняет его в контексте contextifiedObject, а затем возвращает результат. Выполняемый код не имеет доступа к локальной области видимости. Объект contextifiedObject должен быть предварительно контекстуализирован с помощью метода vm.createContext().

Если options является строкой, она указывает имя файла.

В следующем примере скомпилированы и выполнены разные скрипты с использованием одного контекстуализированного объекта:

Модули JavaScript
import { createContext, runInContext } from 'node:vm';

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

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

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

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

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

Добавлено в: v0.3.1
История
Версия Изменения
v22.8.0, v20.18.0

Аргумент contextObject теперь принимает vm.constants.DONT_CONTEXTIFY.

v21.7.0, v20.12.0

Добавлена поддержка vm.constants.USE_MAIN_CONTEXT_DEFAULT_LOADER.

v17.0.0, v16.12.0

Добавлена поддержка атрибутов импорта для параметра importModuleDynamically.

v14.6.0

Теперь поддерживается параметр microtaskMode.

v10.0.0

Теперь поддерживается параметр contextCodeGeneration.

v6.3.0

Теперь поддерживается параметр breakOnSigint.

  • code <string> Код JavaScript для компиляции и запуска.
  • contextObject <Object> | <vm.constants.DONT_CONTEXTIFY> | <undefined> Либо vm.constants.DONT_CONTEXTIFY, либо объект, который будет контекстуализирован. Если undefined, для обеспечения обратной совместимости будет создан пустой контекстуализированный объект.
  • options <Object> | <string>
    • filename <string> Указывает имя файла, используемое в трассировках стека, созданных этим скриптом. По умолчанию: 'evalmachine.<anonymous>'.
    • lineOffset <number> Указывает смещение номера строки, отображаемое в трассировках стека, созданных этим скриптом. По умолчанию: 0.
    • columnOffset <number> Указывает смещение номера столбца первой строки, отображаемое в трассировках стека, созданных этим скриптом. По умолчанию: 0.
    • displayErrors <boolean> Если true, при возникновении Error во время компиляции code строка кода, вызвавшая ошибку, добавляется в трассировку стека. По умолчанию: 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 <Object>
      • strings <boolean> Если установлено значение false, любые вызовы eval или конструкторов функций (Function, GeneratorFunction и т. д.) приведут к возникновению EvalError. По умолчанию: true.
      • wasm <boolean> Если установлено значение false, любая попытка скомпилировать модуль WebAssembly приведёт к возникновению WebAssembly.CompileError. По умолчанию: true.
    • cachedData <Buffer> | <TypedArray> | <DataView> Предоставляет необязательный Buffer или TypedArray, либо DataView с данными кэша кода V8 для указанного исходного кода.
    • importModuleDynamically <Function> | <vm.constants.USE_MAIN_CONTEXT_DEFAULT_LOADER> Используется для указания способа загрузки модулей при вычислении этого скрипта, когда вызывается import(). Этот параметр является частью экспериментального API модулей. Мы не рекомендуем использовать его в производственной среде. Подробную информацию см. в разделе Поддержка динамического import() в API компиляции.
    • microtaskMode <string> Если установлено значение afterEvaluate, микрозадачи (задачи, запланированные через Promise и async function) будут выполнены сразу после запуска скрипта. В этом случае они включаются в области timeout и breakOnSigint.
  • Возвращает: <any> результат последнего оператора, выполненного в скрипте.

Этот метод является сокращённой формой (new vm.Script(code, options)).runInContext(vm.createContext(options), options). Если options является строкой, она указывает имя файла.

Он выполняет несколько действий одновременно:

  1. Создаёт новый контекст.
  2. Если contextObject является объектом, контекстуализирует его в новом контексте. Если contextObject не определён, создаёт новый объект и контекстуализирует его. Если contextObject — vm.constants.DONT_CONTEXTIFY, ничего не контекстуализирует.
  3. Компилирует код как vm.Script
  4. Запускает скомпилированный код в созданном контексте. Код не имеет доступа к области видимости, в которой вызывается этот метод.
  5. Возвращает результат.

В следующем примере компилируется и выполняется код, который увеличивает глобальную переменную и задаёт новую. Эти глобальные переменные находятся в contextObject.

Модули JavaScript
import { runInNewContext, constants } from 'node:vm';

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

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 = runInNewContext(
  'Object.freeze(globalThis); globalThis;',
  constants.DONT_CONTEXTIFY,
);
CommonJS
const { runInNewContext, constants } = require('node:vm');

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

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 = runInNewContext(
  'Object.freeze(globalThis); globalThis;',
  constants.DONT_CONTEXTIFY,
);

vm.runInThisContext(code[, options])

Добавлено в: v0.3.1
История
Версия Изменения
v21.7.0, v20.12.0

Добавлена поддержка vm.constants.USE_MAIN_CONTEXT_DEFAULT_LOADER.

v17.0.0, v16.12.0

Добавлена поддержка атрибутов импорта для параметра importModuleDynamically.

v6.3.0

Теперь поддерживается параметр breakOnSigint.

  • 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> Предоставляет необязательный Buffer или TypedArray либо DataView с данными кэша кода V8 для указанного исходного кода.
    • importModuleDynamically <Function> | <vm.constants.USE_MAIN_CONTEXT_DEFAULT_LOADER> Используется для указания способа загрузки модулей при вычислении этого скрипта, если вызывается import(). Этот параметр относится к экспериментальному API модулей. Мы не рекомендуем использовать его в производственной среде. Подробную информацию см. в разделе Поддержка динамического import() в API компиляции.
  • Возвращает: <any> результат выполнения последнего оператора в скрипте.

vm.runInThisContext() компилирует code, выполняет его в контексте текущего global и возвращает результат. Выполняемый код не имеет доступа к локальной области видимости, но имеет доступ к текущему объекту global.

Если options — строка, она задает имя файла.

В следующем примере показано использование vm.runInThisContext() и функции JavaScript eval() для выполнения одного и того же кода:

Модули JavaScript
import { runInThisContext } from 'node:vm';
let localVar = 'initial value';

const vmResult = 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'
CommonJS
const { runInThisContext } = require('node:vm');
let localVar = 'initial value';

const vmResult = 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').

Пример: запуск HTTP-сервера в виртуальной машине

При использовании script.runInThisContext() или vm.runInThisContext() код выполняется в текущем глобальном контексте V8. Код, переданный этому контексту виртуальной машины, будет иметь собственную изолированную область видимости.

Чтобы запустить простой веб-сервер с помощью модуля node:http, код, переданный контексту, должен самостоятельно вызвать require('node:http') либо получить ссылку на модуль node:http. Например:

Модули JavaScript
import { runInThisContext } from 'node:vm';
import { createRequire } from 'node:module';

const require = createRequire(import.meta.url);

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

  createServer((request, response) => {
    response.writeHead(200, { 'Content-Type': 'text/plain' });
    response.end('Hello World\\n');
  }).listen(8124);

  console.log('Server running at http://127.0.0.1:8124/');
})`;

runInThisContext(code)(require);
CommonJS
const { runInThisContext } = require('node:vm');

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

  createServer((request, response) => {
    response.writeHead(200, { 'Content-Type': 'text/plain' });
    response.end('Hello World\\n');
  }).listen(8124);

  console.log('Server running at http://127.0.0.1:8124/');
})`;

runInThisContext(code)(require);

В данном случае require() использует общее состояние с контекстом, из которого он передан. Это может привести к рискам при выполнении недоверенного кода, например к нежелательному изменению объектов в контексте.

Что значит «контекстизировать» объект?

Весь код JavaScript, выполняемый в Node.js, запускается в области видимости «контекста». Согласно руководству по встраиванию V8:

В V8 контекст — это среда выполнения, которая позволяет запускать отдельные, не связанные друг с другом приложения JavaScript в одном экземпляре V8. Необходимо явно указать контекст, в котором должен выполняться код JavaScript.

При вызове метода vm.createContext() с объектом аргумент contextObject используется для обертки глобального объекта нового экземпляра контекста V8 (если contextObject равно undefined, новый объект будет создан на основе текущего контекста до его контекстизации). Этот контекст V8 предоставляет code возможность выполняться с помощью методов модуля node:vm в изолированной глобальной среде. Процесс создания контекста V8 и связывания его с contextObject во внешнем контексте в этом документе называется «контекстизацией» объекта.

Контекстизация приводит к некоторым особенностям значения globalThis в контексте. Например, его нельзя заморозить, и оно не идентично по ссылке значению contextObject во внешнем контексте.

Модули JavaScript
import { createContext, runInContext } from 'node:vm';

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

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

Чтобы создать контекст с обычным глобальным объектом и получить доступ к глобальному прокси во внешнем контексте с меньшим количеством особенностей, укажите vm.constants.DONT_CONTEXTIFY в качестве аргумента contextObject.

vm.constants.DONT_CONTEXTIFY

Эта константа, используемая в качестве аргумента contextObject в API vm, указывает Node.js создать контекст, не оборачивая его глобальный объект другим объектом специальным для Node.js способом. В результате значение globalThis в новом контексте будет вести себя ближе к обычному.

Модули JavaScript
import { createContext, runInContext, constants } from 'node:vm';

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

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

Если vm.constants.DONT_CONTEXTIFY используется в качестве аргумента contextObject для vm.createContext(), возвращаемый объект представляет собой объект, похожий на прокси глобального объекта в только что созданном контексте, с меньшим количеством особенностей, специфичных для Node.js. Он идентичен по ссылке значению globalThis в новом контексте, может изменяться извне контекста и позволяет напрямую обращаться к встроенным объектам нового контекста.

Модули JavaScript
import { createContext, runInContext, constants } from 'node:vm';

const context = createContext(constants.DONT_CONTEXTIFY);

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

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

// Can be frozen and it affects the inner context.
Object.freeze(context);
try {
  runInContext('baz = 1; baz;', context);
} catch (e) {
  console.log(`${e.constructor.name}: ${e.message}`); // ReferenceError: baz is not defined
}
CommonJS
const { createContext, runInContext, constants } = require('node:vm');

const context = createContext(constants.DONT_CONTEXTIFY);

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

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

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

Взаимодействие тайм-аута с асинхронными задачами и промисами

Promise и async function могут планировать задачи, асинхронно выполняемые движком JavaScript. По умолчанию эти задачи выполняются после завершения всех функций JavaScript в текущем стеке. Это позволяет обойти действие параметров timeout и breakOnSigint.

Например, следующий код, выполняемый с помощью vm.runInNewContext() с тайм-аутом в 5 миллисекунд, планирует бесконечный цикл, который запустится после разрешения промиса. Запланированный цикл не прерывается тайм-аутом:

Модули JavaScript
import { runInNewContext } from 'node:vm';

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

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

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

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

Эту проблему можно решить, передав microtaskMode: 'afterEvaluate' коду, который создает Context:

Модули JavaScript
import { runInNewContext } from 'node:vm';

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

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

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

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

В этом случае микрозадача, запланированная через promise.then(), будет выполнена до возврата из vm.runInNewContext() и прервана механизмом timeout. Это относится только к коду, выполняемому в vm.Context, поэтому, например, vm.runInThisContext() не принимает этот параметр.

Обработчики промисов помещаются в очередь микрозадач контекста, в котором они были созданы. Например, если в приведенном выше примере заменить () => loop() просто на loop, то loop будет помещен в глобальную очередь микрозадач, поскольку это функция из внешнего (основного) контекста, и поэтому она также сможет обойти тайм-аут.

Если асинхронные функции планирования, такие как process.nextTick(), queueMicrotask(), setTimeout(), setImmediate() и т. д., доступны внутри vm.Context, переданные им функции будут добавлены в глобальные очереди, общие для всех контекстов. Поэтому тайм-аут также не контролирует обратные вызовы, переданные этим функциям.

Если microtaskMode имеет значение 'afterEvaluate', будьте осторожны при совместном использовании промисов между контекстами

В режиме 'afterEvaluate' у Context есть собственная очередь микрозадач, отдельная от глобальной очереди микрозадач внешнего (основного) контекста. Хотя этот режим необходим для обеспечения timeout и поддержки breakOnSigint с асинхронными задачами, он также затрудняет совместное использование промисов между контекстами.

В приведенном ниже примере промис создается во внутреннем контексте и передается во внешний. Когда внешний контекст вызывает await для этого промиса, поток выполнения внешнего контекста неожиданным образом прерывается: инструкция log так и не выполняется.

Модули JavaScript
import { createContext, runInContext } from 'node:vm';

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

// runInContext() returns a Promise created in the inner context.
const inner_promise = runInContext('Promise.resolve()', inner_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');
CommonJS
const { createContext, runInContext } = require('node:vm');

// runInContext() returns a Promise created in the inner context.
const inner_context = createContext({}, { microtaskMode: 'afterEvaluate' });

(async () => {
  const inner_promise = runInContext('Promise.resolve()', inner_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');
})();

Чтобы успешно совместно использовать промисы между контекстами с разными очередями микрозадач, необходимо обеспечить выполнение задач во внутренней очереди микрозадач каждый раз, когда внешний контекст добавляет задачу во внутреннюю очередь микрозадач.

Задачи в очереди микрозадач заданного контекста выполняются при каждом вызове runInContext() или SourceTextModule.evaluate() для скрипта или модуля, использующего этот контекст. В нашем примере обычный поток выполнения можно восстановить, запланировав второй вызов runInContext() до 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');
mjscopy

Примечание: Строго говоря, в этом режиме node:vm отступает от буквального требования спецификации ECMAScript о постановке задач в очередь, позволяя асинхронным задачам из разных контекстов выполняться в порядке, отличном от порядка их постановки в очередь.

Поддержка динамического import() в API компиляции

Следующие API поддерживают параметр importModuleDynamically для включения динамического import() в коде, скомпилированном модулем vm.

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

Этот параметр по-прежнему относится к экспериментальному API модулей. Мы не рекомендуем использовать его в производственной среде.

Если параметр importModuleDynamically не указан или имеет значение undefined

Если этот параметр не указан или имеет значение undefined, код, содержащий import(), все еще можно скомпилировать с помощью API vm, но при выполнении скомпилированного кода и фактическом вызове import() результат будет отклонен с ошибкой ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING.

Если importModuleDynamically имеет значение vm.constants.USE_MAIN_CONTEXT_DEFAULT_LOADER

В настоящее время этот параметр не поддерживается для vm.SourceTextModule.

При использовании этого параметра, когда в скомпилированном коде инициируется import(), Node.js использует загрузчик ESM по умолчанию из основного контекста, чтобы загрузить запрошенный модуль и передать его выполняемому коду.

Это дает компилируемому коду доступ к встроенным модулям Node.js, таким как fs или http. Если код выполняется в другом контексте, учитывайте, что объекты, созданные модулями, загруженными из основного контекста, по-прежнему относятся к основному контексту, а не к встроенным классам instanceof в новом контексте.

CommonJS
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);
Модули JavaScript
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);

Этот параметр также позволяет скрипту или функции загружать пользовательские модули:

Модули JavaScript
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);
CommonJS
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. Модуль будет разрешен относительно параметра filename, переданного в vm.Script или vm.compileFunction(). Разрешение может выполняться относительно filename, являющегося абсолютным путем или строкой URL. Если filename — строка, которая не является ни абсолютным путем, ни URL, либо если он не задан, разрешение будет выполняться относительно текущего рабочего каталога процесса. В случае vm.createContext() разрешение всегда выполняется относительно текущего рабочего каталога, поскольку этот параметр используется только тогда, когда отсутствует ссылающийся скрипт или модуль.
  2. Для каждого filename, разрешающегося в определенный путь, результат может быть кэширован после первой успешной загрузки процесса из этого пути, а последующая загрузка того же модуля из того же пути вернет тот же результат. Если filename — строка URL, кэш не будет использоваться, если параметры поиска в URL различаются. Для filename, не являющихся строками URL, в настоящее время нет способа обойти кэширование.

Если 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> Ссылающийся объект — это скомпилированный vm.Script для new vm.Script, vm.runInThisContext, vm.runInContext и vm.runInNewContext. Для vm.compileFunction это скомпилированный Function, для new vm.SourceTextModule — скомпилированный vm.SourceTextModule, а для vm.createContext() — контекст Object.
  • importAttributes <Object> Значение "with", переданное необязательному параметру optionsExpression, либо пустой объект, если значение не было передано.
  • phase <string> Фаза динамического импорта ("source" или "evaluation").
  • Возвращает: <Module Namespace Object> | <vm.Module> Рекомендуется возвращать vm.Module, чтобы воспользоваться отслеживанием ошибок и избежать проблем с пространствами имен, содержащими экспорты функций then.
Модули JavaScript
// 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' } }
CommonJS
// 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' } }
})();

© Joyent, Inc. and other Node contributors
Licensed under the MIT License.
Node.js is a trademark of Joyent, Inc. and is used with its permission.
We are not endorsed by or affiliated with Joyent.
https://nodejs.org/api/vm.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API