Spec-Zone.ru › Node.js 20 LTS

Виртуальная машина (выполнение JavaScript)

Устойчивость: 2 - Стабильно

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

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

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

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

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

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

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

const x = 1;

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

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

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

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

Класс: vm.Script

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

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

new vm.Script(code[, options])

История
Версия Изменения
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.

v0.3.1

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

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

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

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

script.cachedDataRejected

Добавлен в: v5.7.0
  • <логическое значение> | <неопределено>

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

script.createCachedData()

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

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

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

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

const script = new vm.Script(`
function add(a, b) {
  return a + b;
}

const x = add(1, 2);
`);

const cacheWithoutAdd = script.createCachedData();
// In `cacheWithoutAdd` the function `add()` is marked for full compilation
// upon invocation.

script.runInThisContext();

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

script.runInContext(contextifiedObject[, options])

История
Версия Изменения
v6.3.0

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

v0.3.1

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

  • contextifiedObject <Объект> Объект, контекстированный с помощью метода vm.createContext().
  • options <Объект>
    • displayErrors <логическое значение> При true, если при компиляции code возникает ошибка Error, строка кода, вызвавшая ошибку, будет добавлена в отладочный стек. По умолчанию: true.
    • timeout <целое число> Указывает количество миллисекунд для выполнения code перед завершением выполнения. Если выполнение завершится, будет выброшена ошибка Error. Это значение должно быть строго положительным целым числом.
    • breakOnSigint <логическое значение> Если true, получение SIGINT (клавиши Ctrl + C) завершит выполнение и выбросит ошибку Error. Существующие обработчики события, добавленные с помощью process.on('SIGINT'), будут отключены во время выполнения скрипта, но продолжат работать после него. По умолчанию: false.
  • Возвращает: <любое> результат последнего оператора, выполненного в скрипте.

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

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

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

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

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

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

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

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

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

История
Версия Изменения
v14.6.0

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

v10.0.0

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

v6.3.0

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

v0.3.1

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

  • contextObject <Object> Объект, который будет контекстуализирован. Если undefined, будет создан новый объект.
  • options <Object>
    • displayErrors <boolean> Когда true, если во время компиляции code возникает ошибка Error, строка кода, вызвавшая ошибку, будет добавлена в трассировку стека. По умолчанию: true.
    • timeout <integer> Указывает количество миллисекунд для выполнения code перед завершением выполнения. Если выполнение завершится, будет выброшено исключение Error. Это значение должно быть строго положительным целым числом.
    • breakOnSigint <boolean> Если true, получение SIGINT (Ctrl+C) завершит выполнение и выбросит исключение Error. Существующие обработчики события, прикреплённые с помощью process.on('SIGINT'), будут отключены во время выполнения скрипта, но продолжат работать после него. По умолчанию: false.
    • contextName <string> Читаемое человеком имя нового контекста. По умолчанию: 'VM Context i', где i — возрастающий числовой индекс созданного контекста.
    • contextOrigin <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> результат последнего выполненного оператора в скрипте.

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

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

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

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

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

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

script.runInThisContext([options])

История
Версия Изменения
v6.3.0

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

v0.3.1

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

  • 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, а затем выполняется несколько раз:

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

global.globalVar = 0;

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

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

console.log(globalVar);

// 1000 copy

script.sourceMapURL

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

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

Модули MJS

import vm from 'node:vm';

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

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

Модули CJS

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 по своей природе асинхронны, в отличие от синхронного характера объектов vm.Script. Использование функций 'async' может помочь с манипулированием объектами vm.Module.

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

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

Модули MJS

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

Модули CJS

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

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

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

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

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

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

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

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

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

module.dependencySpecifiers

  • <массив строк>

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

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

module.error

  • <любой тип>

Если статус module.status 'errored', это свойство содержит исключение, сгенерированное модулем во время оценки. Если статус другой, доступ к этому свойству приведет к сгенерированному исключению.

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

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

