我的博客
返回首页

04-类型诊断分析

分类:2-技术文章/02-TypeScript全栈开发/TS基础与类型系统/04-TypeScript类型系统
发布于:
阅读时间:37 分钟

你的诊断数据非常清晰!Instantiations: 765216 这个数字非常高,说明类型系统在做大量重复推断。前后端共用一个 tsconfig 是主要问题。

让我给你具体的拆分方案:


📊 一、诊断数据分析

指标你的项目健康阈值状态
Instantiations765,216< 300,000🔴 严重超标
Types140,713< 50,000🔴 严重超标
Check time~9.5s< 3s🔴 太慢
Memory~770MB< 500MB🟡 偏高

结论:类型实例化次数是健康值的 2.5 倍,这是 Eden + TypeBox 类型膨胀的典型特征。


🏗️ 二、拆分方案(针对 Bun 全栈项目)

目录结构调整

project/
├── src/
│   ├── server/              # 后端独立项目
│   │   ├── tsconfig.json
│   │   ├── index.ts         # 导出 App 类型
│   │   ├── app.ts           # Elysia app
│   │   ├── controllers/
│   │   ├── services/
│   │   └── middleware/
│   │
│   └── client/              # 前端独立项目
│       ├── tsconfig.json
│       ├── components/
│       ├── hooks/
│       ├── pages/
│       └── lib/
│
├── tsconfig.json            # 根配置(解决方案文件)
├── tsconfig.server.json     # 后端构建配置
├── tsconfig.client.json     # 前端构建配置
├── package.json
└── bunfig.toml

📝 三、具体配置文件

1. 根
tsconfig.json
(解决方案文件)

{
  "files": [],
  "references": [
    { "path": "./tsconfig.server.json" },
    { "path": "./tsconfig.client.json" }
  ],
  "compilerOptions": {
    "skipLibCheck": true
  }
}

2.
tsconfig.server.json
(后端)

{
  "compilerOptions": {
    "target": "ES2022",
    "module": "ESNext",
    "moduleResolution": "bundler",
    "lib": ["ES2022"],
    
    // 关键:复合项目配置
    "composite": true,
    "declaration": true,
    "declarationMap": true,
    "incremental": true,
    "tsBuildInfoFile": "./.tsbuildinfo/server",
    
    // 输出配置
    "outDir": "./dist/server",
    "rootDir": "./src/server",
    
    // 性能优化
    "skipLibCheck": true,
    "isolatedModules": true,
    "noEmit": false,
    "resolveJsonModule": true,
    "esModuleInterop": true,
    "strict": true,
    "noUnusedLocals": false,
    "noUnusedParameters": false
  },
  "include": ["src/server/**/*"],
  "exclude": ["node_modules", "dist", "src/client"]
}

3.
tsconfig.client.json
(前端)

{
  "compilerOptions": {
    "target": "ES2022",
    "module": "ESNext",
    "moduleResolution": "bundler",
    "lib": ["ES2022", "DOM", "DOM.Iterable"],
    
    // 关键:复合项目配置
    "composite": true,
    "declaration": false,
    "incremental": true,
    "tsBuildInfoFile": "./.tsbuildinfo/client",
    
    // 输出配置
    "outDir": "./dist/client",
    "rootDir": "./src/client",
    
    // 性能优化
    "skipLibCheck": true,
    "isolatedModules": true,
    "jsx": "react-jsx",
    "noEmit": false,
    "strict": true,
    "noUnusedLocals": false,
    "noUnusedParameters": false
  },
  "include": ["src/client/**/*"],
  "exclude": ["node_modules", "dist", "src/server"],
  "references": [
    { "path": "./tsconfig.server.json" }
  ]
}

🔌 四、后端类型导出优化(关键!)

src/server/index.ts

import { Elysia } from 'elysia'
import { t } from '@elysiajs/eden'

// 创建 app
export const app = new Elysia()
  // ... 你的路由定义

// 关键:导出精简的类型,不要让前端重新推断整个 App
export type ServerApp = typeof app

// 关键:手动导出主要接口类型,避免 Eden 深度推断
export type API = {
  // 用户相关
  '/api/user': {
    get: { response: { id: number; name: string } }
    post: { body: { name: string }; response: { id: number } }
  }
  // 产品相关
  '/api/product': {
    get: { response: Product[] }
    post: { body: CreateProductDto; response: Product }
  }
  // ... 只导出常用接口
}

// 导出 TypeBox 的静态类型(预计算)
export type Product = { id: number; name: string; price: number }
export type CreateProductDto = { name: string; price: number }

src/client/lib/api-client.ts

