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

最後更新:2025年3月23日
JavaScript

對很多 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,以反映這次實際安裝的結果。

這是為了讓團隊其他人或部署環境,在下次安裝時可以「盡量」還原一致的環境。

小結一下流程:

內容讀取 package.json 裡的依賴清單
內容參考 package-lock.json 中的具體安裝記錄
內容根據系統與版本,解析依賴與版本衝突
內容安裝所有需要的套件到 node_modules/
內容根據實際安裝結果,更新 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?測試需要一致性,不能每次跑都裝出不同版本
為什麼適合用 npm ci?環境建構要可預測,不容許 lock file 被動到
為什麼適合用 npm ci?需要最乾淨、固定的安裝流程
為什麼適合用 npm ci?避免因環境差異造成執行錯誤

npm install vs npm ci — 全面比較表

npm install✅ 參考使用
npm ci✅ 嚴格依照執行
npm install🔁 自動調整(可能升級或降級)
npm ci❌ 直接報錯,不裝
npm install✅ 可能會更新
npm ci❌ 絕不修改
npm install🔄 繼續使用,可能混入舊資料
npm ci🔥 直接整包刪掉,重新安裝
npm install✅ 可以新增
npm ci❌ 不行,只能用 lock file 裡有的
npm install本機開發、新增套件、調整依賴
npm ciCI/CD、測試、自動化部署、環境重現
npm install中等:需要解析版本、比對現有套件
npm ci🚀 很快:不做判斷、直接安裝 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 install參考 package-lock.json,但會自動調整
yarn install主要參考 yarn.lock,更穩定
npm install自動更新
yarn install預設會更新,但可加 flag 阻止
npm install比較高
yarn install比較低
npm install還不錯
yarn install快很多(特別是 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 ci✅ 是
yarn install --frozen-lockfile✅ 是
npm ci❌ 直接報錯
yarn install --frozen-lockfile❌ 直接報錯
npm ci❌ 絕對不改
yarn install --frozen-lockfile❌ 不會改
npm ci❌ 不可
yarn install --frozen-lockfile❌ 不可
npm ciCI/CD、乾淨環境
yarn install --frozen-lockfileCI/CD、乾淨環境

🔥 額外補充:Yarn 還有這些特點

  • 支援 Plug’n’Play(PnP):Yarn 2 開始有更先進的套件管理模式,跳過 node_modules,加快啟動速度。
  • Zero-Install 支援:透過 .yarn/cache,可以在 repo 裡直接附上套件壓縮檔,clone 完就能跑,不需要另外 install。
  • 自動離線快取:Yarn 裝過一次的套件,之後可以離線安裝。

總結比較:npm vs yarn 的「CI 部署策略」

npmnpm install
yarnyarn install
npmnpm ci
yarnyarn install --frozen-lockfile
npm✅ 預設會(ci)
yarn❌ 你要自己刪
npmpackage-lock.json
yarnyarn.lock
npm✅ 是
yarn❌ frozen-lockfile 模式下不行
npm❌ 比較差
yarn✅ 原生支援離線與快取

常見踩雷問題解析

在實務開發中,明明覺得一切照規矩來,卻還是會遇到這些令人頭痛的情況:

  • 「為什麼 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,到最後才發現只是安裝指令用錯。今天你讀到這裡,就已經走在前面了!