TypeScript 課程 – 編譯與設定(Compiler Configuration)
主題:tsconfig.json 結構
簡介
在使用 TypeScript 開發任何規模的專案時,tsconfig.json 是唯一需要關注的檔案,它告訴編譯器(tsc)該如何把 .ts、.tsx 轉換成 JavaScript。沒有正確的設定,程式碼可能會出現型別錯誤、編譯速度變慢,甚至在生產環境中產生不可預期的行為。
本單元將從 結構、常見選項、實務範例 逐層剖析 tsconfig.json,協助你在專案啟動或升級時,快速建立符合團隊需求的設定檔。
核心概念
1. 基本檔案結構
{
"compilerOptions": { /* 編譯器選項 */ },
"include": [ "src/**/*.ts" ],
"exclude": [ "node_modules", "dist" ],
"files": [ "global.d.ts" ]
}
| 欄位 | 作用 | 常見使用情境 |
|---|---|---|
compilerOptions |
設定編譯器行為,如目標語言、模組系統等。 | 大多數專案的核心設定。 |
include |
指定要編譯的檔案或目錄,支援 glob pattern。 | 僅編譯 src 資料夾。 |
exclude |
排除不需要編譯的路徑,預設會排除 node_modules。 |
避免編譯測試產出或第三方套件。 |
files |
明確列出單一檔案,會覆寫 include/exclude 的行為。 |
加入全局宣告檔或特殊腳本。 |
2. compilerOptions 常用屬性
| 屬性 | 說明 | 範例 |
|---|---|---|
target |
編譯後的 ECMAScript 版本。 (es5、es6、esnext) |
"target": "es2019" |
module |
產出模組系統。 (commonjs、esnext、amd) |
"module": "commonjs" |
strict |
開啟所有嚴格型別檢查。等同於啟用多個子選項。 | "strict": true |
noImplicitAny |
禁止隱式 any。 |
"noImplicitAny": true |
sourceMap |
產生 .map 檔以支援除錯。 |
"sourceMap": true |
outDir |
指定輸出目錄。 | "outDir": "./dist" |
rootDir |
設定根目錄,決定相對路徑的計算方式。 | "rootDir": "./src" |
paths & baseUrl |
設定別名 import。 | "baseUrl": "./", "paths": { "@utils/*": ["src/utils/*"] } |
esModuleInterop |
允許 import default 從 CommonJS 模組匯入。 |
"esModuleInterop": true |
incremental |
啟用增量編譯,加速大型專案。 | "incremental": true |
composite |
用於 Project References,必須與 declaration 同時開啟。 |
"composite": true |
3. 範例一:最小可用的 tsconfig.json
{
"compilerOptions": {
"target": "es6",
"module": "commonjs",
"strict": true,
"outDir": "dist"
},
"include": ["src"]
}
說明:此設定適合小型 Node.js 專案,只需要把
src目錄下的檔案編譯成 ES6、使用 CommonJS 模組,並將結果輸出至dist。
4. 範例二:使用別名與路徑映射
{
"compilerOptions": {
"target": "es2020",
"module": "esnext",
"baseUrl": ".",
"paths": {
"@components/*": ["src/components/*"],
"@models": ["src/models/index.ts"]
},
"moduleResolution": "node",
"esModuleInterop": true,
"sourceMap": true,
"outDir": "build"
},
"include": ["src/**/*.ts", "src/**/*.tsx"]
}
// src/main.ts
import { Button } from '@components/ui/Button';
import { User } from '@models';
console.log(Button, User);
重點:透過
baseUrl+paths,可以在程式碼中使用 簡潔的別名,避免長相對路徑 (../../../) 帶來的維護成本。
5. 範例三:增量編譯與 Project References(大型 monorepo)
// packages/api/tsconfig.json
{
"compilerOptions": {
"composite": true,
"declaration": true,
"declarationMap": true,
"incremental": true,
"outDir": "../../dist/api",
"rootDir": "./src"
},
"include": ["src"],
"references": [{ "path": "../shared" }]
}
// packages/shared/tsconfig.json
{
"compilerOptions": {
"composite": true,
"declaration": true,
"outDir": "../../dist/shared",
"rootDir": "./src"
},
"include": ["src"]
}
說明:
composite必須與declaration同時開啟,才能讓 Project References 正常運作。這樣的設定讓api專案在編譯時只重新編譯變更的shared部分,大幅縮短 CI 時間。
6. extends:共享基礎設定
在多個子專案需要相同的編譯規則時,可建立一個 共用基礎檔:
// tsconfig.base.json
{
"compilerOptions": {
"target": "es2022",
"module": "commonjs",
"strict": true,
"noImplicitAny": true,
"esModuleInterop": true,
"skipLibCheck": true
}
}
子專案只需要 extends:
// tsconfig.json (子專案)
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"outDir": "dist",
"rootDir": "src"
},
"include": ["src"]
}
優點:統一風格、減少重複設定,且只要基礎檔改動,所有子專案即時受惠。
常見陷阱與最佳實踐
| 陷阱 | 可能的後果 | 解決方式 |
|---|---|---|
忘記排除 node_modules |
編譯時間暴增,甚至因第三方 .d.ts 衝突導致錯誤。 |
在 exclude 中明確加入 "node_modules"(即使是預設) |
include/exclude 與 files 同時使用 |
files 會覆寫 include/exclude,導致預期外的檔案未被編譯。 |
只使用一種:若需要全域宣告檔,將其放在 include 內或使用 typeRoots |
過度依賴 any |
失去型別安全,錯誤只能在執行時才被發現。 | 開啟 noImplicitAny、strictNullChecks,並使用 型別別名 或 介面 替代 |
未設定 rootDir |
輸出目錄結構可能與原始目錄不一致,造成路徑錯亂。 | 明確設定 rootDir,尤其在 monorepo 中更為重要 |
esModuleInterop 未開啟卻使用 import foo from "foo" |
會出現 TS1192 錯誤。 |
開啟 esModuleInterop,或改用 import * as foo from "foo" |
incremental 與 composite 混用不當 |
增量檔 (.tsbuildinfo) 可能被意外刪除,導致全量重編譯。 |
確保 .gitignore 中保留 .tsbuildinfo,或在 CI 中重新產生 |
最佳實踐
- 最小化
compilerOptions:僅開啟必要的嚴格模式,避免過度限制開發速度。 - 使用
extends共享設定,讓團隊在不同子模組間保持一致性。 - 開啟
sourceMap與inlineSources(若需要),提升除錯體驗。 - 在 CI 中加入
tsc --noEmit以驗證型別正確性,避免因編譯錯誤部署。 - 定期檢視
tsconfig.json,隨著 TypeScript 版本升級,可能有新選項可提升效能或安全性。
實際應用場景
1. 前端 React 專案(Vite + TypeScript)
需求:支援 JSX、別名 @/components,且在開發模式下使用 esnext,生產模式則降級至 ES5。
{
"compilerOptions": {
"target": "esnext",
"module": "esnext",
"jsx": "react-jsx",
"baseUrl": ".",
"paths": {
"@/*": ["src/*"]
},
"strict": true,
"noEmit": true
},
"include": ["src"]
}
說明:
noEmit讓 Vite 直接負責轉譯,tsc僅作型別檢查。
2. Node.js 後端 (NestJS)
需求:產出 CommonJS、使用 experimentalDecorators、自動生成 .d.ts。
{
"compilerOptions": {
"target": "es2019",
"module": "commonjs",
"declaration": true,
"sourceMap": true,
"outDir": "dist",
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"strict": true
},
"include": ["src/**/*.ts"],
"exclude": ["node_modules", "dist"]
}
3. 多套件 Monorepo(Lerna / Nx)
需求:每個套件共用基礎設定,並使用 Project References。
// 根目錄 tsconfig.json
{
"files": [],
"references": [
{ "path": "packages/shared" },
{ "path": "packages/api" },
{ "path": "packages/web" }
]
}
每個套件皆 extends 根目錄的 tsconfig.base.json,同時在 package.json 中加入 "tsc -b" 作為 build script。
總結
tsconfig.json是 TypeScript 專案的「指揮中心」,正確的結構 能讓編譯快速、錯誤明確、程式碼可維護。- 透過
compilerOptions、include/exclude、extends、references等欄位,你可以細緻控制目標語言、模組系統、型別嚴格度以及多套件間的相依關係。 - 常見的陷阱(如未排除
node_modules、過度使用any)只要遵守 最佳實踐(開啟嚴格模式、使用別名、共享設定),即可在開發與 CI 流程中保持高品質。 - 依照不同的 應用場景(前端框架、Node 後端、Monorepo),只要調整少數屬性,就能得到最適合的編譯行為。
掌握 tsconfig.json 的結構與設定,你將能在任何 TypeScript 專案中,快速建立 一致、可擴充、且具備良好型別安全 的開發環境。祝你編譯順利、程式碼無慮!