Monorepo 在低代码平台中的实践:pnpm 工作区
结论先行:低代码平台天然多包——设计器、画布、渲染器、插件、物料、工具链互相调用,Monorepo(单体仓库)是让它们保持原子提交与版本一致的最优解。pnpm 工作区(Workspace)凭硬链接省磁盘、严格依赖防幽灵依赖(Phantom Dependencies),配合增量构建可把多包仓库的构建时间压回单体水平。拆包先拆边界,再谈工具。
低代码平台(Low-Code Platform)是典型的"包膨胀"重灾区:编辑器内核、几十个插件、多套物料库、多端渲染器、CLI 工具……如果拆成十几个独立仓库,一次跨包改动就要发十几个版本。本文讲 Monorepo 怎么治这个病。
为什么低代码平台适合 Monorepo
判断标准一条:包之间是否高频联动变更。低代码平台恰好是——物料协议改一个字段,属性面板插件、渲染器、校验器要同步修改。多仓库模式下这叫"跨仓同步地狱";Monorepo 下这就是一次普通提交。附带收益还有三条:原子提交保证仓内自洽、重构工具可全局执行、新人 clone 一次就能跑通全链路。
当然,Monorepo 也有代价:仓库权限只能整体控制、CI 流水线需要专门设计、包间依赖图会随时间复杂化。低代码平台的核心代码通常由同一团队维护,权限隔离需求弱,这些代价项几乎都能接受——这正是它适合低代码平台而非所有项目的根本原因。
包拆分:按职责切,不按层切
以一个 Vue3 加 TypeScript 的低代码平台为例,参考目录如下:
packages/
core/ # DSL 类型定义、Schema 校验、表达式引擎
renderer/ # 渲染器:Schema → 组件树
designer/ # 设计器外壳:画布、状态管理
plugins/ # 插件:属性、样式、事件、数据源……
materials/ # 物料:各组件库适配
cli/ # 脚手架与构建工具两条边界纪律:依赖方向单向(designer 可以依赖 core,core 绝不反向依赖 UI 层);每个包有独立的 package.json 入口与类型导出。边界含糊的包,迟早变成"公共垃圾场"。
拆分粒度上有个实用判断:一个包的变更频率与复用频率成正比,才值得独立成包。渲染器与 core 几乎每次同步改动,早期可以合并为一个包,等稳定后再拆——先合后拆的成本远低于先拆后合。
pnpm 工作区的两大机制
硬链接存储:pnpm 把所有依赖在全局只存一份(内容寻址存储),各项目通过硬链接引用——低代码平台动辄几百个传递依赖,磁盘占用可缩减一半以上,安装速度也明显快于 npm。
严格依赖:pnpm 默认只允许包访问自己声明过的依赖,直接治好"幽灵依赖"——代码里 import 了没写进 package.json 的包。npm 扁平化安装下这类代码能跑,一旦上游重构就全线崩溃;在几十个包的 Monorepo 里,幽灵依赖是事故高频源。
# pnpm-workspace.yaml
packages:
- "packages/*"
- "materials/*"增量构建与版本发布
包一多,全量构建不可接受。通行方案:构建编排工具(如 Turborepo)按依赖图做拓扑排序,只重建受影响的包,并缓存构建产物——改一个插件,只重跑它自己和依赖它的两个包。版本发布用变更集(Changesets)类工具:按包收集变更说明,统一生成版本号与更新日志,避免手动维护十几个版本号的心智负担。除了构建缓存,任务编排还应区分开发态与发布态:开发态追求热更新与最小构建范围,发布态追求产物确定性与可复现(锁定依赖版本与构建环境),两套目标不要混在一起优化。
猫拽的实践参考
以猫拽低代码(CatPull)为例,其整体采用 pnpm Monorepo 架构,配合 Vue3 加 Vite 加 TypeScript 技术栈:Vite 的按需编译让开发态各包秒级热更新,TypeScript 项目引用(Project References)让跨包类型跳转如同单文件。对想自研低代码平台的团队,这套组合是当前社区验证最充分的起步配置。
FAQs
Q1:Monorepo 仓库会不会太大、clone 太慢?
A:纯代码仓库通常可控,要防的是构建产物与二进制文件入库。配合 Git 浅克隆与稀疏检出,大仓库的获取成本已经很低。
Q2:什么时候不该用 Monorepo?
A:包之间几乎无联动、发布节奏完全独立、需要严格隔离访问权限时,多仓库更合适。Monorepo 治的是"联动变更成本",不是万能架构。
Q3:pnpm 换 npm 或 yarn 行不行?
A:功能上可行,但代价明确:磁盘占用回升、幽灵依赖重新放开、安装速度下降。新项目没有历史包袱,直接选 pnpm 收益最确定。