module.evaluate([options])

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

Оценить модуль.

Это необходимо вызвать после того, как модуль был связан; иначе он будет отклонен. Его можно также вызвать, когда модуль уже был оценен, в этом случае он либо ничего не сделает, если начальная оценка завершилась успешно (module.status является 'evaluated'), либо повторно сгенерирует исключение, к которому привела начальная оценка (module.status является 'errored').

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

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

module.identifier

  • <строка>

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

module.link(linker)

История
Версия Изменения
v20.10.0

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

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

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

    • extra <Объект>

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

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

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

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

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

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

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

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

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

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

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

module.namespace

  • <Объект>

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

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

module.status

  • <строка>

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

  • 'unlinked': Метод module.link() еще не был вызван.

  • 'linking': Метод module.link() был вызван, но не все Promise, возвращенные функцией связывания, еще не разрешены.

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

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

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

  • 'errored': Модуль был оценен, но при этом было сгенерировано исключение.

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

Класс: vm.SourceTextModule

Добавлен в: v9.6.0
Устойчивость: 1 - Экспериментальная

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

  • Расширяет: <vm.Модуль>

Класс vm.SourceTextModule предоставляет запись модуля исходного текста (Source Text Module Record), как определено в спецификации ECMAScript.

new vm.SourceTextModule(code[, options])

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

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

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

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

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

MJS модули

import vm from 'node:vm';

const contextifiedObject = vm.createContext({ secret: 42 });

const module = new vm.SourceTextModule(
  'Object.getPrototypeOf(import.meta.prop).secret = secret;',
  {
    initializeImportMeta(meta) {
      // Note: this object is created in the top context. As such,
      // Object.getPrototypeOf(import.meta.prop) points to the
      // Object.prototype in the top context rather than that in
      // the contextified object.
      meta.prop = {};
    },
  });
// Since module has no dependencies, the linker function will never be called.
await module.link(() => {});
await module.evaluate();

// Now, Object.prototype.secret will be equal to 42.
//
// To fix this problem, replace
//     meta.prop = {};
// above with
//     meta.prop = vm.runInContext('{}', contextifiedObject);

CJS модули

const vm = require('node:vm');
const contextifiedObject = vm.createContext({ secret: 42 });
(async () => {
  const module = new vm.SourceTextModule(
    'Object.getPrototypeOf(import.meta.prop).secret = secret;',
    {
      initializeImportMeta(meta) {
        // Note: this object is created in the top context. As such,
        // Object.getPrototypeOf(import.meta.prop) points to the
        // Object.prototype in the top context rather than that in
        // the contextified object.
        meta.prop = {};
      },
    });
  // Since module has no dependencies, the linker function will never be called.
  await module.link(() => {});
  await module.evaluate();
  // Now, Object.prototype.secret will be equal to 42.
  //
  // To fix this problem, replace
  //     meta.prop = {};
  // above with
  //     meta.prop = vm.runInContext('{}', contextifiedObject);
})();

sourceTextModule.createCachedData()

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

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

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

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

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

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

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

Класс: vm.SyntheticModule

Добавлен в: v13.0.0, v12.16.0
Устойчивость: 1 - Экспериментальная

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

  • Расширяет: <vm.Модуль>

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

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

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

// Use `module` in linking... copy

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

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

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

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

syntheticModule.setExport(name, value)

Добавлен в: v13.0.0, v12.16.0
  • name <строка> Имя экспорта для установки.
  • value <любой> Значение для установки экспорта.

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

MJS модули

import vm from 'node:vm';

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

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

assert.strictEqual(m.namespace.x, 1);

CJS модули

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

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

История
Версия Изменения
v20.12.0

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

v19.6.0, v18.15.0

Значение возвращаемого значения теперь включает cachedDataRejected с такими же семантиками, как у версии vm.Script , если был передан параметр cachedData.

