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 Type Check Report ===\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(`\nTotal: ${totalDuration}ms, ${totalErrors} errors`);
if (totalDuration > 10000) {
console.warn('\n⚠️ Type check exceeds 10s budget! Consider:');
console.warn(' - Adding more project references');
console.warn(' - Enabling skipLibCheck');
console.warn(' - Using 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 Analysis Report ===\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(` Size: ${sizeKB}KB (budget: ${BUDGET.maxBundleSize / 1024}KB)`);
console.log(` Gzip: ${gzipKB}KB (budget: ${BUDGET.maxGzipSize / 1024}KB)`);
console.log(` Modules: ${metrics.modules}`);
if (metrics.size > BUDGET.maxBundleSize) {
console.warn(` ⚠️ Bundle size exceeds budget!`);
hasViolation = true;
}
if (metrics.gzipSize > BUDGET.maxGzipSize) {
console.warn(` ⚠️ Gzip size exceeds budget!`);
hasViolation = true;
}
console.log();
}
if (hasViolation) {
console.error('❌ Performance budget violated!');
process.exit(1);
} else {
console.log('✅ All bundles within budget.');
}
}
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(`Validation failed: ${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('Invalid UUID format for UserId');
}
return id as UserId;
}
function createOrderId(id: string): OrderId {
if (!/^ORD-\d{8}$/.test(id)) {
throw new Error('Invalid order ID format');
}
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 + 满足模式
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('=== Type Coverage Report ===\n');
console.log(`Files: ${result.typedFiles}/${result.totalFiles}`);
console.log(`Coverage: ${result.coverage.toFixed(1)}%`);
console.log(`Any count: ${result.anyCount}`);
if (result.coverage < 95) {
console.warn('\n⚠️ Type coverage below 95%!');
console.warn(' Run `npx type-coverage --detail` to find untyped code.');
}
if (result.coverage < 80) {
console.error('❌ Type coverage critically low!');
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 Size',
value: size,
budget: budget.maxBundleSize,
unit: 'bytes',
passed: size <= budget.maxBundleSize,
});
results.push({
metric: 'Gzip Size',
value: gzipSize,
budget: budget.maxGzipSize,
unit: 'bytes',
passed: gzipSize <= budget.maxGzipSize,
});
} catch {
results.push({
metric: 'Bundle Size',
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 {
// type errors are handled separately
}
const duration = Date.now() - start;
return {
metric: 'Type Check Time',
value: duration,
budget: budget.maxTypeCheckTime,
unit: 'ms',
passed: duration <= budget.maxTypeCheckTime,
};
}
function runPerformanceBudget(): void {
console.log('=== Performance Budget Check ===\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} (budget: ${budgetStr})`);
if (!result.passed) {
allPassed = false;
}
}
if (!allPassed) {
console.error('\n❌ Performance budget violated!');
process.exit(1);
} else {
console.log('\n✅ All performance budgets met.');
}
}
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::Type check exceeds 15s budget (${DURATION}ms)"
exit 1
fi
- name: Type Coverage
run: |
npx type-coverage --at-least 95 || {
echo "::error::Type coverage below 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 Performance Report\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[] = ['### Runtime Performance Report\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 validation failed: ${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('🔍 Watching for TypeScript changes...');
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 exceeds 50KB chunk limit`);
}
}
});
},
};
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(`⚠️ Potential dead code: ${matches.length} occurrences of ${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[] = [
'### Runtime Performance Profile\n',
'#### Time Hotspots (>10ms)',
];
for (const entry of this.getHotspots()) {
lines.push(
`- **${entry.operation}**: ${entry.duration.toFixed(2)}ms, ` +
`memory: ${(entry.memoryDelta / 1024).toFixed(1)}KB`
);
}
lines.push('\n#### Memory Hotspots (>100KB)');
for (const entry of this.getMemoryHotspots()) {
lines.push(
`- **${entry.operation}**: ${(entry.memoryDelta / 1024).toFixed(1)}KB, ` +
`time: ${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: `User ${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#前端工程