package.jsonの依存関係をチェックするライブラリ

Node.jsを使うプロジェクトでは、そのプロジェクトで使用するライブラリを依存関係として package.jsondependenciesdevDependenciesに定義します。 この依存関係定義に漏れがないかをチェックする、depcopというnpmモジュールを作りました。

https://github.com/ryym/node-depcop

package.json の例:

{
  "name": "my-module",
  "version": "1.0.0",
  "dependencies": {
    "chalk": "^1.1.1",
    "espree": "^3.0.2",
    "glob": "^7.0.0"
  },
  "devDependencies": {
    "gulp": "^3.9.1",
    "gulp-babel": "^6.1.2"
  }
}

内容

このライブラリで調べられるようにしたかったのは、主に以下の3点です。

  1. dependenciesに定義されていないのに、コード内で使用されているモジュールがないか
  2. dependenciesに定義されているのに、コード内で使用されていないモジュールがないか
  3. devDependenciesに定義されているのに、公開されるコードの中で使用されているモジュールがないか

3の「公開されるコード」というのは、テストコードやビルドスクリプトを除いたメインのソースコードの事です。 大抵はlibsrcディレクトリの下に置かれていると思います。

動機

上記のうち、1はCIを回していればそこでテストが落ちるので簡単に気づけます(CI環境では普通、毎回依存関係をインストールするところから始まるため)。 2に関しては無駄な依存関係を持っているという点で問題はあるものの、ライブラリの機能自体には影響しないはずです。 厄介なのは3のパターンで、このケースだとローカル環境やCI環境でのテストは通ってしまいます。なぜなら、それらの環境では普通 dependenciesdevDependenciesの両方の依存関係がインストールされるからです。しかし、実際にそのライブラリが使われるタイミング、 つまり公開されたそのライブラリをユーザーがインストールするタイミング(npm install my-module)ではdependenciesの方しか一緒にインストールされないため、 そこで初めて必要な依存関係がインストールされずエラーになる、という事が起き得ます。

使い方

インストールする。

npm install --save-dev depcop

プロジェクトのルートディレクトリに.depcoprc.jsという設定ファイルを作る。

module.exports = {
  libSources: ['lib/**/*.js'],
  devSources: ['test/**/*.js', 'gulpfile.js'],
  checks: {
    missing: {},
    strayed: {}
    unused: {
      ignore: [/babel/, /mocha/, /istanbul/]
    }
  }
};

コマンドもしくはJavaScriptでチェックを実行する。

# CLI
$(npm bin)/depcop
// JavaScript
import { makeDepcop } from 'depcop';

const depcop = makeDepcop();
const result = depcop.runValidations();
const format = depcop.getFormatter();
console.log(format(result));

設定ファイルの内容は全てコマンドラインでも指定できます。 詳しくはリポジトリのドキュメントに書いてあります。

類似ライブラリ

これを作った後で、eslint-plugin-nodeというライブラリの存在を知りました。 これはNode.js向けのルールを集めたESLintプラグインで、その中の no-unpublished-importルールなどで内容に書いた1と3のチェックは実現できました。実は最初はこのライブラリも ESLintのプラグインとして作ろうかという案もあったのですが、例えば「定義されているのに未使用のモジュールがないかをチェックする」というルールを実現するためには、複数のファイルを調べて使用されているモジュールの情報を集める必要があります。ESLintでは1つのファイル内で完結する ルールしか定義できないので、別のライブラリとして作る事にしました。結果的には丸かぶりせずに済んで良かったです。