TypeScript 編譯與設定:compilerOptions 的 outDir 與 rootDir
簡介
在使用 TypeScript 開發大型專案時,編譯後的檔案結構往往直接影響到後續的部署、測試與維護成本。tsconfig.json 中的 compilerOptions 提供了許多控制編譯行為的參數,其中最常用、也是最容易產生誤解的就是 outDir 與 rootDir。
outDir(output directory)決定 編譯後的 JavaScript、宣告檔 (.d.ts) 以及 source map 要寫入的目錄。rootDir(root directory)則告訴編譯器 原始的 TypeScript 檔案根位置,讓編譯器在產出目錄時能正確保留相對路徑。
如果這兩個設定不當,常會出現「檔案找不到」或「路徑錯亂」的問題,導致開發流程被迫中斷。本文將深入說明 outDir / rootDir 的作用原理、實作範例、常見陷阱與最佳實踐,幫助你在專案中建立 乾淨、可預測的編譯輸出。
核心概念
1. rootDir:告訴編譯器「從哪裡開始找」
rootDir 只是一個 參考點,編譯器會以它為基礎,計算每個檔案相對於根目錄的路徑,之後再把相同的相對路徑套用到 outDir。如果未明確設定,TypeScript 會自動推斷最深的共同祖先目錄,這在小型專案還好,大型或多層結構的專案很容易產生意外。
{
"compilerOptions": {
"rootDir": "src"
}
}
範例說明:
src/app/main.ts、src/utils/helper.ts兩個檔案相對於src的路徑分別是app/main.ts、utils/helper.ts。
2. outDir:決定編譯產出放哪裡
outDir 必須是一個 相對或絕對路徑,編譯器會把所有轉譯後的檔案寫入此目錄,並保持 rootDir 計算出的相對結構。
{
"compilerOptions": {
"outDir": "dist"
}
}
結果:
src/app/main.ts會被編譯成dist/app/main.js,src/utils/helper.ts會變成dist/utils/helper.js。
3. rootDir + outDir 的互動流程
- 掃描
include/files→ 找到所有符合條件的.ts、.tsx檔案。 - 根據
rootDir計算相對路徑(若未設定,使用最深的共同父目錄)。 - 將相對路徑套用到
outDir,產生最終輸出路徑。 - 寫入檔案(包括
.js、.d.ts、.js.map依設定)。
圖示
src/(rootDir) →dist/(outDir)src/models/user.ts→dist/models/user.js
程式碼範例
以下示範 5 個常見的設定與結果,讓你快速掌握實際應用。
範例 1:最簡單的 rootDir + outDir
// tsconfig.json
{
"compilerOptions": {
"target": "ES2020",
"module": "commonjs",
"rootDir": "src",
"outDir": "build"
}
}
# 檔案結構
src/
├─ index.ts
└─ utils/
└─ logger.ts
編譯後
build/ ├─ index.js └─ utils/ └─ logger.js
範例 2:未設定 rootDir,自動推斷的結果
{
"compilerOptions": {
"outDir": "dist"
}
}
src/
├─ server/
│ └─ app.ts
└─ client/
└─ main.ts
自動推斷:最深共同父目錄是
src,所以dist/server/app.js、dist/client/main.js。
若src內還有其他非 TypeScript 檔案(例如.json),也會被一起搬移,可能造成不必要的檔案。
範例 3:使用多層 rootDir 結構
{
"compilerOptions": {
"rootDir": "src/modules",
"outDir": "output"
}
}
src/
└─ modules/
├─ auth/
│ └─ login.ts
└─ data/
└─ fetch.ts
編譯結果
output/ ├─ auth/ │ └─ login.js └─ data/ └─ fetch.js
範例 4:配合 paths 與 baseUrl 使用
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@utils/*": ["src/utils/*"]
},
"rootDir": "src",
"outDir": "lib"
}
}
// src/app.ts
import { format } from "@utils/formatter";
編譯後
lib/app.js仍會保留相同的相對路徑,不會把別名直接寫入磁碟路徑,因此paths僅影響編譯階段的模組解析。
範例 5:搭配 declaration 產生型別宣告檔
{
"compilerOptions": {
"rootDir": "src",
"outDir": "dist",
"declaration": true,
"emitDeclarationOnly": false
}
}
src/
└─ services/
└─ api.ts // 含有 export interface
編譯結果
dist/ └─ services/ ├─ api.js └─ api.d.ts // 介面宣告同步產出
常見陷阱與最佳實踐
| 陷阱 | 可能的症狀 | 解決方式 |
|---|---|---|
未設定 rootDir,導致輸出路徑混亂 |
dist 內出現多層 src、test 目錄,甚至把 node_modules 複製進去。 |
明確指定 rootDir 為專案的程式碼根目錄(通常是 src)。 |
outDir 指向同一層目錄 |
編譯時會 覆寫原始 .ts 檔案,造成資料遺失。 |
確保 outDir 與 rootDir 不相同,且通常放在 dist、build、lib 等獨立資料夾。 |
使用絕對路徑但未加 . 前綴 |
在 Windows 與 Linux 上路徑分隔符不同,可能產生「找不到檔案」的錯誤。 | 使用 相對路徑("./dist")或在 tsconfig.json 中統一使用 POSIX 風格 /。 |
rootDir 包含測試檔 |
測試檔會一併被編譯到 outDir,造成產出體積變大。 |
將測試檔放在 test 或 __tests__,並在 exclude 中排除,或把 rootDir 設為 src。 |
outDir 被 .gitignore 忽略,卻誤提交 |
CI/CD 流程找不到編譯產出,部署失敗。 | 確認 .gitignore 只排除 暫存檔(如 dist/),而 CI 會在建置階段自行產出。 |
最佳實踐:
- 固定
rootDir為src,讓所有程式碼都在同一層級下管理。 outDir建議使用dist、build或lib,保持與原始碼分離。- 在
tsconfig.json中加入exclude或include,明確告訴編譯器哪些檔案不需要編譯。 - 若專案需要 多個輸出目錄(例如
es5與esnext),可以使用extends產生多個配置檔,避免一次設定過於複雜。 - 在 CI/CD pipeline 中加入
npm run clean && tsc,確保每次建置前先清除舊的outDir,避免殘留檔案。
實際應用場景
1. 前端框架(React / Vue)結合 Webpack
- 需求:原始碼放在
src/,Webpack 需要讀取已編譯的 JavaScript。 - 做法:在
tsconfig.json設定rootDir: "src"、outDir: "dist",Webpack 的 entry 指向dist/index.js。這樣即使在開發模式下使用ts-loader,產出的檔案仍保持一致的目錄結構,方便熱更新與 source map。
2. Node.js 套件發佈(npm package)
- 需求:將 TypeScript 寫的套件編譯成 CommonJS 與 ES Module,並同時輸出型別宣告。
- 做法:
{ "compilerOptions": { "rootDir": "src", "outDir": "lib", "declaration": true, "module": "ESNext", "target": "ES2019" }, "include": ["src/**/*.ts"] }src內的檔案會保持原始相對路徑,最終在lib中得到index.js、index.d.ts等,npm publish 時只把lib包入。
3. 單元測試(Jest)與測試編譯
- 需求:測試檔案放在
tests/,不希望它們被編譯到正式產出目錄。 - 做法:
{ "compilerOptions": { "rootDir": "src", "outDir": "dist" }, "exclude": ["tests"] }- Jest 會使用
ts-jest直接編譯測試檔,而tsc只負責產出正式程式碼。
- Jest 會使用
4. 多語系資源檔(i18n)與自動生成
- 需求:把
src/locales/*.ts轉成dist/locales/*.js,讓前端在執行時動態載入。 - 做法:同樣利用
rootDir/outDir,保持locales目錄結構不變,讓程式碼只需要import locale from "./locales/zh-TW"。
總結
rootDir定義 原始程式碼的根目錄,決定相對路徑的計算基準。outDir定義 編譯結果的輸出位置,並保留rootDir計算出的目錄結構。- 正確設定兩者可以避免 路徑錯亂、檔案遺失,提升 專案可維護性與部署一致性。
- 常見陷阱包括未設定
rootDir、outDir與rootDir同層、測試檔被編譯等,透過 明確的exclude/include、分離的輸出資料夾 以及 CI 清理流程 可有效解決。 - 無論是 前端框架、Node 套件、單元測試,或是 多語系資源,
outDir/rootDir都是構建乾淨、可預測編譯產出的關鍵。
掌握了這兩個參數的使用,你就能在任何規模的 TypeScript 專案中,保持 清晰的檔案結構、快速的建置速度,並減少因路徑問題導致的除錯時間。祝你寫程式愉快,編譯順利! 🚀