アプリケーションにおける、dependencies と devDependencies の扱いの違い

配布を目的としている npm ライブラリと、ライブラリを利用して機能を作るアプリケーションでは、dependencies の扱いが異なる。
この記事では主にアプリケーションではどうしたらいいのかに注目して、

  • そもそも dependencies と devDependencies とはなんだったのか
  • ライブラリとアプリケーションでこの2つに違いは生じるのか
  • アプリケーションを作るときにはどのようにライブラリをインストールすればいいのか

を紹介する。
この記事が「アプリケーション開発で、ライブラリをインストールする際に dependencies かそうでないのか考える判断材料」となれば幸いだ。

著者の開発環境は以下の通り

  • npm
    • 8.11.0
  • node
    • 16.16.0

そもそも dependencies と devDependencies とはなんだったのか

以下で述べる違いしか存在しない。
例として

このライブラリを取り扱っていく。

dependencies

https://docs.npmjs.com/cli/v9/configuring-npm/package-json#dependencies
ライブラリをインストールする際のバージョン指定に用いられている。
まず npm で ramda をインストールしてみる。

npm i ramda@0.29.0

この結果、node_modules はこのようになる:

$ tree node_modules -L 1

node_modules
└── ramda

2 directories, 0 files

ramda しかインストールされていない。
これは、https://github.com/ramda/ramda/blob/v0.29.0/package.json#L92 を見てみると、dependencies: {} つまり、自分自身以外に依存関係がないためである。

次に、tsutils をインストールしてみる。

npm i tsutils@3.21.0

この結果、node_modules はこのようになる:

$ tree node_modules -L 1

node_modules
├── tslib
├── tsutils
└── typescript

4 directories, 0 files

tsutils 以外に「tslib」と「typescript」がインストールされた。
これは、https://github.com/ajafff/tsutils/blob/v3.21.0/package.json#L55 と https://github.com/ajafff/tsutils/blob/v3.21.0/package.json#L58 のとおり、peerDependenciesdependencies がインストールされたためである。

つまり、dependencies を指定しているライブラリを install すると、dependencies で指定されているライブラリも node_modules に入ってくる。

devDependencies

https://docs.npmjs.com/cli/v9/configuring-npm/package-json#devdependencies

開発するときにしか用いないライブラリをインストールする際のバージョン指定に用いられている。
今度は ramda ライブラリを開発しているリポジトリを git clone してみる。その上で npm install をする。

mkdir sample
cd sample
git clone https://github.com/ramda/ramda.git -b v0.29.0
cd ramda
npm install

これの結果、node_modules はこのようになる

$ tree node_modules -L 1

node_modules
├── @babel
├── @istanbuljs
├── @rollup
├── @sinonjs
├── @types
├── Base64
...
└── yeast

625 directories, 0 files

625 ディレクトリがインストールされた。
これは、https://github.com/ramda/ramda/blob/v0.29.0/package.json#L93 をみてみると、devDependencies で複数指定しているライブラリがインストールされたためである。devDependencies が依存しているライブラリもインストールされていくので、結果的に 625 にまで膨らんでいる。

ライブラリとアプリケーションでこの2つに違いは生じるのか

さきほどの章での違いでわかったように、ライブラリでdependenciesdevDependenciesを指定すると、そのライブラリを install するときに node_modules に入ってくるものが変わることがわかった。
それでは、アプリケーションではどうだろうか。
配布を目的としないアプリケーションでは、dependenciesdevDependenciesの区別はほぼない。

しかし、「dependencies のみをインストール」したいときには、アプリケーションでのこの区別は意味を持ってくる。

omit 引数

https://docs.npmjs.com/cli/v8/commands/npm-install#omit
npm には omit 引数が存在し、

npm i --omit=dev

とすると、devDependenciesをインストールさせないようにできる。

npm i -D eslint typescript
npm i react
rm -r ./node_modules
npm i --omit=dev

その結果はこれ:

$ tree node_modules -L 1

node_modules
├── @eslint
├── @eslint-community
├── @humanwhocodes
├── @nodelib
├── js-tokens
├── loose-envify
└── react

8 directories, 0 files

Scoped Packages はディレクトリのみできており、中身はない。dependencies で指定した react に関連するライブラリ だけがインストールされている。
ちなみに omit しない場合はこれ:

$ rm -r ./node_modules
$ npm i
$ tree node_modules -L 1

node_modules
├── @eslint
├── @eslint-community
├── @humanwhocodes
├── @nodelib
├── acorn
├── acorn-jsx
├── ajv
├── ansi-regex
...
└── yocto-queue

96 directories, 0 files

アプリケーションを作るときにはどのようにライブラリをインストールすればいいのか

以上をまとめると、この様になる。

  • omit 引数を使う場合は開発でしか使わないときはdevDependenciesに指定、そうでないときはdependenciesに指定する
  • omit 引数を使わない場合は区別しないでいい

当然だが、omit の件以外でも、dependencies と devDependencies フィールドを見て何かしらの処理をするようなサービスを使っていたらこの限りではない。

この記事を書いたあとに教えてもらったのだが、どうやら、npm install には dependencies のみインストールする--productionというものがあるので、そっちを使ったほうがいいと思う。