2087 字
10 分鐘

TypeScript 6 升級清單:先補 tsconfig 隱藏預設,再處理 deprecated 設定

TypeScript 6.0 的升級痛點,通常不是某一個型別突然消失,而是以前由編譯器「猜」出來的設定現在變得更明確。升級後常見的症狀包括 Node 全域型別不見、輸出路徑多了一層 src、舊的 moduleResolution 開始警告,或原本沒有報錯的 side-effect import 被指出找不到檔案。

直接答案是:先把 typesrootDirmodulemoduleResolution 寫進 tsconfig,再用 tsc --showConfig 比對實際設定;ignoreDeprecations: "6.0" 只拿來暫時清點債務,不是升級完成的證明。

先理解 TypeScript 6 改變的是哪些預設#

TypeScript 6.0 的 release notes 把升級分成兩類:一類是「以前依賴隱藏預設,現在要明確指定」;另一類是 deprecated 選項在未來版本會消失。先分開處理,排查時才不會把設定錯誤和程式碼錯誤混在一起。

變更升級後的實際影響建議動作
types 預設改為空陣列processBuffer 或測試 runner 的全域型別可能消失在應用程式或測試專案明確列出需要的 types
rootDir 的推導方式改變outDir 可能多出 src/,或 declaration 路徑改變依專案邊界明確設定 rootDir
strict 預設為 true原本隱含放過的 null、implicit any 開始報錯先分類錯誤,再逐批修正;不要直接關掉 strict
module 預設為 esnext依賴 CommonJS 或特定 bundler 的專案可能出現解析差異依執行環境選 node16nodenextesnext
noUncheckedSideEffectImports 預設開啟import './style.css' 或不存在的副作用 import 會被檢查確認檔案真的存在,必要時補上 ambient module 宣告
moduleResolution: 'node' 等舊模式 deprecated舊設定仍可能暫時能跑,但會累積升級成本依 runtime 或 bundler 改成明確的新解析策略

這份表是「先找差異」用的索引,不代表每個專案都要採用同一組值。尤其 library、Node application、Vite/Astro application 的 module 邊界不同,不能直接複製別人的完整 tsconfig

第一步:把編譯器真正採用的設定印出來#

不要先看 IDE 的紅線。先在升級前和升級後各執行一次 --showConfig,把輸出存成不進版控的暫存檔,才能知道差異來自 TypeScript 版本、extends 鏈,還是工作區的另一份設定。

Terminal window
pnpm exec tsc --showConfig -p tsconfig.json > /tmp/tsconfig-before-or-after.json
pnpm exec tsc --noEmit -p tsconfig.json

如果專案有多份設定,例如 tsconfig.app.jsontsconfig.node.json 或測試專用設定,必須對每一份執行一次。tsc 不帶 -p 時,實際載入的檔案可能不是你正在看的那一份;CI 也可能使用另一個工作目錄。

升級檢查時至少記錄以下欄位:

  • compilerOptions.typesrootDiroutDir
  • modulemoduleResolutiontargetlib
  • strictnoUncheckedSideEffectImportsallowJs
  • extends 展開後是否把 base config 的值覆寫掉。

第二步:補上 typesrootDir 的專案邊界#

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#

不要把 modulemoduleResolution 當成可以各自隨意切換的兩個開關。它們要和最終執行者一致:

專案型態常見起點升級時要驗證
Vite、Astro、Rolldown 等 bundlermodule: "esnext"moduleResolution: "bundler"alias、條件 exports、CSS/asset import 和 production bundle
直接由 Node.js 執行的 ESM packagemodule: "nodenext"moduleResolution: "nodenext"package.jsontype、副檔名、相對 import 是否帶副檔名
需要 Node 相容輸出的 library依發布格式選 node16nodenextdeclaration、雙格式 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 和測試型別。

比較可靠的順序是:

  1. 先用 tsc --showConfig 找出實際生效的 deprecated 選項。
  2. 確認這個選項是被自己的 tsconfig 還是外部 base config 帶入。
  3. 以目標 runtime 或 bundler 的新解析策略取代它。
  4. 跑完 production build 和 consumer smoke test 後,移除 ignoreDeprecations

noUncheckedSideEffectImports 報錯時不要直接加萬用宣告#

前端專案常見的錯誤長這樣:

import './global.css'
import './register-polyfill'

如果路徑真的拼錯,新的檢查很有價值;先確認檔案名稱、大小寫和 bundler alias。若 import 的是 bundler 處理的非 TypeScript 資產,才考慮在型別宣告中明確描述它,而不是用一個過寬的 declare module '*' 把所有拼字錯誤一起隱藏:

src/types/assets.d.ts
declare module '*.css'
declare module '*.svg' {
const url: string
export default url
}

宣告應該貼近真實資產格式;如果 SVG 在專案中其實會被 loader 轉成 component,就應該依 loader 的型別補充,而不是假設它永遠是 URL。

升級驗收:不要只看 tsc 的 exit code#

完成設定調整後,至少做四層驗證:

Terminal window
# 1. 型別與設定
pnpm exec tsc --showConfig -p tsconfig.json
pnpm exec tsc --noEmit -p tsconfig.json
# 2. 產出與路徑
pnpm build
# 3. 執行時 module smoke test
node dist/server.js
# 4. 套件 consumer/測試
pnpm test

node 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 邊界、modulemoduleResolution 管執行環境,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 通過後刪除。

參考資料:

TypeScript 6.0 Release Notes

TypeScript TSConfig Reference

TypeScript 6 升級清單:先補 tsconfig 隱藏預設,再處理 deprecated 設定
https://laplusda.com/posts/typescript-6-upgrade-checklist/
作者
Zero
發佈於
2026-09-16
許可協議
CC BY-NC-SA 4.0
這篇文章有幫助嗎?

回報錯字、失效連結,或告訴我你想看的延伸主題。