VM (JavaScript の実行)#

安定性: 2 - Stable

ソースコード: 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. 

クラス: 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> 提供されたソースに対する V8 のコードキャッシュデータを含むオプションの BufferTypedArray、または DataView を提供します。提供された場合、cachedDataRejected の値は、V8 によるデータの受け入れに応じて true または false に設定されます。
    • produceCachedData <boolean> true で、かつ cachedData が存在しない場合、V8 は code のコードキャッシュデータを生成しようと試みます。成功すると、V8 のコードキャッシュデータを含む Buffer が生成され、返された vm.Script インスタンスの cachedData プロパティに格納されます。cachedDataProduced の値は、コードキャッシュデータが正常に生成されたかどうかに応じて true または false に設定されます。このオプションは script.createCachedData() を優先するため非推奨です。デフォルト: false
    • importModuleDynamically <Function> | <vm.constants.USE_MAIN_CONTEXT_DEFAULT_LOADER> import() が呼び出されたときに、このスクリプトの評価中にモジュールをどのようにロードするかを指定するために使用されます。このオプションは実験的なモジュール API の一部です。本番環境での使用は推奨しません。詳細については、コンパイル API における動的 import() のサポートを参照してください。

options が文字列の場合、それはファイル名を指定します。

新しい vm.Script オブジェクトを作成すると code がコンパイルされますが、実行はされません。コンパイルされた vm.Script は後で複数回実行できます。code はどのグローバルオブジェクトにもバインドされません。代わりに、各実行の直前に、その実行のためだけにバインドされます。

script.cachedDataRejected#

vm.Script を作成するために cachedData が提供された場合、この値は V8 によるデータの受け入れに応じて true または false のいずれかに設定されます。それ以外の場合、値は undefined です。

