從 npm install 到 npm ci:你以為你懂了,其實沒這麼簡單!
更新日期: 2025 年 3 月 23 日
本文為 Node.js 基礎,第 9 篇:
- 初學者指南:什麼是 Runtime 執行環境?
- 初學者指南:Node.js 是什麼?
- 初學者指南:什麼是 Node.js 的 LTS 版本?
- 初學者指南:Node.js 和瀏覽器中常見函數的使用差異
- nvm 是什麼?新手必看的 Node.js 版本管理工具指南
- 函數、模組、套件與框架之間的關係與差異
- NPM 使用指南:如何管理 JavaScript 套件與依賴
- 不用看 README!從 package.json 快速掌握一個專案的完整指南
- 從 npm install 到 npm ci:你以為你懂了,其實沒這麼簡單! 👈進度
若閱讀完本文,建議閱讀 Yarn 套件基礎 系列文
對很多 JavaScript / Node.js 初學者來說,npm install
是再熟悉不過的日常指令,但你有沒有想過:
「當我執行 npm install
時,背後到底發生了什麼?」
「為什麼明明有 package-lock.json
,裝出來的套件還可能不同?」
「那 npm ci
又是什麼?不是只是比較快的安裝方式而已嗎?」
這些問題看似小事,實際上是開發與部署過程中最常踩雷的地方。
只要搞錯指令的使用時機,可能會讓你的專案在本機可以跑,部署卻出錯,甚至出現難以重現的 bug。
別擔心,這篇文章將從零開始,用最清楚的方式告訴你:
npm install
實際在做什麼?npm ci
和它差在哪裡?- 什麼時候該用哪一個?
- 常見誤解與踩雷原因是什麼?
讓你真正理解,而不只是會打指令!
npm install
之後到底發生什麼事?一步步拆解給你看
npm install
看似是一行簡單的指令,但其實背後做了很多事。
如果你想了解它為什麼有時會安裝出不一樣的版本、甚至改動你的 package-lock.json
,那你一定要搞懂它的完整流程。
當你執行:
npm install
實際上會依序執行以下步驟:
讀取 package.json
這是專案的套件清單。npm 會從中找到兩個主要欄位:
"dependencies"
:正式環境會用到的套件"devDependencies"
:開發過程需要的工具(像是測試框架)
npm 根據這兩個欄位決定「要安裝哪些套件」。
參考 package-lock.json
(如果存在)
這個檔案會記錄上一次執行 npm install
時的具體安裝結果,包括:
- 每個套件的確切版本
- 各層依賴的安裝順序與來源
npm 在安裝時會「參考」這份資料,但不一定完全照著執行。如果你的 package.json
定義的版本範圍允許更新,或系統環境不同,它可能還是會抓更新版本。
解析依賴樹與版本號
npm 會根據當前的系統(作業系統、Node.js 版本等),開始「解析」所有套件之間的依賴關係。這包含:
- 判斷每個套件該裝哪個版本
- 決定依賴套件之間是否有衝突或重複
- 考慮哪些子套件是平台限定的(如 Windows 才裝的套件)
這一步驟有時候會讓你在不同平台上裝出不同的 node_modules
結構,即使用的是同一份 package-lock.json
。
安裝套件到 node_modules/
一切準備好之後,npm 開始把每個套件實際安裝到 node_modules/
資料夾中。
這裡裝進去的不只有你在 package.json
寫的套件,還包含它們所需要的所有子套件(這就叫做「依賴樹」)。
更新 package-lock.json
(視情況而定)
如果安裝過程中:
- 你加了新套件
- 某些版本和 lock file 不一致
- 系統環境導致裝了不同的子套件
npm 會自動更新 package-lock.json
,以反映這次實際安裝的結果。
這是為了讓團隊其他人或部署環境,在下次安裝時可以「盡量」還原一致的環境。
小結一下流程:
步驟 | 內容 |
---|---|
1 | 讀取 package.json 裡的依賴清單 |
2 | 參考 package-lock.json 中的具體安裝記錄 |
3 | 根據系統與版本,解析依賴與版本衝突 |
4 | 安裝所有需要的套件到 node_modules/ |
5 | 根據實際安裝結果,更新 package-lock.json(如有需要) |
為什麼 npm install
不一定照 package-lock.json
裝?
這個問題非常多人會誤會,尤其是剛接觸 Node.js 的朋友,常以為只要專案有 package-lock.json
,那 npm install
一定會 100% 照著這份檔案來安裝。
事實上不是這樣,因為:
npm install
是一個為了「開發靈活性」而設計的指令,它會根據當下狀況動態決定怎麼裝,不是一個「只複製原樣」的安裝流程。
我們來看它有哪些「會自己判斷」的情況。
現象一:會根據 package.json
的版本範圍,去抓符合條件的最新版
🧠 關鍵觀念:package.json
裡寫的不是「指定版本」,而是「版本範圍」
舉例:
"axios": "^1.4.0"
這個意思並不是「我只要 1.4.0」,而是「我想要 1.4.x 裡最新的版本,但不要跳到 1.5 或 2.x」。
所以就算你的 package-lock.json
裡面記錄的是:
"axios": {
"version": "1.4.2"
}
但只要 npm 發現有一個 符合 ^1.4.0
條件的新版本(例如 1.4.3),它就可能會跳過 lock file,直接幫你裝新版,然後自動把這次的安裝結果寫進新的 package-lock.json
裡。
❗結果是什麼?
- 你以為裝的是一樣的版本,其實已經變了!
- 這會導致你和團隊其他成員或 CI 機器裝出不同的套件版本。
- 這種差異在某些情況下會引發 難以重現的 bug,非常難除錯。
現象二:會嘗試「自動修復」被改壞的 package-lock.json
npm install
的一個特性是「很有彈性」,只要它還能理解 lock file 的結構,它就會試著幫你補回來。
舉個例子:
你不小心手動打開 package-lock.json
改了一些東西,或是有某些欄位被刪掉了。
當你執行 npm install
:
- npm 並不會馬上報錯
- 它會「嘗試修復」錯誤的部分
- 並且更新 lock file 內容,看起來一切正常
❗為什麼這很危險?
因為你以為「沒有報錯」就表示沒問題,但事實上:
- lock file 裡的內容已經不是原本的版本了
- 專案實際裝的套件可能跟你預期不同
- 在多人協作下,這可能讓 lock file 產生無聲衝突(你改過但別人不知道)
現象三:根據系統平台差異,裝出不同依賴內容
這是很多初學者完全沒注意到的狀況。
🔍 背後原因:
有些 npm 套件會根據以下條件決定該裝什麼內容:
- 你目前用的作業系統(Windows / macOS / Linux)
- Node.js 的版本
- 架構(如 x64、ARM)
例如某個套件在 Linux 上會裝一個子套件叫 fsevents
,但在 Windows 上不需要它。或是有些套件版本只支援特定 Node.js 版本。
這會導致:
即使你複製同一份 package-lock.json
到不同機器上,npm install
還是可能裝出不同的 node_modules
結構。
❗這對部署很致命:
- 你在 macOS 開發時裝得好好的
- 拿去 Linux 的 CI/CD 環境部署卻跑不起來
- 除錯時還會懷疑人生,因為你本機明明「完全沒問題」
小提醒:為什麼 npm install
要這麼「聰明」?
因為它設計給開發時使用,目的就是:
- 讓你可以快速裝上最新版本(只要符合範圍)
- 自動處理依賴、修復衝突
- 不需要你手動改 lock file,就能維持環境正常
但這樣的靈活性,在部署或測試環境中就會成為風險。
記憶法:npm install 就像「幫你出主意的助手」
- 它會根據你的需求、當前情況「幫你做決定」。
- 它會修、會補、會變通,但不是百分百可控。
- 適合開發,不適合正式部署。
那 npm ci
又是什麼?它真的只是比較快而已嗎?
❌ 完全不是!速度只是附加價值,關鍵在「一致性」與「可預測性」
很多人第一次聽到 npm ci
,會以為它只是 npm install
的快版,但其實這個指令的誕生,是為了解決另一個根本性的問題:
在自動化或部署流程中,我們要的不是「靈活」或「聰明」,而是「百分百一致、不變動、不猜測的安裝流程」。
npm ci
是一種「照單全收、完全復刻」的安裝模式
當你執行:
npm ci
npm 做的事情是這樣的:
🧹 第一步:直接刪除整個 node_modules/
資料夾
這一步非常重要。npm ci
不會理會裡面有什麼東西,也不會做比對或合併,而是先砍掉重來。
為什麼?因為只有這樣,才能確保你的環境是乾淨、沒有殘留物的,避免之前安裝的套件「偷偷影響現在的結果」。
📦 第二步:完全根據 package-lock.json
的內容安裝套件
這裡的「完全」是真的完全——npm 不會去解析 package.json
的版本範圍,不會自作主張升級套件,只會忠實地把 lock file 裡指定的版本、順序、來源,一模一樣地重建整個依賴環境。
❌ 第三步:只要 lock file 有錯或不吻合,就直接報錯
例如:
- 你手動改了
package-lock.json
中某個欄位的版本,但那版本實際上不存在 package.json
和package-lock.json
內容不一致
在這些情況下,npm ci
會馬上報錯,完全不會幫你修、補、猜。
這樣看起來很嚴格,但其實超重要,因為它強迫你確保 lock file 是健康且可信任的,才能進行下一步操作。
那麼,npm ci
適合用在哪裡?
場景 | 為什麼適合用 npm ci ? |
---|---|
✅ 自動化測試流程 | 測試需要一致性,不能每次跑都裝出不同版本 |
✅ CI/CD 自動部署 | 環境建構要可預測,不容許 lock file 被動到 |
✅ 建構 Docker 映像檔 | 需要最乾淨、固定的安裝流程 |
✅ 你在用 git hooks 或 pre-build scripts | 避免因環境差異造成執行錯誤 |
npm install
vs npm ci
— 全面比較表
特性 | npm install | npm ci |
---|---|---|
使用 package-lock.json | ✅ 參考使用 | ✅ 嚴格依照執行 |
若版本與 lock file 有衝突 | 🔁 自動調整(可能升級或降級) | ❌ 直接報錯,不裝 |
是否會修改 lock file | ✅ 可能會更新 | ❌ 絕不修改 |
node_modules/ 存在時怎樣處理 | 🔄 繼續使用,可能混入舊資料 | 🔥 直接整包刪掉,重新安裝 |
是否可安裝新套件 | ✅ 可以新增 | ❌ 不行,只能用 lock file 裡有的 |
適合使用情境 | 本機開發、新增套件、調整依賴 | CI/CD、測試、自動化部署、環境重現 |
安裝速度 | 中等:需要解析版本、比對現有套件 | 🚀 很快:不做判斷、直接安裝 lock file 裡的版本 |
什麼時候該用哪一個?用這個原則簡單判斷!
npm install
:給開發者用的
你應該在以下情況用 npm install
:
- 你正在開發、debug、改程式碼
- 你需要新增或更新某個 npm 套件
- 你不介意 lock file 被更新
- 你需要快速測試某個套件是否適用
📌 小提醒:開發時的彈性很重要,所以 npm install
會幫你動來動去,但這就代表環境會有「變數」存在。
npm ci
:給機器或部署流程用的
你應該在以下情況使用 npm ci
:
- 要部署到正式環境(production)
- 正在執行 CI/CD 自動化流程(如 GitHub Actions、Jenkins 等)
- 要在不同機器間建立「完全相同」的執行環境
- 要跑測試流程、不希望環境內容被偷偷改動
📌 小提醒:機器不需要「判斷」,只需要「執行得一模一樣」,這就是 npm ci
存在的意義。
最後記一句話就好!
npm install
是為「開發者」設計的,允許彈性與變化。npm ci
是為「部署流程」設計的,要求嚴謹與一致。
那 yarn
呢?也有 yarn install
跟 yarn ci
嗎?
🤔 簡短回答:
yarn install
有,功能跟npm install
類似但行為比較一致yarn ci
沒有這個指令,但有對應做法,稱為「Zero-Install」或--frozen-lockfile
模式
yarn install
是什麼?
yarn install
是你最常用的指令,跟 npm install
類似,用來根據 package.json
和 yarn.lock
安裝專案的所有依賴。
但它的預設行為更嚴謹一些:
行為比較 | npm install | yarn install |
---|---|---|
依賴解析依據 | 參考 package-lock.json,但會自動調整 | 主要參考 yarn.lock,更穩定 |
lock file 不一致會怎樣? | 自動更新 | 預設會更新,但可加 flag 阻止 |
安裝過程變動性 | 比較高 | 比較低 |
安裝速度 | 還不錯 | 快很多(特別是 Yarn 2+) |
如果你想要「像 npm ci 那樣鎖死版本、不允許 lock file 被動到」,怎麼辦?
Yarn 雖然沒有叫做 yarn ci
的指令,但它提供了類似的旗標,達到一樣的效果。
🔐 yarn install --frozen-lockfile
這個參數就是 Yarn 的「部署用模式」。
當你執行:
yarn install --frozen-lockfile
它會:
- 根據
yarn.lock
嚴格安裝所有套件 - ❌ 如果
package.json
和yarn.lock
不一致,直接報錯 - ❌ 不會自動更新或產生新的
yarn.lock
這個行為就像 npm ci
一樣:
行為 | npm ci | yarn install --frozen-lockfile |
---|---|---|
嚴格照 lock file 安裝 | ✅ 是 | ✅ 是 |
lock file 不一致會怎樣? | ❌ 直接報錯 | ❌ 直接報錯 |
是否會更新 lock file | ❌ 絕對不改 | ❌ 不會改 |
安裝新套件 | ❌ 不可 | ❌ 不可 |
用途 | CI/CD、乾淨環境 | CI/CD、乾淨環境 |
🔥 額外補充:Yarn 還有這些特點
- 支援 Plug’n’Play(PnP):Yarn 2 開始有更先進的套件管理模式,跳過
node_modules
,加快啟動速度。 - Zero-Install 支援:透過
.yarn/cache
,可以在 repo 裡直接附上套件壓縮檔,clone 完就能跑,不需要另外 install。 - 自動離線快取:Yarn 裝過一次的套件,之後可以離線安裝。
總結比較:npm vs yarn 的「CI 部署策略」
比較項目 | npm | yarn |
---|---|---|
安裝指令 | npm install | yarn install |
部署/CI 模式 | npm ci | yarn install --frozen-lockfile |
是否清除 node_modules | ✅ 預設會(ci) | ❌ 你要自己刪 |
Lock 檔案名稱 | package-lock.json | yarn.lock |
是否可自動修復 Lock 檔 | ✅ 是 | ❌ frozen-lockfile 模式下不行 |
離線支援 | ❌ 比較差 | ✅ 原生支援離線與快取 |
常見踩雷問題解析
在實務開發中,明明覺得一切照規矩來,卻還是會遇到這些令人頭痛的情況:
- 「為什麼 lock file 都一樣,我裝出來的套件卻不一樣?」
- 「為什麼我本機測試都沒問題,一佈署就炸?」
- 「到底怎樣才能保證我部署出來的版本跟我開發時一模一樣?」
別擔心,這章幫你一一拆解!
為什麼有 package-lock.json
,卻還是會裝出不一樣的結果?
這是最常見的誤解:
「我 lock file 都 commit 進去了,應該大家裝的結果都一樣吧?」
理論上應該如此,但如果你使用的是 npm install
,實際上可能會出現以下幾個差異原因:
✅ 1. 版本範圍造成的變動
package.json
裡面的版本號常常寫的是「範圍」而非「確定版本」,例如:
"axios": "^1.4.0"
這表示 npm 可以幫你抓到 1.4.x
這個範圍中最新的版本(只要不跳到 1.5.0
)。所以如果 lock file 裡是 1.4.2
,而 npm registry 上已經有了 1.4.3
,你用 npm install
的時候就有機會被更新。
這種情況下,即使 lock file 沒變過,你裝的版本也可能跟別人不同。
✅ 2. 不同平台裝出來的結果不同
有些套件會依據當前作業系統或 Node.js 版本,安裝不同的子套件。例如:
- macOS 下會裝
fsevents
,但 Linux 不會 - 某些 native 套件會根據 Node.js 的 ABI 版本做不同編譯
這代表你在 macOS 上執行 npm install
可能會產生跟 Linux 上完全不同的 node_modules
結構,即使兩邊的 package-lock.json
完全一樣。
✅ 3. npm install
可能會「修補」或「更新」 lock file
如果 lock file 裡某些資料損壞、缺漏,或跟 package.json
的內容有衝突,npm install
通常會默默幫你修好它。雖然這讓開發更方便,但這也意味著:
- 本機環境自動補好了某些錯誤
- 其他開發者或 CI 環境無法複製你那份「被修好後」的結果
- 導致「本機 ok,別人掛掉」的狀況發生
為什麼部署後出錯,但本機沒事?
這種狀況超級常見,尤其是團隊多人協作、或在 CI/CD pipeline 中。
常見原因有:
☑️ 本機用 npm install
,部署機器用的不是相同流程
你在本機安裝時可能發生:
- lock file 有問題 → 被
npm install
自動修復 - 加裝套件 → 但沒把改動的 lock file commit 上去
- 你有安裝某些全域依賴,但部署機器沒有
所以你的機器「幸運地能跑」,但部署機器一安裝:
- 抓到不同的版本
- 沒有自動修復機制
- 損壞的 lock file 被完整照搬,導致爆錯
☑️ node_modules/
殘留舊檔,造成混合狀態
部署時如果沒有先清空 node_modules/
,有可能導致新套件覆蓋舊版本,但某些殘留檔案卻還在,產生奇怪的錯誤或衝突。
舉例:
- 某個套件新版移除了某個檔案
npm install
沒有刪掉舊檔,只是覆蓋新的檔案- 結果你跑的其實是「新舊混合」的版本
這種錯誤在開發環境難以察覺,但一旦佈署到乾淨機器就會立刻爆炸。
那部署前到底要怎麼做才安全?
這邊給你三個字:npm ci
這是目前最穩定、最保險的做法。
✅ 使用 npm ci
,能確保:
- lock file 必須正確、版本對得起來才允許安裝
- 不會偷偷幫你更新任何版本
- 安裝流程每次都一致、可預測
- 自動刪掉
node_modules/
,避免舊檔殘留
這就像是讓 npm 從「聰明助手」變成「嚴格執行員」——照 lock file 怎麼寫,它就怎麼裝,一字不差。
實務建議:部署安全流程這樣做
- ✅ 確保
package.json
和package-lock.json
是同步的(每次裝完新套件都要 commit) - 🧼 部署流程中先刪除
node_modules/
- 🚀 使用
npm ci
而不是npm install
- 🔍 若有平台差異(mac vs Linux),建議使用 Docker 或同一 OS 做 build
這樣你才能確保:
- 測試環境和本機一模一樣
- 正式部署不會出現「我這邊沒問題啊?」的尷尬對話
- Lock file 發揮它該有的保護力,而不是擺好看的
結語:懂這個,你會少掉 80% 的部署痛苦
學會區分 npm install
和 npm ci
,不只是一個指令的問題,而是牽涉到版本控制、環境一致性、除錯效率的根本。
許多工程師花了很多時間排查奇怪的 bug,到最後才發現只是安裝指令用錯。今天你讀到這裡,就已經走在前面了!