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を先に削除してしまったために、上手く動作しないのだと思われる。