單一可執行應用程式#

穩定性:1.1 - 積極開發

原始碼: src/node_sea.cc

此功能讓 Node.js 應用程式能方便地配送到未安裝 Node.js 的系統中。

Node.js 支援建立 單一可執行應用程式,方法是讓 Node.js 注入一個已準備好的 blob,其中可以包含一個已綑綁的腳本,到 node 二進位檔中。在啟動期間,程式會檢查是否有任何已注入的內容。如果找到 blob,程式會執行 blob 中的腳本。否則,Node.js 會以正常方式執行。

單一可執行應用程式功能目前僅支援使用 CommonJS 模組系統執行單一嵌入式腳本。

使用者可以使用 node 二進制檔本身和任何可以將資源注入二進制檔的工具,從其捆綁腳本中建立單一可執行應用程式。

以下是使用其中一個工具 postject 建立單一可執行應用程式的步驟

  1. 建立 JavaScript 檔案

    echo 'console.log(`Hello, ${process.argv[2]}!`);' > hello.js 
  2. 建立組態檔,建立可以注入單一可執行應用程式的 blob(詳細資訊請參閱 建立單一可執行準備 blob

    echo '{ "main": "hello.js", "output": "sea-prep.blob" }' > sea-config.json 
  3. 產生要注入的 blob

    node --experimental-sea-config sea-config.json 
  4. 建立 node 可執行檔的副本,並根據您的需求命名

    • 在非 Windows 系統上
    cp $(command -v node) hello 
    • 在 Windows 上
    node -e "require('fs').copyFileSync(process.execPath, 'hello.exe')" 

    .exe 副檔名是必要的。

  5. 移除二進制檔的簽章(僅限 macOS 和 Windows)

    • 在 macOS 上
    codesign --remove-signature hello 
    • 在 Windows 上(選用)

    signtool 可以從已安裝的 Windows SDK 使用。如果跳過此步驟,請忽略 postject 的任何簽章相關警告。

    signtool remove /s hello.exe 
  6. 使用下列選項執行 postject,將 blob 注入複製的二進制檔

    • hello / hello.exe - 在步驟 4 中建立的 node 可執行檔副本的名稱。
    • NODE_SEA_BLOB - blob 內容將儲存在其中的二進制檔中的資源 / 註記 / 區段的名稱。
    • sea-prep.blob - 在步驟 1 中建立的 blob 的名稱。
    • --sentinel-fuse NODE_SEA_FUSE_fce680ab2cc467b6e072b8b5df1996b2 - Node.js 專案用來偵測檔案是否已注入的 fuse
    • --macho-segment-name NODE_SEA(僅在 macOS 上需要) - blob 內容將儲存在其中的二進制檔中的區段的名稱。

    總之,以下是每個平台所需的指令

    • 在 Linux 上

      npx postject hello NODE_SEA_BLOB sea-prep.blob \
          --sentinel-fuse NODE_SEA_FUSE_fce680ab2cc467b6e072b8b5df1996b2 
    • 在 Windows 上 - PowerShell

      npx postject hello.exe NODE_SEA_BLOB sea-prep.blob `
          --sentinel-fuse NODE_SEA_FUSE_fce680ab2cc467b6e072b8b5df1996b2 
    • 在 Windows - 命令提示字元

      npx postject hello.exe NODE_SEA_BLOB sea-prep.blob ^
          --sentinel-fuse NODE_SEA_FUSE_fce680ab2cc467b6e072b8b5df1996b2 
    • 在 macOS 上

      npx postject hello NODE_SEA_BLOB sea-prep.blob \
          --sentinel-fuse NODE_SEA_FUSE_fce680ab2cc467b6e072b8b5df1996b2 \
          --macho-segment-name NODE_SEA 
  7. 簽署二進位檔(僅限 macOS 和 Windows)

    • 在 macOS 上
    codesign --sign - hello 
    • 在 Windows 上(選用)

    此動作需要有憑證才能執行。不過,未簽署的二進位檔仍然可以執行。

    signtool sign /fd SHA256 hello.exe 
  8. 執行二進位檔

    • 在非 Windows 系統上
    $ ./hello world
    Hello, world! 
    • 在 Windows 上
    $ .\hello.exe world
    Hello, world! 

產生單一可執行檔準備區塊#

可以透過使用將用於建置單一可執行檔的 Node.js 二進位檔的 --experimental-sea-config 旗標,產生注入到應用程式的單一可執行檔準備區塊。它會取得 JSON 格式設定檔的路徑。如果傳遞給它的路徑不是絕對路徑,Node.js 會使用相對於目前工作目錄的路徑。

設定檔目前會讀取下列頂層欄位

{
  "main": "/path/to/bundled/script.js",
  "output": "/path/to/write/the/generated/blob.blob",
  "disableExperimentalSEAWarning": true, // Default: false
  "useSnapshot": false,  // Default: false
  "useCodeCache": true, // Default: false
  "assets": {  // Optional
    "a.dat": "/path/to/a.dat",
    "b.txt": "/path/to/b.txt"
  }
} 

如果路徑不是絕對路徑,Node.js 會使用相對於目前工作目錄的路徑。用於產生區塊的 Node.js 二進位檔的版本,必須與將注入區塊的版本相同。

資源#

使用者可以透過將鍵值路徑字典新增到設定檔中作為 assets 欄位,來包含資源。在建置時,Node.js 會從指定的路徑讀取資源,並將它們打包到準備區塊中。在產生的可執行檔中,使用者可以使用 sea.getAsset()sea.getAssetAsBlob() API 來擷取資源。

{
  "main": "/path/to/bundled/script.js",
  "output": "/path/to/write/the/generated/blob.blob",
  "assets": {
    "a.jpg": "/path/to/a.jpg",
    "b.txt": "/path/to/b.txt"
  }
} 

單一可執行檔應用程式可以如下述方式存取資源

const { getAsset } = require('node:sea');
// Returns a copy of the data in an ArrayBuffer.
const image = getAsset('a.jpg');
// Returns a string decoded from the asset as UTF8.
const text = getAsset('b.txt', 'utf8');
// Returns a Blob containing the asset.
const blob = getAssetAsBlob('a.jpg');
// Returns an ArrayBuffer containing the raw asset without copying.
const raw = getRawAsset('a.jpg'); 

請參閱 sea.getAsset()sea.getAssetAsBlob() API 的文件,以取得更多資訊。

啟動快照支援#

useSnapshot 欄位可用於啟用啟動快照支援。在此情況下,當最終可執行檔啟動時,main 指令碼不會執行。相反地,它會在建立機器上產生單一可執行檔應用程式準備區塊時執行。產生的準備區塊會包含一個快照,擷取由 main 指令碼初始化的狀態。已注入準備區塊的最終可執行檔會在執行階段解除快照序列化。

useSnapshot 為 true 時,主指令碼必須呼叫 v8.startupSnapshot.setDeserializeMainFunction() API,以設定使用者啟動最終可執行檔時需要執行的程式碼。

應用程式在單一可執行檔應用程式中使用快照的典型模式為

  1. 在建立時間,在建立機器上,執行主指令碼以將堆積初始化為準備接收使用者輸入的狀態。指令碼也應該使用 v8.startupSnapshot.setDeserializeMainFunction() 設定主函數。此函數會編譯並序列化到快照中,但不會在建立時間呼叫。
  2. 在執行階段,主函數會在使用者機器上解除序列化的堆積上執行,以處理使用者輸入並產生輸出。

啟動快照指令碼的一般限制也適用於主指令碼,當它用於建立單一可執行檔應用程式的快照時,而且主指令碼可以使用 v8.startupSnapshot API 適應這些限制。請參閱 Node.js 中啟動快照支援的文件

V8 程式碼快取支援#

當設定檔中的 useCodeCache 設為 true 時,在產生單一可執行準備 blob 的過程中,Node.js 會編譯 main 腳本以產生 V8 程式碼快取。產生的程式碼快取會是準備 blob 的一部分,並注入到最終的可執行檔中。當啟動單一可執行應用程式時,Node.js 會使用程式碼快取來加速編譯,而不是從頭開始編譯 main 腳本,然後執行腳本,這將改善啟動效能。

注意:useCodeCachetrue 時,import() 無法運作。

在注入的主腳本中#

單一可執行應用程式 API#

node:sea 內建模組允許從嵌入在可執行檔中的 JavaScript 主腳本與單一可執行應用程式互動。

sea.isSea()#
  • 傳回:<布林值> 此腳本是否在單一可執行應用程式內執行。

sea.getAsset(key[, encoding])#

此方法可用於擷取在建置時設定為綑綁到單一可執行應用程式的資源。當找不到相符的資源時,會擲回錯誤。

  • key <字串> 單一可執行應用程式設定檔中的 assets 欄位指定的字典中資源的鍵。
  • encoding <字串> 如果指定,資源會解碼為字串。TextDecoder 支援的任何編碼都可接受。如果未指定,會傳回包含資源副本的 ArrayBuffer
  • 傳回:<字串> | <ArrayBuffer>

sea.getAssetAsBlob(key[, options])#

類似於 sea.getAsset(),但會以 Blob 回傳結果。找不到相符的資源時會擲回錯誤。

  • key <字串> 單一可執行應用程式設定檔中的 assets 欄位指定的字典中資源的鍵。
  • options <Object>
    • type <string> Blob 的選用 MIME 類型。
  • 傳回:<Blob>

sea.getRawAsset(key)#

此方法可用於擷取在建置時設定為綑綁到單一可執行應用程式的資源。當找不到相符的資源時,會擲回錯誤。

sea.getRawAsset()sea.getAssetAsBlob() 不同,此方法不會傳回副本。它會傳回可執行檔內所綑綁的原始資源。

目前,使用者應避免寫入傳回的陣列緩衝區。如果注入的區段未標示為可寫入或未正確對齊,寫入傳回的陣列緩衝區可能會導致當機。

注入的主程式碼中的 require(id) 並非基於檔案#

注入的主程式碼中的 require() 與非注入模組可用的 require() 不同。它也沒有非注入 require() 擁有的任何屬性,但 require.main 除外。它只能用於載入內建模組。嘗試載入只能在檔案系統中找到的模組會擲回錯誤。

使用者可以將應用程式綑綁成獨立的 JavaScript 檔案,並注入到可執行檔中,而不是依賴於基於檔案的 require()。這也能確保更確定的相依性圖表。

不過,如果仍需要基於檔案的 require(),也可以達成

const { createRequire } = require('node:module');
require = createRequire(__filename); 

注入的主程式碼中的 __filenamemodule.filename#

注入的主程式碼中的 __filenamemodule.filename 的值等於 process.execPath

注入的主程式碼中的 __dirname#

注入的主程式碼中的 __dirname 的值等於 process.execPath 的目錄名稱。

備註#

單一可執行應用程式建立流程#

旨在建立單一可執行 Node.js 應用程式的工具必須將使用 --experimental-sea-config" 準備的 blob 內容注入到

  • 如果 node 二進位檔是 PE 檔案,則命名為 NODE_SEA_BLOB 的資源中
  • 如果 node 二進位檔是 Mach-O 檔案,則命名為 NODE_SEA_BLOBNODE_SEA 區段中
  • 如果 node 二進位檔是 ELF 檔案,則命名為 NODE_SEA_BLOB 的註記中

在二進位檔中搜尋 NODE_SEA_FUSE_fce680ab2cc467b6e072b8b5df1996b2:0 fuse 字串,並將最後一個字元翻轉為 1,以表示已注入資源。

平台支援#

單一可執行檔支援僅在 CI 中定期在下列平台上進行測試

這是因為缺乏更好的工具來產生可執行檔,用於在其他平台上測試此功能。

歡迎提供其他資源注入工具/工作流程的建議。請在 https://github.com/nodejs/single-executable/discussions 開始討論,協助我們記錄它們。