Skip to content

単一実行可能アプリケーション

[履歴]

バージョン変更点
v20.6.0"useSnapshot" のサポートを追加
v20.6.0"useCodeCache" のサポートを追加
v19.7.0, v18.16.0追加: v19.7.0, v18.16.0

[安定版: 1 - 試験版]

安定版: 1 安定性: 1.1 - アクティブ開発中

ソースコード: src/node_sea.cc

この機能により、Node.js がインストールされていないシステムに Node.js アプリケーションを簡単に配布できます。

Node.js は、バンドルされたスクリプトを含むことができる Node.js によって準備された BLOB をnodeバイナリに挿入することで、単一実行可能アプリケーションの作成をサポートしています。起動時に、プログラムは何かが挿入されているかどうかをチェックします。BLOB が見つかった場合、BLOB 内のスクリプトを実行します。それ以外の場合は、Node.js は通常どおり動作します。

単一実行可能アプリケーション機能は現在、CommonJSモジュールシステムを使用した単一の埋め込みスクリプトの実行のみをサポートしています。

ユーザーは、バンドルされたスクリプトとnodeバイナリ自体、およびバイナリにリソースを挿入できるツールを使用して、単一実行可能アプリケーションを作成できます。

このようなツールの一つであるpostjectを使用した単一実行可能アプリケーションの作成手順を以下に示します。

単一実行可能準備 BLOB の生成

アプリケーションに挿入される単一実行可能準備 BLOB は、単一実行可能ファイルのビルドに使用される Node.js バイナリの--experimental-sea-configフラグを使用して生成できます。これは、JSON 形式の構成ファイルへのパスを取ります。渡されたパスが絶対パスでない場合、Node.js はカレントワーキングディレクトリを基準とした相対パスを使用します。

構成は現在、以下のトップレベルフィールドを読み取ります。

json
{
  "main": "/path/to/bundled/script.js",
  "output": "/path/to/write/the/generated/blob.blob",
  "disableExperimentalSEAWarning": true, // デフォルト: false
  "useSnapshot": false, // デフォルト: false
  "useCodeCache": true, // デフォルト: false
  "assets": {
    // オプション
    "a.dat": "/path/to/a.dat",
    "b.txt": "/path/to/b.txt"
  }
}

パスが絶対パスでない場合、Node.js はカレントワーキングディレクトリを基準とした相対パスを使用します。BLOB を生成するために使用される Node.js バイナリのバージョンは、BLOB が挿入されるバージョンと同じでなければなりません。

注: クロスプラットフォーム SEA(例: darwin-arm64linux-x64の SEA を生成)を生成する場合、互換性のない実行可能ファイルの生成を避けるため、useCodeCacheuseSnapshotを false に設定する必要があります。コードキャッシュとスナップショットは、コンパイルされたのと同じプラットフォームでのみロードできるため、異なるプラットフォームでビルドされたコードキャッシュまたはスナップショットのロードを試行すると、生成された実行可能ファイルが起動時にクラッシュする可能性があります。

アセット

assets フィールドとしてキーパス辞書を構成に追加することで、ユーザーはアセットを含めることができます。ビルド時に、Node.js は指定されたパスからアセットを読み取り、準備用 BLOB にバンドルします。生成された実行可能ファイルでは、ユーザーはsea.getAsset()sea.getAssetAsBlob() API を使用してアセットを取得できます。

json
{
  "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"
  }
}

シングル実行可能アプリケーションは、以下のようにアセットにアクセスできます。

js
const { getAsset, getAssetAsBlob, getRawAsset } = require('node:sea')
// ArrayBuffer内のデータのコピーを返します。
const image = getAsset('a.jpg')
// アセットからUTF8としてデコードされた文字列を返します。
const text = getAsset('b.txt', 'utf8')
// アセットを含むBlobを返します。
const blob = getAssetAsBlob('a.jpg')
// コピーせずに生の資産を含むArrayBufferを返します。
const raw = getRawAsset('a.jpg')

詳細については、sea.getAsset()sea.getAssetAsBlob()、およびsea.getRawAsset() API のドキュメントを参照してください。

スタートアップスナップショットサポート

useSnapshotフィールドを使用して、スタートアップスナップショットサポートを有効にすることができます。この場合、最終実行可能ファイルが起動されたときにmainスクリプトは実行されません。代わりに、ビルドマシンでシングル実行可能アプリケーションの準備用 BLOB が生成されたときに実行されます。生成された準備用 BLOB には、mainスクリプトによって初期化された状態をキャプチャするスナップショットが含まれます。準備用 BLOB が挿入された最終実行可能ファイルは、実行時にスナップショットをデシリアライズします。

useSnapshotが true の場合、mainスクリプトは、v8.startupSnapshot.setDeserializeMainFunction() API を呼び出して、ユーザーによって最終実行可能ファイルが起動されたときに実行する必要があるコードを構成する必要があります。

シングル実行可能アプリケーションでスナップショットを使用するアプリケーションの典型的なパターンは次のとおりです。

