LoginSignup
13
5

More than 5 years have passed since last update.

自作パッケージの実行ファイルをnpm installした際にnode_modules/.bin/に登録する

Last updated at Posted at 2018-01-03

TL;DR

  • package.jsonの中でbinとして登録しておくと、npm installのときにnode_modules/.binの配下に勝手に実行ファイルへのリンクが追加される。
  • package.jsonのbinには複数設定できる。
  • 複数設定してもいいけど、個人的にはハブとなる実行ファイル1つを登録して、振り分ける方法にした。

はじめに

npmを使ってinstallをおこなうと、node_modules/.binの中に実行ファイルのリンクが貼られます。
そういえば、自分の作ったパッケージでこれを実現する方法を知らなかったな、というわけで調べました。

最終的なプロジェクトの構成図は以下のようになっています。

プロジェクトA(実行ファイルを作成して、npm publishするパッケージ)
ma2-cmd.png

プロジェクトB(プロジェクトAをpackage.jsonのdependenciesに追加してインストールし、動作検証するパッケージ)
ma2-cmd-runner.png

npm installした時にnode_modules/.bin/ディレクトリに実行ファイルを登録

npm package.json 日本語版 取扱説明書にやり方が書いてありました。

例えば npm ではこう指定されています:
{ "bin" : { "npm" : "./cli.js" } }

これで解決ですね。
あとは、個人的にスクリプト系を一つのパッケージにまとめておきたかったので、package.jsonのbinに複数指定できるのか試してみました。

package.json
"bin": {
    "hoge": "src/hoge.js",
    "fuga": "bin/fuga.js"
}

各jsファイルは以下のように適当です。

src/hoge.js
#!/usr/bin/env node
console.log("Hello");
bin/fuga.js
#!/usr/bin/env node
console.log("World");

配置確認

作業手順
1. プロジェクトAをnpm publish
2. プロジェクトBを新規作成
3. プロジェクトBのpackage.jsonに、プロジェクトAのpackage.jsonのnameで指定した名前をdependenciesに追加
4. プロジェクトBでnpm installして、node_modules/.bin/配下にhogefugaが入っていれば完了

以上のことから、次の2点がわかりました。

  1. 実行ファイルは複数指定できる
  2. パスがあっていれば別にsrcディレクトリだろうが、binディレクトリだろうが関係なし

ちなみに私はverdaccioを使ってプライベートのリポジトリを構築した状態で試したので、ゴミのような履歴が残るとか気にせず何度もnpm publishしながら試しました。
npmのアカウントを作って、npm publishをすると公開されるので、それが嫌な方はご注意を。

実行ファイルの実行確認

$ npm install
$ node_modules/.bin/hoge
Hello

はい、動きました。

実行ファイルを1つにしたい

個人的にはhogeとかfugaとか名前もかぶりそうですし、グローバルにインストールするのは難しいなと感じました。
解決策として、名前がかぶらなさそうな実行ファイル名をつけて、その引数としてhogeとfugaを指定する方法を取ることにしました。

何はともあれ、起点となるjsファイルとして、プロジェクトAにma2-cmd.jsを新規作成します。

ma2-cmd.js
#!/usr/bin/env node
const childProcess = require("child_process");
const execSync = childProcess.execSync;

const cmdPattern = 
    "  hoge || h\n" +
    "  fuga || f\n";

const argv = process.argv.slice(2);
if (argv == null || argv.length < 1) {
    throw new Error("実行するコマンドを指定してください。\n" + cmdPattern);
}

const map = {
    "hoge": "src/hoge.js",
    "h": "src/hoge.js",
    "fuga": "bin/fuga.js",
    "f": "bin/fuga.js"
};

const cmdTarget = map[argv[0]];
if (cmdTarget == null) {
    throw new Error("指定されたコマンド名に紐づくコマンドがありません。\n" + cmdPattern);
}

let args = "";
if (argv.length > 1) {
    args = " " + argv.slice(1)
        .join(" ");
}

const res = execSync("node node_modules/${プロジェクト名}/" + cmdTarget + args).toString();
console.log(res);

package.jsonも修正します。

package.json
"bin":{
    "ma2-cmd": "ma2.js"
}

あとは同じようにpublishして更新します。

  1. プロジェクトAのpackage.jsonのバージョンをあげる。(npm version patchとかみたいにコマンドでもいいし、手動であげてもいいです)
  2. プロジェクトAでnpm publish
  3. プロジェクトBでnpm update

そして、コマンドを打ちます。

$ node_modules/.bin/ma2-cmd hoge
Hello

おまけ

コマンドの短縮

以下のように指定したことで、コマンドを短縮して実行できるようにしています。

ma2.js
const map = {
    "hoge": "src/hoge.js",
    "h": "src/hoge.js",
    "fuga": "bin/fuga.js",
    "f": "bin/fuga.js"
};

つまり、以下のコマンドは同じファイルを実行します。

$ node_modules/.bin/ma2 hoge
$ node_modules/.bin/ma2 h

各コマンドに対する引数の設定

今回のサンプルコードでは使っていませんが、引数を渡せるようにしています。

抜粋すると、このあたりです。

ma2-cmd.js
:
const argv = process.argv.slice(2);
:
const cmdTarget = map[argv[0]];
:
let args = "";
if (argv.length > 1) {
    args = " " + argv.slice(1)
        .join(" ");
}


const res = execSync("node node_modules/${プロジェクト名}/" + cmdTarget + args).toString();
console.log(res);

jsファイルの先頭行のnode宣言

先頭行の宣言、最初必要だと知りませんでした。

#!/usr/bin/env node

他の.binに入っている先人の知恵を物色したところ発見し、調べたらnodeを使うという宣言であると知りました。
参考:Node.jsを使ってCommand line ツールことはじめ

ちなみに、先頭に指定しなかった場合はエラーが出ます。

$ node_modules/.bin/ma2-cmd hoge

node_modules/.bin/ma2: line 1: syntax error near unexpected token `('
node_modules/.bin/ma2: line 1: `const childProcess = require("child_process");`

先頭にnodeを付けると動きます。

$ node node_modules/.bin/ma2-cmd hoge
Hello
13
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
13
5