【nodejsシェルアプリ開発】vercel/pkgでCLI版スネークゲームを作ってみる


※ 当ページには【広告/PR】を含む場合があります。
2022/12/23
2024/08/21
【簡単nodejsアプリ開発】NexeでCLI版スネークゲームを作ってみる・後編
【nodejsアプリ開発】pkg/Express.js/Svelteでポータブルなバイナリ起動のウェブブラウザアプリを作る
蛸壺の技術ブログ|【nodejsシェルアプリ開発】vercel/pkgでCLI版スネークゲームを作ってみる

※本記事で取り上げていた
「vercel/pkg」は2023年末時点で開発プロジェクトは終了となりました。

Node.jsアプリのバイナリ化での移行先として
「SEA(Single Executable Applications)」というのも選択肢になるかもしれません。

詳しくは以下の記事を参考にしてみてください。

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

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

さて、以前の記事で、
「nexe」を利用したCLIアプリケーションのビルドツールの導入と使用方法をスネークゲームで説明しました。

合同会社タコスキングダム|蛸壺の技術ブログ
【簡単nodejsアプリ開発】NexeでCLI版スネークゲームを作ってみる・後編

nodejsのユーティリティ・nexeを使って簡単なCLIスネークゲームをアプリ完成までを解説します。

実際のところ、nodejsをバイナリ化してシェルコマンドアプリとしてパッケージするユーティリティは「nexe」だけではなく、以下のnpm trandの比較に見るように、いくつか選択肢があります。

参考|npm trends - boxednode vs nexe vs pkg

これらのパッケージャには一朝一夕あり、「nexe」はメンテナンス頻度が多く、開発者の活動が活発であることが非常に好ましくありますが、不要なライブラリまでまとめて固めてくれるため、ビルド後のバイナリのサイズはやや大きくなってしまいます。

そこで今回は、非常にきめ細いビルドオプションを指定できる
vercel/pkgを使って、nexeよりどのくらいバイナリを最小化できるかを検証してみます。


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

pkgプロジェクトの準備

では早速、簡単なpkgプログラムを作成していきましょう。

なお以下が手元のnodejs環境です。

            
            $ node --version
v18.12.1
$ npm --version
9.1.2
$ yarn --version
1.22.19
        
適当なプロジェクトフォルダを作って、以下のようにほぼ空のpackage.jsonを追加します。

            
            {
  "name": "snake_pkg",
  "version": "0.0.1",
  "description": "To execise to use pkg with Snake Game."
}
        
次に前回Snake-game用に解説したindex.jsbuildというフォルダを作成し、その中へ追加しておきましょう。

参考|スネークゲームの実装 - 簡単nodejsアプリ開発】NexeでCLI版スネークゲームを作ってみる・後編

なお
pkgの規則では、binに指定したjsファイルがバイナリアプリのエンドポイントになりますので、忘れずにpackage.jsonに追加しておきましょう。

では
pkgを導入してみます。グローバルにインストールしても良いですが、ここではDev扱いで導入しています。

            
            $ yarn add pkg -D
        
ローカルにインストールしたので、pkgコマンドを呼び出すためにpackage.jsonscriptsタグを作成し、pkgを登録します。

            
            {
  "name": "snake_pkg",
  "version": "0.0.1",
  "description": "To execise to use pkg with Snake Game.",
  "bin": "build/index.js",
  "scripts": {
    "pkg": "pkg"
  },
  "devDependencies": {
    "pkg": "^5.8.0"
  }
}
        
一度、pkgコマンドを呼び出せるか試しておきましょう。

            
            $ yarn pkg -h

  pkg [options] <input>

  Options:

    -h, --help           output usage information
    -v, --version        output pkg version
    -t, --targets        comma-separated list of targets (see examples)
    -c, --config         package.json or any json file with top-level config
    --options            bake v8 options into executable to run with them on
    -o, --output         output file name or template for several files
    --out-path           path to save output one or more executables
    -d, --debug          show more information during packaging process [off]
    -b, --build          don't download prebuilt base binaries, build them
    --public             speed up and disclose the sources of top-level project
    --public-packages    force specified packages to be considered public
    --no-bytecode        skip bytecode generation and include source files as plain js
    --no-native-build    skip native addons build
    --no-dict            comma-separated list of packages names to ignore dictionaries. Use --no-dict * to disable all dictionaries
    -C, --compress       [default=None] compression algorithm = Brotli or GZip

  Examples:

  – Makes executables for Linux, macOS and Windows
    $ pkg index.js
  – Takes package.json from cwd and follows 'bin' entry
    $ pkg .
  – Makes executable for particular target machine
    $ pkg -t node14-win-arm64 index.js
  – Makes executables for target machines of your choice
    $ pkg -t node12-linux,node14-linux,node14-win index.js
  – Bakes '--expose-gc' and '--max-heap-size=34' into executable
    $ pkg --options "expose-gc,max-heap-size=34" index.js
  – Consider packageA and packageB to be public
    $ pkg --public-packages "packageA,packageB" index.js
  – Consider all packages to be public
    $ pkg --public-packages "*" index.js
  – Bakes '--expose-gc' into executable
    $ pkg --options expose-gc index.js
  – reduce size of the data packed inside the executable with GZip
    $ pkg --compress GZip index.js
        
