Travis CI で "npm ci" コマンドを使う

# `npm ci` とは

皆さんご存知かもしれませんが、 [`npm@5.7`](https://github.com/npm/npm/releases/tag/v5.7.0) から `npm ci` コマンドが使えるようになりました。

* [ci | npm Documentation](https://docs.npmjs.com/cli/ci)
* [The npm Blog — Introducing `npm ci` for faster, more reliable...](https://blog.npmjs.org/post/171556855892/introducing-npm-ci-for-faster-more-reliable)
* [Release v5.7.0 · npm/npm](https://github.com/npm/npm/releases/tag/v5.7.0)

`npm ci` コマンドを簡単に説明すると、こんな感じです。

```shell-session
$ rm -rf node_modules && npm install
```

ただし、上記のコマンドと違って `npm ci` コマンドは `package-lock.json` ファイルを更新しません。
（ `npm install` コマンドは `package-lock.json` ファイルを更新することがあります）

`package-lock.json` ファイルに書かれてあるパッケージのみをインストールするので、テストやデプロイなどの再現性が要求される環境で非常に有用です。
また、`npm ci` は `npm install` で実行されるいくつかのステップを省略しているので、高速化されています。

# Travis CI での使い方

Travis CI は `npm ci` コマンドに公式に対応しています。以下、マニュアルからの抜粋です。

> If a `package-lock.json` or `npm-shrinkwrap.json` exists and your npm version supports it, Travis CI will use `npm ci` instead of `npm install`.
>
> もし `package-lock.json` または `npm-shrinkwrap.json` が存在し、あなたの npm バージョンがサポートしていれば、Travis CI は `npm install` の代わりに `npm ci` を使うでしょう。

[Building a JavaScript and Node.js project - Travis CI](https://docs.travis-ci.com/user/languages/javascript-with-nodejs/#npm-ci-support)

なので、最新の Node.js バージョンを Travis CI で実行するように指定していれば、 `.travis.yml` ファイルに特に何も書かなくても `npm ci` が使われます。

## キャッシュ

さらに、キャッシュの設定をすれば Travis CI のビルドを速くすることができます。

```yaml:.travis.yml
cache:
  directories:
    - "$HOME/.npm"
```

この方法は [`npm ci` ドキュメント](https://docs.npmjs.com/cli/ci)に記載してあります。

[Travis CI のドキュメント](https://docs.travis-ci.com/user/languages/javascript-with-nodejs/#Caching-with-npm)には、少し違った方法が記載されていますが、

```yaml:.travis.yml
cache:
  directories:
    - "node_modules"
```

`npm ci` コマンドは `node_modules` ディレクトリを削除するので、このキャッシュ指定はあまり意味がないです。

### より簡単な方法（2018-11-08）

以下のようなシンプルな方法がサポートされていました！

```yaml:.travis.yml
cache: npm
```

> This caches `$HOME/.npm` precisely when `npm ci` is the default `script` command. (See above.)

参考: <https://docs.travis-ci.com/user/languages/javascript-with-nodejs/#caching-with-npm>

## npm のバージョンが古いとき

現在の Node.js 8.x 最新版では、npm のバージョンが少し古いです。その場合、`npm ci` が使えないので、`before_install` セクションで最新の npm をインストールするよう設定します。

```yaml:.travis.yml
before_install: npm i -g npm@latest
```

もし複数バージョンの Node.js を使用している場合は、npm のバージョンが古い場合にだけ npm のアップデートすることをおすすめします。
（npm のアップデートはネットワークアクセスを発生させるので、その分ビルド時間が遅くなります）

```yaml:.travis.yml
node_js:
  - 10
  - 8

before_install: if [[ $TRAVIS_NODE_VERSION == 8 ]]; then npm i -g npm@latest; fi
```

`[[ $TRAVIS_NODE_VERSION == 8 ]]` の部分は、以下のような npm バージョンのチェックをした方がより確実なのですが、

```bash
if [[ $(npm -v) < 5.7 ]]; then ...; fi
```

`npm -v` が `5.7.0` のような semver を返すので、単純な Bash ワンライナーでは実現できませんでした。
今後なにか良い方法が見つかれば、この部分を更新します。

### 特別な方法が不要に（2018-11-18）

現在の Node.js 8.x 環境では、デフォルトで `npm ci` コマンドをサポートした npm がインストールされるようになりました。
したがって、 `npm i -g npm@latest` コマンドを実行する必要はありません！

https://travis-ci.org/ybiquitous/ybiq/jobs/453752672#L446

### Ruby を使う（2018-06-08）

Travis CI には Ruby がデフォルトでインストールされているので、 ruby コマンドのワンライナーで semver の比較を行うことができました。

```bash
if ruby -e "exit(1) if Gem::Version.new(ARGV[0]) >= Gem::Version.new('5.7.0')" $(npm -v); then
  npm i -g npm@latest;
fi
```

実際の `.travis.yml` の設定は、こうなります。

```yaml:.travis.yml
before_install:
  # `npm ci` is available since npm@5.7.0
  - if ruby -e "exit(1) if Gem::Version.new(ARGV[0]) >= Gem::Version.new('5.7.0')" $(npm -v); then
      npm i -g npm@latest;
    fi
```

https://travis-ci.org/ybiquitous/ybiq/jobs/389717667#L443
![image.png](https://qiita-image-store.s3.amazonaws.com/0/104952/bf8f15c1-a3b0-cbe9-2d59-d0f7bf25b268.png)

https://travis-ci.org/ybiquitous/ybiq/jobs/389717668#L443
![image.png](https://qiita-image-store.s3.amazonaws.com/0/104952/406f55b0-3e20-1780-cace-e1a8dc1fda6d.png)


## 実際の使用例

私の個人プロジェクトでの使用例です。

```yaml:.travis.yml
language: node_js

node_js:
  - 10
  - 8

cache:
  directories:
    - "$HOME/.npm"

before_install:
  # `npm ci` is available since npm@5.7.0
  - if [[ $TRAVIS_NODE_VERSION == 8 ]]; then npm i -g npm@latest; fi

script:
  - commitlint-travis
  - npm test
```

以下のビルドログを見ると、`npm ci` が実際に使われていることが分かります。

Node 10:
https://travis-ci.org/ybiquitous/ybiq/jobs/387010580#L449
![image.png](https://qiita-image-store.s3.amazonaws.com/0/104952/ace43254-78ae-6bd9-99ad-3130dd91c4fc.png)

Node 8:
https://travis-ci.org/ybiquitous/ybiq/jobs/387010581#L453
![image.png](https://qiita-image-store.s3.amazonaws.com/0/104952/bdecb166-b33a-91d5-9351-64025e9102d1.png)

# まとめ

CI では　`npm install` の代わりに `npm ci` を積極的に使っていきましょう！