スタートアップスナップショットスクリプトの一般的な制約は、シングル実行可能アプリケーションのスナップショットの構築に使用されるmainスクリプトにも適用され、mainスクリプトはv8.startupSnapshot APIを使用してこれらの制約に適応できます。Node.js のスタートアップスナップショットサポートに関するドキュメントを参照してください。

V8 コードキャッシュサポート

設定でuseCodeCachetrueに設定すると、単一実行ファイルの準備ブロブ生成中に、Node.js はmainスクリプトをコンパイルして V8 コードキャッシュを生成します。生成されたコードキャッシュは準備ブロブの一部となり、最終実行ファイルに挿入されます。単一実行ファイルアプリケーションが起動されると、Node.js はmainスクリプトを最初からコンパイルする代わりに、コードキャッシュを使用してコンパイルを高速化してからスクリプトを実行するため、起動パフォーマンスが向上します。

注記: useCodeCachetrueの場合、import()は機能しません。

挿入されたメインスクリプト内

単一実行ファイルアプリケーション API

node:sea組み込みモジュールを使用すると、実行ファイルに埋め込まれた JavaScript メインスクリプトから単一実行ファイルアプリケーションと対話できます。

sea.isSea()

追加されたバージョン: v21.7.0, v20.12.0

  • 戻り値: <boolean> このスクリプトが単一実行ファイルアプリケーション内で実行されているかどうか。

sea.getAsset(key[, encoding])

追加されたバージョン: v21.7.0, v20.12.0

このメソッドは、ビルド時に単一実行ファイルアプリケーションにバンドルするように設定されたアセットを取得するために使用できます。一致するアセットが見つからない場合は、エラーがスローされます。

  • key <string> 単一実行ファイルアプリケーション設定のassetsフィールドで指定されたディクショナリ内にあるアセットのキー。
  • encoding <string> 指定されている場合、アセットは文字列としてデコードされます。TextDecoderでサポートされているエンコーディングであればどれでも使用できます。指定されていない場合、アセットのコピーを含むArrayBufferが代わりに返されます。
  • 戻り値: <string> | <ArrayBuffer>

sea.getAssetAsBlob(key[, options])

追加日時: v21.7.0, v20.12.0

sea.getAsset() と似ていますが、結果を Blob として返します。一致するアセットが見つからない場合、エラーがスローされます。

  • key <string> シングル実行アプリケーション設定の assets フィールドで指定された辞書内にあるアセットのキー。

  • options <Object>

    • type <string> Blob のオプションの MIME タイプ。
  • 戻り値: <Blob>

sea.getRawAsset(key)

追加日時: v21.7.0, v20.12.0

このメソッドは、ビルド時にシングル実行アプリケーションにバンドルされるように設定されたアセットを取得するために使用できます。一致するアセットが見つからない場合、エラーがスローされます。

sea.getAsset()sea.getAssetAsBlob() とは異なり、このメソッドはコピーを返しません。代わりに、実行可能ファイル内にバンドルされた生の資産を返します。

現時点では、返された配列バッファへの書き込みは避けるべきです。挿入されたセクションが書き込み可能としてマークされていない場合、または適切にアラインされていない場合、返された配列バッファへの書き込みはクラッシュを引き起こす可能性があります。

  • key <string> シングル実行アプリケーション設定の assets フィールドで指定された辞書内にあるアセットのキー。
  • 戻り値: <ArrayBuffer>

挿入されたメインスクリプト内の require(id) はファイルベースではありません

挿入されたメインスクリプト内の require() は、挿入されていないモジュールで使用可能な require() とは異なります。また、挿入されていない require() が持つプロパティrequire.main を除いて、どのプロパティも持ちません。組み込みモジュールをロードするためだけに使用できます。ファイルシステム内でのみ見つかるモジュールをロードしようとすると、エラーがスローされます。

ファイルベースの require() に依存する代わりに、アプリケーションをスタンドアロンの JavaScript ファイルにバンドルして実行可能ファイルに挿入できます。これにより、より決定論的な依存関係グラフが保証されます。

ただし、ファイルベースの require() がまだ必要な場合は、次のように実現することもできます。

js
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セグメント内のNODE_SEA_BLOBという名前のセクションに
  • nodeバイナリがELFファイルの場合、NODE_SEA_BLOBという名前のノートに

バイナリ内でNODE_SEA_FUSE_fce680ab2cc467b6e072b8b5df1996b2:0 fuse文字列を検索し、最後の文字を1に反転させて、リソースが注入されたことを示します。

プラットフォームサポート

単一実行ファイルのサポートは、以下のプラットフォームでのみ CI で定期的にテストされています。

これは、他のプラットフォームでこの機能をテストするために使用できる、より優れた単一実行ファイル生成ツールの不足が原因です。

他のリソース注入ツール/ワークフローに関するご提案は大歓迎です。それらを文書化するために、https://github.com/nodejs/single-executable/discussionsでディスカッションを開始してください。