【nodejsアプリ開発】続・SvelteKitでポータブルなバイナリアプリを作れるか(今度はいい感じに成功)


※ 当ページには【広告/PR】を含む場合があります。
2024/08/23
【nodejsシェルアプリ開発】Node.jsのSEA(Single Executable Applications)を試してみよう
蛸壺の技術ブログ|続・SvelteKitでポータブルなバイナリアプリを作れるか(今度はいい感じに成功)

前回は
「Node.js/SEA(Single Executable Applications)」のビルド手順に触れてみました。

合同会社タコスキングダム|蛸壺の技術ブログ
【nodejsシェルアプリ開発】Node.jsのSEA(Single Executable Applications)を試してみよう

『SEA(Single Executable Applications)』を使ってCUIスネークゲームのバイナリをビルド

Node.jsの機能としては未だExperimentalながら、実用的に使えそうな感触を得ました。

そこで今回は、以前に取り組んで良い結果にはならなかった
「SvelteKit」のポータブルアプリ(バイナリ)化をSEAで再チャレンジしてみたいと思います。

合同会社タコスキングダム|蛸壺の技術ブログ
【nodejsアプリ開発】SvelteKitでポータブルなバイナリアプリを作れるか(しかし現状では失敗)

pkgとnexeを使ってSvelteKitフレームワークからnodejsバイナリプログラムが作成可能かを検証してみます。

結果だけいうと、問題なく動作して個人的な感想では喉につっかえた魚の小骨が取れた感じに大満足なものになっています。

ここでは以前の内容にプラスして、SvelteKitのビルド出力に、Node.js/SEAを使うポイントを掻い摘んで解説してきます。


合同会社タコスキングダム|蛸壺の技術ブログ【効果的学習法レポート】nodejsをこれから学びたい人のためのオススメ書籍&教材特集

SvelteKitでadapter-nodeを適用する

まずは適当なSvelteKitプロジェクトを立ち上げ、「adapter-node」でビルドするところは前回と同じです。

参考|SvelteKitへadapter-nodeを導入する

ここはSvelteKitをビルドする手順は前回の内容に委ねます。


合同会社タコスキングダム|蛸壺の技術ブログ【効果的学習法レポート】nodejsをこれから学びたい人のためのオススメ書籍&教材特集

buildフォルダのjsコードの手動補正

先程までで、ViteによるSvelteKitアプリのビルドが完了すると、デフォルトではbuildフォルダの中にESModuleのjsコードが出力されています。

前回の内容でも解説したように、Node.js/SEAはpkg同様に、「CommonJS」のエントリーファイル1つしか指定できません。

合同会社タコスキングダム|蛸壺の技術ブログ
【nodejsシェルアプリ開発】Node.jsのSEA(Single Executable Applications)を試してみよう

『SEA(Single Executable Applications)』を使ってCUIスネークゲームのバイナリをビルド

現状、SvelteKit用のadapter-nodeはCommonJS出力をオプション指定できないため、あまりスマートなやり方ではないですが、多少強引にCommonJSへソースコードの補正してみます。

handler.jsの修正

CommonJSへの手動補正といってもさほどシビアな作業ではありません。

まず1つ目に、nodeで良く問題として見られるトップレベルawaitのエラーに対応します。

            
            //👇トップレベルawait = 以下のようにグローバルスコープにawaitがあるもの
await server.init({ env: process.env });

//↓↓↓↓↓↓↓↓

//👇asyncの即時関数で囲う
(async() => {
    await server.init({ env: process.env });
})();
        
次に問題になるのは「import.meta」で、ES Module(ES2020)から使えるようになったグローバルのメタデータを引き出すことのできるものですが、CommonJSには当然ないものです。

以下のように「import.meta.url」をCommonJS風に書き直すと、

            
            //👇ESModuleで利用されるメタデータの引き出し
const dir = path.dirname(fileURLToPath(import.meta.url));

//↓↓↓↓↓↓↓↓

//👇CommonJS風に書き直し
const import_meta_url = require("url").pathToFileURL(__filename);
const dir = path.dirname(fileURLToPath(import_meta_url));
        
ここまでやってみるとエラーが一旦なくなり、後述するesbuildによるバンドルビルドが通るようになります。


合同会社タコスキングダム|蛸壺の技術ブログ【効果的学習法レポート】nodejsをこれから学びたい人のためのオススメ書籍&教材特集

esbuildからCommonJSフォーマットでバンドル

では、esbuildは以下の要領で導入します。

            
            $ yarn add -D esbuild
        
CommonJS形式でバンドルする場合にはesbuildコマンド引数指定でやると文字列がダラダラ長くなるので、以下のようなconfigファイル経由でビルドします。

            
            import * as esbuild from "esbuild";

await esbuild.build({
    entryPoints: ["build/index.js"],
    platform: "node",
    format: "cjs",
    outExtension: {'.js': '.cjs'},
    bundle: true,
    outfile: "dist/index.cjs",
});
        
ここでのポイントはplatform: "node"でプログラムを動作させるターゲットプラットフォームをNode.jsに指定できます。

また、重要な項目として
format: "cjs"によってバンドル後のJSファイルをCommonJS形式にしています。

プロジェクトにはjsファイルとしてCommonJSとESModuleの双方が混在した形になるため、できるだけ
.cjs.mjsの拡張子をつけてひと目で分かるようにしておくと良いでしょう。

