我們每天寫 JS / TS,它到底跑在什麼上面?答案是某個 runtime——而 runtime 就是一顆 JS 引擎(V8、JavaScriptCore…)+ 一組 host API(決定 JS 看得到檔案、網路、process,還是只看得到 DOM)。一旦握住「引擎 + host API」這個骨架,一票本來要死背的問題會從同一個模型自然推出來——為什麼 Cloudflare Workers 沒有 fs、為什麼 React SSR 有兩個串流 API、Node / Deno / Bun / Edge 到底差在哪、哪些 code 換個環境就跑不動。
為什麼值得學
- 你早就在用 Node.js、只是沒意識到。
npm install、next dev、vite、tsc、eslint、prettier——前端工具鏈幾乎全都是「跑在 Node 上的程式」。即使你不寫 server,你的 dev loop 已經是 Node。 - SSR、邊緣部署、build tooling 都站在 runtime 上。 講「React SSR」其實是「React 在某個 JS runtime 上 render HTML」;講「邊緣部署」其實是「我的 code 跑在 Edge runtime 上」。runtime 是這些題的共同底層——略過這層,上層的選型討論會像在猜謎。
- Web 標準正在跨 runtime 收斂——這兩年才真正成立。 WinterTC(Ecma TC55,前身為 W3C 的 WinterCG)正在把 fetch、Streams、Web Crypto、
AbortController…這些原本是「瀏覽器專屬」的 API,訂成「所有 server-side runtime 都該有的最小共同集」,參與者包含 Node、Deno、Cloudflare、Vercel 等。寫越多 Web 標準 API,你的 code 越能跨 Node / Deno / Bun / Edge 跑。 - AI agent 時代,runtime 是 agent 跑工具的家。 Claude Code 自己是 Node 應用、MCP server 多半是 Node、agent 在你本機跑的 JS / TS 腳本也都靠這層。寫工具給 agent 用、或讀懂 agent 在做什麼,避不掉 runtime 這層認識。
定位與脈絡
一句話:什麼是 JavaScript runtime
JS 引擎 + 一組 host API。 引擎負責把 JS 文字 parse、編譯、執行;host API 是引擎之外、host 額外給 JS 用的東西。差別不在引擎——幾乎全在 host API:
| Runtime | 引擎 | Host API 給你什麼 |
|---|---|---|
| 瀏覽器 | V8 / JSC / SpiderMonkey | DOM、window、document、Web API(fetch、Streams…) |
| Node.js | V8 | fs、net、http、child_process、process、Buffer… |
| Deno | V8 | Web API 優先(fetch、Streams…)+ Deno.* |
| Bun | JavaScriptCore | Web API + Bun.* + 大部分 Node API(相容層) |
| Edge runtime | V8 isolates | Web API only——沒有 fs、沒有 child_process |
連你天天用的瀏覽器其實就是一種 JS runtime。所以「不在瀏覽器跑 JS」的真正意思是:換一組 host API——換成看得到檔案系統、網路 socket、process 的那組。
四個玩家
- Node.js(2009-) by Ryan Dahl。第一個把 V8 嫁接到 server 環境、開出整個 npm 生態的東西,現在還是業界預設。包袱:CommonJS(
require)與 ESM(import)並存的歷史傷疤、node_modules大、TS 一度只能靠外掛工具編。LTS 走 Node 20 → 22 → 24。 - Deno(2020-) by Ryan Dahl(同一個人,“10 things I regret about Node.js” 演講後做的「do-over」)。賣點:Web 標準 API 優先(fetch / Streams / Crypto 內建)、TypeScript 原生不必編、有 permission model(明確允許才能讀檔/連網)、單一執行檔。Deno 1 時代生態小、跑不了大量 npm 套件是最大痛點;Deno 2(2024/10) 起原生理解
package.json與node_modules、支援 npm workspace,可用性大幅改善。 - Bun(v1.0 於 2023/09) by Jarred Sumner。引擎換成 JavaScriptCore(Safari 那顆,不是 V8),主打「比 Node 快數倍」,並把 runtime / package manager / bundler / test runner 整套綁在一個 binary:你裝一個 Bun,dev loop 一條龍。代價:仍在補齊 Node 相容性、生態比 Node 小、
node_modules邊角偶有相容洞。 - Edge runtime——Cloudflare Workers、Vercel Edge Functions、Deno Deploy。這不是「再一個 Node」,是部署型態:把 V8 切成輕量 isolates 撒到全球節點、冷啟動毫秒級、按請求計費。代價:API 大幅縮減(沒有檔案系統、沒有 long-running process、執行時間和記憶體有上限),只能用 Web 標準 API 寫。
值得學的 host API(學 runtime,很大一部分就是學這些)
既然引擎大同小異、差別幾乎全在 host API,那**「學會一個 runtime」很大一部分就是「學會它的 host API」**。下面這幾類是最該熟的——而且它們天然分成「Node 專屬」與「Web 標準」兩半,剛好就是「能不能跨 runtime / 能不能上 Edge」的分界線:
| Host API | 做什麼 | 跨 runtime 可攜性 |
|---|---|---|
node:fs | 讀寫檔案系統 | Node/Deno/Bun ✓、Edge ✗ |
node:child_process | 跑別的程式(AI agent 在本機跑指令的底層) | Node/Deno/Bun ✓、Edge ✗ |
node:http / node:net | 開 HTTP server、TCP socket | Node ✓、Deno/Bun 走相容層、Edge ✗ |
node:process | env / argv / stdin·stdout / exit code | Node/Deno/Bun ✓、Edge 受限 |
Node Streams + Buffer | 串流大資料、pipe;二進位 | Node ✓;跨 runtime 建議改用下面的 Web 版 |
node:crypto | 雜湊、簽章、加解密 | Node/Deno/Bun ✓;跨 runtime 改用 Web Crypto |
fetch / Request / Response | 發/收 HTTP(Web 標準) | 全部 ✓ |
Web Streams(ReadableStream…) + Uint8Array | 串流、二進位(Web 標準) | 全部 ✓ |
Web Crypto(crypto.subtle) | 雜湊、簽章(Web 標準) | 全部 ✓ |
AbortController / AbortSignal | 取消請求 / 操作 / timeout(Web 標準) | 全部 ✓ |
讀法:上半部(node: 命名空間)就是 Edge 沒有、跨 runtime 容易出事的那群;下半部(Web 標準、瀏覽器也有)才是「換 runtime 不換 code」的本錢。 所以學 host API 和學「runtime 可攜性」根本是同一課的兩面——下一節就講為什麼這兩年值得特別押下半部。
Web 標準收斂——這兩年最重要的事
2024–2026 年最大的變化不是「誰最快」,而是所有非瀏覽器 runtime 同時把 fetch、Streams、Request / Response、AbortController、Web Crypto 等 Web API 補齊。這件事由 WinterTC 在推。為什麼這對你重要:
- 同一段 code 越用 Web 標準 API、越能跨 runtime 跑。 用
fetch而不是 Node 的http.request、用Request/Response而不是 Express 的req/res,你的 handler 在 Node、Deno、Bun、Edge 上行為一致。 - 這也解釋了 React SSR 為什麼有兩個串流 API。
renderToPipeableStream用 Node Stream,只能在 Node 跑;renderToReadableStream用 Web Streams,Edge / Deno / Bun 都能跑。兩個 API 不是冗餘、是 host 差異逼出來的。 - 越靠 Web 標準,越能「換 runtime 不換 code」。 想從 Node 遷到 Bun、從 Vercel 換到 Cloudflare,這條捷徑就在這。
常見混淆:runtime ≠ package manager ≠ bundler
這三層被 Bun 綁在一起後,初學者常分不清:
- Runtime——跑 JS 的引擎 + host:Node、Deno、Bun、Edge。
- Package manager——管
node_modules:npm、pnpm、yarn、bun。它們本身都是「跑在 Node 上的程式」(bun 例外,因為內建在 Bun 裡)。 - Bundler——把多檔 JS / TS 打成可發布的包:Vite、esbuild、Rolldown、Webpack。也都是跑在 Node(或 Bun)上。
Bun 特別之處是它一個 binary 把這三件事全做了。便利歸便利——但別把「bun install 比 npm install 快」當成「Bun runtime 比 Node runtime 快」,這是兩件事。
TypeScript 是「runtime 自己解」還是「工具鏈解」
「我寫 TS 能不能直接跑」這件事,現在分裂在 runtime 層:
- Node:v22.6 開始實驗性支援 type stripping、v22.18 / v23.6 起預設可以直接
node script.ts、v24.12 / v25.2 後標為 stable。但 Node 的做法只是把型別擦掉——不做型別檢查,你還是要靠tsc --noEmit才有檢查。 - Deno / Bun:原生跑
.ts,零配置。
附註:真要選,怎麼選
理解了上面那層,選型反而是小事——而且多數時候根本不用選:沒有特殊理由就用 Node,最大生態、最多職缺、你會踩的坑都有人踩過、雲端與教學文件預設都是它。真正會把你推離 Node 的是少數情境:
- 想要 TS / runtime / PM / test 一條龍、願承擔較新框架風險 → Bun。 比 Node 快不是錯(多數 micro-benchmark 確實如此),但「快」幾乎從來不是選型最痛的點;真正要賭的是相容性——你某個 npm 相依(或它依賴的某個 Node API)在 Bun 上行為跟 Node 不完全一樣,而你往往是等它壞掉才發現。生態夠主流的套件多半沒事,吃 Node 底層或冷門原生模組的才是地雷。
- 要 Web 標準乾淨派、想要 sandbox / 權限模型 → Deno。 Deno 2 後可用度大幅改善,但生態仍小一圈,主要看你受不受得了「比 Node 邊緣套件少」。
- 要全球 low-latency、低冷啟動 → Edge runtime。 共通前提是它走 Web-only 的 isolate 模型:通常沒有真正的檔案系統、不適合長跑 process。但確切的限制和計費方式(CPU/wall time 上限、記憶體、可用的 API、價格、分不分方案)每家差很多——別記死任何數字,挑定哪家後直接讀那家的文件,例如 Cloudflare Workers limits、Vercel Functions limits、Deno Deploy。
怎麼入手
runtime 是個底層概念,與其塞進一個大 session 一次做完,不如拆成幾個各自獨立的 agent session,順序也有意義:先讀懂概念 → 把 runtime 裝好設好 → 再用幾個真實小專案,分頭把最常用的 host API 各摸一遍。下面每段 code block 都是一個獨立 session 的 prompt,開新對話、照順序貼給 agent。
第 1 步:把「runtime 這一層」讀懂(一個 session)
這篇給了「JS 引擎 + host API」的骨架,建議至少再從外部把同一個概念走一次才扎實。要找的是針對「runtime 這層」的資源——不是泛論 JS 語言、不是 event loop / async 這些相關但更上層的主題。找資料時作者的權威性比「講得淺白」更重要:簡化沒問題,要是「真懂之後的簡化」,不是含糊的近似。對「JS 寫得熟、但還沒蹲過 server-side 底層」的人來說,下面兩篇權威入門剛好互補——一個是中文 narrative、一個是官方參考:
- Huli — 從「為什麼不能用這個函式」談執行環境(runtime)(資深前端工程師 Huli;亦有英文版)。從「在 browser 跑
btoa沒事、在 Node 卻報btoa is not defined」這個具體故事切入,順勢走進「JS 核心、browser host、Node host」的分層——是中文圈最好的入門 narrative。 - MDN — JavaScript execution model(Mozilla 官方參考;Mozilla 自己維護 SpiderMonkey 引擎)。開頭第一段就用本文同一組詞名稱對齊:「JavaScript execution requires the cooperation of two pieces of software: the JavaScript engine and the host environment.」當英文錨點兼字彙對齊。
想再深一格:Cloudflare — How Workers works,從 V8 isolate 角度補一筆「為什麼 Edge 也是一種 runtime、但跟 Node 不同層」。
👉 複製這段 prompt 開一個 session:
我想真正理解「JavaScript runtime」這層——尤其「JS 引擎 + host API」的分層,以及為什麼 Node / Deno / Bun / Edge 是同一個概念的不同實作。請帶我做這件事,不確定就先問我:1. 讓我自己先讀下面兩篇權威入門(請不要憑記憶複述、不要先總結,我讀完再來討論):- Huli: https://blog.huli.tw/2022/02/09/javascript-runtime/- MDN: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Execution_model如果你判斷還需要 1–2 個權威、不誤導、針對「runtime 這層」(不是 event loop /async)的資源來補強,列出來並各附「為什麼可信」一句話——作者要在 runtime 領域有verifiable 的深度(runtime / 引擎核心貢獻者、官方標準文件、研討會講者之類),別給內容農場或 AI 拼湊的文章。2. 我讀完後,用一段話跟我對齊重點:runtime = JS 引擎 + host API、為什麼差別幾乎全在 host API、這個分層怎麼直接解釋「同一段 code 在某個 runtime 跑得了、在另一個跑不了」——例如 Cloudflare Workers 沒有 fs、Node 與 Bun 的 npm 邊角相容性、React SSR 為什麼有兩個串流 API。3. 出幾題問我(「V8 ≠ Node」是什麼意思、Bun 換 JSC 對 Node 相容的影響、為什麼 Edge的限制不能用「再加些 Node API 就好」解決),我答完後依官方文件或上面列的權威資源(不要憑你的記憶)幫我訂正。若這個 session 過長、方向變過幾次,或你判斷 context 壓縮可能影響你的理解,提醒我:換新 session 通常會更穩、更準。提醒時一併把目前狀態、決策、待辦、關鍵檔案路徑整理成一份專案內的交接 Markdown 給下一輪 agent 用;沒有合適專案資料夾或任務很輕,就直接把摘要貼進新對話。不要頻繁提醒,只在真的會影響品質時提。
第 2 步:把 runtime 裝好、設好(一個 session)
概念有了,先把環境弄對再寫 code。runtime 的裝法這幾年一直在變(版本管理器、LTS 政策、原生 TS 支援陸續變動),別照舊文章硬裝——讓 agent 查當下官方與社群最推薦的做法,並先看你機器上已經有什麼、有沒有打架。
👉 複製這段 prompt 開一個 session:
我要把一個 JS runtime(Node / Deno / Bun,請先幫我評估或問我該用哪個)裝好、設定好,當作後面練習的環境。請依「當下最新」的官方與社群建議帶我,不要照你記憶中的舊裝法,不確定就問我:1. 先看我機器現況:幫我跑並解讀 `node -v`、`which -a node`、有沒有裝 nvm / fnm / Volta、有沒有 Homebrew 裝的 node 跟版本管理器打架、PATH 有沒有問題。2. 查目前官方 + 社群最推薦的裝法並給我推薦與理由:例如 Node 該用版本管理器(nvm / fnm / Volta)還是官方安裝檔、該裝哪個 LTS、要不要開原生 TS 執行;若選 Deno / Bun 則各自的官方裝法。3. 帶我裝好、設好一個乾淨基準(版本管理器、預設版本、確認能不能直接 `node script.ts`),每個設定都說明在幹嘛。4. 出兩題問我(為什麼用版本管理器而不是系統全域裝一個、LTS 跟 Current 差在哪),依官方文件訂正。若這個 session 過長、方向變過幾次,或你判斷 context 壓縮可能影響你的理解,提醒我:換新 session 通常會更穩、更準。提醒時一併把目前狀態、決策、待辦、關鍵檔案路徑整理成一份專案內的交接 Markdown 給下一輪 agent 用;沒有合適專案資料夾或任務很輕,就直接把摘要貼進新對話。不要頻繁提醒,只在真的會影響品質時提。
第 3 步:用真實小專案,分頭把常用 host API 摸過(每個 POC 一個 session)
這步是重點,規劃方式刻意反過來想:先列出「初學者非懂不可的 host API 有哪幾群」,再為每群挑一個最能讓人體會它在解什麼的真實小專案。下表就是這樣排出來的——每個 POC 是一個獨立 session,挑你還不熟的那群做就好,不必全做:
| 真實小專案(POC) | 會摸到的 host API | 它逼你搞懂的核心觀念 |
|---|---|---|
| 檔案處理器 / 迷你 bundler(讀一堆檔 → 轉換 → 輸出) | fs / fs/promises、path、Node Streams | 檔案系統、同步 vs 非同步(readFileSync 為何卡死 server)、用串流處理大檔 |
| Express 式 HTTP 伺服器 + 路由 | node:http(或 Bun.serve)、Request / Response | server 怎麼收發請求、路由、handler 天生非同步 |
| 網頁爬蟲(web scraper) | 全域 fetch、AbortController、Web Streams、Web Crypto | 對外網路呼叫、並發控制(Promise.all vs 逐一 await)、timeout 取消、用 hash 去重 |
| 包一個指令的 CLI dev tool(lint / format runner 之類) | process(argv / env / stdio)、child_process.spawn | 程式怎麼讀參數與環境、跑子程序並即時串流它的輸出——AI agent 在本機跑指令的底層 |
下面是四個 POC 的完整 prompt。每個是一個獨立的 agent session,挑你想做的那個複製整段去開新對話;不必全做。
POC ① 檔案處理器 / 迷你 bundler
👉 複製這段 prompt 開一個 session:
我想透過做一個最小的「檔案處理器 / 迷你 bundler」——讀進一個資料夾的多個 .ts 檔、做簡單轉換(譬如把 import 接成單一字串)、寫出一個合併檔——來搞懂 JS runtime 裡跟「檔案系統」相關的 host API:fs / fs/promises、path、以及 Node Streams。不確定就先問我:1. 入門:用一段話講這幾個 host API 各自在解什麼問題;尤其把「為什麼 server 端不該用readFileSync」(同步呼叫卡住 event loop、整個 process 停在這裡)這件事講清楚。2. 動手 v1:帶我從零做到能跑——讀資料夾、逐檔處理、寫出 bundle。第一版**故意用同步API**(readFileSync / writeFileSync),先跑通。3. 進階:把同步版改成非同步(fs/promises)。然後用一個「會卡」情境讓我親眼比較兩者差別——例如在同支腳本裡也起一個小 HTTP server,跑同步版時 server 對請求停止回應、跑非同步版時不會。4. 大檔:當輸入是數百 MB 的單檔,readFile 把整檔讀進記憶體都不划算——帶我把讀寫改成Node Streams(createReadStream / createWriteStream + pipe),講清楚 stream 解決的是哪個問題、什麼時候根本不用 stream。5. 可攜性:指出這支工具哪些段只能在 Node 跑、為什麼 Edge runtime 不可能跑——並用這個例子說明「沒有 fs」這條限制不是 API 還沒補,是 sandbox 模型決定的。6. 檢查:出幾題問我(readFileSync 為什麼在 server 是地雷、stream 解決的是哪個問題、path.join 為什麼不該用字串相加代替),我答完後依 Node 官方文件(不要憑你的記憶)幫我訂正。若這個 session 過長、方向變過幾次,或你判斷 context 壓縮可能影響你的理解,提醒我:換新 session 通常會更穩、更準。提醒時一併把目前狀態、決策、待辦、關鍵檔案路徑整理成一份專案內的交接 Markdown 給下一輪 agent 用;沒有合適專案資料夾或任務很輕,就直接把摘要貼進新對話。不要頻繁提醒,只在真的會影響品質時提。
POC ② Express 式 HTTP 伺服器 + 路由
👉 複製這段 prompt 開一個 session:
我想透過從零做一個最小的「Express 式 HTTP 伺服器 + 路由」(**不用 Express、不用任何framework**)——支援幾條路由(GET /、GET /api/items、POST /api/items)並回 JSON——來搞懂 JS runtime 裡跟「server 收發網路請求」相關的 host API。不確定就先問我:1. 入門:用一段話講一個 HTTP server 在 runtime 裡到底在做什麼——listen socket、接連線、parse 出 request、把 response 寫回去——以及為什麼 handler 幾乎都是 async。2. 動手 A:用 Node 的 `node:http` 從零寫一遍。自己 parse URL、用 if / switch 寫一個小路由表、用 JSON.stringify 回 response,讓我看到「沒有框架時 handler 長什麼樣」。3. 動手 B:把同一支 server 改寫成 Web 標準的「fetch handler」形式——一個(request: Request) => Promise<Response>——部到 Bun(用 Bun.serve)或 Deno(用 Deno.serve)跑。4. 對照:並排告訴我 (A) `node:http` 是 Node 專屬 host API;(B) `Request` / `Response`是 Web 標準、跨 runtime 可攜;後者也是 Cloudflare Workers / Vercel Edge / Next.jsRoute Handlers 的同一個模型——一旦寫成這形狀,code 幾乎不用改就能上 Edge。5. 親手感受單執行緒:在 handler 裡故意塞一個 5 秒的同步 CPU 迴圈,用 ab / curl 連發幾個請求看會發生什麼——讓我親眼看到「一個 handler 卡住 = 整個 server 卡住」。6. 檢查:出幾題問我(為什麼 `node:http` 的 server 不能直接上 Cloudflare Workers、Request / Response 的好處到底是什麼、handler 為什麼幾乎都是 async),我答完後依Node、Bun、MDN 官方文件(不要憑你的記憶)幫我訂正。若這個 session 過長、方向變過幾次,或你判斷 context 壓縮可能影響你的理解,提醒我:換新 session 通常會更穩、更準。提醒時一併把目前狀態、決策、待辦、關鍵檔案路徑整理成一份專案內的交接 Markdown 給下一輪 agent 用;沒有合適專案資料夾或任務很輕,就直接把摘要貼進新對話。不要頻繁提醒,只在真的會影響品質時提。
POC ③ 網頁爬蟲
👉 複製這段 prompt 開一個 session:
我想透過做一個最小的「網頁爬蟲」——給一份 URL 清單,逐一抓回 HTML、抽出 <title>、存成 JSON——來搞懂 JS runtime 裡跟「對外網路呼叫」相關的 Web 標準 host API:全域 fetch、AbortController、Web Crypto。不確定就先問我:1. 入門:用一段話講為什麼這幾個 API 是 Web 標準(瀏覽器、Node、Deno、Bun、Edge 都有),以及「同步 fetch」為什麼根本不存在——能用 fetch 是因為 runtime 給了 event loop +promise 排程。2. 動手 v1:用最樸素的方式做出來——一個 for loop、每個 URL 逐一 await fetch,抓 response.text()、用 regex 抽 <title>。先跑通。3. 並發控制:把所有 fetch 改成一次發出(Promise.all),我跑一次告訴你結果;然後帶我加「同時最多 N 個」的並發上限,並解釋「all at once」、「逐一」、「有上限的並發」這三種在真實世界各會踩到什麼問題(rate limit、總時長、記憶體 / file descriptor 爆掉)。4. Timeout / 取消:每個 fetch 加 3 秒 timeout,**親手用 AbortController + setTimeout寫一次**(不要包好的 library),讓我看清 AbortController 是泛用的取消機制、不是fetch 的選項。5. 去重:用 Web Crypto 的 `crypto.subtle.digest()` 對每頁內容算 SHA-256,把「不同網址但內容一樣」的頁面去重;順便讓我認識 Web Crypto 跟 Node 的 node:crypto 是兩個不同的 API、什麼時候該用哪個。6. 可攜性實驗:整支爬蟲幾乎全用 Web 標準。帶我把它原封不動丟到 Bun 跑、再部到一個Cloudflare Worker 上跑——讓我親眼看到「code 不改、跨 runtime 直接跑」。7. 檢查:出幾題問我(為什麼沒有同步 fetch、Promise.all 的失敗模式、AbortController 跟timeout 的關係、Web Crypto 為什麼是 async),我答完後依 MDN、Node、CloudflareWorkers 官方文件(不要憑你的記憶)幫我訂正。若這個 session 過長、方向變過幾次,或你判斷 context 壓縮可能影響你的理解,提醒我:換新 session 通常會更穩、更準。提醒時一併把目前狀態、決策、待辦、關鍵檔案路徑整理成一份專案內的交接 Markdown 給下一輪 agent 用;沒有合適專案資料夾或任務很輕,就直接把摘要貼進新對話。不要頻繁提醒,只在真的會影響品質時提。
POC ④ 包一個指令的 CLI dev tool
👉 複製這段 prompt 開一個 session:
我想透過做一個最小的「CLI dev tool」——譬如包一個 typecheck runner,跑`mytool ./src` 會找這個資料夾的 tsconfig.json、起一個 child process 跑 `tsc --noEmit`、把錯誤即時印出來——來搞懂 JS runtime 裡跟「程式怎麼讀參數、怎麼跑別的程式」相關的host API:process、child_process.spawn、stdio 的串流。**這正是 AI agent 在本機跑指令的縮影。** 不確定就先問我:1. 入門:用一段話講 process 物件給了你什麼(argv / env / cwd / stdin / stdout / stderr /exit code),以及 child_process 裡 spawn / exec / execSync 的差別——為什麼幾乎永遠該用spawn(不要 buffer 全部輸出)。2. 動手:用 process.argv 解參數、用 process.env 讀環境變數、用 child_process.spawn 起一個 child 跑 `npx tsc --noEmit`,並把 child 的 stdout / stderr **即時** pipe 到自己的process.stdout / process.stderr——讓使用者看到 tsc 的輸出一行一行冒出來,不是等整個跑完一次吐。3. Timeout / 取消:加一個 --timeout 參數,用 AbortController(spawn 接受 signal)讓子程序超時就被中止;child 結束後依它的 exit code 決定自己的 exit code。4. 親手感受 buffer 為什麼壞:拿一個會輸出超大量 stdout 的指令(譬如一個故意印百萬行的腳本)打進去,先跑串流版;接著請你解釋如果改用 execSync 或 exec、為什麼這支工具會把記憶體吃光甚至 OOM。5. AI agent 視角:請告訴我這個結構(讀 args → 起 child → 串流 stdout → 控 timeout →處理 exit code)正是 Claude Code / MCP server / agent 在本機跑指令的同一個 host API組合,也是 Edge runtime「永遠」不可能跑這種工具的原因——不是 API 沒補,是 sandbox模型不允許 spawn 子程序。6. 檢查:出幾題問我(spawn vs exec 為什麼選 spawn、為什麼 child 的 stdout 要 pipe 不要等全部 buffer 完、process.exit() 為什麼可能截斷還沒寫完的 stdout),我答完後依 Node官方文件(不要憑你的記憶)幫我訂正。若這個 session 過長、方向變過幾次,或你判斷 context 壓縮可能影響你的理解,提醒我:換新 session 通常會更穩、更準。提醒時一併把目前狀態、決策、待辦、關鍵檔案路徑整理成一份專案內的交接 Markdown 給下一輪 agent 用;沒有合適專案資料夾或任務很輕,就直接把摘要貼進新對話。不要頻繁提醒,只在真的會影響品質時提。
延伸主題
- React + TypeScript + SSR:JS runtime 最常被用到的場景。SSR 的整個討論——從 raw Vite 接 SSR、到 Next.js 的快取模型、到 Edge 部署的限制——其實都建在這篇的「runtime 是什麼」之上。
- Web app architecture:runtime 只是 production stack 裡的一個部分,這頁把其他部分(DNS、CDN、LB、DB、object storage、queue、secrets、observability)鋪開——一個人怎麼把一個 web app 端到端送上雲。讀完 runtime、想知道「我寫的這段 code 上線那天還會跟誰共處」,這頁是地圖。
- 回頭補「引擎」那半(選讀):Lydia Hallie — JavaScript Visualized: the JavaScript Engine(知名 JS 視覺化教學作者,以 Keynote 手繪動畫拆解 JS internals)。本篇刻意把引擎當成大同小異的黑盒、火力全押在 host API;但若「引擎到底在 parse、compile、執行什麼,heap、call stack 是什麼」對你其實還是一團模糊,這篇圖解是最好的補課。(偏好影片、想把 JS 底層一次看過的話,她的個人頻道 @theavocoder 有整個 JavaScript Visualized 視覺化系列——execution context、closures、event loop、promise 等;範圍比本篇的「引擎 + host API」更廣,偏 JS 語言內部與 async 排程。)