VM(執行 JavaScript)#

穩定性:2 - 穩定

原始碼: lib/vm.js

node:vm 模組可以在 V8 虛擬機器環境中編譯和執行程式碼。

node:vm 模組並非安全機制。請勿使用它來執行不可信賴的程式碼。

JavaScript 程式碼可以立即編譯並執行,或編譯、儲存,然後稍後執行。

常見的用例是在不同的 V8 Context 中執行程式碼。這表示呼叫的程式碼具有與呼叫程式碼不同的全域物件。

可以透過將物件背景化來提供背景。呼叫的程式碼會將背景中的任何屬性視為全域變數。呼叫的程式碼所造成的任何全域變數變更都會反映在背景物件中。

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 <數字> 指定此腳本產生的堆疊追蹤中顯示的第一行欄位號碼偏移量。預設值:0
    • cachedData <Buffer> | <TypedArray> | <DataView> 提供一個選用的 BufferTypedArray,或具有 V8 的程式碼快取資料的 DataView,供應來源使用。提供時,cachedDataRejected 值會設定為 truefalse,視 V8 是否接受資料而定。
    • produceCachedData <布林>true 且沒有 cachedData 時,V8 會嘗試產生 code 的程式碼快取資料。成功時,會產生一個具有 V8 程式碼快取資料的 Buffer,並儲存在傳回的 vm.Script 執行個體的 cachedData 屬性中。cachedDataProduced 值會設定為 truefalse,視是否成功產生程式碼快取資料而定。此選項已過時,建議使用 script.createCachedData()預設值:false
    • importModuleDynamically <函式> | <vm.constants.USE_MAIN_CONTEXT_DEFAULT_LOADER> 用於指定在呼叫 import() 時,評估此腳本期間應如何載入模組。此選項是實驗性模組 API 的一部分。我們不建議在生產環境中使用它。如需詳細資訊,請參閱 編譯 API 中動態 import() 的支援

如果 options 是字串,則它會指定檔案名稱。

建立新的 vm.Script 物件會編譯 code,但不會執行它。編譯後的 vm.Script 可以稍後執行多次。code 沒有繫結到任何全域物件;而是每次執行前,只針對該執行繫結。

script.cachedDataRejected#

cachedData 提供給 vm.Script 建立時,此值會根據 V8 是否接受資料而設定為 truefalse。否則值為 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() 方法傳回的 context 化 物件。
  • 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' } 

使用 timeoutbreakOnSigint 選項會導致啟動新的事件迴圈和對應的執行緒,這些執行緒會有非零效能開銷。

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,但僅包含必要的 scheme、主機和埠,例如 url.origin 屬性的 URL 物件的值。最重要的是,此字串應省略尾斜線,因為它表示路徑。預設值:''
    • contextCodeGeneration <Object>
      • strings <boolean> 如果設為 false,任何呼叫 eval 或函式建構函式 (FunctionGeneratorFunction 等) 都會擲回 EvalError預設值:true
      • wasm <boolean> 如果設為 false,任何嘗試編譯 WebAssembly 模組都會擲回 WebAssembly.CompileError預設值:true
    • microtaskMode <字串> 如果設為 afterEvaluate,微任務(透過 Promiseasync function 排程的任務)會在腳本執行後立即執行。在這種情況下,它們會包含在 timeoutbreakOnSigint 範圍內。
  • 傳回值:<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 模組。它是 vm.Script 類別的對應類別,它緊密反映 ECMAScript 規範中定義的 模組記錄

然而,與 vm.Script 不同的是,每個 vm.Module 物件都從其建立時起就繫結至一個內容。vm.Module 物件上的操作本質上是非同步的,這與 vm.Script 物件的同步性質相反。使用「非同步」函式有助於操作 vm.Module 物件。

使用 vm.Module 物件需要三個不同的步驟:建立/剖析、連結和評估。以下範例說明這三個步驟。

此實作的層級低於 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>

連結模組相依性。此方法必須在評估之前呼叫,且每個模組只能呼叫一次。

此函式預期傳回 Module 物件或 Promise,最終會解析為 Module 物件。傳回的 Module 必須符合下列兩個不變式

  • 它必須屬於與父 Module 相同的內容。
  • 它的 status 不能是 'errored'

如果傳回的 Modulestatus'unlinked',此方法會以相同的提供 linker 函式,遞迴呼叫傳回的 Module