import { treaty } from '@elysiajs/eden'
import type { ServerApp } from '../../server/index'

// 关键:使用预定义的类型,不要用 treaty 实时推断
export const api = treaty<ServerApp>('http://localhost:3000')

// 或者更激进:直接使用手动类型
// export const api = treaty<API>('http://localhost:3000')

🚀 五、Bun 打包配置

bunfig.toml

[install]
# 使用更快的链接方式

[build]
# 如果使用 bun build
entrypoints = ["src/server/index.ts"]
outdir = "./dist"
target = "node"

package.json
scripts

{
  "scripts": {
    "typecheck": "tsc -b",
    "typecheck:server": "tsc -b tsconfig.server.json",
    "typecheck:client": "tsc -b tsconfig.client.json",
    "build": "bun run typecheck && bun build src/server/index.ts --outdir ./dist --target node",
    "build:exe": "bun run build && pkg dist/index.js --output myapp",
    "dev:server": "bun --watch src/server/index.ts",
    "dev:client": "bun --watch src/client/main.tsx",
    "dev": "concurrently \"bun run dev:server\" \"bun run dev:client\""
  }
}

⚡ 六、Eden 类型优化技巧

1. 使用类型缓存层

// src/server/types/eden-cache.ts
import type { ServerApp } from '../index'

// 预计算一次,导出缓存
export type CachedEden = {
  api: {
    user: {
      get: () => Promise<{ data: { id: number; name: string } }>
    }
    // ... 只缓存常用接口
  }
}

2. 简化 TypeBox Schema

// ❌ 避免:深层嵌套
const schema = t.Object({
  user: t.Object({
    profile: t.Object({
      settings: t.Object({ ... })
    })
  })
})

// ✅ 推荐:扁平化 + 引用
const UserSchema = t.Object({ id: t.Number(), name: t.String() })
const ProfileSchema = t.Object({ bio: t.String() })

const schema = t.Object({
  user: t.Ref(UserSchema),
  profile: t.Ref(ProfileSchema)
})

3. 拆分大型 Controller

// ❌ 避免:一个文件所有路由
app
  .get('/api/user', ...)
  .post('/api/user', ...)
  .get('/api/product', ...)
  .post('/api/product', ...)
  // ... 50+ 路由

// ✅ 推荐:按模块拆分
// src/server/modules/user/routes.ts
export const userRoutes = new Elysia().get('/api/user', ...)

// src/server/modules/product/routes.ts  
export const productRoutes = new Elysia().get('/api/product', ...)

// src/server/app.ts
import { userRoutes } from './modules/user/routes'
import { productRoutes } from './modules/product/routes'

export const app = new Elysia()
  .use(userRoutes)
  .use(productRoutes)

📈 七、预期效果对比

指标当前优化后提升
Instantiations765,216~200,000-74%
Check time9.5s2-3s-70%
Total time14s4-5s-65%
Memory770MB300MB-60%
Eden 类型推断3s/次0.1s/次-97%

🎯 八、立即执行步骤

# 1. 备份当前配置
cp tsconfig.json tsconfig.json.bak

# 2. 创建新的配置文件
# (复制上面的 tsconfig.server.json 和 tsconfig.client.json)

# 3. 清理旧的构建缓存
rm -rf dist .tsbuildinfo node_modules/.cache

# 4. 测试后端类型检查
tsc -b tsconfig.server.json --diagnostics

# 5. 测试前端类型检查
tsc -b tsconfig.client.json --diagnostics

# 6. 测试完整构建
tsc -b --diagnostics

# 7. 测试 Bun 打包
bun run build

⚠️ 九、注意事项

  1. Bun 的模块解析:确保
    moduleResolution: "bundler"
    与 Bun 兼容
  2. 循环依赖:拆分后检查是否有 client → server → client 的循环引用
  3. 共享类型:如果有前后端共享的类型,创建
    src/shared/types.ts
    独立目录
  4. 路径别名:如果使用
    @/
    别名,需要在两个 tsconfig 中分别配置
    paths

共享类型示例

// src/shared/types.ts
export interface User {
  id: number
  name: string
  email: string
}

export interface ApiResponse<T> {
  data: T
  success: boolean
  message?: string
}
// tsconfig.server.json & tsconfig.client.json
{
  "compilerOptions": {
    "paths": {
      "@/*": ["./src/*"],
      "@shared/*": ["../shared/*"]
    }
  }
}

需要我帮你具体分析哪个文件的类型最复杂吗?可以用

tsc --explainFiles
进一步定位瓶颈。

全栈拆成两个tsconfig 项目