從 npm install 到 npm ci:你以為你懂了,其實沒這麼簡單!

更新日期: 2025 年 3 月 23 日

對很多 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.jsonpackage-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 installnpm 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 installyarn ci 嗎?

🤔 簡短回答:

  • yarn install 有,功能跟 npm install 類似但行為比較一致
  • yarn ci 沒有這個指令,但有對應做法,稱為「Zero-Install」或 --frozen-lockfile 模式

yarn install 是什麼?

yarn install 是你最常用的指令,跟 npm install 類似,用來根據 package.jsonyarn.lock 安裝專案的所有依賴。

但它的預設行為更嚴謹一些:

行為比較npm installyarn 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

它會:

  1. 根據 yarn.lock 嚴格安裝所有套件
  2. ❌ 如果 package.jsonyarn.lock 不一致,直接報錯
  3. ❌ 不會自動更新或產生新的 yarn.lock

這個行為就像 npm ci 一樣:

行為npm ciyarn 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 部署策略」

比較項目npmyarn
安裝指令npm installyarn install
部署/CI 模式npm ciyarn install --frozen-lockfile
是否清除 node_modules✅ 預設會(ci)❌ 你要自己刪
Lock 檔案名稱package-lock.jsonyarn.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 怎麼寫,它就怎麼裝,一字不差。

實務建議:部署安全流程這樣做

  1. ✅ 確保 package.jsonpackage-lock.json 是同步的(每次裝完新套件都要 commit)
  2. 🧼 部署流程中先刪除 node_modules/
  3. 🚀 使用 npm ci 而不是 npm install
  4. 🔍 若有平台差異(mac vs Linux),建議使用 Docker 或同一 OS 做 build

這樣你才能確保:

  • 測試環境和本機一模一樣
  • 正式部署不會出現「我這邊沒問題啊?」的尷尬對話
  • Lock file 發揮它該有的保護力,而不是擺好看的

結語:懂這個,你會少掉 80% 的部署痛苦

學會區分 npm installnpm ci,不只是一個指令的問題,而是牽涉到版本控制、環境一致性、除錯效率的根本。

許多工程師花了很多時間排查奇怪的 bug,到最後才發現只是安裝指令用錯。今天你讀到這裡,就已經走在前面了!

Similar Posts