npmではパッケージのインストール元として公式リポジトリ以外に、別フォルダやGitを選択することができます。
package.json
のdependenciesを見てみると、以下の例のように、公式のリポジトリの場合にはバージョン番号が書かれており、Gitを用いている場合にはgit+<GitリポジトリのURL>
が書かれています。
"dependencies": { "base64url": "^3.0.1", "npm-prepare-test-installed": "git+https://github.com/s1r-J/npm-prepare-test-installed.git" }
npmのパッケージにはTypeScriptやBabelを用いてビルド後のコードを実行するパッケージも存在します。 このようなビルドが必要なパッケージはnpmのリポジトリにはビルド後のコードがアップロードされており、パッケージを利用する側ではnpm installによって取得したビルド後のコードを実行します。
しかし、Gitを用いてGitリモートリポジトリ(例えばGitHub)からパッケージを取得する場合には、このように同じようにはいきません。 通常、Gitリポジトリにはビルド前のソースコードだけをアップロードし、ビルド後のコードはアップロードしないからです。 そのため、このようなパッケージをインストールしてもビルド後のコードが存在せず、実行することができません。
今回は、Gitを用いてインストールされるビルドが必要なパッケージに少し工夫を施し、快適に利用できるnpm prepare
という機能を用いた方法を紹介します。
npm prepareとは
npm prepare
について公式ドキュメントで言及されているのはライフサイクルスクリプト( https://docs.npmjs.com/cli/v10/using-npm/scripts#life-cycle-scripts )の項目です。
npmにはpackage.jsonのscriptsプロパティ内に特定の文字列をキーにコマンドを設定すると、特定のタイミングでそのコマンドが自動的に実行されます。これがライフサイクルスクリプトです。
例えば、prepublish
ではパッケージ公開のコマンドであるpublish
が実行される前に自動的に呼び出されたり、postinstall
ではパッケージのインストールをおこなうinstall
が実行された後に呼び出されたりします。
では、npm prepare
が実行されるタイミングは以下の3つとなっています。
- パッケージをパックする(
npm publish
およびnpm pack
)前 - 引数なしでローカルの
npm install
を実行したとき prepublish
の後、prepublishOnly
の前
この記事で紹介する方法に関連するのは2になります。 そして、2については更に注釈がつきます。
注意:Gitを介してprepare
スクリプトを含むパッケージがインストールされる場合、そのパッケージがパッケージされてインストールされる前に、そのdependencies
およびdevDependencies
がインストールされ、そしてprepare
スクリプトが実行されます。
以下は原文
NOTE: If a package being installed through git contains a
prepare
script, itsdependencies
anddevDependencies
will be installed, and the prepare script will be run, before the package is packaged and installed.
Gitでインストールされる側のpacakge.jsonのscriptsにprepare
スクリプトを設定しておくと、そのパッケージがGitを使ってインストールされるときに設定したコマンドが自動的に実行される、ということです。また、prepare
スクリプトはdependencies
anddevDependencies
に記載されているパッケージがインストールされた後に実行されます。
つまり、prepare
スクリプトでビルド処理を書いておけば、ビルドのために必要なdevDependencies
に記載したパッケージがインストールされ、ビルドを実行した上でインストールしてもらうことができるのです。まさに利用される・インストールされる前に準備をおこなうためのスクリプトだと思います(インストールされる前に実行されるスクリプトにpostinstall
がありますが、こちらはdevDependencies
のパッケージがインストールされないことが問題になります。記事の余談を参照。)。
おかげで、Gitリポジトリにビルド結果をプッシュしなくて済みます。
ここで気になるのは、インストールされる側のdevDependencies
に記載されたパッケージが、余計なパッケージとして残ってしまうのではないか、という点です。先述の公式ドキュメントにはこれについての記載はありませんでした(たぶん)。
後述のデモでこの点についても検証していますが、先に結論を書いておくと、devDependencies
のパッケージは残りませんでした。
デモ
prepare
を使ってパッケージのビルドをどのように設定するのか、前述の気になる点はどうなっているのかをデモで紹介します。
デモで使っているソースコードはGitHubに置きました。自由に使ってください(npmには公開していないです)。
サンプルパッケージ
最初に今回デモで使うパッケージの依存関係とそのソースコードについて確認します。
以下の2つのパッケージを利用します。
- インストールされるパッケージ(
npm-prepare-test-installed
):https://github.com/s1r-J/npm-prepare-test-installed npm-prepare-test-installed
を利用するパッケージ(npm-prepare-test
):https://github.com/s1r-J/npm-prepare-test
インストールされるパッケージ(npm-prepare-test-installed
)を見てみます。
こちらのパッケージはTypeScriptで書かれており、実行するにはTypeScriptをビルドしてJavaScriptに変換する必要があります。
package.jsonのscriptsのbuild
にはTypeScriptのビルドをおこなうtscコマンドが書かれています。また、prepare
にはビルドを実施するため、npm run build
を実行するように設定しています。
また、devDependencies
にはtypescript
が記載されており、tscコマンドはこのtypescriptパッケージが存在することで実行できます。
ソースコードの中身は非常に単純で、helloという関数に与えられた引数の人名に挨拶をする文字列を返します。
export default function hello (name: string): string { const target = name || 'anonymous'; return `Hello, ${target}!`; }
次にnpm-prepare-test-installed
を利用するパッケージ(npm-prepare-test
)を見ます。
package.jsonのdependencies
にnpm-prepare-test-installed
が記載されており、Gitを介したインストールをおこなうように設定されています。
"dependencies": { "npm-prepare-test-installed": "git+https://github.com/s1r-J/npm-prepare-test-installed.git" }
ソースコードはこちらも単純です。npm-prepare-test-installed
パッケージからhello関数を読み込み、hello関数にJohn
という文字列を渡して結果を標準出力に出力するというものです。
const hello = require('npm-prepare-test-installed').default; console.log(hello('John'));
インストールされるパッケージ(npm-prepare-test-installed
)がビルドされ、JavaScriptのコードになっていなければ、hello関数が見つけられず、エラーになります。
インストール・実行
パッケージの確認が済んだところで動作を見ています。
npm-prepare-test-installed
を利用するパッケージ(npm-prepare-test
)をGitからクローンします$ git clone https://github.com/s1r-J/npm-prepare-test
- ディレクトリを移動します
$ cd ./npm-prepare-test
- パッケージをインストールします
$ npm install
npm-prepare-test
パッケージを実行します$ node index.js
- 標準出力に以下の出力が確認できます(=
npm-prepare-test-installed
パッケージがビルドされた) Hello, John!
5のようにTypeScriptによるビルドが必要なパッケージを呼び出すことができ、npm-prepare-test-installed
のprepare
が想定通りにdevDependencies
のtypescriptパッケージをインストールし、ビルドを実行してくれたことがわかりました。
node_modulesの確認
npm-prepare-test-installed
のprepare
でインストールされたtypescriptパッケージはどのようになったのかを確認します。
npm-prepare-test
では不要なのにtypescriptパッケージが残っていたら無駄です。
- node_modulesを確認します
$ ls node_modules
- npm-prepare-test-installedしか存在しないことが確認できました
npm-prepare-test-installed/
- さらにnpm-prepare-test-installedディレクトリを確認します
$ ls node_modules/npm-prepare-test-installed
- typescriptパッケージは存在しないことが確認できました(ちなみにビルド後のファイルはdistディレクトリに入っています)
LICENSE README.md dist/ in-files/ package.json
prepare
でインストールされたtypescriptパッケージは、prepare
を実行した後(ビルドが完了した後)に削除されるようです。懸念点は解消されました。
これで安心してGitを使ってインストールされるnpmパッケージには、prepare
にビルド処理を設定することで快適な開発環境を作ることができます。
まとめ
Gitを介してインストールされるnpmパッケージは、npmスクリプトのprepare
にビルド処理を記載しておくことで、インストールされる際に自動的にビルドを実行するように設定できます。ビルドに必要なパッケージはdevDependencies
に書いておけば問題なく(=一般的な開発からpackage.jsonを変更する必要はない)、ビルド前に一時的にインストールされ、ビルド完了後にこれらのパッケージが残ることはありません。
余談(postinstallについて)
prepare
はパッケージがGitを使ってインストールされるとき、dependencies
anddevDependencies
に記載されているパッケージがインストールされた後に、設定しておいたコマンドが自動的に実行される、と紹介しました。
似た動作をするpostinstall
というライフサイクルスクリプトが存在します。これは読んで字のごとく、パッケージがインストールされた後に実行されるスクリプトです。
しかし、postinstall
がprepare
と異なるのはdevDependencies
のパッケージがインストールされるか否かです。
postinstall
はあくまで、インストールされた後に実行されるスクリプトでしかなく、何かしらの準備をおこなうprepare
とは異なります。
postinstall
ではビルドに必要なパッケージを記載しているdevDependencies
がインストールされないため、ビルドに必要なモジュールをdependencies
に記載する必要が発生します。
これはパッケージサイズの肥大や保守性の低下を引き起こすことから推奨されません。