webpack で実行可能な単一ファイルを作る
node.js で cli ツールを作った時に 簡単に実行する方法を考えてみました。 nexe を使う方法もありますが、ファイルサイズが大きくなるので javascript のコードのみまとめる事を想定します。
方針としては
- ライブラリも使いたいので webpack で単一ファイルにまとめる
- まとまったファイルに shebang をつける
- ファイルを path の通った所に置いて実行権限をつける
て感じです。 これを node.js で実現するには以下をやります。
- webpack で単一ファイルにまとめる
- webpack.BannerPlugin で shebang を追加する
- ファイルを path の通った所に置いて実行権限をつける
実行するスクリプトを作成する
いつも通り node.js の package を作ります。
$ mkdir webpack-test && cd webpack-test $ npm init -y $ npm install --save lodash $ mkdir src $ vim src/index.js
ソースを src/index.js に書いたのは後で使う webpack のデフォルト設定に合わせるためです。 src/index.js の内容は webpack の Getting Started を 参考に、こんな感じで。
const _ = require('lodash'); const main = () => { const message = _.join(['hello', 'world'], ' '); console.log(message); }; main();
試しに実行してみます。
$ node index.js hello world
表示されました。
webpack で単一ファイルにまとめる
webpack をインストールして実行してみます。
$ npm install --save-dev webpack webpack-cli $ npx webpack ... $ node ./dist/main.js hello world
./dist/main.js が webpack によって出力されたファイルです。 中身を見ると、lodash もこのファイルに入っている事が分かります。 これで 単一ファイルにはできましたが、 実行可能にするには shebang を追加する必要があります。
実行可能にする
shebang を追加するための webpack の設定をします。 以下のようにコマンドラインでやってもいいのですが、
$ echo '#!/usr/bin/env node' | cat - ./dist/main.js > ./dist/main
今回は webpack の BannerPlugin を使い、さらにファイルに実行権限を追加するところまで自動でやります。 webpack.config.js を作り、以下のようにします。
const webpack = require('webpack'); module.exports = { mode: 'production', // to minify target: 'node', plugins: [ new webpack.BannerPlugin({ banner: '#!/usr/bin/env node', // add shebang raw: true, }), function() { this.hooks.done.tap('chmod', () => { fs.chmodSync(path.resolve(__dirname, 'dist', 'main.js'), '755'); }); }, ], };
もう一度 webpack を実行し、 直接実行できるか試してみます。
$ npx webpack $ ./dist/main.js hello world
実行できました。 あとはこのファイルを PATH の通った所に配置し、 ファイル名を適当に変えれば OK です。
補足: hook の書き方
chmod する所は最初は
function() { this.plugin('done', () => { fs.chmodSync(path.resolve(__dirname, 'dist', 'main.js'), '755'); }); },
と書いていたんですが、
$ nps webpack DeprecationWarning: Tapable.plugin is deprecated. Use new API on `.hooks` instead
のようなメッセージが出たので調べたところ、 最初の例の書き方に変わったみたいです。
バージョン
$ node -v v10.13.0 $ npx webpack -v 4.26.1