VM (JavaScript の実行)#

安定度: 2 - 安定

ソースコード: lib/vm.js

node:vm モジュールを使用すると、V8 仮想マシン コンテキスト内でコードをコンパイルして実行できます。

node:vm モジュールはセキュリティ メカニズムではありません。信頼できないコードを実行するために使用しないでください。

JavaScript コードは、すぐにコンパイルして実行するか、コンパイルして保存し、後で実行できます。

一般的なユースケースは、別の V8 コンテキストでコードを実行することです。これは、呼び出されたコードが、呼び出し元のコードとは異なるグローバル オブジェクトを持つことを意味します。

オブジェクトをコンテキスト化することで、コンテキストを提供できます。呼び出されたコードは、コンテキスト内の任意のプロパティをグローバル変数のように扱います。呼び出されたコードによって引き起こされたグローバル変数への変更は、コンテキスト オブジェクトに反映されます。

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

const x = 1;

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

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

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

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

クラス: vm.Script#

vm.Script クラスのインスタンスには、特定のコンテキストで実行できるプリコンパイルされたスクリプトが含まれています。

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

  • code <string> コンパイルする JavaScript コード。
  • options <Object> | <string>
    • filename <string> このスクリプトによって生成されたスタックトレースで使用されるファイル名を指定します。既定値: 'evalmachine.<anonymous>'
    • lineOffset <number> このスクリプトによって生成されたスタックトレースに表示される行番号オフセットを指定します。既定値: 0
    • columnOffset <number> このスクリプトによって生成されたスタックトレースに表示される 1 行目の列番号オフセットを指定します。既定値: 0
    • cachedData <Buffer> | <TypedArray> | <DataView> 指定されたソースの V8 のコードキャッシュデータを持つオプションの Buffer または TypedArray、または 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#

cachedDatavm.Script の作成時に提供された場合、この値は 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> スクリプトで実行された最後のステートメントの結果。

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

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

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

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

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

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

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

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

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> スクリプトで実行された最後のステートメントの結果。

現在のglobalオブジェクトのコンテキスト内で、vm.Scriptに含まれるコンパイル済みコードを実行します。実行中のコードはローカルスコープにアクセスできませんが、現在の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-vm-modules コマンドフラグが有効になっている場合にのみ使用できます。

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

ただし、vm.Scriptとは異なり、すべてのvm.Moduleオブジェクトは、作成時からコンテキストにバインドされます。vm.Moduleオブジェクトの操作は、vm.Scriptオブジェクトの同期的な性質とは対照的に、本質的に非同期です。「async」関数を使用すると、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 bar = new vm.SourceTextModule(`
  import s from 'foo';
  s;
  print(s);
`, { context: contextifiedObject });

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

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

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

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

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

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

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

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

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

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

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

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

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

module.dependencySpecifiers#

このモジュールのすべての依存関係の指定子。返された配列は、変更を禁止するために凍結されます。

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

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で履行されます。

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

これは、モジュールがリンクされた後に呼び出す必要があります。それ以外の場合は拒否されます。また、モジュールが既に評価されている場合にも呼び出すことができます。この場合、最初の評価が成功した場合(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>

モジュールの依存関係をリンクします。このメソッドは評価前に呼び出す必要があり、モジュールごとに1回だけ呼び出すことができます。

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

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

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

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

リンカー関数は、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' 以外の場合、このステータス文字列は仕様の Cyclic Module Record[[Status]] フィールドに対応します。'errored' は仕様では 'evaluated' に対応しますが、[[EvaluationError]]undefined ではない値に設定されています。

クラス: vm.SourceTextModule#

安定度: 1 - 実験的

この機能は、--experimental-vm-modules コマンドフラグが有効になっている場合にのみ使用できます。

vm.SourceTextModule クラスは、ECMAScript 仕様で定義されている Source Text Module Record を提供します。

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

  • code <string> パースする JavaScript モジュールコード
  • options
    • identifier <string> スタックトレースで使用される文字列。デフォルト: 'vm:module(i)'。ここで i はコンテキスト固有の昇順インデックスです。
    • cachedData <Buffer> | <TypedArray> | <DataView> 提供されたソースの V8 コードキャッシュデータを含む、オプションの Buffer または TypedArray、または DataView を提供します。code は、この cachedData が作成されたモジュールと同じである必要があります。
    • context <Object> この Module をコンパイルおよび評価する コンテキスト化されたオブジェクト。vm.createContext() メソッドによって返されます。コンテキストが指定されていない場合、モジュールは現在の実行コンテキストで評価されます。
    • lineOffset <integer> この Module によって生成されるスタックトレースに表示される行番号オフセットを指定します。デフォルト: 0
    • columnOffset <integer> この Module によって生成されるスタックトレースに表示される、1行目の列番号オフセットを指定します。デフォルト: 0
    • initializeImportMeta <Function> この Module の評価中に import.meta を初期化するために呼び出されます。
    • 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 = {};
    },
  });