エントリーファイルは
build/index.jsを指定します。

これでバンドルされるのはあくまでも
build/index.jsで参照関係にあるリソースのみになります。

注意点したいのは、SvelteKitアプリからローカルで外部呼び出しされるアセットファイルのうち、
build/clientbuild/serverは、URIパスのベースに応じた場所にコピーする必要があります。

設定ファイルができたら、以下のようにesbuildでバンドルさせてみましょう。

            
            $ node esbuild.config.mjs

#👇distフォルダ内に出力されている
$ ls dist/
index.cjs
        

合同会社タコスキングダム|蛸壺の技術ブログ【効果的学習法レポート】nodejsをこれから学びたい人のためのオススメ書籍&教材特集

SEAのバイナリ出力

ここからいよいよSEAでバイナリ化していきます。

手順としては前回と同じです。

合同会社タコスキングダム|蛸壺の技術ブログ
【nodejsシェルアプリ開発】Node.jsのSEA(Single Executable Applications)を試してみよう

『SEA(Single Executable Applications)』を使ってCUIスネークゲームのバイナリをビルド

まず以下のように
sea-config.jsonを作成しておきます。

            
            {
    "main": "dist/index.cjs",
    "output": "sea-prep.blob"
}
        
このsea-config.jsonを使って、中間ファイルとなるblobを出力します。

なお、SEAを使う場合にはNode21以降が必要です。

            
            $ node --version
v22.6.0

$ node --experimental-sea-config sea-config.json
        
次にnode本体の実行ファイル(生node)を、「sveltekit-app」の名前でコピーします。

            
            $ cp $(command -v node) sveltekit-app
        
最後に生nodeへblobを注入することになります。

            
            $ npx postject sveltekit-app NODE_SEA_BLOB sea-prep.blob \
    --sentinel-fuse NODE_SEA_FUSE_fce680ab2cc467b6e072b8b5df1996b2
        
これでSvelteKitのSPAがNode.jsのバイナリアプリへと変換されました。

            
            $ ls -la
-rwxr-xr-x  1 node node 119540864 Aug 23 04:25 sveltekit-app
        
バイナリ単体だと110MB強の容量があります。

アセットファイルと一緒にフォルダにまとめる

先程も説明したように、元々はSvelteKit標準のサーバープログラムをバンドルさせたJSファイルですので、同層のフォルダにアセットファイルをセットしないと正常に動きません。

とりあえず
my-appというフォルダを作り、そこにバイナリファイルとアセットフォルダを放り込みます。

            
            $ mkdir my-app
$ mv sveltekit-app my-app/
$ cp -r build/server build/client  my-app/
        
ということで、今回のSvelteKitアプリバイナリ版は以下のようなフォルダ内で利用します。

            
            my-app
├── client
├── server
└── sveltekit-app
        

合同会社タコスキングダム|蛸壺の技術ブログ【効果的学習法レポート】nodejsをこれから学びたい人のためのオススメ書籍&教材特集

SEAアプリを動作させてみる

せっかくですので、動作させてみます。

            
            #デフォルトでは3000ポートでサーバー起動
$ ./sveltekit-app

#開放ポートを指定したい場合
$ PORT=12345 ./sveltekit-app
        
サーバーが正常に起動したようなら、ブラウザーで開くと...

合同会社タコスキングダム|蛸壺の技術ブログ

やりました。

念願のSvelteKitのNode.jsバイナリ化に成功していることが分かります。

なお、外部からポート番号を指定するときに使った
PORTですが、もう少し変数名をカスタマイズしたいときがあります。

その場合、
svelte.config.jsで、adapterのオプションから、envPrefixを指定します。

            
            import adapter from '@sveltejs/adapter-node';

export default {
    kit: {
        adapter: adapter({
            out: 'build',
            precompress: true,
            envPrefix: 'MY_CUSTOM_'
        })
    }
};
        
これでデプロイ時に設定に使用される環境変数の名前のプレフィックスを変更することができます。

この例では、先程の起動コマンドが、

            
            $ MY_CUSTOM_PORT=12345 ./sveltekit-app
        
というようにポート指定できます。


合同会社タコスキングダム|蛸壺の技術ブログ【効果的学習法レポート】nodejsをこれから学びたい人のためのオススメ書籍&教材特集

まとめ

今回はNode.js/SEAとSvelteKit/adaptor-nodeの組み合わせでNode.jsアプリの実行バイナリ化できるか検証してみました。

これまでのNode.jsの実行バイナリ化ツールではどこかで不具合や相性の悪さがボトルネックとなって失敗していたのが嘘のようにアッサリとバイナリ化できてしまいました。

現状あまり使い道というのが思いつかないのですが、とりあえず今後はこのNode.jsアプリを何かしら使って遊んでみたいと思います。
記事を書いた人

記事の担当:taconocat

ナンデモ系エンジニア

主にAngularでフロントエンド開発することが多いです。 開発環境はLinuxメインで進めているので、シェルコマンドも多用しております。 コツコツとプログラミングするのが好きな人間です。

合同会社タコスキングダム|蛸壺の技術ブログ【効果的学習法レポート】nodejsをこれから学びたい人のためのオススメ書籍&教材特集