v17.0.0, v16.12.0

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

v15.9.0

Опять добавлен параметр importModuleDynamically.

v14.3.0

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

v14.1.0, v13.14.0

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

v10.10.0

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

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

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

vm.constants

Добавлен в: v20.12.0
  • <объект>

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

vm.constants.USE_MAIN_CONTEXT_DEFAULT_LOADER

Добавлен в: v20.12.0
Уровень стабильности: 1.1 - Активное развитие

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

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

END_OF_DOCUMENT_MARKER

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

История
Версия Изменения
v20.12.0

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

v20.11.0

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

v14.6.0

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

v10.0.0

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

v10.0.0

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

v0.3.1

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

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

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

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

global.globalVar = 3;

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

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

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

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

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

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

Предоставленные name и origin контекста становятся видимыми через API инспектора.

vm.isContext(object)

Добавлена в: v0.11.7
  • object <Объект>
  • Возвращает: <логическое>

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

vm.measureMemory([options])

Добавлена в: v13.10.0
Устойчивость: 1 - Экспериментальная

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

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

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

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

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

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

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

История
Версия Изменения
v20.12.0

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

v17.0.0, v16.12.0

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

v6.3.0

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

v0.3.1

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

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

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

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

Следующий пример компилирует и выполняет различные скрипты, используя один и тот же контекстуализированный объект:

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

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

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

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

История
Версия Изменения
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.

v0.3.1

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

  • code <строка> JavaScript-код для компиляции и выполнения.
  • contextObject <Объект> Объект, который будет контекстуализирован. Если undefined, будет создан новый объект.
  • options <Объект> | <строка>
    • filename <строка> Указывает имя файла, используемое в отладке стека, созданной этим скриптом. По умолчанию: 'evalmachine.<anonymous>'.
    • lineOffset <число> Указывает смещение номера строки, отображаемое в отладке стека, созданной этим скриптом. По умолчанию: 0.
    • columnOffset <число> Указывает смещение номера столбца первой строки, отображаемое в отладке стека, созданной этим скриптом. По умолчанию: 0.
    • displayErrors <логическое_значение> Когда true, если при компиляции code произойдёт Error, строка кода, вызвавшая ошибку, будет добавлена в трассировку стека. По умолчанию: true.
    • timeout <целое_число> Указывает количество миллисекунд для выполнения code перед завершением выполнения. Если выполнение завершится, будет выброшена Error. Это значение должно быть строго положительным целым числом.
    • breakOnSigint <логическое_значение> Если true, получение SIGINT (Ctrl+C) завершит выполнение и выбросит Error. Существующие обработчики события, присоединённые через process.on('SIGINT'), будут отключены во время выполнения скрипта, но продолжат работать после него. По умолчанию: false.
    • contextName <строка> Читабельное имя вновь созданного контекста. По умолчанию: 'VM Context i', где i - возрастающий числовой индекс созданного контекста.
    • contextOrigin <строка> Происхождение, соответствующее вновь созданному контексту для отображения. Происхождение должно быть отформатировано как URL, но содержать только схему, хост и порт (при необходимости), подобно значению свойства url.origin объекта URL. Важно, что эта строка должна опускать конечный слэш, так как он обозначает путь. По умолчанию: ''.
    • contextCodeGeneration <Объект>
      • strings <логическое_значение> Если установлено в false, любые вызовы eval или конструкторов функций (Function, GeneratorFunction, и т.д.) выбросят EvalError. По умолчанию: true.
      • wasm <логическое_значение> Если установлено в false, любая попытка скомпилировать модуль WebAssembly вызовет WebAssembly.CompileError. По умолчанию: true.
    • cachedData <Буфер> | <Тип_массива> | <DataView> Предоставляет необязательный Buffer или TypedArray, или DataView с данными кэша кода V8 для предоставленного источника.
    • importModuleDynamically <Функция> | <vm.constants.USE_MAIN_CONTEXT_DEFAULT_LOADER> Используется для указания, как должны загружаться модули во время оценки этого скрипта при вызове import(). Этот параметр относится к экспериментальному API модулей. Мы не рекомендуем использовать его в рабочей среде. Для получения подробной информации см. Поддержка динамического import() в API компиляции.
    • microtaskMode <строка> Если установлено в afterEvaluate, микрозадачи (задачи, запланированные через Promise и async functions) будут выполняться немедленно после выполнения скрипта. В этом случае они включены в области timeout и breakOnSigint.
  • Возвращает: <любой> результат последнего оператора, выполненного в скрипте.

