カテゴリー
【nodejsシェルアプリ開発】Node.jsのSEA(Single Executable Applications)を試してみよう
※ 当ページには【広告/PR】を含む場合があります。
2024/08/21

しばらくNode.jsアプリのバイナリ化の話題から離れていましたが、そうこうしているうちにNode.jsバイナリ化の代表格であった
pkgだけなく他のNode.jsのバイナリ化を目的とした有志のプロジェクトが、Node.jsのバージョンが上がるたびにその変更点の対応に追従できずにお蔵入りする傾向にあるようです。
pkg側の最後のメッセージに、
pkg has been deprecated with 5.8.1 as the last release.
There are a number of successful forked versions of pkg already with various feature additions.
Further, we’re excited about Node.js 21’s support for single executable applications.
Thank you for the support and contributions over the years.
The repository will remain open and archived.
ということで、長い開発の歴史に幕を閉ざしました。
そこで言及されているのが、node21以降で正式に標準サポート入が決まった
現状、Experimentalな段階で不安定な心許なさはありますが、この「SEA」を使って、以前pkg化で遊んでみた自作スネークゲームが動作できるかを検証してみます。
SEAのビルド手順
「SEA」はその名の通り、1つのjsファイルをターゲットとして、nodeコマンドで実行するためのバイナリ化するものです。
現状、ターゲットとするjsファイルは、「CommonJS」限定です。
なので、プロジェクト化したnode.jsアプリをバイナリ化したければ、
esbuild
とりあえず公式のリファレンスを参考に、過去の記事で解説した「スネークゲーム」が動くかをやってみます。
エントリーポイントとなるJSファイル(CJS)を作成する
まず
index.js
JSファイルがきちんとCommonJS形式でバンドルされている必要があるので、事前によく確認しておきましょう。
「sea-config.json」からblobファイルを生成する
実行ファイルに変換するにあたって、SEAでは
sea-config.json
{
"main": "index.js",
"output": "sea-prep.blob"
}
この
sea-config.json
$ node --experimental-sea-config sea-config.json
生node(実行ファイル)をコピー&アプリケーション名に変更
次にnode本体の実行ファイル(生node)を、アプリケーション名にリネームする形でコピーします。
ここでは、アプリケーション名を
「snake-game」
#Windows以外のOS
$ cp $(command -v node) snake-game
#Windowsで作業する場合
$ node -e "require('fs').copyFileSync(process.execPath, 'snake-game.exe')"
試しにこの時点で、
snake-game
$ ./snake-game
Welcome to Node.js v22.6.0.
Type ".help" for more information.
>
当然ですが、nodeが単体で立ち上がることが分かります。
バイナリからシグネチャを外す(macOSかWindowsのみ)
Linuxで作業する分には不要ですが、次の作業で使う
「postject」
#macOSの場合
$ codesign --remove-signature snake-game
#Windowsの場合(オプション)
#※signtoolはWindows SDKに同梱されるツール
$ signtool remove /s snake-game.exe
「postject」ツールでコピーしたバイナリにblobをインジェクションする
作業の最終段階として「postject」から、nodeをコピーしたバイナリにblobを注入します。
postjectがコマンドから利用します。
$ postject <注入先のnodeバイナリ> NODE_SEA_BLOB <注入するblob>
--sentinel-fuse NODE_SEA_FUSE_fce680ab2cc467b6e072b8b5df1996b2:
Node.jsプロジェクトの使う固有フューズ(各OS共通)
--macho-segment-name NODE_SEA:
(macOSのみ)blobの格納されるバイナリのセグメント名
つまり、以下の要領で生nodeプログラムにblobを注入することになります。
#Linuxの場合
$ npx postject snake-game NODE_SEA_BLOB sea-prep.blob \
--sentinel-fuse NODE_SEA_FUSE_fce680ab2cc467b6e072b8b5df1996b2
#Windows(PowerShell)の場合
$ npx postject snake-game.exe NODE_SEA_BLOB sea-prep.blob `
--sentinel-fuse NODE_SEA_FUSE_fce680ab2cc467b6e072b8b5df1996b2
#Windows(コマンドプロンプト)の場合
$ npx postject snake-game.exe NODE_SEA_BLOB sea-prep.blob ^
--sentinel-fuse NODE_SEA_FUSE_fce680ab2cc467b6e072b8b5df1996b2
#macOSの場合
$ npx postject snake-game NODE_SEA_BLOB sea-prep.blob \
--sentinel-fuse NODE_SEA_FUSE_fce680ab2cc467b6e072b8b5df1996b2 \
--macho-segment-name NODE_SEA
バイナリにシグネチャをつけ直す(macOSかWindowsのみ)
出力されたバイナリにシグネチャを付け直します。
特にmacOSのアプリの実行時では必要になりますが、Windowsだとシグネチャなしでも動作するようです。
#macOSの場合
$ codesign --sign - snake-game
#Windowsの場合(オプション):
$ signtool sign /fd SHA256 snake-game.exe
バイナリを実行してみる
最後に、ちゃんとスネークゲームが動作するか試してみましょう。
#Windows以外
$ ./snake-game
#Windowsの場合
$ .\snake-game.exe
確かにバイナリ化に成功しているようです!
600x367

しかし、若干裏でチラつく以下の警告表示が邪魔で気になります。
(node:12582) ExperimentalWarning: Single executable application is an experimental feature and might change at any time
(Use `snake-game --trace-warnings ...` to show where the warning was created)
この
ExperimentalWarning
$ NODE_NO_WARNINGS=1 ./snake-game
とするとエラーを非表示にすることができます。
600x496

DockerコンテナでLinuxターゲットのビルド環境は整える場合の注意点
基本的にはクロスプラットホームビルドで、blobから各OSで動作するCUIコマンドがバイナリ化できます。
Linuxでビルドする場合、公式にもあるように、
Single-executable support is tested regularly on CI only on the following platforms:
Windows
macOS
Linux (all distributions supported by Node.js except Alpine and all architectures supported by Node.js except s390x)
This is due to a lack of better tools to generate single-executables that can be used to test this feature on other platforms.
というように、
nodeの公式コンテナの定番であるalpineを何も考えずにビルド環境につかってしまうと、バイナリ化した実行ファイルは正常に動作しません。
alpineをやめてbookworm-slimなどのイメージをベースにするとちゃんと動くかと思います。
まとめ
以上、Node.js(CUI)アプリの実行バイナリ化で、
pkg
SEA
未だExprerimentalの段階ながら、主だって動作しないわけではないことも分かります。
強いて贅沢を言うのであれば、出力された実行バイナリは100MBを超えてきます。
生nodeのプログラムをそのままコピーしているためある程度のサイズになることはしかた無い気もしますが、第三者へオンラインから配布するには100MB越えて、少し重さが気になるかもしれません。
そこはやり方次第ですが、blobファイルだけ提供しておき、SEAフューズは利用者の環境でやって貰っても良いかも...と考えます。
参考サイト
記事を書いた人
ナンデモ系エンジニア
主にAngularでフロントエンド開発することが多いです。 開発環境はLinuxメインで進めているので、シェルコマンドも多用しております。 コツコツとプログラミングするのが好きな人間です。
カテゴリー