TypeScript 路徑設定與 tsconfig.json 路徑相關設定

AdSense

前言

TypeScript 專案中,模組之間的匯入 (import) 路徑若處理不好,會變成地獄級相對路徑,例如:

1
import { getUser } from "../../../utils/api";

為了解決這問題,我們需要理解 tsconfig.json 中與「路徑」有關的設定,特別是 baseUrlpathsrootDiroutDir

相對路徑與絕對路徑

相對路徑 (relative path)

  • 開頭必須是 ./../
  • 代表是以目前檔案的位置為起點
1
2
3
4
5
// ./ 表示目前目錄
import { add } from "./utils/math";

// ../ 表示上一層目錄
import { config } from "../config/setting";

規則表

符號 意義 範例 實際對應
./ 目前目錄 ./utils/math 從目前檔案的目錄去找
../ 上一層 ../config 從目前檔案往上一層再往下找
../../ 上兩層 ../../types 以此類推

若你寫 import x from "./";,代表要匯入目前目錄下的 index.ts 檔案。

絕對路徑 (absolute path)

  • 不以 ./../ 開頭
  • 依照 baseUrl 設定來決定「從哪裡開始找」
  • 可搭配 paths 建立路徑別名 (alias)
  • 如果沒有設定 baseUrl,TypeScript 會將非相對路徑當作 npm 套件node_modules 裡找

例:

1
import { add } from "utils/math";

如果在 tsconfig.json 中設定 "baseUrl": "./src",那這行就代表:

「請從 src/ 目錄底下去找 utils/math.ts。」

rootDiroutDir

1
2
3
4
5
6
{
"compilerOptions": {
"rootDir": "src",
"outDir": "dist"
}
}
設定 功能
rootDir 指定原始 TypeScript 檔案的來源資料夾,根據這個設定來決定 outDir 中的檔案相對路徑
outDir 指定編譯後 JavaScript 檔案的輸出位置

範例結構:

1
2
3
4
project/
├─ src/
│ └─ index.ts
└─ tsconfig.json

編譯後:

1
2
3
project/
├─ dist/
│ └─ index.js

baseUrl:設定模組搜尋的根

預設情況

TypeScript 會將非相對路徑當作 npm 套件node_modules 裡找,因此:

1
import { add } from "utils/math";

若沒設定 baseUrl,會報錯「找不到模組」。

正確設定方式

1
2
3
4
5
{
"compilerOptions": {
"baseUrl": "./src"
}
}

效果:

匯入語法 實際對應檔案
"utils/math" src/utils/math.ts
"components/Button" src/components/Button.ts

paths:建立路徑別名 (alias)

paths 可搭配 baseUrl 一起使用,讓路徑更語意化、更乾淨。

1
2
3
4
5
6
7
8
9
10
{
"compilerOptions": {
"baseUrl": "./src",
"paths": {
"@/*": ["*"],
"@components/*": ["components/*"],
"@utils/*": ["utils/*"]
}
}
}

使用方式:

1
2
import { Button } from "@components/Button";
import { formatDate } from "@utils/date";

對應:

1
2
3
4
5
6
project/
├─ src/
│ ├─ components/Button.ts
│ ├─ utils/date.ts
│ └─ index.ts
└─ tsconfig.json

在執行環境中啟用 alias

TypeScript 編譯器知道 alias,但執行環境 (Node.js、Vite、Webpack) 不一定知道。

Vite

1
2
3
4
5
6
7
// vite.config.ts
import { defineConfig } from "vite";
import tsconfigPaths from "vite-tsconfig-paths";

export default defineConfig({
plugins: [tsconfigPaths()],
});

vite-tsconfig-paths 會自動讀取你的 tsconfig.json 裡的 "baseUrl""paths" 設定。

Webpack (需手動設定 resolve.alias)

Webpack 不會自動讀取 tsconfig,所以要在 webpack.config.js (或 .ts) 裡自己指定 alias。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// webpack.config.js
const path = require("path");

module.exports = {
// 其他設定 ...
resolve: {
extensions: [".ts", ".tsx", ".js"],
alias: {
"@": path.resolve(__dirname, "src"),
"@components": path.resolve(__dirname, "src/components"),
"@utils": path.resolve(__dirname, "src/utils"),
},
},
};

__dirname 表示當前檔案所在的資料夾:/Users/.../project
path.resolve(__dirname, "src") 表示:/Users/.../project/src

這樣你就能在專案中這樣寫:

1
2
import { Button } from "@components/Button";
import { formatDate } from "@utils/date";

Webpack 的 alias 是「執行時期」設定。TypeScript 的 paths 是「編譯時期」設定。它們要同步一致,否則編譯能過、執行會失敗。

Node.js (執行原生 TypeScript 或編譯後 JS)

Node.js 本身不會讀 tsconfig.json,視執行方式不同,使用不同方法。最常使用 tsconfig-paths 套件:

1
npm i tsconfig-paths -D

package.json 修改啟動命令:

1
2
3
4
5
{
"scripts": {
"dev": "ts-node -r tsconfig-paths/register src/index.ts"
}
}

這樣 Node.js 在執行時會自動解析 tsconfig.json 裡的 alias。

includeexclude

控制 TypeScript 要編譯哪些檔案。

1
2
3
4
{
"include": ["src/**/*"], // 包含 `src/` 底下的所有檔案與子資料夾 (無限層)
"exclude": ["node_modules", "dist"]
}
  • include:指定要包含的檔案或資料夾 (預設 ["**/*"])
  • exclude:指定要忽略的檔案或資料夾 (預設 ["node_modules"])

tsconfig.json 完整範例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
{
"compilerOptions": {
"target": "ES2020",
"module": "ESNext",
"rootDir": "src",
"outDir": "dist",
"baseUrl": "src",
"paths": {
"@/*": ["*"],
"@utils/*": ["utils/*"],
"@components/*": ["components/*"]
},
"strict": true,
"esModuleInterop": true,
"moduleResolution": "node"
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}

使用方式:

1
2
import { Button } from "@components/Button";
import { add } from "@utils/math";

總結

設定項目 功能 範例
rootDir 原始 TypeScript 檔案的根目錄,決定「結構」 "src"
outDir 編譯後 JS 輸出目錄 "dist"
baseUrl import 非相對路徑 module 的搜尋 (解析) 起點 "./src"
paths 自訂 alias "@/*": ["*"]
include 指定要編譯的檔案,決定「範圍」 "src/**/*"
exclude 排除不需編譯的檔案,決定「範圍」 "node_modules"
  • ./ → 目前目錄
  • ../ → 上一層目錄
  • 沒有 ./../ → TypeScript 視為「非相對路徑」,依 baseUrl 尋找,或當作 npm 套件node_modules 裡找
  • 搭配 paths 可建立語意化的 alias

參考資料:
TypeScript 官方文件:Module Resolution
Vite 官方文件:vite-tsconfig-paths 外掛