npm にはlink
というコマンドが用意されており、これを使うことで npm パッケージの開発効率が上がる。
既存のパッケージに手を加えた際の動作確認にも使えるので、OSS 活動の効率も上がる。
この記事では、npm link
の仕組みと、それをどのように利用できるのかについて説明する。
動作確認に使った npm のバージョンは6.14.5
。
Node.js のバージョンは12.17.0
。これ以前のバージョンだと以下の動作確認でエラーが出るので注意。
サンプル用のパッケージとアプリを作る
まずはパッケージを作成する。
my-package-dir
というディレクトリを作り、そこに以下の内容のpackage.json
を作成する。
{ "name": "my-package", "type": "module", "main": "main.js" }
そして、以下の内容のmain.js
を作る。
export default (arg1, arg2) => arg1 + arg2;
これで、my-package
をパッケージとして公開すると、main.js
でexport
している関数を使えるようになる。
だが、公開する前にローカル環境で確認したい。こういうケースでnpm link
が使える。
取り敢えず、確認用のアプリを作る。
my-package-dir
とは別にmy-app-dir
を作り、そこに移動。
以下のpackage.json
とapp.js
を作る。
{ "name": "my-app", "type": "module" }
import myPackage from 'my-package'; console.log(myPackage(2, 3));
この状態で$ node app.js
を実行すると、my-package
をインストールしていないので当然エラーになる。
$ node app.js (node:19472) ExperimentalWarning: The ESM module loader is experimental. internal/modules/run_main.js:54 internalBinding('errors').triggerUncaughtException( ^ Error [ERR_MODULE_NOT_FOUND]: Cannot find package 'my-package' imported from /Users/numb/my-app-dir/app.js
npm link でシンボリックリンクを作成する
ここから、npm link
を使う。
まずはmy-package-dir
に移動。そこでnpm link
を実行する。
そうすると、シンボリックリンクが作成されたことが分かる。
$ npm link /Users/numb/.ndenv/versions/v12.17.0/lib/node_modules/my-package -> /Users/numb/my-package-dir
/Users/numb/.ndenv/versions/v12.17.0
の部分は、環境によって異なる。これ以降は{prefix}
と表記する。
{prefix}/lib/node_modules/
を見てみると、確かにmy-package
が作られている。
$ ls -l {prefix}/lib/node_modules total 0 lrwxr-xr-x 1 numb staff 26 6 5 00:48 my-package -> /Users/numb/my-package-dir drwxr-xr-x 24 numb staff 768 6 2 14:46 npm
{prefix}/lib/node_modules/
は、グローバルインストールしたパッケージがインストールされる場所。
つまり、npm パッケージをグローバルインストールすると、ここに格納される。
$ npm i -g cowsay $ ls -l {prefix}/lib/node_modules total 0 drwxr-xr-x 11 numb staff 352 6 5 00:54 cowsay lrwxr-xr-x 1 numb staff 26 6 5 00:48 my-package -> /Users/numb/my-package-dir drwxr-xr-x 24 numb staff 768 6 2 14:46 npm $ npm un -g cowsay $ ls -l {prefix}/lib/node_modules total 0 lrwxr-xr-x 1 numb staff 26 6 5 00:48 my-package -> /Users/numb/my-package-dir drwxr-xr-x 24 numb staff 768 6 2 14:46 npm
そして、{prefix}/lib/node_modules/my-package
はmy-package-dir
のシンボリックリンクなので、同じものを指す。
$ ls /Users/numb/.ndenv/versions/v12.17.0/lib/node_modules/my-package main.js package-lock.json package.json $ ls /Users/numb/my-package-dir main.js package-lock.json package.json
シンボリックリンクの名前はディレクトリ名ではなくpackage.json
のname
の値なので、注意する。
npm link {パッケージ名} でパッケージをインストールする
これで、my-package
をインストールする準備が整った。
my-app-dir
に移動し、そこで$ npm link my-package
を実行する。
$ npm link my-package /Users/numb/my-app-dir/node_modules/my-package -> /Users/numb/.ndenv/versions/v12.17.0/lib/node_modules/my-package -> /Users/numb/my-package-dir
そうすると、node_modules
にmy-package
というファイルが作られる。
これは先程作成された{prefix}/lib/node_modules/my-package
にリンクされている。そして先程確認したように、このファイルはmy-package-dir
にリンクされている。
これにより、my-app-dir/node_modules
にmy-package-dir
をインストールしたのと同じ効果を得られるのである。
確認のため、app.js
を実行してみる。
$ node app.js 5
無事に動いていることを確認できた。
そして、node_modules/my-package
はシンボリックリンクなので、my-package-dir
と常に同期している。my-package-dir
の変更はリアルタイムに反映される。
例えば、my-package-dir/main.js
でexport
している関数を変更して、引数を 3 つ取るようにする。
export default (arg1, arg2, arg3) => arg1 + arg2 + arg3;
そしてmy-app-dir/main.js
を以下のように書き換える。
import myPackage from 'my-package'; console.log(myPackage(2, 3, 4));
そうすると、my-package-dir/main.js
の変更が反映されていることが分かる。
$ node app.js 9
このように、パッケージに手を加えてもその都度インストールし直す必要はない。そのため、クローンしてきた既存のパッケージに手を加えて試行錯誤する作業も、やりやすくなる。
CLI ツールの動作確認を行う
npm link
は、CLI 機能の動作確認にも役に立つ。
npm link
によって、CLI 機能をローカルで試せるようになる。
npm パッケージの CLI 機能の基本については、以下の記事に書いた。
my-package
に CLI 機能を追加して、それをローカルで試す。
まず、my-package-dir/hello.js
を作成する。
#!/usr/bin/env node
console.log('Hello!!!');
そして、my-package-dir/package.json
を、以下の内容に書き換える。
{ "name": "my-package", "type": "module", "main": "main.js", "bin": { "hello": "hello.js" } }
この状態で、my-package-dir
で$ npm link
を実行する。
$ npm link {prefix}/bin/hello -> {prefix}/lib/node_modules/my-package/hello.js {prefix}/lib/node_modules/my-package -> /Users/numb/my-package-dir
シンボリックリンクが 2 つ作られた。後者は、最初に$ npm link
したときと同じもの。
それに加えて、{prefix}/bin/hello
が作られた。
まず、{prefix}/bin/
について説明しておく。
グローバルインストールした npm パッケージに CLI 機能がついていた場合、そのコマンドのシンボリックリンクがここに作成されていく。
また、最初からnpm
やnpx
というシンボリックリンクが作成されており、これにより、$ npm
や$ npx
というコマンドを利用できるようになっている。
$ ls -l /Users/numb/.ndenv/versions/v12.17.0/bin/ total 92424 lrwxr-xr-x 1 numb staff 39 6 5 01:26 hello -> ../lib/node_modules/my-package/hello.js -rwxr-xr-x 1 numb staff 47320288 5 30 04:04 node lrwxr-xr-x 1 numb staff 38 6 2 14:46 npm -> ../lib/node_modules/npm/bin/npm-cli.js lrwxr-xr-x 1 numb staff 38 6 2 14:46 npx -> ../lib/node_modules/npm/bin/npx-cli.js
同じ要領で、hello
コマンドも利用できるようになった。
ただ、筆者のようにndenv
を使っている場合は、$ ndenv rehash
を行う必要がある。
$ hello -bash: hello: コマンドが見つかりません $ ndenv rehash $ hello Hello!!!
無事に実行できた。
グローバルインストールされたのと同じ状態なので、どのディレクトリからも利用できる。
そして、先程と同じようにシンボリックリンクなので、my-package-dir/hello.js
の変更は同期されている。
#!/usr/bin/env node
console.log('Cowabunga!!!');
$ hello Cowabunga!!!
npm unlink でシンボリックリンクを削除する
動作確認が終わったら、npm unlink
でシンボリックリンクを削除できる。
まず、my-app-dir/node_modules
にシンボリックリンクがあるので、これを削除する。
$ ls node_modules/ my-package
my-app-dir
で$ npm unlink my-package
を実行すれば削除される。
次に、{prefix}/lib/node_modules
と{prefix}/bin
に作成されているシンボリックリンクを削除する。
$ ls {prefix}/lib/node_modules/ my-package npm $ ls {prefix}/bin hello node npm npx
my-package-dir
で$ npm unlink
を実行すると、これが削除される。
$ ls {prefix}/lib/node_modules/ npm $ ls {prefix}/bin node npm npx
先に{prefix}/lib/node_modules/my-package
と{prefix}/bin/hello
を削除してしまうと、my-app-dir/node_modules/my-package
を削除できない。
このファイルのリンク先である{prefix}/lib/node_modules/my-package
を先に削除してしまったために、上手く動作しないのだと思われる。