link() 傳回 Promise,當所有連結執行個體解析為有效 Module 時,它會解析;如果連結器函式擲回例外或傳回無效 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' 之外,此狀態字串對應於規格的 循環模組記錄[[Status]] 欄位。'errored' 對應於規格中的 'evaluated',但 [[EvaluationError]] 設定為非 undefined 的值。

類別:vm.SourceTextModule#

穩定性:1 - 實驗性

此功能僅在啟用 --experimental-vm-modules 命令旗標時可用。

vm.SourceTextModule 類別提供 ECMAScript 規格中定義的 原始文字模組記錄

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

  • code <string> 要剖析的 JavaScript 模組程式碼
  • 選項
    • identifier <string> 堆疊追蹤中使用的字串。預設:'vm:module(i)',其中 i 是特定於內容的遞增索引。
    • cachedData <Buffer> | <TypedArray> | <DataView> 提供一個選用的 BufferTypedArray,或 DataView,其中包含 V8 的程式碼快取資料,供應給程式碼來源。code 必須與建立此 cachedData 的模組相同。
    • context <Object> 內容化 的物件,由 vm.createContext() 方法傳回,用於編譯並評估此 Module。如果未指定內容,則會在目前的執行內容中評估模組。
    • lineOffset <整數> 指定由這個 Module 產生的堆疊追蹤中顯示的行號偏移量。預設:0
    • columnOffset <整數> 指定由這個 Module 產生的堆疊追蹤中顯示的第一行欄位號碼偏移量。預設:0
    • initializeImportMeta <函式> 在評估這個 Module 時呼叫,以初始化 import.meta
    • importModuleDynamically <函式> 用於指定在呼叫 import() 時,在評估這個模組時應如何載入模組。此選項是實驗性模組 API 的一部分。我們不建議在生產環境中使用它。有關詳細資訊,請參閱 編譯 API 中對動態 import() 的支援

建立新的 SourceTextModule 實例。

指定給 import.meta 物件的屬性(為物件)可能允許模組存取指定 context 之外的資訊。使用 vm.runInContext() 在特定 context 中建立物件。

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 規格中定義的合成模組記錄。合成模組的目的是提供一個通用介面,以將非 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> 在評估模組時呼叫。
  • 選項
    • identifier <string> 堆疊追蹤中使用的字串。預設:'vm:module(i)',其中 i 是特定於內容的遞增索引。
    • context <Object> 語境化物件,由 vm.createContext() 方法傳回,用於編譯和評估此 Module

建立新的 SyntheticModule 執行個體。

指定給此執行個體的匯出物件可能會允許模組的匯入者存取指定 context 之外的資訊。使用 vm.runInContext() 在特定語境中建立物件。

syntheticModule.setExport(name, value)#

  • name <字串> 要設定的匯出的名稱。
  • 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 <字串> 要編譯的函式主體。
  • params <字串陣列> 包含函式所有參數的字串陣列。
  • options <Object>
    • filename <字串> 指定此腳本產生的堆疊追蹤中所使用的檔案名稱。預設:''
    • lineOffset <number> 指定此腳本產生的堆疊追蹤中顯示的行號偏移量。預設:0
    • columnOffset <數字> 指定此腳本產生的堆疊追蹤中顯示的第一行欄位號碼偏移量。預設值:0
    • cachedData <Buffer> | <TypedArray> | <DataView> 提供一個選擇性的 BufferTypedArray,或 DataView,其中包含 V8 的程式碼快取資料,供應來源使用。這必須是由先前的 vm.compileFunction() 呼叫產生,且具有相同的 codeparams
    • produceCachedData <布林值> 指定是否產生新的快取資料。預設值:false
    • parsingContext <物件> 已設定語境的物件,指定的函式應編譯在其中。
    • contextExtensions <物件陣列> 包含一組語境擴充功能的陣列(包裝目前範圍的物件),在編譯時套用。預設值:[]
  • importModuleDynamically <函式> | <vm.constants.USE_MAIN_CONTEXT_DEFAULT_LOADER> 用於指定在呼叫 import() 時,在評估此函式期間應如何載入模組。此選項是實驗性模組 API 的一部分。我們不建議在生產環境中使用它。如需詳細資訊,請參閱 編譯 API 中對動態 import() 的支援
  • 傳回:<函式>

將提供的程式碼編譯到提供的語境中(如果未提供語境,則使用目前的語境),並將其包裝在具有指定 params 的函式內傳回。

vm.constants#

傳回包含 VM 作業中常用的常數的物件。

vm.constants.USE_MAIN_CONTEXT_DEFAULT_LOADER#