// 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);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()#

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

クラス: vm.SyntheticModule#

安定度: 1 - 実験的

この機能は、--experimental-vm-modules コマンドフラグが有効になっている場合にのみ使用できます。

vm.SyntheticModule クラスは、WebIDL 仕様で定義されている Synthetic Module Record を提供します。合成モジュールの目的は、非 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> エクスポートに設定する値。

このメソッドは、モジュールがリンクされた後、エクスポートの値を設定するために使用されます。モジュールがリンクされる前に呼び出された場合、ERR_VM_MODULE_STATUS エラーがスローされます。

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);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> このスクリプトによって生成されたスタックトレースに表示される 1 行目の列番号オフセットを指定します。既定値: 0
    • cachedData <Buffer> | <TypedArray> | <DataView> 提供されたソースの V8 コードキャッシュデータを含む、オプションの Buffer または TypedArray、または DataView を提供します。これは、同じ codeparams を使用した以前の vm.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 - アクティブな開発

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

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

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

  • contextObject <Object>
  • options <Object>
    • name <string> 新しく作成されたコンテキストの人間が読める名前。デフォルト: 'VM Context i'。ここで i は作成されたコンテキストの昇順の数値インデックスです。
    • origin <string> オリジン は、表示目的で新しく作成されたコンテキストに対応します。オリジンは URL のようにフォーマットする必要がありますが、スキーム、ホスト、およびポート(必要な場合)のみを含める必要があります。これは、url.origin プロパティの値のようなものです。URL オブジェクト。特に、この文字列は末尾のスラッシュを省略する必要があります。これはパスを示すためです。デフォルト: ''
    • codeGeneration <Object>
      • strings <boolean> falseに設定すると、evalまたは関数コンストラクター(FunctionGeneratorFunctionなど)への呼び出しは、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 の一部です。本番環境での使用はお勧めしません。詳細については、コンパイル 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 として明示的に渡された)場合、新しく空の コンテキスト化された オブジェクトが返されます。

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

コンテキストに提供された name および origin は、インスペクター API を通して表示されます。

vm.isContext(object)#

指定された object オブジェクトが vm.createContext() を使用してコンテキスト化されている場合は true を返します。

vm.measureMemory([options])#

安定度: 1 - 実験的

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

  • options <Object> オプション。
    • mode <string> 'summary' または 'detailed' のいずれかです。summary モードでは、メインコンテキストで測定されたメモリのみが返されます。detailed モードでは、現在の V8 アイソレートに既知のすべてのコンテキストで測定されたメモリが返されます。デフォルト: 'summary'
    • execution <string> 'default' または 'eager' のいずれかです。デフォルトの実行では、次のスケジュールされたガベージコレクションが開始されるまで(または、次の GC の前にプログラムが終了する場合は決して) Promise は解決されません。eager 実行では、メモリを測定するために GC がすぐに開始されます。デフォルト: 'default'
  • 戻り値:<Promise> メモリが正常に測定された場合、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);
  }); 

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

  • code <string> コンパイルおよび実行する JavaScript コード。
  • contextifiedObject <Object> code がコンパイルおよび実行されるときに global として使用される、コンテキスト化された オブジェクト。
  • options <Object> | <string>
    • filename <string> このスクリプトによって生成されたスタックトレースで使用されるファイル名を指定します。既定値: 'evalmachine.<anonymous>'
    • lineOffset <number> このスクリプトによって生成されたスタックトレースに表示される行番号オフセットを指定します。既定値: 0
    • columnOffset <number> このスクリプトによって生成されたスタックトレースに表示される 1 行目の列番号オフセットを指定します。既定値: 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> コンテキスト化されるオブジェクト。undefined の場合、新しいオブジェクトが作成されます。
  • options <Object> | <string>
    • filename <string> このスクリプトによって生成されたスタックトレースで使用されるファイル名を指定します。既定値: 'evalmachine.<anonymous>'
    • lineOffset <number> このスクリプトによって生成されたスタックトレースに表示される行番号オフセットを指定します。既定値: 0
    • columnOffset <number> このスクリプトによって生成されたスタックトレースに表示される 1 行目の列番号オフセットを指定します。既定値: 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.originプロパティの値のように、URLオブジェクト。最も重要なのは、この文字列は、パスを示す末尾のスラッシュを省略する必要があることです。デフォルト: ''
    • 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によってスケジュールされたタスク)は、スクリプトの実行直後に実行されます。この場合、それらはtimeoutおよびbreakOnSigintのスコープに含まれます。
  • 返り値: <any> スクリプトで実行された最後のステートメントの結果。

