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 --production
npm install --only=prod
npm install --omit=dev
- 環境変数を
NODE_ENV=production
としてnpm install
npm ci --production
npm ci --only=prod
npm 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=dev
npm install --only=prod
npm 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