script.createCachedData()#

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()`. 

script.runInContext(contextifiedObject[, options])#

  • contextifiedObject <Object> vm.createContext() メソッドによって返されるコンテキスト化されたオブジェクト。
  • options <Object>
    • displayErrors <boolean> true の場合、code のコンパイル中に Error が発生すると、エラーの原因となったコード行がスタックトレースに添付されます。デフォルト: true
    • timeout <integer> 実行を終了する前に code を実行するミリ秒数を指定します。実行が終了した場合、Error がスローされます。この値は正の整数でなければなりません。
    • breakOnSigint <boolean> true の場合、SIGINT (Ctrl+C) を受信すると実行を終了し、Error をスローします。process.on('SIGINT') を介してアタッチされた既存のイベントハンドラはスクリプト実行中は無効になりますが、その後は引き続き機能します。デフォルト: false
  • 戻り値: <any> スクリプトで実行された最後のステートメントの結果。

vm.Script オブジェクトに含まれるコンパイル済みコードを、指定された contextifiedObject 内で実行し、結果を返します。実行中のコードはローカルスコープにアクセスできません。

次の例では、グローバル変数をインクリメントし、別のグローバル変数の値を設定するコードをコンパイルし、そのコードを複数回実行します。グローバル変数は context オブジェクトに含まれています。

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

timeout または breakOnSigint オプションを使用すると、新しいイベントループと対応するスレッドが開始され、ゼロではないパフォーマンスオーバーヘッドが発生します。

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

  • contextObject <Object> | <vm.constants.DONT_CONTEXTIFY> | <undefined> vm.constants.DONT_CONTEXTIFY またはコンテキスト化されるオブジェクト。undefined の場合、後方互換性のために空のコンテキスト化されたオブジェクトが作成されます。
  • options <Object>
    • displayErrors <boolean> true の場合、code のコンパイル中に Error が発生すると、エラーの原因となったコード行がスタックトレースに添付されます。デフォルト: true
    • timeout <integer> 実行を終了する前に code を実行するミリ秒数を指定します。実行が終了した場合、Error がスローされます。この値は正の整数でなければなりません。
    • breakOnSigint <boolean> true の場合、SIGINT (Ctrl+C) を受信すると実行を終了し、Error をスローします。process.on('SIGINT') を介してアタッチされた既存のイベントハンドラはスクリプト実行中は無効になりますが、その後は引き続き機能します。デフォルト: false
    • contextName <string> 新しく作成されたコンテキストの人間が読める名前。デフォルト: 'VM Context i'。ここで i は作成されたコンテキストの昇順の数値インデックスです。
    • contextOrigin <string> 表示目的で新しく作成されたコンテキストに対応するオリジン。オリジンは URL のようにフォーマットする必要がありますが、URL オブジェクトの url.origin プロパティの値のように、スキーム、ホスト、およびポート (必要な場合) のみを含める必要があります。特に、この文字列は末尾のスラッシュを省略する必要があります。これはパスを示すためです。デフォルト: ''
    • contextCodeGeneration <Object>
      • strings <boolean> false に設定すると、eval または関数コンストラクタ (FunctionGeneratorFunction など) の呼び出しは EvalError をスローします。デフォルト: true
      • wasm <boolean> false に設定すると、WebAssembly モジュールをコンパイルしようとすると WebAssembly.CompileError がスローされます。デフォルト: true
    • microtaskMode <string> afterEvaluate に設定すると、マイクロタスク (Promiseasync function を介してスケジュールされたタスク) はスクリプトの実行直後に実行されます。その場合、それらは timeoutbreakOnSigint のスコープに含まれます。
  • 戻り値: <any> スクリプトで実行された最後のステートメントの結果。

このメソッドは script.runInContext(vm.createContext(options), options) のショートカットです。一度にいくつかのことを行います:

  1. 新しいコンテキストを作成します。
  2. contextObject がオブジェクトの場合、それを新しいコンテキストでコンテキスト化しますcontextObject が undefined の場合、新しいオブジェクトを作成してコンテキスト化しますcontextObjectvm.constants.DONT_CONTEXTIFY の場合、何もコンテキスト化しません
  3. vm.Script オブジェクトに含まれるコンパイル済みコードを作成されたコンテキスト内で実行します。コードはこのメソッドが呼び出されたスコープにアクセスできません。
  4. 結果を返します。

次の例では、グローバル変数を設定するコードをコンパイルし、そのコードを異なるコンテキストで複数回実行します。グローバル変数は個々の context に設定され、その中に含まれます。

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

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

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

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

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

script.runInThisContext([options])#

  • options <Object>
    • displayErrors <boolean> true の場合、code のコンパイル中に Error が発生すると、エラーの原因となったコード行がスタックトレースに添付されます。デフォルト: true
    • timeout <integer> 実行を終了する前に code を実行するミリ秒数を指定します。実行が終了した場合、Error がスローされます。この値は正の整数でなければなりません。
    • breakOnSigint <boolean> true の場合、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 

script.sourceMapURL#

スクリプトがソースマップのマジックコメントを含むソースからコンパイルされた場合、このプロパティはソースマップの URL に設定されます。

import vm from 'node:vm';

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

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

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

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

クラス: vm.Module#

安定性: 1 - Experimental

この機能は --experimental-vm-modules コマンドフラグが有効な場合にのみ利用可能です。

vm.Module クラスは、VM コンテキストで ECMAScript モジュールを使用するための低レベルインターフェイスを提供します。これは、ECMAScript 仕様で定義されているモジュールレコードを密接に模倣する vm.Script クラスの対応物です。

ただし、vm.Script とは異なり、すべての vm.Module オブジェクトは作成時からコンテキストにバインドされます。

vm.Module オブジェクトを使用するには、作成/解析、リンク、評価の 3 つの異なるステップが必要です。これらの 3 つのステップは、次の例で示されています。

この実装は、ECMAScript モジュールローダーよりも低いレベルにあります。また、まだローダーと対話する方法はありませんが、サポートは計画されています。

import vm from 'node:vm';

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

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

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

// Step 2
//
// "Link" the imported dependencies of this Module to it.
//
// Obtain the requested dependencies of a SourceTextModule by
// `sourceTextModule.moduleRequests` and resolve them.
//
// Even top-level Modules without dependencies must be explicitly linked. The
// array passed to `sourceTextModule.linkRequests(modules)` can be
// empty, however.
//
// Note: This is a contrived example in that the resolveAndLinkDependencies
// creates a new "foo" module every time it is called. In a full-fledged
// module system, a cache would probably be used to avoid duplicated modules.

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

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

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

    return requestedModule;
  });

  module.linkRequests(requestedModules);
}

resolveAndLinkDependencies(rootModule);
rootModule.instantiate();

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

// Prints 42.
await rootModule.evaluate();const vm = require('node:vm');

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

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

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

  // Step 2
  //
  // "Link" the imported dependencies of this Module to it.
  //
  // Obtain the requested dependencies of a SourceTextModule by
  // `sourceTextModule.moduleRequests` and resolve them.
  //
  // Even top-level Modules without dependencies must be explicitly linked. The
  // array passed to `sourceTextModule.linkRequests(modules)` can be
  // empty, however.
  //
  // Note: This is a contrived example in that the resolveAndLinkDependencies
  // creates a new "foo" module every time it is called. In a full-fledged
  // module system, a cache would probably be used to avoid duplicated modules.

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

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

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

      return requestedModule;
    });

    module.linkRequests(requestedModules);
  }

  resolveAndLinkDependencies(rootModule);
  rootModule.instantiate();

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

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

module.error#

module.status'errored' の場合、このプロパティには評価中にモジュールによってスローされた例外が含まれます。ステータスがそれ以外の場合、このプロパティにアクセスすると例外がスローされます。

throw undefined; との曖昧さの可能性があるため、例外がスローされなかった場合に値 undefined を使用することはできません。

ECMAScript 仕様の循環モジュールレコード[[EvaluationError]] フィールドに対応します。

module.evaluate([options])#

  • options <Object>
    • timeout <integer> 実行を終了する前に評価するミリ秒数を指定します。実行が中断された場合、Error がスローされます。この値は正の整数でなければなりません。
    • breakOnSigint <boolean> true の場合、SIGINT (Ctrl+C) を受信すると実行を終了し、Error をスローします。process.on('SIGINT') を介してアタッチされた既存のイベントハンドラはスクリプト実行中は無効になりますが、その後は引き続き機能します。デフォルト: false
  • 戻り値: <Promise> 成功時に undefined で fulfill されます。

モジュールを評価します。

これはモジュールがリンクされた後に呼び出す必要があります。そうでない場合は reject されます。モジュールがすでに評価されている場合にも呼び出すことができます。その場合、最初の評価が成功で終了した場合 (module.status'evaluated') は何もしないか、最初の評価で生じた例外を再スローします (module.status'errored')。

このメソッドは、モジュールが評価中 (module.status'evaluating') には呼び出せません。

ECMAScript 仕様の循環モジュールレコードEvaluate() 具体メソッドフィールドに対応します。

module.identifier#

コンストラクタで設定された、現在のモジュールの識別子。

module.link(linker)#

  • linker <Function>
    • specifier <string> 要求されたモジュールの指定子

      import foo from 'foo';
      //              ^^^^^ the module specifier 
    • referencingModule <vm.Module> link() が呼び出される Module オブジェクト。

    • extra <Object>

      • attributes <Object> 属性からのデータ
        import foo from 'foo' with { name: 'value' };
        //                         ^^^^^^^^^^^^^^^^^ the attribute 
        ECMA-262 によると、サポートされていない属性が存在する場合、ホストはエラーをトリガーすることが期待されます。
      • assert <Object> extra.attributes のエイリアス。
    • 戻り値: <vm.Module> | <Promise>

  • 戻り値: <Promise>

モジュールの依存関係をリンクします。このメソッドは評価の前に呼び出す必要があり、モジュールごとに一度しか呼び出せません。

モジュールを同期または非同期にリンクするには、sourceTextModule.linkRequests(modules)sourceTextModule.instantiate() を使用してください。

この関数は Module オブジェクト、または最終的に Module オブジェクトに解決される Promise を返すことが期待されます。返される Module は、次の 2 つの不変条件を満たす必要があります:

  • Module と同じコンテキストに属している必要があります。
  • その status'errored' であってはなりません。

返された Modulestatus'unlinked' の場合、このメソッドは、同じ提供された linker 関数で返された Module に対して再帰的に呼び出されます。

link() は、すべてのリンクインスタンスが有効な Module に解決されたときに解決されるか、リンカ関数が例外をスローするか無効な Module を返した場合に reject される Promise を返します。

リンカ関数は、ECMAScript 仕様で実装定義されている HostResolveImportedModule 抽象操作にほぼ対応しますが、いくつかの重要な違いがあります:

モジュールのリンク中に使用される実際の HostResolveImportedModule 実装は、リンク中にリンクされたモジュールを返すものです。その時点ですべてのモジュールは完全にリンクされているため、HostResolveImportedModule 実装は仕様どおり完全に同期的です。

ECMAScript 仕様の循環モジュールレコードLink() 具体メソッドフィールドに対応します。

module.namespace#

モジュールの名前空間オブジェクト。これは、リンク (module.link()) が完了した後にのみ利用可能です。

ECMAScript 仕様の GetModuleNamespace 抽象操作に対応します。

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#

安定性: 1 - Experimental

この機能は --experimental-vm-modules コマンドフラグが有効な場合にのみ利用可能です。

vm.SourceTextModule クラスは、ECMAScript 仕様で定義されているソーステキストモジュールレコードを提供します。

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

  • code <string> 解析する JavaScript モジュールコード
  • options
    • identifier <string> スタックトレースで使用される文字列。デフォルト: 'vm:module(i)'。ここで i はコンテキスト固有の昇順インデックスです。
    • cachedData <Buffer> | <TypedArray> | <DataView> 提供されたソースに対する V8 のコードキャッシュデータを含むオプションの BufferTypedArray、または DataView を提供します。code は、この cachedData が作成されたモジュールと同じでなければなりません。
    • context <Object> この Module をコンパイルおよび評価するための、vm.createContext() メソッドによって返されるコンテキスト化されたオブジェクト。コンテキストが指定されていない場合、モジュールは現在の実行コンテキストで評価されます。
    • lineOffset <integer> この Module によって生成されるスタックトレースに表示される行番号オフセットを指定します。デフォルト: 0
    • columnOffset <integer> この Module によって生成されるスタックトレースに表示される最初の行の列番号オフセットを指定します。デフォルト: 0
    • initializeImportMeta <Function> import.meta を初期化するために、この Module の評価中に呼び出されます。
    • importModuleDynamically <Function> import() が呼び出されたときに、このモジュールの評価中にモジュールをどのようにロードするかを指定するために使用されます。このオプションは実験的なモジュール API の一部です。本番環境での使用は推奨しません。詳細については、コンパイル API における動的 import() のサポートを参照してください。

新しい SourceTextModule インスタンスを作成します。

import.meta オブジェクトに割り当てられたプロパティがオブジェクトである場合、モジュールが指定された context の外の情報にアクセスできる可能性があります。特定のコンテキストでオブジェクトを作成するには vm.runInContext() を使用してください。

import vm from 'node:vm';

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

const module = new vm.SourceTextModule(
  'Object.getPrototypeOf(import.meta.prop).secret = secret;',
  {
    initializeImportMeta(meta) {
      // Note: this object is created in the top context. As such,
      // Object.getPrototypeOf(import.meta.prop) points to the
      // Object.prototype in the top context rather than that in
      // the contextified object.
      meta.prop = {};
    },
  });
// The module has an empty `moduleRequests` array.
module.linkRequests([]);
module.instantiate();
await module.evaluate();

// Now, Object.prototype.secret will be equal to 42.
//
// To fix this problem, replace
//     meta.prop = {};
// above with
//     meta.prop = vm.runInContext('{}', contextifiedObject);const vm = require('node:vm');
const contextifiedObject = vm.createContext({ secret: 42 });
(async () => {
  const module = new vm.SourceTextModule(
    'Object.getPrototypeOf(import.meta.prop).secret = secret;',
    {
      initializeImportMeta(meta) {
        // Note: this object is created in the top context. As such,
        // Object.getPrototypeOf(import.meta.prop) points to the
        // Object.prototype in the top context rather than that in
        // the contextified object.
        meta.prop = {};
      },
    });
  // The module has an empty `moduleRequests` array.
  module.linkRequests([]);
  module.instantiate();
  await module.evaluate();
  // Now, Object.prototype.secret will be equal to 42.
  //
  // To fix this problem, replace
  //     meta.prop = {};
  // above with
  //     meta.prop = vm.runInContext('{}', contextifiedObject);
})();

sourceTextModule.createCachedData()#

SourceTextModule コンストラクタの cachedData オプションで使用できるコードキャッシュを作成します。Buffer を返します。このメソッドは、モジュールが評価される前であれば何回でも呼び出すことができます。

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

sourceTextModule.dependencySpecifiers#

安定性: 0 - 非推奨: 代わりに sourceTextModule.moduleRequests を使用してください。

このモジュールのすべての依存関係の指定子。返される配列は、変更を許可しないように凍結されます。

ECMAScript 仕様の循環モジュールレコード[[RequestedModules]] フィールドに対応します。

sourceTextModule.hasAsyncGraph()#

依存関係グラフを反復処理し、その依存関係またはこのモジュール自体のいずれかのモジュールにトップレベルの await 式が含まれている場合は true を返し、それ以外の場合は false を返します。

グラフが大きい場合、検索が遅くなる可能性があります。

これを実行するには、まずモジュールをインスタンス化する必要があります。モジュールがまだインスタンス化されていない場合、エラーがスローされます。

sourceTextModule.hasTopLevelAwait()#

モジュール自体にトップレベルの await 式が含まれているかどうかを返します。

これは、ECMAScript 仕様の循環モジュールレコードのフィールド [[HasTLA]] に対応します。

sourceTextModule.instantiate()#

リンクされた要求されたモジュールでモジュールをインスタンス化します。

これにより、再エクスポートされたバインディング名を含む、モジュールのインポートされたバインディングが解決されます。解決できないバインディングがある場合、エラーが同期的にスローされます。

要求されたモジュールに循環依存が含まれている場合、このメソッドを呼び出す前に、サイクルのすべてのモジュールで sourceTextModule.linkRequests(modules) メソッドを呼び出す必要があります。

sourceTextModule.linkRequests(modules)#

モジュールの依存関係をリンクします。このメソッドは評価の前に呼び出す必要があり、モジュールごとに一度しか呼び出せません。

modules 配列内のモジュールインスタンスの順序は、解決される sourceTextModule.moduleRequests の順序に対応している必要があります。2 つのモジュール要求が同じ指定子とインポート属性を持つ場合、それらは同じモジュールインスタンスで解決されなければならず、そうでない場合は ERR_MODULE_LINK_MISMATCH がスローされます。例えば、このモジュールの要求をリンクする場合:

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

2 つのモジュール要求は同一ですが 2 つのフェーズにあるため、modules 配列には同じインスタンスへの 2 つの参照が含まれている必要があります。

モジュールに依存関係がない場合、modules 配列は空にすることができます。

ユーザーは sourceTextModule.moduleRequests を使用して ECMAScript 仕様でホスト定義されている HostLoadImportedModule 抽象操作を実装し、sourceTextModule.linkRequests() を使用して、すべての依存関係を持つモジュール上で仕様で定義されている FinishLoadingImportedModule をバッチで呼び出すことができます。

依存関係の解決が同期的か非同期的かを決定するのは SourceTextModule の作成者次第です。

modules 配列内の各モジュールがリンクされた後、sourceTextModule.instantiate() を呼び出します。

sourceTextModule.moduleRequests#

このモジュールの要求されたインポート依存関係。返される配列は、変更を許可しないように凍結されます。

たとえば、次のようなソーステキストが与えられた場合:

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

sourceTextModule.moduleRequests の値は次のようになります:

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

クラス: vm.SyntheticModule#

安定性: 1 - Experimental

この機能は --experimental-vm-modules コマンドフラグが有効な場合にのみ利用可能です。

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

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

  • exportNames <string[]> モジュールからエクスポートされる名前の配列。
  • evaluateCallback <Function> モジュールが評価されるときに呼び出されます。
  • options
    • identifier <string> スタックトレースで使用される文字列。デフォルト: 'vm:module(i)'。ここで i はコンテキスト固有の昇順インデックスです。
    • context <Object> この Module をコンパイルおよび評価するための、vm.createContext() メソッドによって返されるコンテキスト化されたオブジェクト。

新しい SyntheticModule インスタンスを作成します。

このインスタンスのエクスポートに割り当てられたオブジェクトは、モジュールのインポータが指定された context の外の情報にアクセスできるようにする可能性があります。特定のコンテキストでオブジェクトを作成するには、vm.runInContext() を使用してください。

syntheticModule.setExport(name, value)#

  • name <string> 設定するエクスポートの名前。
  • value <any> エクスポートに設定する値。

このメソッドは、指定された値でモジュールのエクスポートバインディングスロットを設定します。

import vm from 'node:vm';

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

await m.evaluate();

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

型: ModuleRequest#

  • 型: <Object>
    • specifier <string> 要求されたモジュールの指定子。
    • attributes <Object> ImportDeclarationWithClause に渡された "with" の値、または値が提供されなかった場合は空のオブジェクト。
    • phase <string> 要求されたモジュールのフェーズ ("source" または "evaluation")。

ModuleRequest は、指定されたインポート属性とフェーズでモジュールをインポートする要求を表します。

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

  • code <string> コンパイルする関数の本体。
  • params <string[]> 関数のすべてのパラメータを含む文字列の配列。
  • options <Object>
    • filename <string> このスクリプトによって生成されるスタックトレースで使用されるファイル名を指定します。デフォルト: ''
    • lineOffset <number> このスクリプトによって生成されるスタックトレースに表示される行番号のオフセットを指定します。デフォルト: 0
    • columnOffset <number> このスクリプトによって生成されるスタックトレースに表示される最初の行の列番号のオフセットを指定します。デフォルト: 0
    • cachedData <Buffer> | <TypedArray> | <DataView> 提供されたソースに対する V8 のコードキャッシュデータを含むオプションの BufferTypedArray、または DataView を提供します。これは、同じ codeparamsvm.compileFunction() を事前に呼び出して生成されたものでなければなりません。
    • produceCachedData <boolean> 新しいキャッシュデータを生成するかどうかを指定します。デフォルト: false
    • parsingContext <Object> 当該関数がコンパイルされるべきコンテキスト化されたオブジェクト。
    • contextExtensions <Object[]> コンパイル中に適用されるコンテキスト拡張 (現在のスコープをラップするオブジェクト) のコレクションを含む配列。デフォルト: []
    • importModuleDynamically <Function> | <vm.constants.USE_MAIN_CONTEXT_DEFAULT_LOADER> import() が呼び出されたときに、この関数の評価中にモジュールをどのようにロードするかを指定するために使用されます。このオプションは実験的なモジュール API の一部です。本番環境での使用は推奨しません。詳細については、コンパイル API における動的 import() のサポートを参照してください。
  • 戻り値: <Function>

指定されたコードを提供されたコンテキストにコンパイルし (コンテキストが指定されていない場合は現在のコンテキストが使用されます)、指定された params を持つ関数内にラップして返します。

vm.constants#

VM 操作で一般的に使用される定数を含むオブジェクトを返します。

vm.constants.USE_MAIN_CONTEXT_DEFAULT_LOADER#

安定性: 1.1 - Active development

vm.Script および vm.compileFunction()importModuleDynamically オプションとして使用できる定数で、Node.js がメインコンテキストのデフォルト ESM ローダーを使用して要求されたモジュールをロードするようにします。

詳細については、コンパイル API における動的 import() のサポートを参照してください。

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 オブジェクトの url.origin プロパティの値のように、スキーム、ホスト、およびポート (必要な場合) のみを含める必要があります。特に、この文字列は末尾のスラッシュを省略する必要があります。これはパスを示すためです。デフォルト: ''
    • codeGeneration <Object>
      • strings <boolean> false に設定すると、eval または関数コンストラクタ (FunctionGeneratorFunction など) の呼び出しは EvalError をスローします。デフォルト: true
      • wasm <boolean> false に設定すると、WebAssembly モジュールをコンパイルしようとすると WebAssembly.CompileError がスローされます。デフォルト: true
    • microtaskMode <string> afterEvaluate に設定すると、マイクロタスク (Promiseasync function を介してスケジュールされたタスク) は script.runInContext() を通じてスクリプトが実行された直後に実行されます。その場合、それらは timeoutbreakOnSigint のスコープに含まれます。
    • importModuleDynamically <Function> | <vm.constants.USE_MAIN_CONTEXT_DEFAULT_LOADER> リファラースクリプトやモジュールなしでこのコンテキストで import() が呼び出されたときに、モジュールをどのようにロードするかを指定するために使用されます。このオプションは実験的なモジュール API の一部です。本番環境での使用は推奨しません。詳細については、コンパイル API における動的 import() のサポートを参照してください。
  • 戻り値: <Object> コンテキスト化されたオブジェクト。

指定された contextObject がオブジェクトの場合、vm.createContext() メソッドはそのオブジェクトを準備しvm.runInContext() または script.runInContext() の呼び出しで使用できるようにその参照を返します。そのようなスクリプト内では、グローバルオブジェクトは contextObject によってラップされ、既存のすべてのプロパティを保持しつつ、標準のグローバルオブジェクトが持つ組み込みオブジェクトや関数も持ちます。vm モジュールによって実行されるスクリプトの外では、グローバル変数は変更されません。

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

global.globalVar = 3;

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

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

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

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

contextObject が省略された場合 (または明示的に undefined として渡された場合)、新しい空のコンテキスト化されたオブジェクトが返されます。

新しく作成されたコンテキストのグローバルオブジェクトがコンテキスト化されると、通常のグローバルオブジェクトと比較していくつかの癖があります。例えば、凍結することはできません。コンテキスト化の癖なしでコンテキストを作成するには、contextObject 引数として vm.constants.DONT_CONTEXTIFY を渡します。詳細は vm.constants.DONT_CONTEXTIFY のドキュメントを参照してください。

vm.createContext() メソッドは、主に複数のスクリプトを実行するために使用できる単一のコンテキストを作成するのに役立ちます。たとえば、Web ブラウザをエミュレートする場合、このメソッドを使用してウィンドウのグローバルオブジェクトを表す単一のコンテキストを作成し、そのコンテキスト内で全ての <script> タグを一緒に実行できます。

提供されたコンテキストの nameorigin は、インスペクタ API を通じて表示されます。

vm.isContext(object)#

指定された object オブジェクトが vm.createContext() を使用してコンテキスト化されている場合、または vm.constants.DONT_CONTEXTIFY を使用して作成されたコンテキストのグローバルオブジェクトである場合に true を返します。

vm.measureMemory([options])#

安定性: 1 - Experimental

V8 に認識され、現在の V8 アイソレートに認識されているすべてのコンテキスト、またはメインコンテキストによって使用されているメモリを測定します。

  • options <Object> オプション。
    • mode <string> 'summary' または 'detailed' のいずれか。サマリーモードでは、メインコンテキストで測定されたメモリのみが返されます。詳細モードでは、現在の V8 アイソレートに認識されているすべてのコンテキストで測定されたメモリが返されます。デフォルト: 'summary'
    • execution <string> 'default' または 'eager' のいずれか。デフォルトの実行では、プロミスは次にスケジュールされたガベージコレクションが開始されるまで解決されません。これには時間がかかる場合があります (または、次の GC の前にプログラムが終了した場合は決して解決されません)。eager 実行では、メモリを測定するために GC がすぐに開始されます。デフォルト: 'default'
  • 戻り値: <Promise> メモリが正常に測定された場合、プロミスはメモリ使用量に関する情報を含むオブジェクトで解決されます。それ以外の場合は、ERR_CONTEXT_NOT_INITIALIZED エラーで reject されます。

返されたプロミスが解決されるオブジェクトの形式は 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);
  }); 

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

  • code <string> コンパイルして実行する JavaScript コード。
  • contextifiedObject <Object> code がコンパイルされ実行されるときに global として使用されるコンテキスト化されたオブジェクト。
  • options <Object> | <string>
    • filename <string> このスクリプトによって生成されるスタックトレースで使用されるファイル名を指定します。デフォルト: 'evalmachine.<anonymous>'
    • lineOffset <number> このスクリプトによって生成されるスタックトレースに表示される行番号のオフセットを指定します。デフォルト: 0
    • columnOffset <number> このスクリプトによって生成されるスタックトレースに表示される最初の行の列番号のオフセットを指定します。デフォルト: 0
    • displayErrors <boolean> true の場合、code のコンパイル中に Error が発生すると、エラーの原因となったコード行がスタックトレースに添付されます。デフォルト: true
    • timeout <integer> 実行を終了する前に code を実行するミリ秒数を指定します。実行が終了した場合、Error がスローされます。この値は正の整数でなければなりません。
    • breakOnSigint <boolean> true の場合、SIGINT (Ctrl+C) を受信すると実行を終了し、Error をスローします。process.on('SIGINT') を介してアタッチされた既存のイベントハンドラはスクリプト実行中は無効になりますが、その後は引き続き機能します。デフォルト: false
    • cachedData <Buffer> | <TypedArray> | <DataView> 提供されたソースに対する V8 のコードキャッシュデータを含むオプションの BufferTypedArray、または DataView を提供します。
    • importModuleDynamically <Function> | <vm.constants.USE_MAIN_CONTEXT_DEFAULT_LOADER> import() が呼び出されたときに、このスクリプトの評価中にモジュールをどのようにロードするかを指定するために使用されます。このオプションは実験的なモジュール API の一部です。本番環境での使用は推奨しません。詳細については、コンパイル API における動的 import() のサポートを参照してください。

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 } 

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 の場合、code のコンパイル中に Error が発生すると、エラーの原因となったコード行がスタックトレースに添付されます。デフォルト: true
    • timeout <integer> 実行を終了する前に code を実行するミリ秒数を指定します。実行が終了した場合、Error がスローされます。この値は正の整数でなければなりません。
    • breakOnSigint <boolean> true の場合、SIGINT (Ctrl+C) を受信すると実行を終了し、Error をスローします。process.on('SIGINT') を介してアタッチされた既存のイベントハンドラはスクリプト実行中は無効になりますが、その後は引き続き機能します。デフォルト: false
    • contextName <string> 新しく作成されたコンテキストの人間が読める名前。デフォルト: 'VM Context i'。ここで i は作成されたコンテキストの昇順の数値インデックスです。
    • contextOrigin <string> 表示目的で新しく作成されたコンテキストに対応するオリジン。オリジンは URL のようにフォーマットする必要がありますが、URL オブジェクトの url.origin プロパティの値のように、スキーム、ホスト、およびポート (必要な場合) のみを含める必要があります。特に、この文字列は末尾のスラッシュを省略する必要があります。これはパスを示すためです。デフォルト: ''
    • contextCodeGeneration <Object>
      • strings <boolean> false に設定すると、eval または関数コンストラクタ (FunctionGeneratorFunction など) の呼び出しは EvalError をスローします。デフォルト: true
      • wasm <boolean> false に設定すると、WebAssembly モジュールをコンパイルしようとすると WebAssembly.CompileError がスローされます。デフォルト: true
    • cachedData <Buffer> | <TypedArray> | <DataView> 提供されたソースに対する V8 のコードキャッシュデータを含むオプションの BufferTypedArray、または DataView を提供します。
    • importModuleDynamically <Function> | <vm.constants.USE_MAIN_CONTEXT_DEFAULT_LOADER> import() が呼び出されたときに、このスクリプトの評価中にモジュールをどのようにロードするかを指定するために使用されます。このオプションは実験的なモジュール API の一部です。本番環境での使用は推奨しません。詳細については、コンパイル API における動的 import() のサポートを参照してください。
    • microtaskMode <string> afterEvaluate に設定すると、マイクロタスク (Promiseasync function を介してスケジュールされたタスク) はスクリプトの実行直後に実行されます。その場合、それらは timeoutbreakOnSigint のスコープに含まれます。
  • 戻り値: <any> スクリプトで実行された最後のステートメントの結果。

このメソッドは (new vm.Script(code, options)).runInContext(vm.createContext(options), options) のショートカットです。options が文字列の場合、それはファイル名を指定します。

一度にいくつかのことを行います:

  1. 新しいコンテキストを作成します。
  2. contextObject がオブジェクトの場合、それを新しいコンテキストでコンテキスト化しますcontextObject が undefined の場合、新しいオブジェクトを作成してコンテキスト化しますcontextObjectvm.constants.DONT_CONTEXTIFY の場合、何もコンテキスト化しません
  3. コードを vm.Script としてコンパイルします
  4. コンパイルされたコードを作成されたコンテキスト内で実行します。コードはこのメソッドが呼び出されたスコープにアクセスできません。
  5. 結果を返します。

次の例では、グローバル変数をインクリメントし、新しいものを設定するコードをコンパイルして実行します。これらのグローバル変数は contextObject に含まれています。

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

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

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

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

vm.runInThisContext(code[, options])#

  • code <string> コンパイルして実行する JavaScript コード。
  • options <Object> | <string>
    • filename <string> このスクリプトによって生成されるスタックトレースで使用されるファイル名を指定します。デフォルト: 'evalmachine.<anonymous>'
    • lineOffset <number> このスクリプトによって生成されるスタックトレースに表示される行番号のオフセットを指定します。デフォルト: 0
    • columnOffset <number> このスクリプトによって生成されるスタックトレースに表示される最初の行の列番号のオフセットを指定します。デフォルト: 0
    • displayErrors <boolean> true の場合、code のコンパイル中に Error が発生すると、エラーの原因となったコード行がスタックトレースに添付されます。デフォルト: true
    • timeout <integer> 実行を終了する前に code を実行するミリ秒数を指定します。実行が終了した場合、Error がスローされます。この値は正の整数でなければなりません。
    • breakOnSigint <boolean> true の場合、SIGINT (Ctrl+C) を受信すると実行を終了し、Error をスローします。process.on('SIGINT') を介してアタッチされた既存のイベントハンドラはスクリプト実行中は無効になりますが、その後は引き続き機能します。デフォルト: false
    • cachedData <Buffer> | <TypedArray> | <DataView> 提供されたソースに対する V8 のコードキャッシュデータを含むオプションの BufferTypedArray、または DataView を提供します。
    • importModuleDynamically <Function> | <vm.constants.USE_MAIN_CONTEXT_DEFAULT_LOADER> import() が呼び出されたときに、このスクリプトの評価中にモジュールをどのようにロードするかを指定するために使用されます。このオプションは実験的なモジュール API の一部です。本番環境での使用は推奨しません。詳細については、コンパイル API における動的 import() のサポートを参照してください。
  • 戻り値: <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' 

vm.runInThisContext() はローカルスコープにアクセスできないため、localVar は変更されません。対照的に、直接の eval() 呼び出しはローカルスコープにアクセスできるため、localVar の値は変更されます。このようにして、vm.runInThisContext()間接的な eval() 呼び出し、例えば (0,eval)('code') に非常に似ています。

例: VM 内で HTTP サーバーを実行する#

script.runInThisContext() または vm.runInThisContext() のいずれかを使用する場合、コードは現在の V8 グローバルコンテキスト内で実行されます。この VM コンテキストに渡されたコードは、独自の分離されたスコープを持ちます。

node:http モジュールを使用して単純な Web サーバーを実行するには、コンテキストに渡されたコードが独自に 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); 

上記の場合の require() は、それが渡されたコンテキストと状態を共有します。これは、信頼できないコードが実行された場合にリスクをもたらす可能性があり、例えば、コンテキスト内のオブジェクトを意図しない方法で変更する可能性があります。

オブジェクトを「コンテキスト化」するとはどういう意味ですか?#

Node.js 内で実行されるすべての JavaScript は、「コンテキスト」のスコープ内で実行されます。V8 Embedder's Guide によると:

V8 では、コンテキストは、分離された、無関係な JavaScript アプリケーションを V8 の単一インスタンスで実行できるようにする実行環境です。JavaScript コードを実行したいコンテキストを明示的に指定する必要があります。

メソッド vm.createContext() がオブジェクトとともに呼び出されると、contextObject 引数は新しい V8 コンテキストインスタンスのグローバルオブジェクトをラップするために使用されます (contextObjectundefined の場合、コンテキスト化される前に現在のコンテキストから新しいオブジェクトが作成されます)。この V8 コンテキストは、node:vm モジュールのメソッドを使用して実行される code に、それが動作できる分離されたグローバル環境を提供します。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 

通常のグローバルオブジェクトを持つコンテキストを作成し、より少ない癖で外部コンテキストのグローバルプロキシにアクセスするには、contextObject 引数として vm.constants.DONT_CONTEXTIFY を指定します。

vm.constants.DONT_CONTEXTIFY#

この定数は、vm API の contextObject 引数として使用されると、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
} 

vm.createContext()contextObject 引数として vm.constants.DONT_CONTEXTIFY が使用されると、返されるオブジェクトは、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
} 

非同期タスクと Promise とのタイムアウトの相互作用#

Promiseasync function は、JavaScript エンジンによって非同期に実行されるタスクをスケジュールできます。デフォルトでは、これらのタスクは現在のスタック上のすべての JavaScript 関数が実行を終えた後に実行されます。これにより、timeoutbreakOnSigint オプションの機能を回避できます。

たとえば、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'); 

これは、Context を作成するコードに microtaskMode: 'afterEvaluate' を渡すことで対処できます:

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

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

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

この場合、promise.then() を通じてスケジュールされたマイクロタスクは vm.runInNewContext() から戻る前に実行され、timeout 機能によって中断されます。これは vm.Context で実行されるコードにのみ適用されるため、例えば vm.runInThisContext() はこのオプションを取りません。

Promise のコールバックは、それらが作成されたコンテキストのマイクロタスクキューに入ります。たとえば、上記の例で () => loop() が単に loop に置き換えられた場合、loop は外部 (メイン) コンテキストからの関数であるため、グローバルマイクロタスクキューにプッシュされ、したがってタイムアウトを回避することもできます。

process.nextTick(), queueMicrotask(), setTimeout(), setImmediate() などの非同期スケジューリング関数が vm.Context 内で利用可能になった場合、それらに渡された関数はすべてのコンテキストで共有されるグローバルキューに追加されます。したがって、それらの関数に渡されたコールバックもタイムアウトで制御できません。

microtaskMode'afterEvaluate' の場合、コンテキスト間での Promise の共有に注意してください#

'afterEvaluate' モードでは、Context は外部 (メイン) コンテキストが使用するグローバルマイクロタスクキューとは別の独自のマイクロタスクキューを持ちます。このモードは非同期タスクで timeout を強制し breakOnSigint を有効にするために必要ですが、コンテキスト間でプロミスを共有することを難しくします。

以下の例では、プロミスが内部コンテキストで作成され、外部コンテキストと共有されます。外部コンテキストがプロミスを await すると、外部コンテキストの実行フローが驚くべき方法で中断されます: ログステートメントは決して実行されません。

import * as vm from 'node:vm';

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

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

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

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

異なるマイクロタスクキューを持つコンテキスト間でプロミスを正常に共有するには、外部コンテキストが内部マイクロタスクキューにタスクをエンキューするたびに、内部マイクロタスクキューのタスクが実行されることを保証する必要があります。

特定のコンテキストのマイクロタスクキューのタスクは、このコンテキストを使用するスクリプトまたはモジュールで runInContext() または SourceTextModule.evaluate() が呼び出されるたびに実行されます。私たちの例では、await inner_promiserunInContext() の 2 回目の呼び出しをスケジュールすることで、通常の実行フローを復元できます。

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

await inner_promise;

console.log('OK'); 

注意: 厳密に言えば、このモードでは、node:vm は、異なるコンテキストからの非同期タスクがエンキューされた順序とは異なる順序で実行されることを許可することにより、ジョブをエンキューするための ECMAScript 仕様の文言から逸脱しています。

コンパイル API における動的 import() のサポート#

以下の API は、vm モジュールによってコンパイルされたコードで動的 import() を有効にするための importModuleDynamically オプションをサポートしています。

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

このオプションはまだ実験的なモジュール API の一部です。本番環境での使用は推奨しません。

importModuleDynamically オプションが指定されていないか undefined の場合#

このオプションが指定されていない場合、または undefined の場合、import() を含むコードは vm API によってコンパイルできますが、コンパイルされたコードが実行されて実際に import() を呼び出すと、結果は ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING で reject されます。

importModuleDynamicallyvm.constants.USE_MAIN_CONTEXT_DEFAULT_LOADER の場合#

このオプションは現在 vm.SourceTextModule ではサポートされていません。

このオプションを使用すると、コンパイルされたコードで import() が開始されたときに、Node.js はメインコンテキストのデフォルト ESM ローダーを使用して要求されたモジュールをロードし、実行中のコードに返します。

これにより、コンパイルされるコードから fshttp などの Node.js 組み込みモジュールにアクセスできます。コードが別のコンテキストで実行される場合、メインコンテキストからロードされたモジュールによって作成されたオブジェクトは依然としてメインコンテキストのものであり、新しいコンテキストの組み込みクラスの instanceof ではないことに注意してください。

const { Script, constants } = require('node:vm');
const script = new Script(
  'import("node:fs").then(({readFile}) => readFile instanceof Function)',
  { importModuleDynamically: constants.USE_MAIN_CONTEXT_DEFAULT_LOADER });

// false: URL loaded from the main context is not an instance of the Function
// class in the new context.
script.runInNewContext().then(console.log);import { Script, constants } from 'node:vm';

const script = new Script(
  'import("node:fs").then(({readFile}) => readFile instanceof Function)',
  { importModuleDynamically: constants.USE_MAIN_CONTEXT_DEFAULT_LOADER });

// false: URL loaded from the main context is not an instance of the Function
// class in the new context.
script.runInNewContext().then(console.log);

このオプションにより、スクリプトまたは関数がユーザーモジュールをロードすることもできます:

import { Script, constants } from 'node:vm';
import { resolve } from 'node:path';
import { writeFileSync } from 'node:fs';

// Write test.js and test.txt to the directory where the current script
// being run is located.
writeFileSync(resolve(import.meta.dirname, 'test.mjs'),
              'export const filename = "./test.json";');
writeFileSync(resolve(import.meta.dirname, 'test.json'),
              '{"hello": "world"}');

// Compile a script that loads test.mjs and then test.json
// as if the script is placed in the same directory.
const script = new Script(
  `(async function() {
    const { filename } = await import('./test.mjs');
    return import(filename, { with: { type: 'json' } })
  })();`,
  {
    filename: resolve(import.meta.dirname, 'test-with-default.js'),
    importModuleDynamically: constants.USE_MAIN_CONTEXT_DEFAULT_LOADER,
  });

// { default: { hello: 'world' } }
script.runInThisContext().then(console.log);const { Script, constants } = require('node:vm');
const { resolve } = require('node:path');
const { writeFileSync } = require('node:fs');

// Write test.js and test.txt to the directory where the current script
// being run is located.
writeFileSync(resolve(__dirname, 'test.mjs'),
              'export const filename = "./test.json";');
writeFileSync(resolve(__dirname, 'test.json'),
              '{"hello": "world"}');

// Compile a script that loads test.mjs and then test.json
// as if the script is placed in the same directory.
const script = new Script(
  `(async function() {
    const { filename } = await import('./test.mjs');
    return import(filename, { with: { type: 'json' } })
  })();`,
  {
    filename: resolve(__dirname, 'test-with-default.js'),
    importModuleDynamically: constants.USE_MAIN_CONTEXT_DEFAULT_LOADER,
  });

// { default: { hello: 'world' } }
script.runInThisContext().then(console.log);

メインコンテキストのデフォルトローダーを使用してユーザーモジュールをロードする際には、いくつかの注意点があります:

  1. 解決されるモジュールは、vm.Script または vm.compileFunction() に渡される filename オプションに相対的になります。解決は、絶対パスまたは URL 文字列である filename で機能します。filename が絶対パスでも URL でもない文字列である場合、または undefined の場合、解決はプロセスの現在の作業ディレクトリに相対的になります。vm.createContext() の場合、このオプションはリファラースクリプトやモジュールがない場合にのみ使用されるため、解決は常に現在の作業ディレクトリに相対的になります。
  2. 特定のパスに解決される任意の filename について、プロセスがそのパスから特定のモジュールをロードすることに成功すると、結果はキャッシュされる可能性があり、同じパスから同じモジュールを後続でロードすると同じものが返されます。filename が URL 文字列の場合、検索パラメータが異なればキャッシュはヒットしません。URL 文字列ではない filename の場合、現在キャッシュ動作を回避する方法はありません。

importModuleDynamically が関数の場合#

importModuleDynamically が関数の場合、コンパイルされたコードで import() が呼び出されたときに呼び出され、ユーザーが要求されたモジュールをどのようにコンパイルおよび評価するかをカスタマイズできます。現在、このオプションが機能するには、Node.js インスタンスを --experimental-vm-modules フラグ付きで起動する必要があります。フラグが設定されていない場合、このコールバックは無視されます。評価されたコードが実際に import() を呼び出すと、結果は ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING_FLAG で reject されます。

コールバック importModuleDynamically(specifier, referrer, importAttributes) には次のシグネチャがあります:

  • specifier <string> import() に渡された指定子
  • referrer <vm.Script> | <Function> | <vm.SourceTextModule> | <Object> リファラーは、new vm.Scriptvm.runInThisContextvm.runInContextvm.runInNewContext の場合はコンパイルされた vm.Script です。vm.compileFunction の場合はコンパイルされた Functionnew vm.SourceTextModule の場合はコンパイルされた vm.SourceTextModulevm.createContext() の場合はコンテキスト Object です。
  • importAttributes <Object> optionsExpression オプションパラメータに渡された "with" の値、または値が提供されなかった場合は空のオブジェクト。
  • phase <string> 動的インポートのフェーズ ("source" または "evaluation")。
  • 戻り値: <Module Namespace Object> | <vm.Module> エラートラッキングを利用し、then 関数エクスポートを含む名前空間の問題を回避するために、vm.Module を返すことが推奨されます。
// This script must be run with --experimental-vm-modules.
import { Script, SyntheticModule } from 'node:vm';

const script = new Script('import("foo.json", { with: { type: "json" } })', {
  async importModuleDynamically(specifier, referrer, importAttributes) {
    console.log(specifier);  // 'foo.json'
    console.log(referrer);   // The compiled script
    console.log(importAttributes);  // { type: 'json' }
    const m = new SyntheticModule(['bar'], () => { });
    await m.link(() => { });
    m.setExport('bar', { hello: 'world' });
    return m;
  },
});
const result = await script.runInThisContext();
console.log(result);  //  { bar: { hello: 'world' } }// This script must be run with --experimental-vm-modules.
const { Script, SyntheticModule } = require('node:vm');

(async function main() {
  const script = new Script('import("foo.json", { with: { type: "json" } })', {
    async importModuleDynamically(specifier, referrer, importAttributes) {
      console.log(specifier);  // 'foo.json'
      console.log(referrer);   // The compiled script
      console.log(importAttributes);  // { type: 'json' }
      const m = new SyntheticModule(['bar'], () => { });
      await m.link(() => { });
      m.setExport('bar', { hello: 'world' });
      return m;
    },
  });
  const result = await script.runInThisContext();
  console.log(result);  //  { bar: { hello: 'world' } }
})();