vm.runInNewContext() は、最初に指定された contextObject をコンテキスト化(または、undefined として渡された場合は新しい contextObject を作成)し、code をコンパイルし、作成されたコンテキスト内で実行してから、結果を返します。実行中のコードはローカルスコープにアクセスできません。

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

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

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

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

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

vm.runInThisContext(code[, options])#

  • code <string> コンパイルおよび実行する JavaScript コード。
  • options <Object> | <string>
    • filename <string> このスクリプトによって生成されたスタックトレースで使用されるファイル名を指定します。既定値: 'evalmachine.<anonymous>'
    • lineOffset <number> このスクリプトによって生成されたスタックトレースに表示される行番号オフセットを指定します。既定値: 0
    • columnOffset <number> このスクリプトによって生成されたスタックトレースに表示される 1 行目の列番号オフセットを指定します。既定値: 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 エンベッダーのガイド によると、

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

メソッド vm.createContext() が呼び出されると、contextObject 引数 (または contextObjectundefined の場合は新しく作成されたオブジェクト) は、内部的に V8 コンテキストの新しいインスタンスに関連付けられます。この V8 コンテキストは、node:vm モジュールのメソッドを使用して実行される code に、操作可能な隔離されたグローバル環境を提供します。V8 コンテキストを作成し、それを contextObject に関連付けるプロセスが、このドキュメントでオブジェクトの「コンテキスト化」と呼んでいるものです。

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

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

例えば、vm.runInNewContext()によって実行され、タイムアウトが5ミリ秒に設定された次のコードは、Promiseが解決された後に実行される無限ループをスケジュールします。スケジュールされたループはタイムアウトによって中断されることはありません。

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内で利用可能になっている場合、それらに渡された関数はグローバルキューに追加され、すべてのコンテキストで共有されます。したがって、これらの関数に渡されたコールバックもタイムアウトによって制御できません。

コンパイル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である場合、import()を含むコードはvm APIによってコンパイルできます。ただし、コンパイルされたコードが実行され、実際にimport()を呼び出すと、結果はERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSINGでリジェクトされます。

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のいずれでもない文字列である場合、または未定義である場合、解決はプロセスの現在の作業ディレクトリを基準にします。vm.createContext()の場合、このオプションはリファラースクリプトまたはモジュールがない場合にのみ使用されるため、解決は常に現在の作業ディレクトリを基準にします。
  2. 特定のパスに解決される任意のfilenameについて、プロセスがそのパスから特定のモジュールをロードすると、結果がキャッシュされる可能性があり、同じパスからの同じモジュールの後続のロードは同じものを返します。filenameがURL文字列の場合、検索パラメーターが異なる場合はキャッシュがヒットしません。URL文字列ではないfilenameの場合、現時点ではキャッシュの動作を回避する方法はありません。

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> リファラーは、new vm.Scriptvm.runInThisContextvm.runInContextvm.runInNewContextの場合はコンパイルされたvm.Scriptです。vm.compileFunctionの場合はコンパイルされたFunctionnew vm.SourceTextModuleの場合はコンパイルされたvm.SourceTextModulevm.createContext()の場合はコンテキストObjectです。
  • importAttributes <Object> optionsExpressionのオプションパラメーターに渡される"with"値、または値が提供されていない場合は空のオブジェクト。
  • 戻り値: <モジュール名前空間オブジェクト> | <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' } }
})();