穩定性:1.1 - 積極開發

一個常數,可用作 vm.Scriptvm.compileFunction()importModuleDynamically 選項,以便 Node.js 使用主語境的預設 ESM 載入器載入所要求的模組。

有關詳細資訊,請參閱 編譯 API 中動態 import() 的支援

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

  • contextObject <Object>
  • options <Object>
    • name <string> 新建立的內容的人類可讀名稱。預設值:'VM Context i',其中 i 是建立的內容的遞增數字索引。
    • origin <string> Origin 對應於新建立的內容以供顯示。來源應格式化為 URL,但僅包含必要的 scheme、主機和埠,例如 url.origin 屬性的 URL 物件的值。最重要的是,這個字串應省略尾斜線,因為那表示路徑。預設值:''
    • codeGeneration <Object>
      • strings <boolean> 如果設為 false,任何呼叫 eval 或函式建構函式 (FunctionGeneratorFunction 等) 都會擲回 EvalError預設值:true
      • wasm <boolean> 如果設為 false,任何嘗試編譯 WebAssembly 模組都會擲回 WebAssembly.CompileError預設值:true
    • microtaskMode <字串> 如果設為 afterEvaluate,微任務(透過 Promiseasync function 排定的任務)將在腳本透過 script.runInContext() 執行後立即執行。在這種情況下,它們會包含在 timeoutbreakOnSigint 範圍內。
    • importModuleDynamically <函式> | <vm.constants.USE_MAIN_CONTEXT_DEFAULT_LOADER> 用於指定在這個內容中呼叫 import() 時,應如何載入模組,而沒有參考腳本或模組。這個選項是實驗性模組 API 的一部分。我們不建議在生產環境中使用它。有關詳細資訊,請參閱 編譯 API 中動態 import() 的支援
  • 傳回:<物件> 語境化物件。

如果給定 contextObjectvm.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() 方法主要用於建立一個單一內容,可以用來執行多個指令碼。例如,如果模擬一個網頁瀏覽器,這個方法可以用來建立一個代表視窗全域物件的單一內容,然後在這個內容中一起執行所有 <script> 標籤。

提供的內容 nameorigin 會透過 Inspector API 可見。

vm.isContext(object)#

如果給定的 object 物件已使用 vm.createContext() 語境化,則傳回 true

vm.measureMemory([options])#

穩定性:1 - 實驗性

測量 V8 已知的記憶體,以及當前 V8 隔離或主內容所使用的所有內容。

  • options <Object> 選擇性。
    • mode <string> 'summary''detailed'。在摘要模式中,只會傳回主內容測量的記憶體。在詳細模式中,會傳回當前 V8 隔離中所有已知內容測量的記憶體。預設:'summary'
    • execution <string> 'default''eager'。使用預設執行時,承諾會等到下一個排定的垃圾回收開始後才會解析,這可能會花一段時間(或如果程式在下次 GC 之前結束,則永遠不會解析)。使用熱切執行時,GC 會立即啟動以測量記憶體。預設:'default'
  • 傳回:<Promise>如果成功測量記憶體,承諾將會解析為包含記憶體使用資訊的物件。否則,它將會以ERR_CONTEXT_NOT_INITIALIZED錯誤拒絕。

傳回的承諾可能解析的物件格式特定於 V8 引擎,並且可能隨著 V8 的不同版本而改變。

