npm install --productionみたいなの色々ありすぎ問題
まえおきと結論
npmで依存関係を管理するプロジェクトを考えます。その際、プロダクションビルドには
npm install --production
を使うんだよ、という話は多くの方がご存知かと思います。オプションもそれっぽいですし、なんだか安心して使ってしまいます。
しかし、その詳細について調べていると似たような項目が山ほどあることがわかりました。今回は混乱の記録をまとめます😵💫
結論から言うと、このコマンドは最新のnpmにおいて非推奨です。 とくに問題なく動きますが、その違いを意識したうえで
npm install --omit=dev
ないしは
npm ci --omit=dev
に移行することを検討しましょう。
検証条件
調べたこと
package.jsonとpackage-lock.jsonについての確認
npmを用いてプロジェクトを構成する場合、そこで利用するライブラリの一覧はpackage.jsonに記述されます。すなわち、package.jsonは依存関係の管理を行っているわけですね。
このpackage.jsonには、利用するライブラリを以下の2つの属性に分けて記載します[1]。
| 属性 | 記載するもの |
|---|---|
dependencies |
実行時に必要なライブラリ |
devDependencies |
開発時にのみ必要なライブラリ |
たとえば、日付を操作するためにMoment.jsを導入したとします。これはプロダクションコードの中で使用したいので前者に記載しましょう。なにも指定せずにnpm installすれば前者に入ります。
# 事前にnpm initを実施しておく
npm install moment
一方、ユニットテストを行うためにJestを導入する場合を考えます。これはそれ自体がプロダクションコードとして配布されるわけではなく、あくまでそれを書いたり保守したりするためのものですね。このようなライブラリは--save-devか-Dを付して実行することでdevDependenciesに追加します。
npm install --save-dev jest
さて、いまの状態はこんな感じですね。実行のタイミングでバージョンは変わるはずなのでご了承ください。
$ tree . -a -L 1
.
├── node_modules
├── package-lock.json
└── package.json
$ cat package.json
{
# 省略
"dependencies": {
"moment": "^2.29.4"
},
"devDependencies": {
"jest": "^29.7.0"
}
}
package-lock.jsonには、以下に対して実際にインストールされたバージョンが記述されます。
-
package.jsonに記載された依存先のライブラリ - そのライブラリの依存先すべて
実際のソースコードはnode_modulesにあり、もし削除してしまってもpackage.jsonにある情報をもとにnpm installから復元できます。
しかし、これでは問題があります。npm installでインストールされるバージョンは、そのタイミングで条件を満たす最新のものです。ライブラリのバージョンが好き勝手に変化してしまうと、アプリの動作が保証できなくなってしまいます![2]
そこで依存先すべてのバージョンが記載されたpackage-lock.jsonを共有し、これに基づいてインストールを行えば、すべてのライブラリについてまったく同じソースコードを利用することができるようになるわけですね😛
これはnpm ciから実行できます。再現性が大切なプロダクションビルドやCI/CD環境ではこちらを利用することが一般的です。
混乱したところ
まえおきはここまでにして、以上の説明の中から今回取り上げたいトピックは2つです。
- プロダクションビルド時には
devDependenciesのライブラリは無視する -
package-lock.jsonに基づいてインストールするにはnpm ciを利用する
そして、この1.を実現するにはnpm installやnpm ci時に以下を実行すればOKなようです。
-
--productionオプションをつける -
--only=prodオプションをつける -
--omit=devオプションをつける - 環境変数を
NODE_ENV=productionとして実行する
したがって以下の2×4通りあることになります。多いって!!
npm install --productionnpm install --only=prodnpm install --omit=dev- 環境変数を
NODE_ENV=productionとしてnpm install npm ci --productionnpm ci --only=prodnpm ci --omit=dev- 環境変数を
NODE_ENV=productionとしてnpm ci
ということで、これらの違いとそれぞれどのようなケースに利用するのかを考えてみます😩
devDependenciesを除くためのオプションあれこれ
さて、改めて確認すると以下の選択肢がありました。
-
--productionオプションをつける -
--only=prodオプションをつける -
--omit=devオプションをつける - 環境変数を
NODE_ENV=productionとして実行する
はじめに結論をいうと、推奨されるオプションは--omit=devです。 確認すると、これはdevDependenciesを無視してdependenciesのみをインストールします。
実際にやってみましょう!
$ npm install --omit=dev
up to date, audited 2 packages in 689ms
found 0 vulnerabilities
$ ls node_modules
@ampproject/ @babel/ @bcoe/ @istanbuljs/ @jest/ @jridgewell/ @sinclair/ @sinonjs/ @types/ moment/
あれ?@babelとか@jestも含まれちゃってるじゃん、と思ったかもしれません。npmでは、特定のパッケージがインストールされる際に、その名前空間に基づいて事前にディレクトリが作成されます。 つまり、@jestのようなスコープ付きパッケージ名を持つライブラリの場合、まずその名前空間に対応する親ディレクトリ(@jest)が作成されてしまうのですね。
実際に確認すると、node_modules/moment以外は空になっていることがわかります👻
$ tree . -a -L 3
.
├── node_modules
│ ├── (省略)
│ ├── @jest
│ ├── @jridgewell
│ ├── @sinclair
│ ├── @sinonjs
│ ├── @types
│ └── moment
│ ├── (省略)
│ └── ts3.1-typings
├── package-lock.json
└── package.json
さて、次に--only=prodと--productionですが、これらは--omit=devのエイリアスでありDEPRECATEDです。 ドキュメントにも明記されていますね![3]
only
DEPRECATED: Use --omit=dev to omit dev dependencies from the install.
When set to prod or production, this is an alias for --omit=dev.
production
DEPRECATED: Use --omit=dev instead.
Alias for --omit=dev
試しに実行してみると「代わりに--omit=devを使ってね」というWarningが出ます。
$ npm install --production
npm WARN config production Use `--omit=dev` instead.
$ npm install --only=prod
npm WARN config only Use `--omit=dev` to omit dev dependencies from the install.
つまり以下の3つはまったく同じです!
npm install --omit=devnpm install --only=prodnpm install --production
一方、環境変数を利用するケースも確認しましょう。確かにNODE_ENVを設定するとインストールされるライブラリの数が大幅に減っていることが確認できますね。
$ npm install
added 291 packages, and audited 293 packages in 1s
32 packages are looking for funding
run `npm fund` for details
found 0 vulnerabilities
$ export NODE_ENV=production
$ npm install
up to date, audited 2 packages in 541ms
found 0 vulnerabilities
$ unset NODE_ENV # 戻しておく
環境変数を指定するケースとオプションを指定するケース、なにか違いはあるのでしょうか? --omit=devのドキュメントには次のように記載があります。
If the resulting omit list includes 'dev', then the NODE_ENV environment variable will be set to 'production' for all lifecycle scripts.
なるほど、--omit=devとした場合は一連の処理がNODE_ENV=productionとして実行されるようです。ということは結局、4つはまったく同じ意味であることになります。
異なるのはアプローチだけですね。細かい違いを言えば、--omit=devは
- いちいち環境変数を汚さずに済む
-
--omitは引数で他の操作もできるので拡張性がある
くらいでしょうか。
じゃあnpm install --omit=devってどこで使うの?
プロダクションビルドでは開発環境の再現が大切です。そのためpackage-lock.jsonに基づいてビルドするため、npm ciを使用することが一般的です。その際には不必要なライブラリをインストールしたくないので--omit=devオプションを指定します。
では、npm install --omit=devを使うケースはどこでしょうか?🤔
たとえば
- 本番環境で
package-lock.jsonを更新せざるをえない場合 - 本番環境で
package-lock.jsonが存在せず、新規に生成したい場合
などが思いつきましたが、この状況に陥ることはまずないと思います。その他に強いて言えばmasterブランチ上で開発を行わざるを得ない状況かもしれません。
たとえば、Gitflowではmasterブランチで問題が発生した場合、そこからhotfixブランチを切って修正します。このブランチは本番環境に極めて近いものなので、その検証にはdevDependenciesの影響を与えたくありません。そのため新規にライブラリを追加してデバッグを行う場合にnpm install --omit=devを使うべきかもしれないですね。
まとめ
- 以下の4つの動作はまったく同じであり、アプローチが違うだけ
-
--omit=devオプションをつける -
--only=prodオプションをつける -
--productionオプションをつける - 環境変数を
NODE_ENV=productionとして実行する
-
- いずれも
devDependenciesを無視してdependenciesに記載のライブラリのみをインストールする - 推奨されるのは
--omit=devオプションか環境変数の指定-
--production,--only=prodは非推奨であり実行時にWarningが表示される
-
- プロダクションビルドには
npm ci --omit=devを使用しましょう
補足:--includeオプションについて
似たものにincludeオプションがあります。ドキュメントには--omit=<type>の逆(inverse)だと書かれていますね。
Option that allows for defining which types of dependencies to install.
This is the inverse of --omit=<type>.
Dependency types specified in --include will not be omitted, regardless of the order in which omit/include are specified on the command-line.
この「逆」というのがちょっとわかりにくいのですが、たとえば
npm install --include=prod
としたときにnpm install --omit=devと同じ処理になるわけではありません。
これは「もともと除外されている項目を引数に指定して、それを含めてインストールする」という意味になります。たとえば、NODE_ENV=productionとして次のコマンドを実行するとdevDependenciesも含めてインストールされるというわけですね。
$ export NODE_ENV=production
$ npm install --include=dev
up to date, audited 293 packages in 689ms
32 packages are looking for funding
run `npm fund` for details
こちらはDEPRECATEDではありません。もし利用する場合は--onlyとの違いを意識したいですね!
-
これらの他に
optionalDependenciesやpeerDependenciesなどもあります。前者は利用可能ならインストールされるが、もし利用不可であっても他の処理を中断したくない場合に使用するもの、後者は指定したバージョンを満たしてなくても警告を出さなくするための機能です。が、実際のところ頻繁に利用されるものではないので本記事ではスルーして解説は公式ドキュメントに譲ります。 ↩︎ -
たとえば、上述の
package.jsonでは"moment": "^2.29.4"、すなわち2.x.x系を指定しており、これを満たすものなら何でもOKだと宣言していることになってしまいます。そこで"moment": "2.29.4"のように特定のバージョンを指定することもできます。しかし、このmomentが依存するライブラリのバージョンまでは指定できません。そのため、すべての依存先のバージョンをpackage-lock.jsonに記載しておくのですね。 ↩︎
Discussion