TypeScript 6 升級清單:先補 tsconfig 隱藏預設,再處理 deprecated 設定
TypeScript 6.0 的升級痛點,通常不是某一個型別突然消失,而是以前由編譯器「猜」出來的設定現在變得更明確。升級後常見的症狀包括 Node 全域型別不見、輸出路徑多了一層 src、舊的 moduleResolution 開始警告,或原本沒有報錯的 side-effect import 被指出找不到檔案。
直接答案是:先把 types、rootDir、module 與 moduleResolution 寫進 tsconfig,再用 tsc --showConfig 比對實際設定;ignoreDeprecations: "6.0" 只拿來暫時清點債務,不是升級完成的證明。
先理解 TypeScript 6 改變的是哪些預設
TypeScript 6.0 的 release notes 把升級分成兩類:一類是「以前依賴隱藏預設,現在要明確指定」;另一類是 deprecated 選項在未來版本會消失。先分開處理,排查時才不會把設定錯誤和程式碼錯誤混在一起。
| 變更 | 升級後的實際影響 | 建議動作 |
|---|---|---|
types 預設改為空陣列 | process、Buffer 或測試 runner 的全域型別可能消失 | 在應用程式或測試專案明確列出需要的 types |
rootDir 的推導方式改變 | outDir 可能多出 src/,或 declaration 路徑改變 | 依專案邊界明確設定 rootDir |
strict 預設為 true | 原本隱含放過的 null、implicit any 開始報錯 | 先分類錯誤,再逐批修正;不要直接關掉 strict |
module 預設為 esnext | 依賴 CommonJS 或特定 bundler 的專案可能出現解析差異 | 依執行環境選 node16、nodenext 或 esnext |
noUncheckedSideEffectImports 預設開啟 | import './style.css' 或不存在的副作用 import 會被檢查 | 確認檔案真的存在,必要時補上 ambient module 宣告 |
moduleResolution: 'node' 等舊模式 deprecated | 舊設定仍可能暫時能跑,但會累積升級成本 | 依 runtime 或 bundler 改成明確的新解析策略 |
這份表是「先找差異」用的索引,不代表每個專案都要採用同一組值。尤其 library、Node application、Vite/Astro application 的 module 邊界不同,不能直接複製別人的完整 tsconfig。
第一步:把編譯器真正採用的設定印出來
不要先看 IDE 的紅線。先在升級前和升級後各執行一次 --showConfig,把輸出存成不進版控的暫存檔,才能知道差異來自 TypeScript 版本、extends 鏈,還是工作區的另一份設定。
pnpm exec tsc --showConfig -p tsconfig.json > /tmp/tsconfig-before-or-after.jsonpnpm exec tsc --noEmit -p tsconfig.json如果專案有多份設定,例如 tsconfig.app.json、tsconfig.node.json 或測試專用設定,必須對每一份執行一次。tsc 不帶 -p 時,實際載入的檔案可能不是你正在看的那一份;CI 也可能使用另一個工作目錄。
升級檢查時至少記錄以下欄位:
compilerOptions.types、rootDir、outDir。module、moduleResolution、target與lib。strict、noUncheckedSideEffectImports和allowJs。extends展開後是否把 base config 的值覆寫掉。
第二步:補上 types 和 rootDir 的專案邊界
TypeScript 6 將 types 的預設改成 [],這不等於「不載入任何套件」,而是只讓你明確列出的 @types 套件提供全域型別。Node.js 專案常見的寫法是:
{ "compilerOptions": { "types": ["node"], "rootDir": "./src", "outDir": "./dist" }}測試檔案若需要 Vitest、Jest 或 Playwright 的全域型別,建議在測試專用 tsconfig 加入對應值,不要為了讓 production source 通過而把所有測試型別灌進主設定:
{ "extends": "./tsconfig.json", "compilerOptions": { "types": ["node", "vitest/globals"] }, "include": ["src", "tests"]}rootDir 則是輸出結構的邊界。若以前讓編譯器依照所有輸入檔案自行推導,新增 scripts/ 或測試資料夾後,輸出可能被迫反映更高層目錄。把應用程式的 src 明確設為 rootDir 後,若有跨出 src 的檔案,錯誤反而能提醒你重新決定 include 邊界,而不是默默改變 dist 結構。
第三步:依執行環境選 module 與 moduleResolution
不要把 module 和 moduleResolution 當成可以各自隨意切換的兩個開關。它們要和最終執行者一致:
| 專案型態 | 常見起點 | 升級時要驗證 |
|---|---|---|
| Vite、Astro、Rolldown 等 bundler | module: "esnext"、moduleResolution: "bundler" | alias、條件 exports、CSS/asset import 和 production bundle |
| 直接由 Node.js 執行的 ESM package | module: "nodenext"、moduleResolution: "nodenext" | package.json 的 type、副檔名、相對 import 是否帶副檔名 |
| 需要 Node 相容輸出的 library | 依發布格式選 node16/nodenext | declaration、雙格式 exports 與 consumer 的解析結果 |
moduleResolution: "node"(也常標成 node10)的問題不是今天一定不能編譯,而是它代表舊的解析模型。先確認 package exports 和實際部署方式,再一次把 module 與 resolution 換成相配的組合;不要只為了消掉警告,把 moduleResolution 單獨改成另一個值。
若專案同時有 source build 和 test runner,建議把它們的設定拆開,並在 CI 中分別跑 type-check、unit test 與 production build。這比讓一個超大的 tsconfig 同時迎合所有工具更容易維護。
ignoreDeprecations 只能用來爭取時間
升級過程中可能先看到一批 deprecated 設定。可以暫時加入:
{ "compilerOptions": { "ignoreDeprecations": "6.0" }}但要把它視為有期限的 migration 開關,並在 issue 或 upgrade checklist 記錄要移除的欄位。它只會隱藏部分棄用警告,不會恢復舊的預設,也不會修好錯誤的 module 邊界。若你加了 ignoreDeprecations 後 build 通過,仍要繼續檢查 dist 結構、執行時 import、declaration 和測試型別。
比較可靠的順序是:
- 先用
tsc --showConfig找出實際生效的 deprecated 選項。 - 確認這個選項是被自己的 tsconfig 還是外部 base config 帶入。
- 以目標 runtime 或 bundler 的新解析策略取代它。
- 跑完 production build 和 consumer smoke test 後,移除
ignoreDeprecations。
noUncheckedSideEffectImports 報錯時不要直接加萬用宣告
前端專案常見的錯誤長這樣:
import './global.css'import './register-polyfill'如果路徑真的拼錯,新的檢查很有價值;先確認檔案名稱、大小寫和 bundler alias。若 import 的是 bundler 處理的非 TypeScript 資產,才考慮在型別宣告中明確描述它,而不是用一個過寬的 declare module '*' 把所有拼字錯誤一起隱藏:
declare module '*.css'declare module '*.svg' { const url: string export default url}宣告應該貼近真實資產格式;如果 SVG 在專案中其實會被 loader 轉成 component,就應該依 loader 的型別補充,而不是假設它永遠是 URL。
升級驗收:不要只看 tsc 的 exit code
完成設定調整後,至少做四層驗證:
# 1. 型別與設定pnpm exec tsc --showConfig -p tsconfig.jsonpnpm exec tsc --noEmit -p tsconfig.json
# 2. 產出與路徑pnpm build
# 3. 執行時 module smoke testnode dist/server.js
# 4. 套件 consumer/測試pnpm testnode dist/server.js 只是示意,請換成專案真正的啟動入口。library 則應在乾淨的暫存 consumer 中安裝 tarball,測試 ESM、CommonJS 和 declaration;前端 bundler 應確認 production bundle 可以載入 CSS、圖片和 dynamic import。
如果這是 Astro、Vite 或其他整合框架的升級,可以把本篇的 tsconfig 檢查放進框架升級清單一起跑,例如Astro 7 升級前的 Vite 8 與建置檢查。重點不是追求一份「全域通用」設定,而是確保每個執行邊界都有明確 owner。
結論:先明確化,再移除過渡設定
TypeScript 6 升級最穩定的策略,是先把編譯器以前代替你猜的部分寫出來:types 管全域型別、rootDir 管 source 邊界、module 與 moduleResolution 管執行環境,ignoreDeprecations 只暫時包住還沒清掉的舊設定。最後用設定展開、production build、runtime smoke test 和 consumer 測試一起驗收,才知道你修的是 warning,還是實際的輸出與執行行為。
常見問題
Q: types 改成 [] 後,為什麼 process 找不到?
A: TypeScript 6 不再從預設清單自動載入全域型別。Node.js 專案在可用的 tsconfig 中加入 "types": ["node"];測試專案再依需要加入測試 runner 的型別。不要把所有 @types 都塞進 production 設定,否則型別邊界會失去意義。
Q: rootDir 一定要設成 src 嗎?
A: 不一定。rootDir 應該是你希望輸出路徑相對於哪個 source root 的明確邊界。應用程式常見是 src,但 library、monorepo package 或含有 scripts 的專案可能需要另一個值;重點是不要讓新增輸入檔案偷偷改變 dist 結構。
Q: 可以一直保留 ignoreDeprecations: "6.0" 嗎?
A: 不建議。它適合當短期遷移護欄,不會恢復舊預設,也不會替你驗證 module、輸出路徑或 side-effect import。請把移除它列成明確工作項目,並在 production build 與 consumer smoke test 通過後刪除。
參考資料:
回報錯字、失效連結,或告訴我你想看的延伸主題。