VISALL改造记录
| 层级 | 当前方案 | 引入 Turborepo | 影响兼容性 |
|---|---|---|---|
| 任务调度 | npm scripts | Turborepo | ❌ 不影响 |
| 编译工具 | Rollup + esbuild + Babel | 保持不变 | ✅ 完全兼容 |
| 兼容性处理 | Babel + polyfills | 保持不变 | ✅ 完全兼容 |
| 输出格式 | ESM/UMD | 保持不变 | ✅ 完全兼容 |
pnpm workspace 基础
当前状态分析
VISALL项目目前配置了npm workspace,但未真正使用pnpm workspace:
现状:
package.json中有"workspaces": ["packages/*"]配置- 使用
package-lock.json,子包有独立node_modules - 实际是npm workspace,不是pnpm workspace
npm workspace vs pnpm workspace 核心区别
| 特性 | npm workspace | pnpm workspace |
|---|---|---|
| 依赖管理机制 | 符号链接 + 实际文件 | 纯符号链接 + 内容寻址存储 |
| 磁盘空间使用 | 依赖可能重复,占用大 | 严格去重,节省50-70%空间 |
| 安装速度 | 中等(有重复下载) | 快(去重+硬链接) |
| 依赖解析 | 外部依赖可能重复安装 | 外部依赖严格去重 |
| 配置方式 | package.json中配置 | pnpm-workspace.yaml文件 |
实际结构对比
当前npm workspace结构:
1 | |
理想的pnpm workspace结构:
1 | |
命令对比
| 功能 | npm workspace | pnpm workspace |
|---|---|---|
| 安装所有依赖 | npm install |
pnpm install |
| 安装到特定包 | npm install lodash -w table |
pnpm add lodash --filter table |
| 运行脚本 | npm run build -w table |
pnpm run --filter table build |
| 查看依赖树 | npm ls |
pnpm why <package> |
对VISALL的影响
当前问题:
- 1.4GB磁盘空间占用过大
- 子包依赖重复安装
- 安装速度慢
pnpm workspace优势:
- 节省50-70%磁盘空间(约700MB-1GB)
- 更快的安装速度
- 更好的依赖管理
迁移方案
1 | |
流程
pnpm workspace
构建工具层面:
scripts/esbuild-watch.cjs 脚本完全可用
concurrently 并行执行功能正常
cross-env 环境变量设置正常
browsersync 热重载功能正常
Turborepo
当前构建流程
graph TD
Start([开始构建]) --> PreCheck{预检查}
PreCheck -->|✅ 通过| TestPhase["测试阶段<br/>~15秒"]
PreCheck -->|❌ 失败| Error1[构建失败]
TestPhase --> Jest["Jest 单元测试<br/>jest --passWithNoTests"]
Jest -->|✅ 测试通过| TypePhase["类型生成阶段<br/>~25秒"]
Jest -->|❌ 测试失败| Error2["构建失败"]
TypePhase --> TSC["TypeScript 编译器<br/>tsc --emitDeclarationOnly"]
TSC --> TypeFiles["生成类型定义<br/>types/*.d.ts"]
TypeFiles --> MainBuild["主项目打包阶段<br/>~45秒"]
MainBuild --> RollupConfig["加载 Rollup 配置<br/>rollup.config.js"]
RollupConfig --> InputFiles["读取入口文件<br/>src/index.ts<br/>src/index.common.ts"]
InputFiles --> Transform["代码转换管道"]
Transform --> TSCompile["TypeScript 编译<br/>esbuild + babel"]
TSCompile --> ModuleResolve["模块解析<br/>@rollup/plugin-node-resolve"]
ModuleResolve --> CommonJSTransform["CommonJS 转换<br/>@rollup/plugin-commonjs"]
CommonJSTransform --> AssetProcess["资源处理<br/>@rollup/plugin-url"]
AssetProcess --> StyleProcess["样式处理<br/>rollup-plugin-styles"]
StyleProcess --> Optimize["代码优化<br/>Tree Shaking + Terser"]
Optimize --> MainOutput["主项目输出"]
MainOutput --> ESMBundle["bundle.esm.js<br/>bundle.esm.min.js"]
MainOutput --> UMDBundle["bundle.umd.js<br/>bundle.umd.min.js"]
MainOutput --> CommonBundle["common.esm.js<br/>common.umd.js"]
MainBuild --> ParallelPackages["子包并行构建<br/>~35秒"]
ParallelPackages --> TablePkg["Table 包构建<br/>~12秒"]
ParallelPackages --> DonutPkg["Operational-Donut 包<br/>~8秒"]
ParallelPackages --> TimelinePkg["Timeline 包<br/>~10秒"]
ParallelPackages --> TextPkg["Text 包<br/>~5秒"]
TablePkg --> TableRollup["Rollup 构建<br/>rollup -c"]
TableRollup --> TableOutput["dist/index.js<br/>dist/index.esm.js<br/>dist/index.d.ts"]
DonutPkg --> ViteBuild["Vite 构建<br/>vite build"]
ViteBuild --> DonutOutput["Vite 优化产物"]
TimelinePkg --> TimelineRollup["Rollup 构建<br/>rollup -c"]
TimelineRollup --> TimelineOutput["Timeline 产物"]
TextPkg --> TextRollup["Rollup 构建<br/>rollup -c"]
TextRollup --> TextOutput["Text 产物"]
TableOutput --> Success["构建成功<br/>总耗时: ~120秒"]
DonutOutput --> Success["构建成功<br/>总耗时: ~120秒"]
TimelineOutput --> Success["构建成功<br/>总耗时: ~120秒"]
TextOutput --> Success["构建成功<br/>总耗时: ~120秒"]
ESMBundle --> Success["构建成功<br/>总耗时: ~120秒"]
UMDBundle --> Success["构建成功<br/>总耗时: ~120秒"]
CommonBundle --> Success["构建成功<br/>总耗时: ~120秒"]
TypeFiles --> Success["构建成功<br/>总耗时: ~120秒"]
Success --> Publish["是否发布?"]
Publish -->|是| PublishFlow["发布流程<br/>pnpm publish --recursive"]
Publish -->|否| End(["构建完成"])
PublishFlow --> End
%% 样式定义
classDef phaseBox fill:#e1f5fe,stroke:#01579b,stroke-width:2px
classDef toolBox fill:#f3e5f5,stroke:#4a148c,stroke-width:2px
classDef outputBox fill:#e8f5e8,stroke:#1b5e20,stroke-width:2px
classDef errorBox fill:#ffebee,stroke:#b71c1c,stroke-width:2px
classDef timeBox fill:#fff3e0,stroke:#e65100,stroke-width:2px
class TestPhase,TypePhase,MainBuild,ParallelPackages phaseBox
class Jest,TSC,RollupConfig,TSCompile,ModuleResolve,CommonJSTransform,AssetProcess,StyleProcess,Optimize,TableRollup,ViteBuild,TimelineRollup,TextRollup toolBox
class TypeFiles,ESMBundle,UMDBundle,CommonBundle,TableOutput,DonutOutput,TimelineOutput,TextOutput outputBox
class Error1,Error2 errorBox
class Success timeBox
Turborepo流程图
graph TD
subgraph Current [当前构建方式 - 串行执行 120秒]
S1[开始] --> T1["测试阶段<br/>15秒"]
T1 --> TY1["类型生成<br/>25秒"]
TY1 --> M1["主项目打包<br/>45秒"]
M1 --> P1["Table包构建<br/>12秒"]
P1 --> P2["Donut包构建<br/>8秒"]
P2 --> P3["Timeline包构建<br/>10秒"]
P3 --> P4["Text包构建<br/>5秒"]
P4 --> E1["构建完成<br/>总计: 120秒"]
end
subgraph Optimized [Turborepo优化后 - 并行+缓存 40秒]
S2[开始] --> Cache["检查缓存"]
Cache -->|缓存命中| Skip["跳过构建<br/>~2秒"]
Cache -->|缓存未命中| T2["测试阶段<br/>8秒"]
T2 --> TY2["类型生成<br/>5秒"]
TY2 --> M2["主项目打包<br/>15秒"]
M2 --> PT1["Table包<br/>5秒"]
M2 --> PT2["Donut包<br/>3秒"]
M2 --> PT3["Timeline包<br/>4秒"]
M2 --> PT4["Text包<br/>2秒"]
PT1 --> E2["构建完成<br/>总计: 40秒"]
PT2 --> E2["构建完成<br/>总计: 40秒"]
PT3 --> E2["构建完成<br/>总计: 40秒"]
PT4 --> E2["构建完成<br/>总计: 40秒"]
Skip --> E2["构建完成<br/>总计: 40秒"]
end
subgraph Benefits [性能提升效果]
B1["🚀 首次构建<br/>120s → 80s<br/>⬇️ 33%"]
B2["⚡ 增量构建<br/>45s → 6s<br/>⬇️ 87%"]
B3["💾 缓存命中<br/>120s → 2s<br/>⬇️ 98%"]
B4["🔄 并行执行<br/>串行 → 4路并行<br/>⬆️ 400%"]
end
%% 连接线
E1 -.->|优化对比| B1
E2 -.->|性能提升| B2
%% 样式定义
classDef currentBox fill:#ffcdd2,stroke:#d32f2f,stroke-width:2px,color:#000
classDef optimizedBox fill:#c8e6c9,stroke:#388e3c,stroke-width:2px,color:#000
classDef benefitBox fill:#fff3e0,stroke:#f57c00,stroke-width:2px,color:#000
classDef parallelNode fill:#e1bee7,stroke:#8e24aa,stroke-width:2px,color:#000
classDef cacheNode fill:#bbdefb,stroke:#1976d2,stroke-width:2px,color:#000
class S1,T1,TY1,M1,P1,P2,P3,P4,E1 currentBox
class S2,T2,TY2,M2,E2 optimizedBox
class PT1,PT2,PT3,PT4 parallelNode
class Cache,Skip cacheNode
class B1,B2,B3,B4 benefitBox
“构建” = 完整的代码转换和打包流程:
代码验证:测试 + 类型检查
代码转换:TypeScript → JavaScript
模块打包:多文件 → 单一/多个输出文件
资源优化:压缩、Tree shaking、资源内联
格式输出:生成多种模块格式(ESM/UMD)
这就是为什么 120秒的构建时间成为瓶颈,也是 Turborepo 能够显著优化的原因 - 通过缓存和并行执行避免重复的转换和打包操作。
🚫 为什么不是可选的?
1. 构建性能已成为瓶颈
120秒的构建时间严重影响开发效率
没有缓存机制,重复工作严重
CI/CD 时间过长影响部署
2. 开发体验问题突出
跨包调试困难
热更新慢且不稳定
新人上手成本高(1个月)
3. 项目正在快速发展
50+图表组件持续增长
团队规模扩大
技术债务累积
🎯 总结
对于 VISALL 项目,Turborepo 不是可选的优化,而是必需的基础设施。原因如下:
项目规模:13个包的 monorepo 已达到必需使用的规模
性能瓶颈:120秒构建时间严重影响开发效率
发展趋势:项目持续增长,问题会进一步恶化
成本效益:实施成本2个月,收益立即且持续
建议:立即开始 Turborepo 改造,按照项目已有的10周计划执行。这不是一个技术选型问题,而是一个项目发展的必然需求。