本記事で対象となる開発
本記事では、これらを前提として狭い範囲に対して主張を行います ![]()
Webアプリケーションを開発する
- ライブラリは本記事の主張からは対象外とする
JavaScript で書かれたアプリケーションコードは利用者のブラウザで実行される
- Node.js で実行されるサーバーアプリケーションではない
ビルド済みのファイルは Git で管理しない
- ビルド済みファイルで発生する差分はソースファイル、ビルド環境の差分でしかない
- コンフリクトしやすい
- テストは Git のリモートリポジトリを clone し、CI サービスで実行する
- デプロイは Git のリモートリポジトリを clone し、CD サービスでビルドしたものを動作環境に設置する
dependencies, devDependencies を使いわけるとなにが変わるか
npm install --production を有効に使うことができます。
npm install --production は dependencies に含まれるパッケージだけをインストールします。
With the --production flag (or when the NODE_ENV environment variable is set to production), npm will not install modules listed in devDependencies.
これによりインストール時間の短縮、ディスク使用量の削減が期待できます。
アプリケーションにはどのようなステージがあるか
dependencies, devDependencies を使い分けることの直接的な効能は npm install 時の --production オプションによってインストールするパッケージを限定できるということです。よって、使い分けの基準を考えるにあたって npm install が実行される場面を考えます。
今日では GitHub Actions や CircleCI といった CI/CD サービスを使うようになり、それはテスト、ビルド、リリースといったステージごとにコンピューティングリソースを分けるようになりました。つまり npm install もアプリケーション開発のステージごとに実行されるようになりました。
アプリケーション開発のステージのうち、コードが関係してくるのは以下です。
- コーディング
- テスト
- ビルド
- 実行
このうち、node_modules をパッケージ単位で扱うの必要があるのは、上から「実行」を除いた以下の3つです。
- コーディング
- テスト
- ビルド
実行時は Webpack をはじめとしたバンドラーによって、各パッケージはファイルにまとめられ、ファイル単位ではパッケージという概念が消え去るからです。
ステージごとに必要なパッケージを考える
以下のパッケージを用いたアプリケーション開発を例に、dependencies, devDependencies の使い分けを考えます。
| パッケージ名 | 簡単な説明 |
|---|---|
| react | UIライブラリ |
| @babel/core | トランスパイラー |
| webpack | バンドラー |
| webpack-dev-server | webpack を使うときのライブリロード用のサーバー |
| eslint | リンター |
| jest | テストツール |
アプリケーション開発をステージに分け、各ステージでどのパッケージが必要か考えます
コーディング(開発)
コーディングと言っていますが普段のローカル開発のステージです。デバッグ実行し、このステージでテストをすることもあります。
基本的にすべてのパッケージを使用します。
- react
- @babel/core
- webpack
- webpack-dev-server
- eslint
- jest
テスト
テスト時も、Git のリモートリポジトリから取得したソースコードを @babel/core でトランスパイルします。
今回は jest を使った単体テストのみを期待しているため、webpack, webpack-dev-server はテスト時には不要かと思います。
- react
- @babel/core
- eslint
- jest
ビルド
webpack-dev-server, eslint, jest は必要ありません。
- react
- @babel/core
- webpack
一旦まとめ
どのステージでも共通して react, @babel/core が使用されることが分かりました。
dependencies, devDependencies を使い分ける目的は、npm install --production オプションによって dependencies に含まれるパッケージのみをインストールし、負荷をへらすことでした。
そのため、どのステージにも共通して使用される react, @babel/core は、dependencies に該当することが分かります。devDependencies として振り落としたい場面がないからです。
逆に、「コーディング」ステージのみで使用されるパッケージである webpack-dev-server は、インストールすることが無駄であるステージがいくつもあります。真っ先に npm install --production で振り落としたい対象でしょうから devDependencies に含めるとよいことが分かります。
今回の例では「コーディング」ステージに該当するのは webpack-dev-server のみですが、他には例えば「git のコマンドに特定の処理をフックする husky」や「コードフォーマッターの prettier(「テスト」ステージでチェックしなければ)」も当てはまります。
さて、残りはビルドステージで使われる webpack、テストステージで使われる eslint, jest です。
ここから、
- ビルドステージ・テストステージ両方のパッケージを
dependenciesに含めるのか - ビルドステージ・テストステージ両方のパッケージを
devDependenciesに含めるのか - ビルドステージに該当するパッケージを
dependenciesに含め、テストステージに該当するパッケージをdevDependenciesに含めるのか
などを考えていきます。
そもそも dependencies, devDependencies を使い分ける目的は、インストール時間の短縮、ディスク使用量の削減でした。
この目的が至上命令であるならば、使用するパッケージがどのステージに該当するかを考え、ステージごとのパッケージサイズの数・ファイルサイズから、開発するアプリケーションごとに、dependencies, devDependencies を使い分けを考えることになります。
例えば
- 今回はコーディングステージに属するパッケージは数が多いから、ビルド時にもテスト時にもインストールされたくない。よって、ビルド・テストステージで使うパッケージは
dependenciesにしよう - 今回はビルドステージに属するパッケージは数が多いから、テスト時にビルドステージのパッケージにインストールかかる負荷が気になる。ビルドステージは
dependenciesにしよう
といったふうにです。
しかし、アプリケーション開発に導入するパッケージは、用件のたびに増減・変更されます。上記のように使い分けを厳格なルールとすると、パッケージの変更のたびに調査・使い分けの変更が発生し、プロジェクト開発において、より負担がかかることになるでしょう。
例えばテストステージで使うパッケージのサイズがビルドステージで使うパッケージのサイズを上回ったので、ビルドステージのパッケージを devDependencies に移動し、テストステージのパッケージを dependencies に移動するようなことが起こります。
これから分かるように、「インストール時間の短縮、ディスク使用量の削減」という目的を完璧に果たすことは大変です。また、果たしたとして何秒のインストール時間の節約になり何バイトディスクが節約されるでしょうか。
これ以降は「インストール時間の短縮、ディスク使用量の削減」という目的からは外れないように、管理しやすい dependencies, devDependecies を使い分ける目安を提案していきます。
使い分けの目安:ビルドステージは dependencies, テストステージは devDependencies
ビルドステージに該当するパッケージは dependencies に、テストステージに該当するパッケージは devDependenciesに含めてください。
理由を述べます。
dependencies にはランタイムに必要なパッケージを含めることにすると直感的
devDependencies には、その名前から開発用のパッケージが含まれることが期待されます。
dependencies はその逆ですので、ランタイムに必要なパッケージが含まれることを期待します。ビルド済みのファイルを Git 管理下に含めない場合、ビルドステージで使うパッケージはランタイムに必要なパッケージといえます。
そのためビルドステージで使うパッケージは dependencies に含めます。
E2E テストを導入するとパッケージサイズが膨大になる(ならない?)
puppeteer を導入すると Chromium がダウンロードされ、100MB~200MB になり、それだけで他のパッケージを圧倒する負荷になります。
puppeteer を devDependencies に含めると npm install --production ではインストールされません。テストステージで使うパッケージは膨大になることが予想されるために devDependenncies に含めます。
って言っても、今日日 CI では Chromium インストール済みイメージを使うから、npm install 時の Chromium ダウンロードはスキップするんですよね……
まとめ
冒頭で示した前提に該当するアプリケーション開発においては、導入するパッケージは、アプリケーション開発のどのステージで使用されるかを考え、そのステージによって dependencies, devDependencies どちらに含めるか判断しようと述べました。
考えるべきステージは以下の3つがあり、
- コーディング
- テスト
- ビルド
dependencies, devDependencies への判断の目安は以下です
-
devDependencies- コーディングステージのみで使用するパッケージ
- ビルドステージでは使用しない、テストステージで使用するパッケージ
-
dependencies- すべてのステージで使用するパッケージ
- ビルドステージで使用するパッケージ
そもそも Webアプリケーション開発においてビルドツールが devDependencies に含まれることに違和感があり、ビルドツールは dependencies ではないかということに思いを巡らせた結果、本記事の主張に至りました。
「前提」で、かなり場面を限定化しているので一般的に適用できる内容か怪しいのですが。
本記事が dependencies, devDependencies の使い分けに困っている方の手助けになれば幸いです。ここまで読んでいただきありがとうございました。