前端Monorepo实践分享
在前端开发中,Monorepo(单一仓库)实践逐渐成为了一种趋势,许多大型科技公司如Google和Meta等都在实践中采用了Monorepo模式,并取得了显著的效果。例如,Google内部自研了Piper,能够支持基于文件级别的访问控制列表,对代码进行了更加精细化的管理。鉴于Monorepo实践在大型科技公司中的成功应用,以及它所带来的诸多优势,相信在未来,随着前端技术的持续进步和项目规模的不断扩大,Monorepo实践极有可能成为前端开发领域的主流模式之一。
01 项目背景
原供应链前端工程代码采用多个代码仓库进行多工程的代码管理,并独立构建打包,随着业务发展,此方式暴露出如下现象:
-
无法共同维护相同业务组件库和工具库;
-
代码检测规范无法共享;
-
项目开发构建包管理分散,因重复安装依赖,导致调试和构建打包耗时长;
-
同类业务需求,需手动在多个工程代码进行复制同步,影响开发效率。
为了实现业务组件、配置和样式在不同工程间的共享,项目组前端团队引入了Monorepo技术。在Monorepo模式中,将业务组件置于共享目录下,便可通过源码引入实现代码共享,统一构建流程和代码规范,享受业务组件复用模式带来的开发便利,提高开发效率和代码维护质量。下表列出了Monorepo方式和多仓库方式的优缺点比较:
Monorepo Auto业务组件库基于中心统一BeP组件库进行开发实现,集成Auto组件的框架,可以保证UI交互一致性的同时,实现使用JSON配置来生成页面,从而减少页面开发工作量,极大提升效率。
02 工程架构
项目组采用"PNPM+Workspace"方案来实现Monorepo模式下跨应用工程架构。此工程架构的优势体现在:
-
开发学习成本低;
-
已有工程改造成本低;
-
业务组件引用简单。
1 构建共享框架
1.1 Monorepo工程架构
Monorepo工程架构如下:
工程目录设计如下:
1.2 Monorepo初始化子项目方式
Monorepo主工程初始化子项目,可以采用下面两种方式中的一种:
方式1:合并代码库。多工程代码仓库子项目代码同步到主工程目录下面即可。
方式2:代码库分离。如果是已多工程代码仓库方式,可以通过git submodule命令进行子项目添加,这样可以达到再业务并行迭代的时候进行渐进式升级。
1.3 Monorepo主工程配置
Monorepo主工程配置安装全局依赖和对应子工程启动命令,使用pnpm安装依赖:
pnpm add vue@2.7.14 vuex@3.6.2 vue-router@3.6.5 pp-bgyfw-ui@1.0.31 -wD
在package.json中配置好对应子工程启动命令:
{
"name":
"scm-mono-apps",
"version": "1.0.0",
"scripts": {
"op:dev": "pnpm --filter @scm-web/operation run serve",
}
}
启用对应子工程过程及效果:
2 构建共享业务组件库
通过构建共享业务组件库,实现了跨多个项目的复用性。在本项目实践中,项目组开发了包括AutoForm、AutoInfo、AutoModel、AutoTab、AutoTable、AutoOperation等组件,形成了集中管理和共享一套公共的UI组件库。这些组件通过统一的样式和行为规范,显著增强了不同应用端用户体验的一致性。
2.1 Monorepo共享组件框架
Monorepo共享组件架构如下:
组件库目录设计如下:
2.2 业务组件库工程共享实现
在主仓的packages/components/package.json文件配置如下:
{
"name": "@scm-web/components", // 提供给子项目工程安装使用
"version": "1.0.0",
"main": "index.js", // 在这里引入index.js文件
...
}
项目组在packages/components/index.js设立统一入口点,用于集中导出所有公共业务组件。为了高效管理和使用这些组件,项目组在Monorepo根目录中使用pnpm安装@scm-web/components包,然后在项目中引入所需的业务组件,即可快速集成和使用这些高质量的UI组件。
// packages/index.js
export { default as AutoForm } from './AutoForm/index.vue';
export { default as AutoTable } from './AutoTable/index.vue';
....
3 构建共享配置及规范
项目组通过引入Monorepo工程框架的工具链,实现了统一工程配置、统一代码检测规则及统一代码规范约束。同时通过采用原子化CSS引擎,增加开发样式编写效率,提升了整体代码的可读性、可维护性和可复用性。
以下是详细实现过程:
首先,主工程使用pnpm安装对应的开发业务工具链:eslint+prettier+husky+lint-staged+ commitlint+tailwindcss。各业务工具链工作原理见下图:
然后,配置相关业务工具链文件及工具目录设计:
最后,配置pre-commit命令实现自动提交检测和格式化。
4 构建共享工具类
通过在Monorepo中实现工具类的共享,多个应用端只需维护一套工具类代码,可达到工具一致性。Monorepo主仓根目录创建utils目录如下:
在主仓packages/utils/index.js入口文件中将工具类进行整合,并通过修改packages/utils/package.json文件,进行声明式共享业务工具包配置,确保各应用端能够统一引用和维护这套工具类。
export * from './lib/priceConversion';
export * from './lib/requestHook';
...
03 最佳实践
1 通用业务组件共享案例
在Monorepo仓库根目录执行相关命令安装组件,各子项目工程即可使用共享业务组件库。
// 供应链平台端工程添加业务组件库
pnpm add @scm-web/components --filter @scm-web/operation
// 供应链供应商工程添加业务组件库
pnpm add @scm-web/components --filter @scm-web/supplier
执行完pnpm添加命令后,再在对应的子工程/package.json中配置依赖。
通过JSON配置+@scm-web/components,生成交互页面流程见下图:
实现原理图解如下:
2 tailwindcss配置规范共享案例
首先,Monorepo主仓库目安装tailwindcss相关包依赖。接着,各子项目配置依赖父tailwind.config.js。然后,配置自定义原子化CSS index.css,在工程代码根目录main.js引入自定义样式
tailwindcss工作原理见下图:
通过定制的tailwindcss claa声明即可快速生成页面样式布局流程如下:
综上可见,在页面上使用原子化CSS后,摒弃了冗余繁琐的style。这种方式降低了学习成本低,提升了开发效率。
3 业务工具类共享案例
Monorepo仓库根目录执行相关命令安装utils包,并在相应子工程/package.json配置变更,对应的子仓即可识别到工作区的"@scm-web/utils"包。
各子项目可引入共享的utils包,参考如下引入:
import { createActions } from '@scm-web/utils';//使用utils包
export const usePurchaseStore = defineStore({
id: 'order', actions: {
// 使用共享工具类方法实现工厂自动生成接口列表模式
...createActions(purchaseApi),
},
});
04 项目效果
通过采用Monorepo相关技术栈升级工程架构,在供应链平台的项目上,解决了以下实际业务问题:
-
使用pnpm+workspace建立Monorepo框架,支持多项目间代码共享,提高开发效率和部署速度。
-
借助Monorepo的共享特性,封装了Auto组件库,实现多项目间业务组件共享,确保交互体验一致性和高效开发模式。
-
通过Monorepo的代码管理方式,实现了代码规范和配置的共享,提升了多工程维护效率和代码约束的一致性。
供应链平台端-主商品管理、供应商商品管理:
供应链供应商端-商品管理:
本文作者:
黄启聪:碧桂园服务前端开发高级工程师
杨玉梅:碧桂园服务前端开发高级工程师
伍诗聪:碧桂园服务前端开发主任工程师
胡天海:碧桂园服务产品研发高级工程师
指导人:
古松青:碧桂园服务技术总监
张旺其:碧桂园服务产品研发专家