AltJS製のNodeモジュールをGitHubから直接インストールすると同時に、そのライブラリのビルドを自動化するpackage.jsonの設定


この投稿では、NodeモジュールをGitHubから直接インストールする際、そのライブラリをインストールのタイミングでビルドする方法を紹介します。

## この手法はどういうときに使うか？

普段、`yarn add ライブラリ名`をすると、npmjs.orgのレジストリにアップロードされたモジュールが、手元のnode_modulesに入ってきます。仮に、ライブラリのソースコードがTypeScriptやBebelなどのAltJSであったとしても、node_modulesにインストールされるのは普通ビルド後のJavaScriptです。これはライブラリ開発者がnpmjs.orgに公開するとき、AltJSからJavaScriptにビルドしたものをアップロードしてくれているからです。

なので、モジュール利用者は、ライブラリのソースコードがもともとどんな言語で書かれているかを気にする必要がありませんし、ましてや、モジュール利用者が`npm install`しするたびに、わざわざライブラリのソースコードからビルドする必要は普通ありません。

### どうしてもソースコードからインストール&ビルドしたい場合に使う

しかし、場合によっては「普通」じゃないインストールをしたい場合があります。どうしてもソースコードからインストールする必要がある場合です。

例えば、「ライブラリのGitHubにプルリクエストを送って取り込まれたけれど、npmjs.orgには未公開。でも、すぐにそのリビジョンのコードを使いたい」といったケースです。

このケースでは、どこにもビルド済みのコードがなく、TypeScriptやBebelなどのAltJSのコードがGitHub上にあるだけ、という状況になります。

YarnやNPMでは、GitHubのURLを指定してmasterブランチをnode_modulesにインストールすることができます:

```bash
npm install 'https://github.com/suin/isObject.js.git#master'
# or
yarn add 'https://github.com/suin/isObject.js.git#master'
```

しかし、この方法ではAltJSのコードしかインストールされず、必要となるビルド後のJSファイルが手に入りません:

```bash
ls -a1 node_modules/@suin/is-object
CHANGELOG.md
index.ts # TypeScriptしかない！
LICENSE
package.json
```

このままでは、モジュールを`import`/`require()`しても使えません。

モジュールのインストールと同時にビルドを実行する必要があるのです。

## モジュールインストール時にビルドを実行するためのpackage.jsonの設定

では、`npm install`や`yarn add`と同時に、インストールしたモジュールのビルドを実行するにはどうしたらいいか、どういう設定をpackage.jsonに施したらいいかについて説明します。

### ステップ1: モジュールのビルド方法を調べる

まず、自プロジェクトのpackage.jsonをいじる前に、ビルドするパッケージのpackage.jsonを開いて情報収集します。

例えば、`@suin/is-object`パッケージの[package.json](https://github.com/suin/isObject.js/blob/master/package.json)は以下のようになっています:

```package.json
{
  "name": "@suin/is-object",
  "version": "1.1.3",
  "description": "TypeScript friendly isObject function",
  ...
  "main": "index.js",
  ...
  "scripts": {
    "build": "tsc",
    ...
  },
  "devDependencies": {
    ...
  }
}
```

この中で注目するのは、`main`フィールドと`scripts`フィールドです。

1つ目の`main`フィールドはビルド後の成果物のファイル名です。ライブラリ利用者の我々としては、これが欲しいのでこのファイル名をメモしておきます。

2つ目の`scripts`フィールドには、たいていビルドするためのコマンドが定義されています。多くの場合、`build`という名前で定義することが多いです。ここはライブラリの開発者次第なので、よく調べてみる必要があります。ライブラリによってはREADME.mdやCONTRIBUTING.mdにビルドの手順が書いてあるかもしれないので、そこも目を通しておきます。この`@suin/is-object`パッケージの例では、`build`コマンドだけで良いことがわかります。

また、パッケージがYarnとNPMどちらでビルドしているのかも調べておきます。これは、yarn.lockとpackage-lock.jsonどちらがあるかで判断できます。

### ステップ2: `postinstall`にビルドのスクリプトを組む

上の調査で、ビルド後の成果物ファイル名とビルド方法が分かったら、自プロジェクトの`package.json`に手を加えて、モジュールのインストール時にビルドを実行するように設定します。

`yarn add`や`npm install`後に何かコマンドを実行するには、`postinstall`を`scripts`フィールドに追加します:

```package.json
{
  "name": "my-app",
  "scripts": {
    "postinstall": "echo インストール後になにかするよ"
  }
}
```

![CleanShot 2020-07-01 at 09.23.12@2x.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/889/1a709568-08f1-46f5-f9f9-c5a396c169bd.png)

`postinstall`にはシェルスクリプトが書けるので、ここにモジュールをビルドするためのコマンドを書いていきます。`@suin/is-object`パッケージの例では、次のようなステップでビルドします。

1. 成果物ファイルの`index.js`があるか確認する。
2. あればビルドしない。→ 終了
3. なければ、
    4. モジュールが依存するパッケージをインストールする。
    5. モジュールをビルドする。 → 終了

これをシェルスクリプトに起こすと、次のようになります:

```bash
set -eux
# 成果物ファイルの`index.js`があるか確認する。
if [ ! -f ./node_modules/@suin/is-object/index.js ]
then # なければ、
  cd ./node_modules/@suin/is-object
  # モジュールが依存するパッケージをインストールする。
  yarn install --frozen-lockfile
  # モジュールをビルドする。
  yarn build
fi
```

このシェルスクリプトを`postinstall`に書いておきます:

```package.json
{
  "scripts": {
    "postinstall": "set -eux; if [ ! -f ./node_modules/@suin/is-object/index.js ]; then cd ./node_modules/@suin/is-object; yarn install --frozen-lockfile; yarn build; fi"
}
```

この状態で、モジュールをGitHubからインストールしてみます:

```
yarn add 'https://github.com/suin/isObject.js.git#master'
```

実行結果は次のようになります。ライブラリのインストール後に、ライブラリのビルドが行われました:

![CleanShot 2020-07-01 at 09.49.27@2x.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/889/7bab6a10-1f90-552b-397f-09b3d4eecee7.png)

以上で、package.jsonの設定は完了です。

これにより、AltJS製のNodeモジュールをGitHubから直接インストールすると同時に、そのライブラリのビルドが自動化されます。