公式にも出ているヘルプが表示されました。

pkgは見てのようにビルドオプションを細かく直接指定することでも利用できるCLIコマンドです。

とはいえ、コマンドオプションにビルド設定をすべて指定していくのは結構しんどいので、
package.jsonpkgタグを作成し、ビルドオプションをそこに集約化させたほうが良いでしょう。

            
            {
  "name": "snake_pkg",
  "version": "0.0.1",
  "description": "To execise to use pkg with Snake Game.",
  "bin": "build/index.js",
  "scripts": {
    "pkg": "pkg"
  },
  "pkg": {
    "scripts": "build/**/*.js",
    "assets": [
        "assets/**/*",
        "static/**/*"
    ],
    "targets": ["latest-linux-x64"],
    "outputPath": "dist"
  }
  "devDependencies": {
    "pkg": "^5.8.0"
  }
}
        
このプロジェクトではindex.jsファイルだけですので利用はしませんが、スクリプトファイルを分割した場合、pkgタグのscriptsに指定したディレクトリパスにマッチするjsファイルがビルド中にインクルードされます。

他に
assetsタグに指定したディレクトリパス(複数指定可能)にマッチしたリソースに限定して、pkgコンパイラが画像やフォントなどの静的なファイルにアクセスできます。

targetsは、出力するバイナリの動作する実行環境ターゲットです。このターゲットについては簡潔に次の節で説明します。

outputPathはビルド後のバイナリの出力ディレクトリを指定します。

ビルドターゲット

公式にも言及されていますが、
pkgで利用できるターゲットは以下の通りです。

            
            nodeRange:
    (node8)
    node10
    node12
    node14
    node16
    latest

platform:
    alpine
    linux
    linuxstatic
    win
    macos
    (freebsd)

arch:
    x86
    x64
    arm64
        
なお、(*)は非サポートですが、おそらく動くだろうというオプションです。

これら3つの要素の組み合わせによる
pkgでのターゲット命名規則は、

            
            [nodeRange]-[platform]-[arch]
        
になっています。

pkgのビルド

では先程までで、プロジェクトフォルダのファイル構造(node_modulesは無視)は以下のようになっていると思います。

            
            $ tree -L 2 -I node_modules
.
├── build
│   └── index.js
├── package.json
└── yarn.lock
        
後はもうバイナリビルドするだけで、package.jsonからビルドするには以下のようにコマンドを叩きます。

            
            $ yarn pkg .
        
すると、distフォルダ以下にpackage.jsonで指定した実行ファイルが生成されていることが確認できます。

            
            $ tree -L 2 -I node_modules
.
├── build
│   └── index.js
├── dist
│   └── snake_pkg #👈バイナリファイル
├── package.json
└── yarn.lock
        

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

pkgでビルドしたバイナリファイルの実行

最後に、指定したターゲットの環境で動くかをチェックしてみます。

先程の設定では、
「64bitアーキテクチャのLinuxOS」に対してビルドしてみましたので、手元のDebian11パソコンで動作させてみると、

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

と期待通りに動いています。

nexeバイナリとの比較

最後にnexeバイナリとのビルド後の比較も行いましょう。

nexeでのバイナリビルドに関しては
前回の方で特集しています。

比較としてx64アーキテクチャのLinux(Debian11)をターゲットとします。

まずはnexeバイナリの方ですが、

            
            $ du -sSk snake_nexe
72392 snake_nexe
        
ということで、約72.4MBのファイルサイズです。

しかも現時点でNexeのビルドターゲットが
linux-x64-14.15.3が最新版ですので、node18には対応していません。

対して、pkgですが、

            
            $ du -sSk snake_pkg
45220 snake_pkg
        
と約45.2MBとなり、これはnexeと比べてもかなりスリムです。

しかも、nexeの良いところは現在の環境のnodejsバージョンに併せてターゲットのライブラリを自動設定してくれていて、ここでは
v18.5.0-linux-x64を適用しています。

すでにnode18に対応していて、しかもビルド後のバイナリサイズは小さい、というのがpkgの大きなアドバンテージと言えそうです。


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

参考サイト

vercel/pkg - Single-Command Node.js Binary Compiler

記事を書いた人

記事の担当:taconocat

ナンデモ系エンジニア

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

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