Day 1 到 Day 3 寫完後,我原本有一種「好,這段差不多懂了」的感覺。
不過我覺得要回想一下。
從頭問自己:
npm run dev
↓
接下來到底發生什麼?
一路答到:
npm 讀 package.json
↓
找到 scripts.dev
↓
得到 "vite"
然後就卡住了。
更尷尬的是,看到 PATH、shell、node_modules/.bin,每個字都好像看過,但要我自己串成一句話,就開始亂掉 @@
vite 之後發生什麼?npm run dev
前半段我已經可以自己說:
npm run dev
↓
npm 讀 package.json
↓
找到 scripts.dev
↓
得到 "vite"
問題是:
vite
只是一個 command 名稱,又不是完整路徑。
那到底是誰負責去找它?
這時我才發現,前幾天雖然一直看到 shell 這個字,但......其實沒有真的把它放進腦袋裡。
我目前先用最白話的方式理解:
Terminal
= 我輸入指令、看輸出的地方
Shell
= 負責接收、理解並處理我輸入的 command 的程式
PATH
= shell 尋找外部 command 時會依序查看的「資料夾清單」
甚至有點把 shell 和 bash 搞反。
比較正確的關係是:
shell
├── bash
├── zsh
└── fish
bash、zsh 都是 shell 的不同種類。
所以可以先把 shell 想成一個「負責聽指令的人」。
例如我在 Terminal 輸入:
vite
概念上就是:
我在 Terminal 輸入 vite
↓
shell 收到「vite」
↓
shell:這個 command 在哪?
↓
去看 PATH
這裡是我最容易講錯的地方。
我以前聽到 PATH,腦中很容易把它想成:
某一條檔案路徑
但在現在這個問題裡,更有用的理解是:
PATH是 shell 用來搜尋 command 的一串資料夾清單,而且有順序。
概念上例如:
node_modules/.bin
/usr/local/bin
/usr/bin
/bin
如果 shell 要找:
vite
它就會依序嘗試:
node_modules/.bin/vite
/usr/local/bin/vite
/usr/bin/vite
/bin/vite
找到前面的,就可以先用前面的。
所以 PATH 裡放的不是:
node_modules/.bin/vite
而是整個資料夾:
node_modules/.bin
這個差一小段,心智模型卻差很多。
.bin 資料夾放進 PATH?我一開始還問了一個有點笨但其實很關鍵的問題:
為什麼 npm 不直接把
node_modules/.bin/vite放進 PATH 就好?
答案是:因為 PATH 的工作本來就是放「資料夾」,不是列出每一個 command。
假設 .bin 裡有:
node_modules/.bin/
├── vite
├── eslint
└── tsc
npm 只要把:
node_modules/.bin
放進 PATH,shell 之後就可以依照不同名稱去找:
vite → node_modules/.bin/vite
eslint → node_modules/.bin/eslint
tsc → node_modules/.bin/tsc
這樣就不用為每一個 command 各塞一條完整路徑。
所以 npm run dev 中間這段:
scripts.dev = "vite"
↓
npm 執行 script 時準備 PATH
↓
把相關的 node_modules/.bin 排到前面
↓
shell 收到「vite」
↓
shell 按 PATH 的順序找
↓
找到 node_modules/.bin/vite
這裡我之前很容易講成:
npm 把 node_modules/.bin/vite 放進 PATH
這是不精確的。
比較精準的是:
npm 把
node_modules/.bin這個**資料夾加進 PATH 的前段;shell 再去這個資料夾裡找vite**。
.bin 排前面?這裡又多懂一點。
假設我的電腦全域剛好也有一個 Vite,而專案自己也有:
全域 Vite:7
專案 Vite:8
如果執行:
npm run dev
npm script 的環境會讓專案相關的 node_modules/.bin 排在 PATH 很前面,所以 shell 通常會先找到:
專案/node_modules/.bin/vite
也就是專案自己的 Vite。
這其實很合理。
不同專案可能需要不同版本:
專案 A → Vite 6
專案 B → Vite 8
如果全部都只靠電腦全域那一份,就很容易出現:
我這台可以跑
同事那台版本不同
CI 又是另一個版本
所以我現在會記成一句:
PATH 有順序,而 npm 會讓專案自己的 command 優先被找到。
node_modules/.bin/vite 之後呢?這就接回 Day 3。
我們看過:
ls -l node_modules/.bin/vite
在 macOS/Linux 上,常會看到類似:
node_modules/.bin/vite -> ../vite/bin/vite.js
所以:
node_modules/.bin/vite
↓
只是 command 入口
↓
指向 node_modules/vite/bin/vite.js
而 Vite 套件自己的 package.json 裡,也會有類似:
"bin": {
"vite": "bin/vite.js"
}
也就是它自己宣告:
command 名稱:vite
↓
入口檔:bin/vite.js
到這裡,Day 1~Day 3 才真的接成一條線。
現在從頭走:
npm run dev
↓
npm 讀專案的 package.json
↓
找到 scripts.dev
↓
得到 "vite"
↓
npm 準備執行 script 的環境
↓
相關的 node_modules/.bin 排進 PATH 前面
↓
shell 收到 command:vite
↓
shell 按 PATH 的順序找
↓
找到 node_modules/.bin/vite
↓
.bin/vite 指向 Vite 套件的入口
↓
node_modules/vite/bin/vite.js
↓
Vite 開始執行
以前我的版本大概只有:
npm run dev
↓
網站跑起來
現在中間終於不是一團黑箱。
這次先不要看上面的流程圖。
npm run env | grep '^PATH='
先只找一件事:
node_modules/.bin
是不是出現在很前面。
vite 在哪如果 package.json 還有這個暫時的 script:
"where-vite": "command -v vite"
執行:
npm run where-vite
看看是不是找到:
.../node_modules/.bin/vite
.bin/vite 指去哪ls -l node_modules/.bin/vite
把:
.bin/vite
和:
vite/bin/vite.js
重新接起來。
npm run dev
↓
npm 讀 __________
↓
找到 __________
↓
得到 "vite"
↓
npm 執行 script 時準備 __________
↓
把 __________ 排到前面
↓
__________ 按順序找 vite
↓
找到 node_modules/.bin/vite
↓
指向 node_modules/vite/bin/vite.js
這次真正發現自己卡在哪:不是 package.json,也不是 .bin,而是中間的 shell → PATH 還沒接起來。
在這個情境裡,PATH 比較像是有順序的資料夾清單。
.bin/vite 放進 PATHnpm 放進 PATH 的是像:
node_modules/.bin
shell 再去裡面找 vite。
方向反了xddd
比較像:
shell
├── bash
└── zsh
bash、zsh 是 shell 的不同實作。
一邊照顧快三個月的娃娃一邊自問自答,晚安