VM (выполнение JavaScript)
Исходный код: 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
Экземпляры класса vm.Script содержат предварительно скомпилированные скрипты, которые можно выполнять в определённых контекстах.
new vm.Script(code[, options])
-
code<string> Код JavaScript для компиляции. -
options<Object> | <string>-
filename<string> Задаёт имя файла, используемое в трассировках стека, создаваемых этим скриптом. По умолчанию:'evalmachine.<anonymous>'. -
lineOffset<number> Задаёт смещение номера строки, отображаемое в трассировках стека, создаваемых этим скриптом. По умолчанию:0. -
columnOffset<number> Задаёт смещение номера столбца первой строки, отображаемое в трассировках стека, создаваемых этим скриптом. По умолчанию:0. -
cachedData<Buffer> | <TypedArray> | <DataView> Предоставляет необязательный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
Если при создании vm.Script указан параметр cachedData, этому значению будет присвоено true или false в зависимости от того, примет ли V8 эти данные. В противном случае значение равно undefined.
script.createCachedData()
- Возвращает: <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()`. copy
script.runInContext(contextifiedObject[, options])
-
contextifiedObject<Object> Контекстуализированный объект, возвращённый методомvm.createContext(). -
options<Object>-
displayErrors<boolean> Если задано значениеtrue, при возникновенииErrorво время компиляцииcodeстрока кода, вызвавшая ошибку, добавляется в трассировку стека. По умолчанию:true. -
timeout<integer> Задаёт время в миллисекундах, в течение которого выполняетсяcodeдо принудительного завершения. При завершении выполнения будет выброшенError. Значение должно быть строго положительным целым числом. -
breakOnSigint<boolean> Если задано значениеtrue, получениеSIGINT(Ctrl+C) приведёт к завершению выполнения и выбрасываниюError. Обработчики события, добавленные ранее с помощьюprocess.on('SIGINT'), отключаются на время выполнения скрипта, но продолжают работать после его завершения. По умолчанию:false.
-
- Возвращает: <any> результат выполнения последнего оператора скрипта.
Выполняет скомпилированный код, содержащийся в объекте 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]])
-
contextObject<Object> | <vm.constants.DONT_CONTEXTIFY> | <undefined> Либоvm.constants.DONT_CONTEXTIFY, либо объект, который будет контекстуализирован. Если значениеundefined, для обратной совместимости будет создан пустой контекстуализированный объект. -
options<Object>-
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.
-
-
microtaskMode<string> Если задано значениеafterEvaluate, микрозадачи (задачи, запланированные с помощьюPromiseиasync function) будут выполняться сразу после выполнения скрипта. В этом случае они включаются в области видимостиtimeoutиbreakOnSigint.
-
- Возвращает: <any> результат выполнения последнего оператора скрипта.
Этот метод является сокращённой формой script.runInContext(vm.createContext(options), options). Он выполняет сразу несколько действий:
- Создаёт новый контекст.
- Если
contextObject— объект, он контекстуализируется в новом контексте. ЕслиcontextObjectне определён, создаётся новый объект и контекстуализируется. ЕслиcontextObject—vm.constants.DONT_CONTEXTIFY, контекстуализация не выполняется. - Выполняет скомпилированный код, содержащийся в объекте
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' }]
// This would throw if the context is created from a contextified object.
// vm.constants.DONT_CONTEXTIFY allows creating contexts with ordinary
// global objects that can be frozen.
const freezeScript = new vm.Script('Object.freeze(globalThis); globalThis;');
const frozenContext = freezeScript.runInNewContext(vm.constants.DONT_CONTEXTIFY); copy
script.runInThisContext([options])
-
options<Object>-
displayErrors<boolean> Если задано значениеtrue, при возникновенииErrorво время компиляцииcodeстрока кода, вызвавшая ошибку, добавляется в трассировку стека. По умолчанию: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
Если скрипт скомпилирован из исходного кода, содержащего специальный комментарий source map, этому свойству будет присвоен URL-адрес карты исходного кода.
Модули JavaScript
import vm from 'node:vm';
const script = new vm.Script(`
function myFunc() {}
//# sourceMappingURL=sourcemap.json
`);
console.log(script.sourceMapURL);
// Prints: sourcemap.jsonCommonJS
const vm = require('node:vm');
const script = new vm.Script(`
function myFunc() {}
//# sourceMappingURL=sourcemap.json
`);
console.log(script.sourceMapURL);
// Prints: sourcemap.jsonClass: vm.Module
Эта функция доступна только при включенном флаге команды --experimental-vm-modules.
Класс vm.Module предоставляет низкоуровневый интерфейс для использования модулей ECMAScript в контекстах VM. Он является аналогом класса vm.Script, который в точности соответствует записям модулей, определенным в спецификации ECMAScript.
Однако, в отличие от vm.Script, каждый объект vm.Module привязан к контексту с момента создания. Операции над объектами vm.Module по своей природе асинхронны, в отличие от синхронной природы объектов vm.Script. Использование функций «async» может помочь при работе с объектами 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 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();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 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<Object>-
timeout<integer> Задает количество миллисекунд, в течение которых будет выполняться вычисление, прежде чем выполнение будет прервано. Если выполнение будет прервано, будет вызвана ошибкаError. Это значение должно быть строго положительным целым числом. -
breakOnSigint<boolean> Если значениеtrue, получениеSIGINT(Ctrl+C) завершит выполнение и вызовет ошибкуError. Существующие обработчики события, добавленные с помощьюprocess.on('SIGINT'), отключаются во время выполнения скрипта, но продолжают работать после его завершения. По умолчанию:false.
-
- Возвращает: <Promise> При успешном выполнении разрешается значением
undefined.
Вычисляет модуль.
Этот метод необходимо вызвать после связывания модуля; в противном случае он будет отклонен. Его также можно вызвать, если модуль уже вычислен. В этом случае он либо ничего не сделает, если первоначальное вычисление завершилось успешно (module.status имеет значение 'evaluated'), либо повторно вызовет исключение, возникшее при первоначальном вычислении (module.status имеет значение 'errored').
Этот метод нельзя вызвать во время вычисления модуля (module.status имеет значение 'evaluating').
Соответствует полю конкретного метода Evaluate() в записях циклических модулей спецификации ECMAScript.
module.identifier
Идентификатор текущего модуля, заданный в конструкторе.
module.link(linker)
-
linker<Function>-
specifier<string> Спецификатор запрашиваемого модуля:import foo from 'foo'; // ^^^^^ the module specifier copy
-
referencingModule<vm.Module> ОбъектModule, для которого вызываетсяlink(). -
extra<Object> -
Возвращает: <vm.Module> | <Promise>
-
- Возвращает: <Promise>
Связывает зависимости модуля. Этот метод необходимо вызвать до вычисления, и его можно вызвать только один раз для каждого модуля.
Ожидается, что функция вернет объект Module или Promise, который в конечном итоге разрешится в объект Module. Возвращаемый Module должен удовлетворять двум следующим инвариантам:
- Он должен принадлежать тому же контексту, что и родительский
Module. - Его
statusне должен иметь значение'errored'.
Если status возвращенного Module имеет значение '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()был вызван, но еще не все промисы, возвращенные функцией связывания, разрешились. -
'linked': Модуль успешно связан, и все его зависимости связаны, ноmodule.evaluate()еще не вызывался. -
'evaluating': Модуль вычисляется посредствомmodule.evaluate()для самого себя или родительского модуля. -
'evaluated': Модуль успешно вычислен. -
'errored': Модуль вычислен, но было вызвано исключение.
За исключением 'errored', эта строка статуса соответствует полю [[Status]] записи Cyclic Module Record из спецификации. 'errored' соответствует 'evaluated' в спецификации, но с [[EvaluationError]], установленным в значение, отличное от undefined.
Класс: vm.SourceTextModule
Эта функция доступна только при включенном флаге команды --experimental-vm-modules.
- Расширяет: <vm.Module>
Класс vm.SourceTextModule предоставляет запись модуля с исходным текстом, определенную в спецификации ECMAScript.
new vm.SourceTextModule(code[, options])
-
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 = {};
},
});
// 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);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 = {};
},
});
// 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()
- Возвращает: <Buffer>
Создает кэш кода, который можно использовать с параметром 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
Эта функция доступна только при включенном флаге команды --experimental-vm-modules.
- Расширяет: <vm.Module>
Класс vm.SyntheticModule предоставляет запись синтетического модуля, определенную в спецификации WebIDL. Синтетические модули предназначены для предоставления универсального интерфейса, позволяющего включать источники, отличные от JavaScript, в графы модулей ECMAScript.
const vm = require('node:vm');
const source = '{ "a": 1 }';
const module = new vm.SyntheticModule(['default'], function() {
const obj = JSON.parse(source);
this.setExport('default', obj);
});
// Use `module` in linking... copy
new vm.SyntheticModule(exportNames, evaluateCallback[, options])
-
exportNames<string[]> Массив имен, которые будут экспортированы из модуля. -
evaluateCallback<Function> Вызывается при вычислении модуля. -
options-
identifier<string> Строка, используемая в трассировках стека. По умолчанию:'vm:module(i)', гдеi— возрастающий индекс, специфичный для контекста. -
context<Object> Объект с контекстом, возвращенный методомvm.createContext(), в котором следует компилировать и вычислять этотModule.
-
Создает новый экземпляр SyntheticModule.
Объекты, назначенные экспортам этого экземпляра, могут предоставить импортирующим модуль доступ к информации за пределами указанного context. Используйте vm.runInContext() для создания объектов в определенном контексте.
syntheticModule.setExport(name, value)
-
name<string> Имя экспорта, которому нужно присвоить значение. -
value<any> Значение, присваиваемое экспорту.
Этот метод используется после связывания модуля для задания значений экспортов. Если вызвать его до связывания модуля, будет вызвана ошибка ERR_VM_MODULE_STATUS.
Модули JavaScript
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);CommonJS
const vm = require('node:vm');
(async () => {
const m = new vm.SyntheticModule(['x'], () => {
m.setExport('x', 1);
});
await m.link(() => {});
await m.evaluate();
assert.strictEqual(m.namespace.x, 1);
})();
vm.compileFunction(code[, params[, options]])
-
code<string> Тело компилируемой функции. -
params<string[]> Массив строк, содержащий все параметры функции. -
options<Object>-
filename<string> Задает имя файла, используемое в трассировках стека, создаваемых этим скриптом. По умолчанию:''. -
lineOffset<number> Задает смещение номера строки, отображаемое в трассировках стека, создаваемых этим скриптом. По умолчанию:0. -
columnOffset<number> Задает смещение номера столбца первой строки, отображаемое в трассировках стека, создаваемых этим скриптом. По умолчанию:0. -
cachedData<Buffer> | <TypedArray> | <DataView> Предоставляет необязательный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
Возвращает объект, содержащий часто используемые константы для операций VM.
vm.constants.USE_MAIN_CONTEXT_DEFAULT_LOADER
Константа, которую можно использовать в качестве параметра importModuleDynamically для vm.Script и vm.compileFunction(), чтобы Node.js использовал загрузчик ESM по умолчанию из основного контекста для загрузки запрашиваемого модуля.
Подробную информацию см. в разделе Поддержка динамического import() в API компиляции.
vm.createContext([contextObject[, options]])
-
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, глобальные переменные останутся неизменными.
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.constants.DONT_CONTEXTIFY в качестве аргумента contextObject. Подробности см. в документации по vm.constants.DONT_CONTEXTIFY.
Метод vm.createContext() в первую очередь полезен для создания одного контекста, в котором можно запускать несколько скриптов. Например, при эмуляции веб-браузера этот метод можно использовать для создания одного контекста, представляющего глобальный объект окна, а затем запускать все теги <script> в этом контексте.
Переданные name и origin контекста видны через API Inspector.
vm.isContext(object)
Возвращает true, если данный объект object был преобразован в контекст с помощью vm.createContext() или если он является глобальным объектом контекста, созданного с помощью vm.constants.DONT_CONTEXTIFY.
vm.measureMemory([options])
Измеряет объем памяти, отслеживаемой 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.
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])
-
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 является строкой, она задает имя файла.
В следующем примере компилируются и выполняются разные скрипты с использованием одного объекта, преобразованного в контекст:
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]])
-
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 является строкой, она задает имя файла.
Он выполняет сразу несколько действий:
- Создает новый контекст.
- Если
contextObjectявляется объектом, преобразует его в контекст нового контекста. ЕслиcontextObjectне определен, создает новый объект и преобразует его в контекст. ЕслиcontextObject—vm.constants.DONT_CONTEXTIFY, не выполняет преобразование в контекст. - Компилирует код как
vm.Script - Выполняет скомпилированный код в созданном контексте. Код не имеет доступа к области видимости, в которой вызван этот метод.
- Возвращает результат.
В следующем примере компилируется и выполняется код, который увеличивает глобальную переменную и создает новую. Эти глобальные переменные находятся в contextObject.
const vm = require('node:vm');
const contextObject = {
animal: 'cat',
count: 2,
};
vm.runInNewContext('count += 1; name = "kitty"', contextObject);
console.log(contextObject);
// Prints: { animal: 'cat', count: 3, name: 'kitty' }
// This would throw if the context is created from a contextified object.
// vm.constants.DONT_CONTEXTIFY allows creating contexts with ordinary global objects that
// can be frozen.
const frozenContext = vm.runInNewContext('Object.freeze(globalThis); globalThis;', vm.constants.DONT_CONTEXTIFY); copy
vm.runInThisContext(code[, options])
-
code<string> Код JavaScript для компиляции и выполнения. -
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 компиляции.
-
- Возвращает: <any> результат выполнения последнего оператора в скрипте.
vm.runInThisContext() компилирует code, выполняет его в контексте текущего global и возвращает результат. Выполняемый код не имеет доступа к локальной области видимости, но имеет доступ к текущему объекту global.
Если options — строка, она задает имя файла.
В следующем примере показано, как с помощью vm.runInThisContext() и функции JavaScript eval() выполнить один и тот же код:
const vm = require('node:vm');
let localVar = 'initial value';
const vmResult = vm.runInThisContext('localVar = "vm";');
console.log(`vmResult: '${vmResult}', localVar: '${localVar}'`);
// Prints: vmResult: 'vm', localVar: 'initial value'
const evalResult = eval('localVar = "eval";');
console.log(`evalResult: '${evalResult}', localVar: '${localVar}'`);
// Prints: evalResult: 'eval', localVar: 'eval' 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:
В V8 контекст — это среда выполнения, позволяющая отдельным, не связанным друг с другом приложениям JavaScript работать в одном экземпляре V8. Необходимо явно указать контекст, в котором должен выполняться любой код JavaScript.
Когда метод vm.createContext() вызывается с объектом, аргумент contextObject используется для обертки глобального объекта нового экземпляра контекста V8 (если contextObject равен undefined, перед контекстуализацией из текущего контекста создается новый объект). Этот контекст V8 предоставляет code среду выполнения, использующую методы модуля node:vm, с изолированной глобальной средой, в которой он может работать. Процесс создания контекста V8 и связывания его с contextObject во внешнем контексте и называется в этом документе «контекстуализацией» объекта.
Контекстуализация приводит к некоторым особенностям значения globalThis в контексте. Например, его нельзя заморозить, и оно не является ссылочно равным contextObject во внешнем контексте.
const vm = require('node:vm');
// An undefined `contextObject` option makes the global object contextified.
const context = vm.createContext();
console.log(vm.runInContext('globalThis', context) === context); // false
// A contextified global object cannot be frozen.
try {
vm.runInContext('Object.freeze(globalThis);', context);
} catch (e) {
console.log(e); // TypeError: Cannot freeze
}
console.log(vm.runInContext('globalThis.foo = 1; foo;', context)); // 1 copy Чтобы создать контекст с обычным глобальным объектом и получить доступ к глобальному прокси во внешнем контексте с меньшим количеством особенностей, укажите vm.constants.DONT_CONTEXTIFY в качестве аргумента contextObject.
vm.constants.DONT_CONTEXTIFY
Эта константа, используемая в качестве аргумента contextObject в API vm, указывает Node.js создать контекст, не оборачивая его глобальный объект другим объектом особым для Node.js способом. В результате значение globalThis в новом контексте будет вести себя ближе к обычному.
const vm = require('node:vm');
// Use vm.constants.DONT_CONTEXTIFY to freeze the global object.
const context = vm.createContext(vm.constants.DONT_CONTEXTIFY);
vm.runInContext('Object.freeze(globalThis);', context);
try {
vm.runInContext('bar = 1; bar;', context);
} catch (e) {
console.log(e); // Uncaught ReferenceError: bar is not defined
} copy Если vm.constants.DONT_CONTEXTIFY используется в качестве аргумента contextObject для vm.createContext(), возвращаемый объект будет подобен прокси для глобального объекта в только что созданном контексте, с меньшим количеством особенностей Node.js. Он ссылочно равен значению globalThis в новом контексте, может изменяться извне контекста и позволяет напрямую обращаться к встроенным объектам нового контекста.
const vm = require('node:vm');
const context = vm.createContext(vm.constants.DONT_CONTEXTIFY);
// Returned object is reference equal to globalThis in the new context.
console.log(vm.runInContext('globalThis', context) === context); // true
// Can be used to access globals in the new context directly.
console.log(context.Array); // [Function: Array]
vm.runInContext('foo = 1;', context);
console.log(context.foo); // 1
context.bar = 1;
console.log(vm.runInContext('bar;', context)); // 1
// Can be frozen and it affects the inner context.
Object.freeze(context);
try {
vm.runInContext('baz = 1; baz;', context);
} catch (e) {
console.log(e); // Uncaught ReferenceError: baz is not defined
} copy Взаимодействие тайм-аута с асинхронными задачами и промисами
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, переданные им функции будут добавлены в глобальные очереди, общие для всех контекстов. Поэтому обратные вызовы, переданные этим функциям, также не контролируются тайм-аутом.
Поддержка динамического import() в API компиляции
Следующие API поддерживают параметр importModuleDynamically, который включает динамический import() в коде, скомпилированном модулем vm.
new vm.Scriptvm.compileFunction()new vm.SourceTextModulevm.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);При загрузке пользовательских модулей с помощью загрузчика по умолчанию из основного контекста есть несколько нюансов:
- Модуль будет разрешаться относительно параметра
filename, переданного вvm.Scriptилиvm.compileFunction(). Разрешение может выполняться относительноfilename, заданного абсолютным путем или строкой URL. Еслиfilename— строка, которая не является ни абсолютным путем, ни URL, или если он не задан, разрешение будет выполняться относительно текущего рабочего каталога процесса. В случаеvm.createContext()разрешение всегда выполняется относительно текущего рабочего каталога, поскольку этот параметр используется только при отсутствии ссылающегося скрипта или модуля. - Для любого
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<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, или пустой объект, если значение не было передано. - Возвращает: <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/dist/latest-v20.x/docs/api/vm.html