カテゴリー
【nodejsアプリ開発】SvelteKitでポータブルなバイナリアプリを作れるか(しかし現状では失敗)
※ 当ページには【広告/PR】を含む場合があります。
2023/02/09

例えば公式では以下のアダプターが用意されています。
+ Cloudflare (Pages): @sveltejs/adapter-cloudflare
+ Cloudflare (Workers): @sveltejs/adapter-cloudflare-workers
+ Netlify: @sveltejs/adapter-netlify
+ Node.js(サーバーサイド): @sveltejs/adapter-node
+ SSG(Static Site Generation)用: @sveltejs/adapter-static
+ Vercel: @sveltejs/adapter-vercel
他にもAWSなどのSaaSプラットフォーム向け公開されてるサードパーティ製アダプターも存在しています。
前回は単体のコアな
他方でSvelteKitでnodejsバイナリアプリをビルドしたいと思っても、SvelteKitではフルスタックの次世代の開発フレームワークとして注目されている
SvelteKit自体は面倒なセットアップなしにViteによる開発環境がインストール一発で整うことが魅力ですが、余りに“いたれりつくせり”であるため、nodejsバイナリアプリを生成するには実は向いていない...というのが現状です。
現状でSvelteKitからNodejsバイナリアプリを生成する方法を検証した結果を紹介します。
SvelteKitへadapter-nodeを導入する
nodejsアプリを出力ターゲットとするため、今回利用するアダプターは
adapter-node
以下のコマンドでnpmパッケージをインストールしましょう。
$ yarn add @sveltejs/adapter-node -D
インストール後に、もともとSvelteKitで標準になっているアダプター(
adapter-auto
adapter-node
svelte.config.js
//👇コメントアウト
//import adapter from '@sveltejs/adapter-auto';
//👇adapter-nodeに置換
import adapter from '@sveltejs/adapter-node';
import { vitePreprocess } from '@sveltejs/kit/vite';
/** @type {import('@sveltejs/kit').Config} */
const config = {
preprocess: [vitePreprocess()],
kit: {
adapter: adapter()
}
};
export default config;
とするだけで準備が完了です。
あとはいつもの要領でビルドするだけです。
$ yarn build
これだけでVite無しにnodeで動くアプリに出力してくれます。 すこぶる簡単ですね♪

デフォルトでは
build
この時点で、nodeで動かせることを確認しましょう。
$ node build/index.js
ブラウザから起動したサーバーにアクセスさせてみると、

というようにブラウザから見れていればOKです。
またここではポートはデフォルトの3000から別の番号に変えていますが、出力先のフォルダを変えたい場合や環境変数のプリフィックスを設定したいときには以下の公式のようにアダプタに設定を行ってみてください。
このようにnodeコマンド単体で動かすのであれば、
adapter-node
他方で、ここからnodejsバイナリアプリを生成しようとすると色々と制約が足かせになってきます。
以降では、どこでバイナリ化がつまずくのか、「pkg」と「nexe」のケースを個別に紹介します。
pkgでSvelteKitアプリのバイナリ化
まずは、「pkg」によるバイナリ化を試してみましょう。
ずばりいうと、
というのは、
- pkgは現状でcommonjsしか使えない
- Vite(SvelteKit-nodeアダプター)はesmしか出力対応していない
という、Javascriptの深刻なテーマである
ということで、もうこうなるとダメ元で
babel
pkgアプリ用に適当なプロジェクトフォルダを作って、先程生成したSvelteKitの生成物(buildフォルダ)をそこにコピーして、
package.json
$ mkdir sk-binary
$ cp build sk-binary/
$ cd sk-binary
$ touch package.json
以下のようなpkgバイナリアプリ向けに
package.json
{
"name": "pkg-sveltekit",
"version": "0.0.1",
"description": "How to make a pkg app with SvelteKit.",
"bin": "lib/index.js",
"scripts": {
"pkg": "pkg",
"convert": "babel --plugins @babel/plugin-transform-modules-commonjs build -d lib",
"build": "yarn pkg . --debug"
},
"pkg": {
"targets": ["latest-linux-x64"],
"outputPath": "dist"
},
"devDependencies": {
"@babel/cli": "^7.18.10",
"@babel/core": "^7.18.10",
"@babel/plugin-transform-modules-commonjs": "^7.20.11",
"@babel/preset-env": "^7.18.10",
"nexe": "^4.0.0-rc.2",
"pkg": "^5.8.0"
}
}
ファイルの準備が出来たらパッケージをインストール&babelトランスパイル、pkgビルドしてみます。
$ yarn install
$ yarn convert
$ yarn build
とりあえずCommonjsにすれば動くかというとそうではありません。
まずコンパイルエラーとなるのが、
SyntaxError: await is only valid in async functions and the top level bodies of modules
at new Script (node:vm:102:7)
at Socket.<anonymous> ([eval]:18:19)
at Socket.emit (node:events:537:28)
at addChunk (node:internal/streams/readable:324:12)
at readableAddChunk (node:internal/streams/readable:297:9)
at Readable.push (node:internal/streams/readable:234:10)
at Pipe.onStreamRead (node:internal/stream_base_commons:190:23)
というnodeで良く問題として見られる
トップレベルawait
これは自動ではBabelもどうしてくれるものでもないので、試しに該当の箇所を手直ししてみます。
//👇トップレベルawait = 以下のようにグローバルスコープにawaitがあるもの
await server.init({ env: process.env });
//↓↓↓↓↓↓↓↓
//👇asyncの即時関数で囲う
(async() => {
await server.init({ env: process.env });
})();
これでエラー自体はなくなります。
次にエラーで生じる問題は以下のようなものです。
SyntaxError: Cannot use 'import.meta' outside a module
at new Script (node:vm:102:7)
at Socket.<anonymous> ([eval]:18:19)
at Socket.emit (node:events:537:28)
at addChunk (node:internal/streams/readable:324:12)
at readableAddChunk (node:internal/streams/readable:297:9)
at Readable.push (node:internal/streams/readable:234:10)
at Pipe.onStreamRead (node:internal/stream_base_commons:190:23)
この
import.meta
これをCommonJS風に例えば
import.meta.url
//👇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));
ここまでやってみるとエラーが一旦なくなりビルドが通ります。
これでようやく動くかなとバイナリを早速起動してみてると...

現状ははっきりとしない内部エラーでページのルーティングに失敗してしまうようです。
nexeでSvelteKitアプリのバイナリ化
もう一つせっかくですので、nodejsアプリバイナリの雄・「nexe」を試してみます。
nexeの最大の魅力は
これで先程のpkgでクリティカルであったCommonJSかどうかはあまり意識しなくても良くなります。
ですが、依然として「Nexe」の最新バージョンは
Nexeの公式を見てもv16まで引き上げるのにいまだ四苦八苦されているようで中々開発が難航しています。
残念ながらこちらも結論から言えばまだ満足にSvelteKitアプリをそのままバイナリ化は出来ません。
こちらもNodejsの悩ましい問題である、
とりあえず先程と同じプロジェクトからpkgはやめてnexeに鞍替えします。
{
"name": "pkg-sveltekit",
"version": "0.0.1",
"private": true,
"description": "How to make a pkg app with SvelteKit.",
"type": "module",
"scripts": {
"nexe": "nexe build/index.js --target=linux-x64-14.15.3"
},
"devDependencies": {
"nexe": "^4.0.0-rc.2"
}
}
これだけでnexeのバイナリ化が可能です。
$ yarn install
$ yarn nexe
でバイナリパッケージが生成出来ました。
それから起動してみると...
$./sk-binary
file:///********/sk-binary/dist/handler.js:12725
this.cookies ??= [];
^^^
SyntaxError: Unexpected token '??='
at Loader.moduleStrategy (internal/modules/esm/translators.js:145:18)
というエラーが出てきます。
ここでエラーとなっている
「??」
「??=」
ちなみにnodev14から
「??」
「??=」
this.cookies ??= [];
//↓↓↓↓↓↓↓↓
this.cookies = this.cookies ?? [];
再度ビルドすると、以下のようなエラーが出るようになります。
internal/process/esm_loader.js:74
internalBinding('errors').triggerUncaughtException(
^
Error [ERR_MODULE_NOT_FOUND]: Cannot find package 'stream' imported from ******/handler.js
at packageResolve (internal/modules/esm/resolve.js:655:9)
at moduleResolve (internal/modules/esm/resolve.js:696:18)
at Loader.defaultResolve [as _resolve] (internal/modules/esm/resolve.js:810:11)
at Loader.resolve (internal/modules/esm/loader.js:86:40)
at Loader.getModuleJob (internal/modules/esm/loader.js:230:28)
at ModuleWrap.<anonymous> (internal/modules/esm/module_job.js:56:40)
at link (internal/modules/esm/module_job.js:55:36) {
code: 'ERR_MODULE_NOT_FOUND'
これは
node-fetch
fetch API
こうなってくると自前で修正するとなるとかなり作業が厳しくなってきますし、せっかくのSvelteKit/Viteの良さとも言えるクライアント・サーバー側を区別無く動作できるFetch APIの実装が台無しになります。
事情を鑑みてもここはNexeのアップグレードも待った方が賢明と言えます。
まとめ
SvelteKitはサクッと高機能なVite開発環境が設定なしに使えるようになる一方で、より古いJavascriptやnodejsの後方互換性には柔軟に対応することは出来ないようです。
あくまでも現状での話ですが、Svelteアプリをバイナリ化したい場合には素直にコアなSvelteをそのまま使う方が良いようです。
ただし、CommonJSもやがてはESMへと吸収されていく流れは止められないと想像できるため、あまり古いnodejs環境を使い続けることに固執すべきでないことも頭に入れておきましょう。
参考サイト
記事を書いた人
ナンデモ系エンジニア
主にAngularでフロントエンド開発することが多いです。 開発環境はLinuxメインで進めているので、シェルコマンドも多用しております。 コツコツとプログラミングするのが好きな人間です。
カテゴリー