昨天,我們替 AI 準備好了一本進專案前要先看的入職手冊。
到這裡,第一關「規劃設計」終於走完了~
接下來要正式進入「核心開發」了。
但一份 Code 要真的跑起來,靠的不只有 Code 本身。
它還需要正確的執行環境、套件、設定和開發工具。
這些東西準備好了,專案才真的有辦法開始工作。
這就是今天要拆的:
環境建置(Environment Setup)。
我以前聽到「環境建置」,一直以為大概就是:
把該裝的東西裝一裝。
後來才知道,它的範圍其實更大。
環境建置(Environment Setup),可以先理解成:
把這個專案需要的開發環境,準備到可以正常開發與執行的過程。
先把整張地圖攤開來看:
專案開發
│
├─ 開發環境 Development Environment
│
│ ├─ 作業系統 Operating System
│ │ └─ macOS / Windows / Linux
│ │
│ ├─ 執行環境 Runtime
│ │ └─ Node.js
│ │
│ ├─ 套件管理工具 Package Manager
│ │ └─ npm
│ │
│ ├─ 專案依賴 Dependencies
│ │ └─ Vue / Vite / Express ...
│ │
│ ├─ 環境設定 Configuration
│ │ └─ Environment Variables 等
│ │
│ └─ 開發工具 Development Tools
│ ├─ VS Code:Source Code Editor
│ └─ Git:Version Control System
│
└─ 環境建置 Environment Setup
= 把這個專案需要的環境
準備到可以正常開發與執行的過程
這張圖不是說每個專案都一定會用完全相同的工具。
換一種語言、框架或團隊,內容本來就可能不同。
但它至少先幫我解開了一個以前一直混在一起的問題:
以前在我腦袋裡,它們全部只是:
「反正寫程式都會用到的東西。」
現在才知道,每個工具其實都負責不同工作。
我第一次學著安裝套件的時候,其實完全不知道自己在做什麼。
老師要我們輸入:
npm install
我也就照著打。
然後開始看到幾個完全不知道在幹嘛的東西:
package.json
package-lock.json
node_modules/
package.json 跟 package-lock.json 到底差在哪?
為什麼 package-lock.json 打開那麼長?
更誇張的是 node_modules。
我明明也沒記得自己下載幾個東西,為什麼裡面可以塞成這麼大一包?
我當時第一個反應是:
「等一下,我是不是下載錯東西了?」
後來才知道,這三個東西其實剛好分別扮演不同角色。
package.json
→ 專案宣告「我需要什麼」
package-lock.json
→ 記錄「這次實際解析成哪些版本」
node_modules/
→ 套件真正被安裝下來的地方
package.json 是 Node.js / npm 專案裡很重要的一份設定檔,會記錄套件依賴、Scripts 和其他專案設定。
常見欄位大概有這些:
| 欄位 | 是什麼 |
|---|---|
scripts |
定義專案可以執行的 Script Command |
dependencies |
Production 執行時需要的套件 |
devDependencies |
開發、測試、Lint、Build 等開發流程需要的工具 |
engines |
描述專案對 Node.js、npm 等執行環境版本的需求 |
type |
決定 Node.js 如何解讀 .js 模組,常見為 module 或 commonjs |
private |
設為 true 時,避免這個 Package 被意外發布到 npm Registry |
今天先抓最常碰到的三個:
{
"scripts": {
"dev": "vite",
"build": "vite build",
"test": "vitest"
},
"dependencies": {
"vue": "^3.5.0"
},
"devDependencies": {
"vite": "^7.0.0",
"vitest": "^3.0.0"
}
}
也就是:
dependencies、devDependencies、scripts。
這兩個區塊真正的差別在於:
這個套件是 Production 所需要的依賴,還是主要用在開發流程?
可以先這樣記:
| 類型 | 什麼時候需要 |
|---|---|
dependencies |
Production 所需要的依賴 |
devDependencies |
開發、測試、Lint、Build 等開發流程需要的工具 |
例如:
{
"dependencies": {
"vue": "^3.5.0"
},
"devDependencies": {
"vite": "^7.0.0",
"vitest": "^3.0.0"
}
}
這是我以前最不懂的第二個檔案:
package-lock.json
因為 package.json 裡明明就已經有:
"vue": "^3.5.0"
為什麼還需要另一份版本紀錄?
原因是 package.json 裡的版本不一定代表:
永遠只能安裝這一個精確版本。
例如 ^3.5.0 允許 npm 在符合版本規則的範圍裡解析版本。
而真正安裝時,也不只有 Vue 自己。
Vue 可能還依賴其他 Package,那些 Package 又可能再依賴其他東西。
最後會形成一整棵:
依賴樹(Dependency Tree)。
這時候就需要 Lockfile(鎖定檔)。
npm 使用的 Lockfile,就是:
package-lock.json
它會把這次實際解析出的完整 Dependency Tree 和精確版本記錄下來。
所以:
package.json
→ 我要哪些套件+允許哪些版本範圍
package-lock.json
→ 這次整棵依賴樹實際解析成哪些版本
這也是為什麼 package-lock.json 打開來常常長得很可怕。
不是它壞掉了。
只是它記錄的比我原本想像的多很多。
最後就是那個第一次看到很容易被嚇到的:
node_modules/
它不是套件清單。
而是:
套件真正被安裝到本機之後所在的資料夾。
而且裡面不只會有我直接安裝的套件。
例如我的專案需要 A:
我的專案
└─ A
但是 A 自己又需要 B 和 C:
我的專案
└─ A
├─ B
└─ C
B、C 這種被其他套件間接帶進來的依賴,就叫做傳遞依賴(Transitive Dependencies)。
所以 node_modules 才常常大得很誇張。
原來不是我一次下載錯了一整座倉庫。
是 npm 把這道菜真正需要的材料和材料的材料,都一起搬進廚房了。
搞懂前三個角色之後,再回頭看:
npm install
就清楚很多。
它會根據 package.json 宣告的依賴,並在有 package-lock.json 時參考其中鎖定的依賴樹,把需要的套件安裝到 node_modules。
node_modules/
所以團隊開發時,一個很常見的流程會是:
git clone 專案
↓
進入專案資料夾
↓
npm install
↓
安裝專案需要的套件
↓
產生 node_modules
↓
準備環境設定
↓
npm run dev
↓
執行專案定義好的開發指令
也就是說,我不需要把自己電腦裡的 node_modules 整包傳給隊友。
專案已經透過 package.json 和 package-lock.json 留下依賴資訊。
隊友 Clone 專案後,再執行:
npm install
就可以重新把需要的套件安裝到自己的電腦。
不過做到這裡,環境建置還沒有完全結束。
有些設定會隨著開發環境、測試環境或正式環境不同而改變,例如 API URL、Database URL、PORT,甚至 API Key、JWT Secret 等。
這類設定常會透過 環境變數(Environment Variables) 提供給程式。
而 .env,就是本機開發時很常用來保存這些 Environment Variables 的檔案形式之一。
例如:
API_URL=http://localhost:3000
PORT=5173
可以先這樣分:
Environment Variables
→ 程式執行時取得的環境設定
.env
→ 保存這些設定的一種常見檔案形式
這裡還有一個很重要的資安問題。
因為 .env 可能包含 API Key、Database Password、JWT Secret 等敏感資訊,因此含有真實敏感值的 .env 通常不應提交進 Git。
團隊反而常留一份:
.env.example
只告訴其他開發者:
「這個專案需要哪些環境變數。」
但不把真正的密鑰放進去。
所以環境建置不只是:
「套件有沒有安裝好?」
還要確認:
「這個環境需要的設定,有沒有準備好?」
環境和套件準備完之後,開發時下一個很常看到的指令就是:
npm run dev
以前我一直下意識覺得:
dev 應該是 npm 裡某種固定的開發模式吧?
其實不是。
npm run 會去 package.json 的 scripts 裡找到指定名稱,再執行右邊真正的 command。
例如:
| script 名稱 | 要執行的 command |
|---|---|
dev |
vite |
build |
vite build |
test |
vitest |
放回 package.json:
{
"scripts": {
"dev": "vite",
"build": "vite build",
"test": "vitest"
}
}
所以:
npm run dev
其實是在做:
npm run dev
↓
去 scripts 找名稱叫 dev 的項目
↓
找到:
"dev": "vite"
↓
執行 vite
一般自訂 Script 的名稱可以自己取。
只是團隊通常會用:
dev
build
test
lint
format
這類大家一看就知道用途的名字。
右邊放的則是實際要執行的 command,不一定是某個 npm 套件。
所以:
npm run 不是「執行某個套件」,而是「執行專案定義好的一條 Script」。
前面解決的是:
怎麼讓大家把同一個專案需要的套件和設定準備起來。
但就算 Code 和套件完全相同,開發者使用的環境還是可能不同。
BuJo 團隊成員使用的開發環境不完全一樣,因此有些 Script 也需要考慮不同作業系統的執行方式。
例如後端測試 Script 需要先設定:
NODE_OPTIONS
這類環境變數,再執行 Jest。
Windows 常見的 Command Prompt 與 macOS / Linux 常見的 POSIX Shell,在設定環境變數的語法上並不相同。
如果把某一套 Shell 的寫法直接寫死在 package.json,就可能變成:
某些環境可以直接執行
某些環境卻跑不起來
所以 BuJo 後端使用了:
cross-env
讓同一條 npm Script 可以用比較一致的方式設定 Environment Variables,不需要每個作業系統各維護一套 command。
這也是環境建置很實際的一部分:
團隊需要考慮的,不只是某一台電腦能不能跑,而是實際會參與開發的環境能不能用同一套方式工作。
我現在重新看環境建置,最有感的不是學會更多指令。
而是那些以前只會照著 README 複製貼上的東西,終於開始有了前因後果。
npm install、npm run dev 不再只是要背起來的咒語;package.json、package-lock.json、.env 也不再只是散落在專案根目錄的檔案。
它們其實都在回答同一件事:
這個專案需要什麼,才能被重新跑起來。
以前我只知道把專案跑起來。
現在開始知道:
跑得起來,本身也有一套可以被理解、記錄和重現的條件。
環境準備好了。
下一篇,就真的要走進 Code 裡面了。
而 Code 一多,下一個問題很快就會冒出來:
這麼多檔案,到底應該怎麼分,才不會最後連自己都找不到?
下一篇,就來拆專案結構設計。
iThome鐵人賽