カテゴリー
【nodejsアプリ開発】続・SvelteKitでポータブルなバイナリアプリを作れるか(今度はいい感じに成功)
※ 当ページには【広告/PR】を含む場合があります。
2024/08/23

前回は
Node.jsの機能としては未だExperimentalながら、実用的に使えそうな感触を得ました。
そこで今回は、以前に取り組んで良い結果にはならなかった
結果だけいうと、問題なく動作して個人的な感想では喉につっかえた魚の小骨が取れた感じに大満足なものになっています。
ここでは以前の内容にプラスして、SvelteKitのビルド出力に、Node.js/SEAを使うポイントを掻い摘んで解説してきます。
SvelteKitでadapter-nodeを適用する
まずは適当なSvelteKitプロジェクトを立ち上げ、
ここはSvelteKitをビルドする手順は前回の内容に委ねます。
buildフォルダのjsコードの手動補正
先程までで、ViteによるSvelteKitアプリのビルドが完了すると、デフォルトでは
build
ESModule
前回の内容でも解説したように、Node.js/SEAはpkg同様に、「CommonJS」のエントリーファイル1つしか指定できません。
現状、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」
以下のように「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によるバンドルビルドが通るようになります。
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"
また、重要な項目として
format: "cjs"
プロジェクトにはjsファイルとしてCommonJSとESModuleの双方が混在した形になるため、できるだけ
.cjs
.mjs
エントリーファイルは
build/index.js
これでバンドルされるのはあくまでも
build/index.js
注意点したいのは、SvelteKitアプリからローカルで外部呼び出しされるアセットファイルのうち、
build/client
build/server
設定ファイルができたら、以下のようにesbuildでバンドルさせてみましょう。
$ node esbuild.config.mjs
#👇distフォルダ内に出力されている
$ ls dist/
index.cjs
SEAのバイナリ出力
ここからいよいよSEAでバイナリ化していきます。
手順としては前回と同じです。
まず以下のように
sea-config.json
{
"main": "dist/index.cjs",
"output": "sea-prep.blob"
}
この
sea-config.json
なお、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
SEAアプリを動作させてみる
せっかくですので、動作させてみます。
#デフォルトでは3000ポートでサーバー起動
$ ./sveltekit-app
#開放ポートを指定したい場合
$ PORT=12345 ./sveltekit-app
サーバーが正常に起動したようなら、ブラウザーで開くと...
544x343

やりました。
念願のSvelteKitのNode.jsバイナリ化に成功していることが分かります。
なお、外部からポート番号を指定するときに使った
PORT
その場合、
svelte.config.js
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
というようにポート指定できます。
まとめ
今回はNode.js/SEAとSvelteKit/adaptor-nodeの組み合わせでNode.jsアプリの実行バイナリ化できるか検証してみました。
これまでのNode.jsの実行バイナリ化ツールではどこかで不具合や相性の悪さがボトルネックとなって失敗していたのが嘘のようにアッサリとバイナリ化できてしまいました。
現状あまり使い道というのが思いつかないのですが、とりあえず今後はこのNode.jsアプリを何かしら使って遊んでみたいと思います。
記事を書いた人
ナンデモ系エンジニア
主にAngularでフロントエンド開発することが多いです。 開発環境はLinuxメインで進めているので、シェルコマンドも多用しております。 コツコツとプログラミングするのが好きな人間です。
カテゴリー