本文為 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 cinpm 做的事情是這樣的:
🧹 第一步:直接刪除整個 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,到最後才發現只是安裝指令用錯。今天你讀到這裡,就已經走在前面了!