TypeScript 專案配置最佳實踐:從 tsconfig 到建置工具
6 分鐘閱讀 1,100 字
TypeScript 專案配置最佳實踐:從 tsconfig 到建置工具
一個良好的 TypeScript 專案配置能讓你在開發時獲得最強的型別保護,在建置時輸出乾淨的 JavaScript,並讓整個團隊保持一致的程式碼品質。但 tsconfig.json 的選項多達數十個,很多開發者對各選項的含義一知半解。本文整理出最重要的配置項目和對應的最佳實踐。
基礎 tsconfig.json 解析
{
"compilerOptions": {
// ===== 目標環境 =====
"target": "ES2022",
// 輸出的 JavaScript 版本。現代 Node.js(18+)和瀏覽器支援 ES2022
// 不要設太低,否則編譯器需要生成大量 polyfill 程式碼
"module": "NodeNext",
// 模組系統。Node.js 專案用 NodeNext,瀏覽器/Vite 專案用 ESNext
"moduleResolution": "NodeNext",
// 模組解析策略,需與 module 一致
"lib": ["ES2022", "DOM", "DOM.Iterable"],
// 引入的型別庫。瀏覽器專案需要 DOM,Node.js 專案不需要
// ===== 嚴格模式(全部開啟!)=====
"strict": true,
// 開啟所有嚴格型別檢查,等同於開啟下面這些:
// strictNullChecks, strictFunctionTypes, strictBindCallApply
// strictPropertyInitialization, noImplicitAny, noImplicitThis, ...
"noUncheckedIndexedAccess": true,
// array[0] 的型別會是 T | undefined,強迫你處理 undefined
// 這個 strict 沒有包含,但強烈建議開啟
"exactOptionalPropertyTypes": true,
// { foo?: string } 中,foo 只能是 string 或不存在,不能是 undefined
// ===== 輸出設定 =====
"outDir": "./dist",
"rootDir": "./src",
"declaration": true, // 生成 .d.ts 型別宣告檔
"declarationMap": true, // 生成 .d.ts.map,讓 IDE 能跳轉到原始碼
"sourceMap": true, // 生成 source map,方便除錯
// ===== 品質控制 =====
"noUnusedLocals": true, // 未使用的區域變數報錯
"noUnusedParameters": true, // 未使用的函式參數報錯
"noImplicitReturns": true, // 函式所有分支必須有 return
"noFallthroughCasesInSwitch": true, // switch case 必須有 break
// ===== 其他重要設定 =====
"esModuleInterop": true, // 允許 import React from 'react'(而非 import * as React)
"forceConsistentCasingInFileNames": true, // 檔名大小寫一致(避免 Linux/Mac 差異)
"skipLibCheck": true // 跳過 node_modules 中的型別檢查(加速建置)
},
"include": ["src"],
"exclude": ["node_modules", "dist"]
}多環境配置:extends 繼承
大型專案通常有多個目標環境,使用 extends 避免重複:
// tsconfig.base.json(共用基礎設定)
{
"compilerOptions": {
"strict": true,
"noUncheckedIndexedAccess": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"forceConsistentCasingInFileNames": true,
"esModuleInterop": true
}
}// tsconfig.json(開發用,啟用 source map)
{
"extends": "./tsconfig.base.json",
"compilerOptions": {
"target": "ES2022",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"outDir": "./dist",
"sourceMap": true,
"declaration": true
},
"include": ["src"]
}// tsconfig.test.json(測試用)
{
"extends": "./tsconfig.json",
"compilerOptions": {
"types": ["vitest/globals", "node"]
},
"include": ["src", "tests"]
}路徑別名(Path Aliases)
告別 ../../../utils/helper,使用簡潔的路徑:
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["src/*"],
"@components/*": ["src/components/*"],
"@utils/*": ["src/utils/*"],
"@types/*": ["src/types/*"]
}
}
}但注意:TypeScript 的 paths 只是型別層面的別名,執行時需要對應工具支援:
// Vite(vite.config.ts)
import { defineConfig } from 'vite';
import { resolve } from 'path';
export default defineConfig({
resolve: {
alias: {
'@': resolve(__dirname, 'src')
}
}
});
// Node.js(tsconfig-paths 或 tsc-alias)
// tsconfig-paths 在執行時解析
npx ts-node -r tsconfig-paths/register src/index.ts
// tsc-alias 在編譯後替換路徑
npx tsc && npx tsc-alias嚴格模式的實際影響
// 開啟 strictNullChecks 後
function getUserName(id: string): string {
const user = users.find(u => u.id === id);
// ❌ 錯誤:user 可能是 undefined
return user.name;
// ✅ 正確寫法
if (!user) throw new Error(`User ${id} not found`);
return user.name;
}
// noUncheckedIndexedAccess 的影響
const arr = [1, 2, 3];
const first = arr[0];
// first 的型別是 number | undefined,需要檢查
const safeFirst = arr[0] ?? 0; // 使用 nullish coalescing型別宣告管理
// package.json
{
"name": "my-library",
"version": "1.0.0",
"main": "./dist/index.cjs",
"module": "./dist/index.js",
"types": "./dist/index.d.ts", // 指定型別宣告檔的位置
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.js",
"require": "./dist/index.cjs"
}
}
}建置工具整合
使用 tsx 在開發時執行 TypeScript
npm install --save-dev tsx{
"scripts": {
"dev": "tsx watch src/index.ts",
"build": "tsc",
"start": "node dist/index.js"
}
}使用 tsup 建置函式庫
npm install --save-dev tsup// tsup.config.ts
import { defineConfig } from 'tsup';
export default defineConfig({
entry: ['src/index.ts'],
format: ['cjs', 'esm'], // 同時輸出 CommonJS 和 ESModule
dts: true, // 生成 .d.ts
sourcemap: true,
clean: true, // 建置前清除 dist/
splitting: false, // 單一入口不需要 code splitting
treeshake: true
});ESLint 型別感知規則
// eslint.config.js
import tseslint from 'typescript-eslint';
export default tseslint.config(
tseslint.configs.strictTypeChecked,
tseslint.configs.stylisticTypeChecked,
{
languageOptions: {
parserOptions: {
projectService: true,
tsconfigRootDir: import.meta.dirname
}
},
rules: {
// 禁止 @ts-ignore(用 @ts-expect-error 替代)
'@typescript-eslint/ban-ts-comment': ['error', {
'ts-ignore': 'allow-with-description'
}],
// 強制使用型別導入
'@typescript-eslint/consistent-type-imports': ['error', {
prefer: 'type-imports'
}],
// 避免浮動的 Promise(未 await 的 async 呼叫)
'@typescript-eslint/no-floating-promises': 'error'
}
}
);常見配置錯誤
skipLibCheck: false:在 CI 中非常慢,除非你在維護函式庫,否則應設為true"module": "CommonJS"+"moduleResolution": "NodeNext":不相容的組合paths 別名沒有對應建置工具設定:型別檢查通過但執行時報錯
strict: false節省時間?:長期看反而增加 bug 率,一開始就開嚴格模式更划算
推薦的配置基礎
可以使用社群維護的 tsconfig 基礎包:
npm install --save-dev @tsconfig/strictest{
"extends": "@tsconfig/strictest",
"compilerOptions": {
"target": "ES2022",
"module": "NodeNext",
"outDir": "./dist"
}
}小結
好的 TypeScript 配置是一次性投資,能為整個專案生命週期帶來收益。核心原則是:開啟最嚴格的型別檢查,讓型別錯誤在編譯時就被發現,而不是等到執行時崩潰。搭配 ESLint 的型別感知規則,TypeScript 就能發揮出最大的防禦性程式設計能力。
分享這篇文章