vm.runInNewContext() сначала контекстуализирует переданный contextObject (или создаёт новый contextObject, если передан как undefined), компилирует code, выполняет его в созданном контексте, затем возвращает результат. Выполнение кода не имеет доступа к локальной области.

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

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

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

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

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

vm.runInThisContext(code[, options])

История
Версия Изменения
v20.12.0

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

v17.0.0, v16.12.0

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

v6.3.0

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

v0.3.1

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

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

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

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

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

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

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

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

Поскольку vm.runInThisContext() не имеет доступа к локальному объему, localVar остается неизменным. В отличие от eval(), имеет доступ к локальному объему, поэтому значение localVar изменяется. Таким образом, vm.runInThisContext() очень похож на непрямой eval() вызов, например (0,eval)('code').

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

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

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

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

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

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

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

vm.runInThisContext(code)(require); copy

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

Что означает "контекстизация" объекта?

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

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

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

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

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

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

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

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

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

Это можно исправить, передав microtaskMode: 'afterEvaluate' коду, создающему Context:

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

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

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

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

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

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

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

Следующие API поддерживают параметр importModuleDynamically, чтобы включить динамический импорт в коде, скомпилированном модулем vm.

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

Этот параметр всё ещё является частью экспериментального API модулей. Не рекомендуется использовать его в рабочей среде.

Если параметр importModuleDynamically не указан или не определён

Если этот параметр не указан или имеет значение 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 в новом контексте.

Модули CJS

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

Модули MJS

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

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

Модули MJS

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

Модули CJS

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, кэш не будет использован, если у него есть разные параметры поиска. Для filename которые не являются строками URL, в настоящее время нет способа обойти поведение кэширования.

Если importModuleDynamically является функцией

Если importModuleDynamically является функцией, она будет вызвана при вызове import() в скомпилированном коде, чтобы пользователи могли настроить способ компиляции и оценки запрошенного модуля. В настоящее время экземпляр Node.js должен быть запущен с флагом --experimental-vm-modules для работы этого параметра. Если флаг не установлен, этот коллбэк будет проигнорирован. Если оценённый код фактически вызывает import(), результат будет отклонен с ошибкой ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING_FLAG.

У коллбэка importModuleDynamically(specifier, referrer, importAttributes) следующий сигнатура:

  • specifier <строка> спецификатор, переданный import()
  • referrer <vm.Script> | <Функция> | <vm.SourceTextModule> | <Объект> Ссылка — это скомпилированный vm.Script для new vm.Script, vm.runInThisContext, vm.runInContext и vm.runInNewContext. Это скомпилированный Function для vm.compileFunction, скомпилированный vm.SourceTextModule для new vm.SourceTextModule и контекст Object для vm.createContext().
  • importAttributes <Объект> Значение "with" , переданное необязательному параметру optionsExpression, или пустой объект, если значение не было предоставлено.
  • Возвращает: <Объект пространства имён модуля> | <vm.Module> Рекомендуется возвращать vm.Module для использования отслеживания ошибок и предотвращения проблем с пространствами имён, содержащими then экспорт функций.

Модули MJS

// 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' } }

Модули CJS

// 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/dist/latest-v20.x/docs/api/vm.html

Spec-Zone.ru

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