TypeScript效能最佳化實戰:從編譯時到執行時的5種生產模式
前端工程
你的TypeScript專案正在慢性死亡
10萬行程式碼的TypeScript專案,tsc一次要45秒,HMR重新整理卡3秒,打包產物2.4MB,線上首屏3.8秒——這不是個例,這是2026年大多數TypeScript專案的真實寫照。型別系統本該是開發效率的倍增器,卻變成了效能殺手。Project References沒人配,skipLibCheck不敢開,泛型巢狀五層,執行時type guard寫在熱路徑上,bundle裡塞滿了沒tree-shake掉的死程式碼。
本文將從核心概念出發,帶你完成型別檢查加速→建構最佳化→執行時效能→記憶體效率→生產監控的5種生產模式,從編譯時到執行時,從開發體驗到線上指標,一步不落。
核心概念
| 概念 | 說明 |
|---|---|
| Type Checking Speed | TypeScript編譯器對型別進行檢查的速度,受專案結構、型別複雜度、設定影響 |
| Project References | 將大型專案拆分為多個子專案,實現增量編譯和並行型別檢查 |
| Incremental Build | 利用快取只重新檢查變更部分,避免全量型別檢查 |
| Tree-Shaking | 建構工具識別並移除未參考程式碼,減少最終bundle體積 |
| Structural Typing | TypeScript的結構化型別系統,透過形狀而非名義進行型別相容判斷 |
| Branded Types | 透過唯一標記建立名義型別,在結構型別系統中實現型別隔離 |
| Const Assertions | as const斷言,將值推斷為最窄的字面量型別,減少型別空間膨脹 |
| Performance Budget | 為bundle大小、編譯時間等設定預算閾值,CI中自動攔截效能退化 |
最佳化流程
編譯時最佳化:
專案拆分(Project References) → 增量編譯(Incremental) → skipLibCheck → 隔離模組編譯
建構時最佳化:
tsup/esbuild替代tsc → Tree-Shaking → Code Splitting → Bundle壓縮
執行時最佳化:
避免熱路徑type guard → 高效泛型 → 內聯型別斷言 → 減少執行時型別檢查
記憶體最佳化:
結構型別複用 → Branded Types替代類別繼承 → const assertions → 型別收窄
生產監控:
型別覆蓋率 → Bundle分析 → Performance Budget → CI整合
問題分析:TypeScript效能的5大瓶頸
- 型別檢查速度慢:單一大專案全量tsc耗時隨程式碼量線性增長,10萬行程式碼專案tsc需30-60秒,CI流水線被型別檢查阻塞
- 建構產物臃腫:tsc只做型別檢查不做tree-shaking,barrel exports導致大量死程式碼進入bundle,執行時型別守衛程式碼膨脹
- 執行時效能損耗:熱路徑上頻繁的
typeof/instanceof檢查,深層泛型實例化產生的執行時開銷,過度使用class裝飾器 - 記憶體效率低下:複雜型別推斷佔用大量編譯器記憶體,執行時大量相似結構型別建立重複物件,未使用const assertion導致型別空間膨脹
- 缺乏效能監控:沒有型別覆蓋率指標,bundle大小無預算控制,編譯時間退化無感知,線上效能問題無法回溯到型別設計
分步實操:5種生產模式
模式1:型別檢查速度最佳化
// tsconfig.base.json - 基礎設定
{
"compilerOptions": {
"target": "ES2022",
"module": "ESNext",
"moduleResolution": "bundler",
"strict": true,
"skipLibCheck": true,
"incremental": true,
"tsBuildInfoFile": ".tsbuildinfo",
"declaration": true,
"declarationMap": true,
"sourceMap": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"isolatedModules": true
}
}
// packages/core/tsconfig.json - 核心套件設定
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"outDir": "./dist",
"rootDir": "./src"
},
"include": ["src"],
"references": [
{ "path": "../shared" }
]
}
// packages/shared/tsconfig.json - 共享套件設定
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"composite": true,
"outDir": "./dist",
"rootDir": "./src"
},
"include": ["src"]
}
// packages/app/tsconfig.json - 應用套件設定
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"outDir": "./dist",
"rootDir": "./src",
"noEmit": true
},
"include": ["src"],
"references": [
{ "path": "../core" },
{ "path": "../shared" }
]
}
// tsconfig.json - 頂層解決方案設定
{
"files": [],
"references": [
{ "path": "packages/shared" },
{ "path": "packages/core" },
{ "path": "packages/app" }
]
}
// scripts/typecheck.ts - 增量型別檢查指令碼
import { execSync } from 'child_process';
import { readFileSync, existsSync, unlinkSync } from 'fs';
import { performance } from 'perf_hooks';
interface TypeCheckResult {
project: string;
duration: number;
errors: number;
cached: boolean;
}
function typecheckProject(projectPath: string, clean: boolean = false): TypeCheckResult {
const buildInfoFile = `${projectPath}/.tsbuildinfo`;
const wasCached = existsSync(buildInfoFile) && !clean;
if (clean && existsSync(buildInfoFile)) {
unlinkSync(buildInfoFile);
}
const start = performance.now();
let errors = 0;
try {
execSync(`npx tsc --build ${projectPath} --verbose`, {
encoding: 'utf-8',
stdio: 'pipe',
});
} catch (error: any) {
const output = error.stdout || error.stderr || '';
const errorMatches = output.match(/error TS\d+:/g);
errors = errorMatches ? errorMatches.length : 0;
}
const duration = performance.now() - start;
return {
project: projectPath,
duration: Math.round(duration),
errors,
cached: wasCached,
};
}
function runFullTypeCheck(clean: boolean = false): void {
const projects = [
'packages/shared',
'packages/core',
'packages/app',
];
console.log('=== TypeScript 型別檢查報告 ===\n');
let totalDuration = 0;
let totalErrors = 0;
for (const project of projects) {
const result = typecheckProject(project, clean);
totalDuration += result.duration;
totalErrors += result.errors;
const status = result.errors === 0 ? '✓' : '✗';
const cacheStatus = result.cached ? '(cached)' : '(full)';
console.log(
`${status} ${result.project}: ${result.duration}ms ${cacheStatus}, ${result.errors} errors`
);
}
console.log(`\n總計: ${totalDuration}ms, ${totalErrors} errors`);
if (totalDuration > 10000) {
console.warn('\n⚠️ 型別檢查超過10秒預算!建議:');
console.warn(' - 新增更多Project References');
console.warn(' - 啟用skipLibCheck');
console.warn(' - 使用isolatedModules');
}
}
const isClean = process.argv.includes('--clean');
runFullTypeCheck(isClean);
// src/types/performance-optimized.ts - 最佳化型別定義減少檢查負擔
// ❌ 複雜的條件型別導致型別檢查緩慢
type DeepPartial<T> = T extends object
? { [K in keyof T]?: DeepPartial<T[K]> }
: T;
type DeepRequired<T> = T extends object
? { [K in keyof T]-?: DeepRequired<T[K]> }
: T;
// ✅ 使用內建工具型別,避免重複實例化
type CachedPartial<T> = Partial<T>;
type CachedRequired<T> = Required<T>;
// ❌ 遞迴型別深度過大
type FlattenDeep<T> = T extends Array<infer U>
? FlattenDeep<U>
: T;
// ✅ 限制遞迴深度
type FlattenN<T, N extends number, D extends any[] = []> =
D['length'] extends N
? T
: T extends Array<infer U>
? FlattenN<U, N, [...D, 1]>
: T;
type Flatten3<T> = FlattenN<T, 3>;
// ❌ 過度使用模板字面量型別
type Route = `/api/${string}/${string}`;
// ✅ 使用聯合型別替代模板字面量
type ApiRoute =
| '/api/users'
| '/api/users/:id'
| '/api/posts'
| '/api/posts/:id'
| '/api/comments'
| '/api/comments/:id';
// ❌ 大型聯合型別
type Status = 'pending' | 'processing' | 'approved' | 'rejected' | 'cancelled' | 'refunded' | 'completed' | 'archived' | 'draft' | 'published';
// ✅ 分組聯合型別
type OrderStatus = 'pending' | 'processing' | 'completed' | 'cancelled';
type ContentStatus = 'draft' | 'published' | 'archived';
type PaymentStatus = 'approved' | 'rejected' | 'refunded';
// skipLibCheck最佳化的.d.ts
declare module 'heavy-lib' {
export interface HeavyConfig {
readonly apiKey: string;
readonly endpoint: string;
readonly timeout: number;
readonly retries: number;
}
export function createClient(config: HeavyConfig): HeavyClient;
export interface HeavyClient {
getData<T>(id: string): Promise<T>;
setData<T>(id: string, data: T): Promise<void>;
}
}
模式2:建構最佳化與tsup/esbuild
// tsup.config.ts - tsup建構設定
import { defineConfig } from 'tsup';
export default defineConfig([
{
entry: ['src/index.ts'],
format: ['esm', 'cjs'],
dts: {
resolve: true,
compilerOptions: {
skipLibCheck: true,
composite: false,
},
},
splitting: true,
treeshake: true,
minify: true,
sourcemap: true,
clean: true,
outDir: 'dist',
target: 'es2022',
platform: 'node',
external: ['react', 'react-dom'],
esbuildOptions(options) {
options.logLevel = 'info';
options.chunkNames = 'chunks/[name]-[hash]';
},
},
{
entry: ['src/browser.ts'],
format: ['esm'],
treeshake: true,
minify: true,
sourcemap: true,
outDir: 'dist/browser',
target: 'es2022',
platform: 'browser',
esbuildOptions(options) {
options.conditions = ['browser'];
},
},
]);
// src/index.ts - 最佳化匯出結構以支援tree-shaking
// ❌ barrel export導致tree-shaking失效
// export * from './utils';
// export * from './validators';
// export * from './transformers';
// ✅ 明確命名匯出,便於bundler分析依賴
export { validateEmail, validateUrl } from './validators/email';
export { validateAge, validateRange } from './validators/number';
export { formatDate, parseDate } from './transformers/date';
export { formatCurrency, parseCurrency } from './transformers/currency';
// ✅ 使用條件匯出減少瀏覽器bundle
export type { UserSchema, CreateUserInput } from './types/user';
export type { PaginationParams, SearchParams } from './types/pagination';
// scripts/bundle-analysis.ts - Bundle分析指令碼
import { build } from 'esbuild';
import { readFileSync, writeFileSync } from 'fs';
import { gzipSync } from 'zlib';
interface BundleMetrics {
name: string;
size: number;
gzipSize: number;
modules: number;
}
const BUDGET = {
maxBundleSize: 100 * 1024,
maxGzipSize: 30 * 1024,
maxChunkSize: 50 * 1024,
};
async function analyzeBundle(entryPoint: string): Promise<BundleMetrics> {
const result = await build({
entryPoints: [entryPoint],
bundle: true,
minify: true,
write: false,
metafile: true,
format: 'esm',
target: 'es2022',
external: ['react', 'react-dom'],
});
const outputFiles = result.outputFiles || [];
const metafile = result.metafile;
let totalSize = 0;
for (const file of outputFiles) {
totalSize += file.contents.byteLength;
}
const gzipSize = gzipSync(Buffer.from(outputFiles[0]?.contents || [])).byteLength;
const moduleCount = metafile ? Object.keys(metafile.inputs).length : 0;
return {
name: entryPoint,
size: totalSize,
gzipSize,
modules: moduleCount,
};
}
async function runBundleAnalysis(): Promise<void> {
console.log('=== Bundle 分析報告 ===\n');
const entryPoints = [
'src/index.ts',
'src/browser.ts',
];
let hasViolation = false;
for (const entry of entryPoints) {
const metrics = await analyzeBundle(entry);
const sizeKB = (metrics.size / 1024).toFixed(1);
const gzipKB = (metrics.gzipSize / 1024).toFixed(1);
console.log(`${entry}:`);
console.log(` 大小: ${sizeKB}KB (預算: ${BUDGET.maxBundleSize / 1024}KB)`);
console.log(` Gzip: ${gzipKB}KB (預算: ${BUDGET.maxGzipSize / 1024}KB)`);
console.log(` 模組: ${metrics.modules}`);
if (metrics.size > BUDGET.maxBundleSize) {
console.warn(` ⚠️ Bundle大小超過預算!`);
hasViolation = true;
}
if (metrics.gzipSize > BUDGET.maxGzipSize) {
console.warn(` ⚠️ Gzip大小超過預算!`);
hasViolation = true;
}
console.log();
}
if (hasViolation) {
console.error('❌ 效能預算違規!');
process.exit(1);
} else {
console.log('✅ 所有bundle都在預算內。');
}
}
runBundleAnalysis();
// package.json - 建構指令碼最佳化
{
"scripts": {
"typecheck": "tsc --build",
"typecheck:clean": "tsc --build --clean && tsc --build",
"build": "tsup",
"build:analyze": "tsup --metafile && node scripts/bundle-analysis.ts",
"dev": "tsup --watch",
"check:all": "npm run typecheck && npm run build && npm run test",
"prepack": "npm run build"
}
}
模式3:執行時效能最佳化
// src/runtime/hot-path.ts - 熱路徑型別守衛最佳化
// ❌ 在熱路徑上使用複雜型別守衛
function processData(data: unknown): ProcessedData {
if (
typeof data === 'object' &&
data !== null &&
'id' in data &&
typeof (data as any).id === 'string' &&
'name' in data &&
typeof (data as any).name === 'string' &&
'age' in data &&
typeof (data as any).age === 'number' &&
'email' in data &&
typeof (data as any).email === 'string'
) {
return data as ProcessedData;
}
throw new Error('Invalid data');
}
// ✅ 最小化型別守衛,信任編譯時型別
interface ProcessedData {
id: string;
name: string;
age: number;
email: string;
}
function processDataOptimized(data: unknown): ProcessedData {
if (typeof data !== 'object' || data === null) {
throw new Error('Expected object');
}
return data as ProcessedData;
}
// ✅ 分層校驗:熱路徑快速檢查,冷路徑嚴格校驗
function processHotPath(data: unknown): ProcessedData {
if (typeof data !== 'object' || data === null) {
throw new Error('Expected object');
}
return data as ProcessedData;
}
function processColdPath(data: unknown): ProcessedData {
const result = ProcessedDataSchema.safeParse(data);
if (!result.success) {
throw new Error(`校驗失敗: ${result.error.message}`);
}
return result.data;
}
import { z } from 'zod';
const ProcessedDataSchema = z.object({
id: z.string().uuid(),
name: z.string().min(1),
age: z.number().int().min(0),
email: z.string().email(),
});
// src/runtime/generics.ts - 高效泛型模式
// ❌ 泛型實例化導致執行時膨脹
class DataStore<T> {
private items: T[] = [];
add(item: T): void {
this.items.push(item);
}
get(index: number): T | undefined {
return this.items[index];
}
filter(predicate: (item: T) => boolean): T[] {
return this.items.filter(predicate);
}
map<U>(transform: (item: T) => U): U[] {
return this.items.map(transform);
}
reduce<U>(reducer: (acc: U, item: T) => U, initial: U): U {
return this.items.reduce(reducer, initial);
}
}
// ✅ 使用函數式工具替代泛型類別
interface DataStore {
readonly items: readonly unknown[];
}
function createStore<T>(initialItems: T[] = []): DataStore {
return { items: Object.freeze([...initialItems]) };
}
function addItem<T>(store: DataStore, item: T): DataStore {
return { items: Object.freeze([...store.items, item]) };
}
function getItem<T>(store: DataStore, index: number): T {
return store.items[index] as T;
}
function filterItems<T>(store: DataStore, predicate: (item: T) => boolean): T[] {
return store.items.filter(predicate as (item: unknown) => boolean) as T[];
}
// ✅ 使用條件型別替代執行時分支
type Result<T, E = Error> =
| { readonly ok: true; readonly value: T }
| { readonly ok: false; readonly error: E };
function ok<T>(value: T): Result<T> {
return { ok: true, value };
}
function err<E>(error: E): Result<never, E> {
return { ok: false, error };
}
function mapResult<T, U, E>(result: Result<T, E>, fn: (value: T) => U): Result<U, E> {
return result.ok ? ok(fn(result.value)) : result;
}
function flatMapResult<T, U, E>(
result: Result<T, E>,
fn: (value: T) => Result<U, E>
): Result<U, E> {
return result.ok ? fn(result.value) : result;
}
// ✅ 避免執行時型別抹除的開銷
interface TypeTag<T extends string> {
readonly __type: T;
}
type UserId = string & TypeTag<'UserId'>;
type OrderId = string & TypeTag<'OrderId'>;
function createUserId(id: string): UserId {
return id as UserId;
}
function createOrderId(id: string): OrderId {
return id as OrderId;
}
function getUser(id: UserId): Promise<User | null> {
return Promise.resolve(null);
}
function getOrder(id: OrderId): Promise<Order | null> {
return Promise.resolve(null);
}
interface User {
id: UserId;
name: string;
}
interface Order {
id: OrderId;
total: number;
}
// src/runtime/iteration.ts - 高效迭代模式
// ❌ 每次迭代建立新陣列
function processUsers(users: User[]): ProcessedUser[] {
return users
.filter((u) => u.age >= 18)
.map((u) => ({ ...u, displayName: u.name.toUpperCase() }))
.filter((u) => u.displayName.length > 0)
.map((u) => ({ ...u, score: calculateScore(u) }));
}
// ✅ 單次遍歷替代多次鏈式呼叫
function processUsersOptimized(users: User[]): ProcessedUser[] {
const result: ProcessedUser[] = [];
for (const u of users) {
if (u.age < 18) continue;
const displayName = u.name.toUpperCase();
if (displayName.length === 0) continue;
result.push({
...u,
displayName,
score: calculateScore(u),
});
}
return result;
}
// ✅ 使用生成器延遲計算
function* filterUsers(users: Iterable<User>): Generator<User> {
for (const u of users) {
if (u.age >= 18) yield u;
}
}
function* mapUsers(users: Iterable<User>): Generator<ProcessedUser> {
for (const u of users) {
yield {
...u,
displayName: u.name.toUpperCase(),
score: calculateScore(u),
};
}
}
function processUsersLazy(users: User[]): ProcessedUser[] {
return [...mapUsers(filterUsers(users))];
}
interface ProcessedUser extends User {
displayName: string;
score: number;
}
function calculateScore(user: User): number {
return user.name.length * user.age;
}
模式4:記憶體效率最佳化
// src/memory/structural-typing.ts - 結構型別複用
// ❌ 每個介面獨立定義,編譯器為每個建立獨立型別
interface UserResponse {
id: string;
name: string;
email: string;
role: 'admin' | 'editor' | 'viewer';
createdAt: string;
}
interface UserListItem {
id: string;
name: string;
email: string;
role: 'admin' | 'editor' | 'viewer';
createdAt: string;
}
interface UserProfile {
id: string;
name: string;
email: string;
role: 'admin' | 'editor' | 'viewer';
createdAt: string;
bio: string;
avatar: string;
}
// ✅ 使用基礎型別+組合複用
interface BaseEntity {
readonly id: string;
readonly createdAt: string;
readonly updatedAt: string;
}
type UserRole = 'admin' | 'editor' | 'viewer';
interface UserBase extends BaseEntity {
readonly name: string;
readonly email: string;
readonly role: UserRole;
}
type UserResponse = UserBase;
type UserListItem = UserBase;
interface UserProfile extends UserBase {
readonly bio: string;
readonly avatar: string;
}
// ✅ 使用Pick/Omit從基礎型別派生
type UserSummary = Pick<UserBase, 'id' | 'name' | 'role'>;
type UserEmailInfo = Pick<UserBase, 'id' | 'email'>;
type UserWithoutRole = Omit<UserBase, 'role'>;
// src/memory/branded-types.ts - Branded Types替代類別繼承
// ❌ 使用類別繼承導致執行時原型鏈開銷
class Entity {
constructor(
public id: string,
public createdAt: Date,
) {}
}
class UserEntity extends Entity {
constructor(
id: string,
createdAt: Date,
public name: string,
public email: string,
) {
super(id, createdAt);
}
}
class OrderEntity extends Entity {
constructor(
id: string,
createdAt: Date,
public total: number,
public status: string,
) {
super(id, createdAt);
}
}
// ✅ 使用Branded Types + 純資料物件
interface Brand<T extends string> {
readonly __brand: T;
}
type Branded<T, B extends string> = T & Brand<B>;
type UserId = Branded<string, 'UserId'>;
type OrderId = Branded<string, 'OrderId'>;
type ProductId = Branded<string, 'ProductId'>;
function createUserId(id: string): UserId {
if (!/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(id)) {
throw new Error('UserId的UUID格式無效');
}
return id as UserId;
}
function createOrderId(id: string): OrderId {
if (!/^ORD-\d{8}$/.test(id)) {
throw new Error('訂單ID格式無效');
}
return id as OrderId;
}
interface User {
readonly id: UserId;
readonly name: string;
readonly email: string;
readonly role: UserRole;
}
interface Order {
readonly id: OrderId;
readonly userId: UserId;
readonly total: number;
readonly status: OrderStatus;
readonly items: ReadonlyArray<OrderItem>;
}
type OrderStatus = 'pending' | 'processing' | 'shipped' | 'delivered' | 'cancelled';
interface OrderItem {
readonly productId: ProductId;
readonly quantity: number;
readonly unitPrice: number;
}
// ✅ Branded Types確保型別安全,零執行時開銷
function getUserOrders(userId: UserId, orders: ReadonlyArray<Order>): ReadonlyArray<Order> {
return orders.filter((o) => o.userId === userId);
}
// 編譯時防止ID混用
// getUserOrders(createOrderId('ORD-20260616'), orders); // ❌ 型別錯誤
// src/memory/const-assertions.ts - const assertions最佳化
// ❌ 寬泛型別導致編譯器建立大量型別實例
const ROUTES = {
users: '/api/users',
posts: '/api/posts',
comments: '/api/comments',
};
type RouteKey = keyof typeof ROUTES; // string
type RouteValue = typeof ROUTES[keyof typeof ROUTES]; // string
// ✅ const assertion鎖定字面量型別
const ROUTES = {
users: '/api/users',
posts: '/api/posts',
comments: '/api/comments',
} as const;
type RouteKey = keyof typeof ROUTES; // 'users' | 'posts' | 'comments'
type RouteValue = typeof ROUTES[keyof typeof ROUTES]; // '/api/users' | '/api/posts' | '/api/comments'
// ✅ const assertion + satisfies模式
const HTTP_METHODS = ['GET', 'POST', 'PUT', 'DELETE', 'PATCH'] as const;
type HttpMethod = typeof HTTP_METHODS[number]; // 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH'
const STATUS_CODES = {
OK: 200,
CREATED: 201,
BAD_REQUEST: 400,
UNAUTHORIZED: 401,
FORBIDDEN: 403,
NOT_FOUND: 404,
INTERNAL_ERROR: 500,
} as const;
type StatusCode = typeof STATUS_CODES[keyof typeof STATUS_CODES];
// 200 | 201 | 400 | 401 | 403 | 404 | 500
// ✅ 使用satisfies確保型別正確同時保留字面量型別
const API_CONFIG = {
baseUrl: 'https://api.example.com',
timeout: 5000,
retries: 3,
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
},
} as const satisfies Record<string, string | number | Record<string, string>>;
type ApiConfigKey = keyof typeof API_CONFIG; // 'baseUrl' | 'timeout' | 'retries' | 'headers'
// ✅ 凍結物件防止執行時修改
const IMMUTABLE_CONFIG = Object.freeze({
MAX_RETRIES: 3,
TIMEOUT_MS: 5000,
PAGE_SIZE: 20,
MAX_PAGE_SIZE: 100,
} as const);
type Config = typeof IMMUTABLE_CONFIG;
// src/memory/type-narrowing.ts - 高效型別收窄
// ❌ 多次重複型別守衛
function handleData(data: unknown): string {
if (typeof data === 'object' && data !== null && 'type' in data) {
if ((data as any).type === 'user') {
if (typeof (data as any).name === 'string') {
return (data as any).name;
}
}
if ((data as any).type === 'order') {
if (typeof (data as any).id === 'string') {
return (data as any).id;
}
}
}
return 'unknown';
}
// ✅ 使用判別聯合 + 型別守衛函式
interface UserData {
readonly type: 'user';
readonly name: string;
readonly email: string;
}
interface OrderData {
readonly type: 'order';
readonly id: string;
readonly total: number;
}
type AppData = UserData | OrderData;
function isUserData(data: AppData): data is UserData {
return data.type === 'user';
}
function isOrderData(data: AppData): data is OrderData {
return data.type === 'order';
}
function handleDataOptimized(data: AppData): string {
switch (data.type) {
case 'user':
return data.name;
case 'order':
return data.id;
}
}
// ✅ 使用型別謂詞快取
type TypeGuard<T> = (value: unknown) => value is T;
function createTypeGuard<T>(check: (value: unknown) => boolean): TypeGuard<T> {
return (value: unknown): value is T => check(value);
}
const isString = createTypeGuard<string>((v) => typeof v === 'string');
const isNumber = createTypeGuard<number>((v) => typeof v === 'number');
const isBoolean = createTypeGuard<boolean>((v) => typeof v === 'boolean');
function parseEnvValue<T>(
value: string | undefined,
guard: TypeGuard<T>,
defaultValue: T,
): T {
if (value === undefined) return defaultValue;
const parsed: unknown = value;
return guard(parsed) ? parsed : defaultValue;
}
const port = parseEnvValue(process.env.PORT, isNumber, 3000);
const debug = parseEnvValue(process.env.DEBUG, isBoolean, false);
模式5:生產監控與效能預算
// scripts/type-coverage.ts - 型別覆蓋率檢查
import { execSync } from 'child_process';
interface TypeCoverageResult {
totalFiles: number;
typedFiles: number;
coverage: number;
anyCount: number;
unsafeCount: number;
}
function checkTypeCoverage(): TypeCoverageResult {
try {
const output = execSync(
'npx type-coverage --detail --at-least 95',
{ encoding: 'utf-8', stdio: 'pipe' }
);
const match = output.match(/(\d+)\/(\d+) files/);
const anyMatch = output.match(/(\d+) any/);
const typedFiles = match ? parseInt(match[1], 10) : 0;
const totalFiles = match ? parseInt(match[2], 10) : 0;
const anyCount = anyMatch ? parseInt(anyMatch[1], 10) : 0;
return {
totalFiles,
typedFiles,
coverage: totalFiles > 0 ? (typedFiles / totalFiles) * 100 : 0,
anyCount,
unsafeCount: anyCount,
};
} catch {
return {
totalFiles: 0,
typedFiles: 0,
coverage: 0,
anyCount: -1,
unsafeCount: -1,
};
}
}
function reportTypeCoverage(): void {
const result = checkTypeCoverage();
console.log('=== 型別覆蓋率報告 ===\n');
console.log(`檔案: ${result.typedFiles}/${result.totalFiles}`);
console.log(`覆蓋率: ${result.coverage.toFixed(1)}%`);
console.log(`any數量: ${result.anyCount}`);
if (result.coverage < 95) {
console.warn('\n⚠️ 型別覆蓋率低於95%!');
console.warn(' 執行 `npx type-coverage --detail` 找出無型別程式碼。');
}
if (result.coverage < 80) {
console.error('❌ 型別覆蓋率嚴重不足!');
process.exit(1);
}
}
reportTypeCoverage();
// scripts/performance-budget.ts - 效能預算CI檢查
import { readFileSync } from 'fs';
import { gzipSync } from 'zlib';
interface PerformanceBudget {
maxBundleSize: number;
maxGzipSize: number;
maxTypeCheckTime: number;
minTypeCoverage: number;
maxChunkCount: number;
}
const DEFAULT_BUDGET: PerformanceBudget = {
maxBundleSize: 100 * 1024, // 100KB
maxGzipSize: 30 * 1024, // 30KB
maxTypeCheckTime: 15000, // 15s
minTypeCoverage: 95, // 95%
maxChunkCount: 10,
};
interface BudgetCheckResult {
metric: string;
value: number;
budget: number;
unit: string;
passed: boolean;
}
function checkBundleBudget(
bundlePath: string,
budget: PerformanceBudget = DEFAULT_BUDGET,
): BudgetCheckResult[] {
const results: BudgetCheckResult[] = [];
try {
const content = readFileSync(bundlePath);
const size = content.byteLength;
const gzipSize = gzipSync(content).byteLength;
results.push({
metric: 'Bundle大小',
value: size,
budget: budget.maxBundleSize,
unit: 'bytes',
passed: size <= budget.maxBundleSize,
});
results.push({
metric: 'Gzip大小',
value: gzipSize,
budget: budget.maxGzipSize,
unit: 'bytes',
passed: gzipSize <= budget.maxGzipSize,
});
} catch {
results.push({
metric: 'Bundle大小',
value: -1,
budget: budget.maxBundleSize,
unit: 'bytes',
passed: false,
});
}
return results;
}
function checkTypeCheckBudget(
budget: PerformanceBudget = DEFAULT_BUDGET,
): BudgetCheckResult {
const start = Date.now();
try {
execSync('npx tsc --noEmit', { encoding: 'utf-8', stdio: 'pipe' });
} catch {
// 型別錯誤另行處理
}
const duration = Date.now() - start;
return {
metric: '型別檢查時間',
value: duration,
budget: budget.maxTypeCheckTime,
unit: 'ms',
passed: duration <= budget.maxTypeCheckTime,
};
}
function runPerformanceBudget(): void {
console.log('=== 效能預算檢查 ===\n');
const results: BudgetCheckResult[] = [
...checkBundleBudget('dist/index.js'),
checkTypeCheckBudget(),
];
let allPassed = true;
for (const result of results) {
const status = result.passed ? '✓' : '✗';
const valueStr = result.value >= 0
? result.unit === 'bytes'
? `${(result.value / 1024).toFixed(1)}KB`
: `${result.value}${result.unit}`
: 'N/A';
const budgetStr = result.unit === 'bytes'
? `${(result.budget / 1024).toFixed(1)}KB`
: `${result.budget}${result.unit}`;
console.log(`${status} ${result.metric}: ${valueStr} (預算: ${budgetStr})`);
if (!result.passed) {
allPassed = false;
}
}
if (!allPassed) {
console.error('\n❌ 效能預算違規!');
process.exit(1);
} else {
console.log('\n✅ 所有效能預算達標。');
}
}
import { execSync } from 'child_process';
runPerformanceBudget();
// .github/workflows/typescript-perf.yml - CI效能監控
// GitHub Actions工作流程設定
/*
name: TypeScript Performance
on:
pull_request:
paths:
- '**.ts'
- '**.tsx'
- 'tsconfig*.json'
- 'package.json'
jobs:
performance:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: npm
- run: npm ci
- name: Type Check
run: |
START=$(date +%s%N)
npx tsc --noEmit
END=$(date +%s%N)
DURATION=$(( (END - START) / 1000000 ))
echo "Type check duration: ${DURATION}ms"
if [ $DURATION -gt 15000 ]; then
echo "::error::型別檢查超過15秒預算 (${DURATION}ms)"
exit 1
fi
- name: Type Coverage
run: |
npx type-coverage --at-least 95 || {
echo "::error::型別覆蓋率低於95%"
exit 1
}
- name: Bundle Analysis
run: |
npm run build
node scripts/performance-budget.ts
- name: Comment PR
uses: actions/github-script@v7
with:
script: |
const fs = require('fs');
const report = fs.readFileSync('perf-report.txt', 'utf8');
github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
body: `## 📊 TypeScript效能報告\n${report}`
});
*/
// src/monitoring/runtime-metrics.ts - 執行時效能指標收集
interface PerformanceMetric {
name: string;
value: number;
timestamp: number;
tags: Record<string, string>;
}
class PerformanceCollector {
private metrics: PerformanceMetric[] = [];
private readonly maxMetrics = 1000;
recordMetric(name: string, value: number, tags: Record<string, string> = {}): void {
this.metrics.push({
name,
value,
timestamp: Date.now(),
tags,
});
if (this.metrics.length > this.maxMetrics) {
this.metrics = this.metrics.slice(-this.maxMetrics);
}
}
timeAsync<T>(name: string, fn: () => Promise<T>, tags?: Record<string, string>): Promise<T> {
const start = performance.now();
return fn().finally(() => {
const duration = performance.now() - start;
this.recordMetric(name, duration, { ...tags, unit: 'ms' });
});
}
timeSync<T>(name: string, fn: () => T, tags?: Record<string, string>): T {
const start = performance.now();
try {
return fn();
} finally {
const duration = performance.now() - start;
this.recordMetric(name, duration, { ...tags, unit: 'ms' });
}
}
getMetrics(name?: string): PerformanceMetric[] {
if (name) {
return this.metrics.filter((m) => m.name === name);
}
return [...this.metrics];
}
getAverage(name: string): number {
const named = this.getMetrics(name);
if (named.length === 0) return 0;
return named.reduce((sum, m) => sum + m.value, 0) / named.length;
}
getP95(name: string): number {
const named = this.getMetrics(name);
if (named.length === 0) return 0;
const sorted = named.map((m) => m.value).sort((a, b) => a - b);
const index = Math.ceil(sorted.length * 0.95) - 1;
return sorted[index];
}
generateReport(): string {
const metricNames = [...new Set(this.metrics.map((m) => m.name))];
const lines: string[] = ['### 執行時效能報告\n'];
for (const name of metricNames) {
const avg = this.getAverage(name).toFixed(2);
const p95 = this.getP95(name).toFixed(2);
const count = this.getMetrics(name).length;
lines.push(`- **${name}**: avg=${avg}ms, p95=${p95}ms, n=${count}`);
}
return lines.join('\n');
}
}
const perfCollector = new PerformanceCollector();
async function fetchWithMetrics<T>(
url: string,
schema: z.ZodSchema<T>,
): Promise<T> {
return perfCollector.timeAsync(
'api.fetch',
async () => {
const response = await fetch(url);
const raw = await response.json();
const result = schema.safeParse(raw);
if (!result.success) {
throw new Error(`API校驗失敗: ${result.error.message}`);
}
return result.data;
},
{ url: new URL(url).pathname },
);
}
常見陷阱
陷阱1:skipLibCheck導致型別不安全
// ❌ 全域skipLibCheck關閉了所有.d.ts檢查
// tsconfig.json
{
"compilerOptions": {
"skipLibCheck": true // 關閉所有函式庫型別檢查
}
}
// ✅ skipLibCheck只跳過node_modules中的.d.ts,自己的.d.ts仍需檢查
// tsconfig.json
{
"compilerOptions": {
"skipLibCheck": true // 安全:只影響node_modules
}
}
// 自己的types目錄單獨設定strict檢查
// types/tsconfig.json
{
"compilerOptions": {
"strict": true,
"skipLibCheck": false
}
}
陷阱2:barrel exports破壞tree-shaking
// ❌ index.ts中re-export所有模組
// src/index.ts
export * from './users';
export * from './orders';
export * from './products';
export * from './analytics';
// ✅ 明確按需匯出
// src/index.ts
export { UserService } from './users/service';
export { OrderService } from './orders/service';
export type { User, CreateUserInput } from './users/types';
export type { Order, CreateOrderInput } from './orders/types';
// ✅ package.json中設定sideEffects
// package.json
{
"sideEffects": false,
"module": "./dist/index.mjs",
"exports": {
".": {
"import": "./dist/index.mjs",
"require": "./dist/index.cjs"
},
"./users": {
"import": "./dist/users.mjs"
}
}
}
陷阱3:Project References設定不完整
// ❌ 缺少composite選項導致參照失效
// packages/shared/tsconfig.json
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"outDir": "./dist"
// 缺少 "composite": true
}
}
// ✅ 被參照的專案必須啟用composite
// packages/shared/tsconfig.json
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"composite": true,
"declaration": true,
"declarationMap": true,
"outDir": "./dist",
"rootDir": "./src"
},
"include": ["src"]
}
陷阱4:執行時過度使用型別守衛
// ❌ 每次迴圈都做完整型別檢查
function processItems(items: unknown[]): Result[] {
return items.map((item) => {
const validated = ItemSchema.parse(item); // 每次都完整校驗
return transform(validated);
});
}
// ✅ 入口校驗一次,內部信任型別
function processItems(items: unknown[]): Result[] {
const validated = z.array(ItemSchema).parse(items); // 一次性校驗
return validated.map(transform);
}
// ✅ 或使用分層校驗
function processItems(items: unknown[]): Result[] {
return items.map((item) => {
if (!isPlainObject(item)) throw new Error('Invalid item');
return transform(item as Item);
});
}
function isPlainObject(value: unknown): value is Record<string, unknown> {
return typeof value === 'object' && value !== null && !Array.isArray(value);
}
陷阱5:忽略編譯器記憶體限制
// ❌ 大型專案預設1.5GB記憶體不夠
// 執行tsc時記憶體溢位
// FATAL ERROR: Reached heap limit Allocation failed - JavaScript heap out of memory
// ✅ 增加Node.js記憶體限制
// package.json
{
"scripts": {
"typecheck": "NODE_OPTIONS='--max-old-space-size=4096' tsc --build",
"typecheck:ci": "NODE_OPTIONS='--max-old-space-size=8192' tsc --build"
}
}
// ✅ 拆分專案減少單個編譯器記憶體需求
// 使用Project References將大專案拆分為多個子專案
// 每個子專案獨立編譯,記憶體峰值大幅降低
錯誤排查表
| 錯誤現象 | 可能原因 | 解決方案 |
|---|---|---|
tsc超過30秒 |
單一大專案無Project References | 拆分為多個子專案,啟用增量編譯 |
error TS6307 |
Project References缺少composite | 被參照專案新增"composite": true |
| bundle體積異常大 | barrel exports破壞tree-shaking | 改為明確命名匯出,設定sideEffects |
| HMR重新整理慢 | tsc全量重新檢查 | 啟用incremental和isolatedModules |
FATAL ERROR: Reached heap limit |
編譯器記憶體溢位 | 增加--max-old-space-size或拆分專案 |
| 型別推斷逾時 | 遞迴型別深度過大 | 限制遞迴深度,使用FlattenN<T, 3> |
Type instantiation is excessively deep |
條件型別巢狀過深 | 簡化型別邏輯,使用中間型別別名 |
| 執行時效能差 | 熱路徑頻繁型別守衛 | 分層校驗,熱路徑最小化檢查 |
skipLibCheck後型別錯誤 |
自訂.d.ts有問題 | 自訂types目錄單獨strict檢查 |
| bundle中包含未使用程式碼 | tsconfig importsNotUsedAsValues |
使用tsup/esbuild替代tsc打包 |
進階最佳化
編譯器效能調校
// tsconfig.perf.json - 極致效能設定
{
"extends": "./tsconfig.base.json",
"compilerOptions": {
"incremental": true,
"tsBuildInfoFile": "./.tsbuildinfo",
"skipLibCheck": true,
"isolatedModules": true,
"verbatimModuleSyntax": true,
"noEmit": true,
"assumeChangesOnlyAffectDirectDependencies": true,
"disableSourceOfProjectReferenceRedirect": true,
"disableReferencedProjectLoad": true
}
}
// scripts/tsc-watch.ts - 智慧增量監聽
import { watch } from 'chokidar';
import { execSync } from 'child_process';
import { relative, dirname, extname } from 'path';
import { performance } from 'perf_hooks';
interface WatchConfig {
include: string[];
exclude: string[];
debounceMs: number;
projects: Record<string, string[]>;
}
const watchConfig: WatchConfig = {
include: ['src/**/*.ts'],
exclude: ['src/**/*.test.ts', 'src/**/*.spec.ts'],
debounceMs: 300,
projects: {
'packages/shared': ['packages/shared/src/**/*.ts'],
'packages/core': ['packages/core/src/**/*.ts'],
'packages/app': ['packages/app/src/**/*.ts'],
},
};
let debounceTimer: NodeJS.Timeout | null = null;
function getAffectedProject(filePath: string): string | null {
for (const [project, patterns] of Object.entries(watchConfig.projects)) {
for (const pattern of patterns) {
const dir = pattern.replace('/**/*.ts', '');
if (filePath.startsWith(dir)) {
return project;
}
}
}
return null;
}
function typecheckAffected(filePath: string): void {
const project = getAffectedProject(filePath);
if (!project) return;
const start = performance.now();
try {
execSync(`npx tsc --build ${project}`, { encoding: 'utf-8', stdio: 'pipe' });
const duration = (performance.now() - start).toFixed(0);
console.log(`✓ ${project}: ${duration}ms`);
} catch (error: any) {
const duration = (performance.now() - start).toFixed(0);
console.error(`✗ ${project}: ${duration}ms`);
console.error(error.stdout || error.message);
}
}
const watcher = watch(watchConfig.include, {
ignored: watchConfig.exclude,
persistent: true,
ignoreInitial: true,
});
watcher.on('change', (filePath) => {
if (debounceTimer) clearTimeout(debounceTimer);
debounceTimer = setTimeout(() => {
typecheckAffected(filePath);
}, watchConfig.debounceMs);
});
watcher.on('add', (filePath) => {
typecheckAffected(filePath);
});
console.log('🔍 監聽TypeScript變更...');
Bundle最佳化策略
// scripts/advanced-bundle-opt.ts - 進階bundle最佳化
import { build, Plugin } from 'esbuild';
const sizeLimitPlugin: Plugin = {
name: 'size-limit',
setup(build) {
build.onEnd((result) => {
for (const output of result.outputFiles || []) {
const sizeKB = output.contents.byteLength / 1024;
const path = output.path;
if (sizeKB > 50) {
console.warn(`⚠️ ${path}: ${sizeKB.toFixed(1)}KB超過50KB區塊限制`);
}
}
});
},
};
const deadCodeEliminationPlugin: Plugin = {
name: 'dead-code-elimination',
setup(build) {
build.onEnd((result) => {
const content = result.outputFiles?.[0]?.text || '';
const deadCodePatterns = [
/if\s*\(\s*false\s*\)/g,
/if\s*\(\s*true\s*\)\s*\{/g,
/\/\/\s*@ts-ignore/g,
];
for (const pattern of deadCodePatterns) {
const matches = content.match(pattern);
if (matches && matches.length > 0) {
console.warn(`⚠️ 可能的死程式碼: ${matches.length}處${pattern}`);
}
}
});
},
};
async function buildOptimized(): Promise<void> {
await build({
entryPoints: ['src/index.ts'],
bundle: true,
minify: true,
treeShaking: true,
format: 'esm',
target: 'es2022',
outdir: 'dist',
metafile: true,
splitting: true,
plugins: [sizeLimitPlugin, deadCodeEliminationPlugin],
external: ['react', 'react-dom', 'zod'],
define: {
'process.env.NODE_ENV': '"production"',
},
logLevel: 'info',
});
}
buildOptimized();
執行時效能Profile
// src/monitoring/profiler.ts - 執行時效能分析器
interface ProfileEntry {
operation: string;
startTime: number;
endTime: number;
duration: number;
memoryBefore: number;
memoryAfter: number;
memoryDelta: number;
}
class RuntimeProfiler {
private entries: ProfileEntry[] = [];
private readonly maxEntries = 500;
profile<T>(operation: string, fn: () => T): T {
const memBefore = process.memoryUsage().heapUsed;
const startTime = performance.now();
try {
return fn();
} finally {
const endTime = performance.now();
const memAfter = process.memoryUsage().heapUsed;
this.entries.push({
operation,
startTime,
endTime,
duration: endTime - startTime,
memoryBefore: memBefore,
memoryAfter: memAfter,
memoryDelta: memAfter - memBefore,
});
if (this.entries.length > this.maxEntries) {
this.entries = this.entries.slice(-this.maxEntries);
}
}
}
async profileAsync<T>(operation: string, fn: () => Promise<T>): Promise<T> {
const memBefore = process.memoryUsage().heapUsed;
const startTime = performance.now();
try {
return await fn();
} finally {
const endTime = performance.now();
const memAfter = process.memoryUsage().heapUsed;
this.entries.push({
operation,
startTime,
endTime,
duration: endTime - startTime,
memoryBefore: memBefore,
memoryAfter: memAfter,
memoryDelta: memAfter - memBefore,
});
if (this.entries.length > this.maxEntries) {
this.entries = this.entries.slice(-this.maxEntries);
}
}
}
getHotspots(thresholdMs: number = 10): ProfileEntry[] {
return this.entries
.filter((e) => e.duration > thresholdMs)
.sort((a, b) => b.duration - a.duration);
}
getMemoryHotspots(thresholdBytes: number = 1024 * 100): ProfileEntry[] {
return this.entries
.filter((e) => e.memoryDelta > thresholdBytes)
.sort((a, b) => b.memoryDelta - a.memoryDelta);
}
generateReport(): string {
const lines: string[] = [
'### 執行時效能Profile\n',
'#### 時間熱點 (>10ms)',
];
for (const entry of this.getHotspots()) {
lines.push(
`- **${entry.operation}**: ${entry.duration.toFixed(2)}ms, ` +
`記憶體: ${(entry.memoryDelta / 1024).toFixed(1)}KB`
);
}
lines.push('\n#### 記憶體熱點 (>100KB)');
for (const entry of this.getMemoryHotspots()) {
lines.push(
`- **${entry.operation}**: ${(entry.memoryDelta / 1024).toFixed(1)}KB, ` +
`時間: ${entry.duration.toFixed(2)}ms`
);
}
return lines.join('\n');
}
}
const profiler = new RuntimeProfiler();
// 使用範例
const users = profiler.profile('fetchUsers', () => {
return Array.from({ length: 1000 }, (_, i) => ({
id: `user-${i}`,
name: `使用者 ${i}`,
email: `user${i}@example.com`,
}));
});
const result = profiler.profile('processUsers', () => {
return users.filter((u) => u.email.includes('example')).map((u) => u.name);
});
console.log(profiler.generateReport());
方案對比
| 最佳化維度 | tsc增量編譯 | Project References | tsup/esbuild | Vite | Turbopack |
|---|---|---|---|---|---|
| 編譯速度 | ★★★ | ★★★★ | ★★★★★ | ★★★★★ | ★★★★★ |
| 設定複雜度 | 低 | 中 | 低 | 低 | 低 |
| 增量支援 | 是 | 是 | 否(全量但極快) | 是 | 是 |
| 型別檢查 | 內建 | 內建 | 外置(tsc) | 外置(tsc) | 外置(tsc) |
| Tree-Shaking | 否 | 否 | 是 | 是 | 是 |
| Bundle大小 | N/A | N/A | 優 | 優 | 優 |
| 適用場景 | 函式庫開發 | 大型專案 | 函式庫/工具 | Web應用 | Next.js |
| 生產就緒 | ★★★★★ | ★★★★★ | ★★★★★ | ★★★★★ | ★★★★ |
| 執行時最佳化 | 型別守衛最佳化 | Branded Types | const assertions | 生成器 | 分層校驗 |
|---|---|---|---|---|---|
| 效能提升 | ★★★★ | ★★★ | ★★ | ★★★ | ★★★★ |
| 程式碼侵入 | 中 | 低 | 低 | 中 | 中 |
| 型別安全 | ★★★ | ★★★★★ | ★★★★★ | ★★★ | ★★★★ |
| 學習成本 | 低 | 低 | 低 | 中 | 低 |
| 適用場景 | 熱路徑 | ID型別 | 常數設定 | 大資料集 | API邊界 |
TypeScript效能最佳化不是「錦上添花」,而是生產專案的「生命線」。 編譯時用Project References和增量編譯提速10倍,建構時用tsup/esbuild替代tsc獲得tree-shaking,執行時避免熱路徑型別守衛,記憶體用Branded Types和const assertions減少膨脹,生產用效能預算和型別覆蓋率守住底線。效能不是最佳化出來的,是設計出來的——從專案第一天就配好Project References,從第一個模組就用明確匯出,從第一個型別就用const assertion。
推薦工具
本站提供瀏覽器本地工具,免註冊即可試用 →
#TypeScript性能#类型优化#编译加速#运行时优化#TypeScript 5.5#2026#前端工程