傳回的結果不同於 v8.getHeapSpaceStatistics() 傳回的統計資料,因為 vm.measureMemory() 測量 V8 引擎目前執行個體中每個特定 V8 語境的記憶體可及性,而 v8.getHeapSpaceStatistics() 的結果測量目前 V8 執行個體中每個堆空間佔用的記憶體。

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

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

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

  • code <string>要編譯和執行的 JavaScript 程式碼。
  • contextifiedObject <Object>當編譯和執行code時,將會用作global語境化物件。
  • options <Object> | <string>
    • filename <string> 指定此腳本產生的堆疊追蹤中所使用的檔名。預設:'evalmachine.<anonymous>'
    • lineOffset <number> 指定此腳本產生的堆疊追蹤中顯示的行號偏移量。預設:0
    • columnOffset <數字> 指定此腳本產生的堆疊追蹤中顯示的第一行欄位號碼偏移量。預設值: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>提供一個選用的 BufferTypedArray,或 DataView,其中包含 V8 的程式碼快取資料,供應給的來源。
    • 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 <數字> 指定此腳本產生的堆疊追蹤中顯示的第一行欄位號碼偏移量。預設值: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,但僅包含必要的 scheme、主機和埠,例如 url.origin 屬性的 URL 物件的值。最重要的是,此字串應省略尾斜線,因為它表示路徑。預設值:''
    • contextCodeGeneration <Object>
      • strings <boolean> 如果設為 false,任何呼叫 eval 或函式建構函式 (FunctionGeneratorFunction 等) 都會擲回 EvalError預設值:true
      • wasm <boolean> 如果設為 false,任何嘗試編譯 WebAssembly 模組都會擲回 WebAssembly.CompileError預設值:true
    • cachedData <Buffer> | <TypedArray> | <DataView>提供一個選用的 BufferTypedArray,或 DataView,其中包含 V8 的程式碼快取資料,供應給的來源。
    • importModuleDynamically <Function> | <vm.constants.USE_MAIN_CONTEXT_DEFAULT_LOADER>用於指定在呼叫 import() 時,在評估此腳本期間應如何載入模組。此選項是實驗性模組 API 的一部分。我們不建議在生產環境中使用它。有關詳細資訊,請參閱編譯 API 中動態import() 的支援
    • microtaskMode <字串> 如果設為 afterEvaluate,微任務(透過 Promiseasync function 排程的任務)會在腳本執行後立即執行。在這種情況下,它們會包含在 timeoutbreakOnSigint 範圍內。
  • 傳回值:<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 <數字> 指定此腳本產生的堆疊追蹤中顯示的第一行欄位號碼偏移量。預設值: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>提供一個選用的 BufferTypedArray,或 DataView,其中包含 V8 的程式碼快取資料,供應給的來源。
    • 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 模組執行簡單的網路伺服器,傳遞給環境的程式碼必須自行呼叫 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 參數(或如果 contextObjectundefined 則為新建立的物件)會在內部與 V8 環境的新執行個體關聯。此 V8 環境會提供使用 node:vm 模組方法執行的 code 一個孤立的全球環境,讓它可以在其中執行。建立 V8 環境並將其與 contextObject 關聯的程序就是本文檔所稱的「將物件環境化」。

逾時互動與非同步工作和承諾#

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

這可以用傳遞 microtaskMode: 'afterEvaluate' 給建立 Context 的程式碼來解決

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

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

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

在這種情況下,透過 promise.then() 安排的微任務會在從 vm.runInNewContext() 傳回之前執行,並會因 timeout 功能而中斷。這只適用於在 vm.Context 中執行的程式碼,所以例如 vm.runInThisContext() 沒有這個選項。

承諾回呼會進入建立它們的內容的微任務佇列。例如,如果在上述範例中將 () => loop() 替換為 loop,則 loop 會推入到全域微任務佇列,因為它是一個來自外部(主)內容的函式,因此也能跳脫逾時。

如果非同步安排函式(例如 process.nextTick()queueMicrotask()setTimeout()setImmediate() 等)在 vm.Context 內部提供,傳遞給它們的函式會新增到全域佇列,而這些佇列是由所有內容共用的。因此,傳遞給這些函式的回呼也無法透過逾時控制。

編譯 API 中對動態 import() 的支援#

下列 API 支援 importModuleDynamically 選項,以啟用 vm 模組編譯的程式碼中的動態 import()

  • 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 載入器載入請求的模組,並將其傳回正在執行的程式碼。

這讓正在編譯的程式碼可以使用 Node.js 內建模組,例如 fshttp。如果程式碼在不同的內容中執行,請注意,從主內容載入的模組所建立的物件仍來自主內容,而不是新內容中內建類別的 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.Scriptvm.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 <字串> 傳遞給 import() 的指定元
  • referrer <vm.Script> | <函式> | <vm.SourceTextModule> | <物件> 對於 new vm.Scriptvm.runInThisContextvm.runInContextvm.runInNewContext,參照者是已編譯的 vm.Script。對於 vm.compileFunction,參照者是已編譯的 函式;對於 new vm.SourceTextModule,參照者是已編譯的 vm.SourceTextModule;對於 vm.createContext(),參照者是內容 物件
  • importAttributes <物件> 傳遞給 optionsExpression 選用參數的 "with" 值,或是在未提供值的情況下傳遞空物件。
  • 傳回:<模組命名空間物件> | <vm.Module> 建議傳回 vm.Module 以利用錯誤追蹤,並避免命名空間包含 then 函式外傳的問題。
// 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' } }
})();