Monorepo单仓库多工程0-1搭建全流程
在做项目前思考几个问题:
-
团队并非一个人单打独斗,那么如何设计工作流程,如何打造一个众人皆赞的项目根基?
-
项目依赖纷繁复杂,如何做好依赖管理和公共库管理?
-
如何深入理解框架,真正做到框架的精通和技术选型的准确拿捏?
-
从最基本的网络请求库说起,如何设计一个稳定灵活的多端 Fetch 库?
-
如何借力 Low Code / No Code 技术,实现越来越智能的应用搭建方案?
-
如何统一中后台项目架构,实现跨业务线的产研效率提升?
-
如何开发设计一套适合业务的组件库,封装分层样式,最大限度做到复用,提升开发效率?
-
如何制定跨端方案,Write Once,Run Everywhere 是否真的可行?
-
如何处理各种模块化规范,以及精确做到代码拆分的最佳实践?
-
如何区分开发边界,比如前端如何更好地利用 Node.js 方案开疆扩土?
这都直接决定了前端的业务价值,体现了前端团队的技术能力。
从项目中的痛点提取基础建设的意义,从个人发展瓶颈总结工程化架构和底层设计
一、Monorepo 介绍
Monorepo 是一种项目代码管理方式,指单个仓库中管理多个项目,有助于简化代码共享、版本控制、构建和部署等方面的复杂性,并提供更好的可重用性和协作性。Monorepo 提倡了开放、透明、共享的组织文化,这种方法已经被很多大型公司广泛使用,如 Google、Facebook 和 Microsoft 等。
二、Monorepo 演进
阶段一:单仓库巨石应用, 一个 Git 仓库维护着项目代码,随着迭代业务复杂度的提升,项目代码会变得越来越多,越来越复杂,大量代码构建效率也会降低,最终导致了单体巨石应用,这种代码管理方式称之为 Monolith。
阶段二:多仓库多模块应用,于是将项目拆解成多个业务模块,并在多个 Git 仓库管理,模块解耦,降低了巨石应用的复杂度,每个模块都可以独立编码、测试、发版,代码管理变得简化,构建效率也得以提升,这种代码管理方式称之为 MultiRepo-Multi Repository。
阶段三:单仓库多模块应用,随着业务复杂度的提升,模块仓库越来越多,MultiRepo这种方式虽然从业务上解耦了,但增加了项目工程管理的难度,随着模块仓库达到一定数量级,会有几个问题:跨仓库代码难共享;分散在单仓库的模块依赖管理复杂(底层模块升级后,其他上层依赖需要及时更新,否则有问题);增加了构建耗时。于是将多个项目集成到一个仓库下,共享工程配置,同时又快捷地共享模块代码,成为趋势,这种代码管理方式称之为 MonoRepo-Mono Repository。
三、Monorepo 优劣

| 场景 | Monolithic 架构 | MultiRepo多仓库多模块应用 | MonoRepo单仓库多模块应用 |
|---|---|---|---|
| 代码可见性 | ✅ 编码方便、代码复用度高❌ 项目增多后,启动十分缓慢,开发效率降低,调试成本高昂 | ✅ 代码隔离,研发者只需关注自己负责的仓库 ❌ 包管理按照各自owner划分,当出现问题时,需要到依赖包中进行判断并解决。❌ 代码复用能力差。各项目独立管理,代码很难共享,存在重复造轮子的问题 | ✅ 一个仓库中多个相关项目,很容易看到整个代码库的变化趋势,更好的团队协作。✅ 启动快速,调试方便✅ 可以执行原子性提交、大规模修改✅ 代码共享比较容易 |
| 依赖管理 | ✅ 统一的工程配置,新项目可以直接进入开发,无需复杂的工程配置 | ❌ 多个仓库都有自己的 node_modules,存在依赖重复安装情况,占用磁盘内存大。 | ✅ 统一的工程配置,多项目代码都在一个仓库中,相同版本依赖提升到顶层只安装一次,节省磁盘内存, |
| 代码权限 | ✅ 仓库权限收敛,易于管控 | ✅ 各项目单独仓库,不会出现代码被误改的情况,单个项目出现问题不会影响其他项目。❌ 权限管理复杂。各项目都有一套单独的仓库、部署权限,太过分散,不利于管理❌ 各项目代码风格不一致,不利于 Code Review 开展 | ✅ 代码一致性高、代码透明度高,方便跨项目贡献代码✅ 权限收敛,易于管控❌ 所有项目都在一个仓库提交,commit 信息嘈杂凌乱,需要良好的控制 |
| 开发迭代 | ❌ 代码体积庞大,不利于阅读和重构❌ 所有项目代码对团队成员都可见 | ✅ 仓库体积小,模块划分清晰,可维护性强。 ❌ 多仓库来回切换(编辑器及命令行),项目多的话效率很低。多仓库见存在依赖时,需要手动 npm link,操作繁琐。 ❌ 依赖管理不便,多个依赖可能在多个仓库中存在不同版本,重复安装,npm link 时不同项目的依赖会存在冲突。 | ✅ 多个项目都在一个仓库中,可看到相关项目全貌,编码非常方便。 ✅ 代码复用高,方便进行代码重构。 ❌ 多项目在一个仓库中,代码体积多大几个 G,git clone时间较长。 ✅ 依赖调试方便,依赖包迭代场景下,借助工具自动 npm link,直接使用最新版本依赖,简化了操作流程。 |
| 工程配置 | ✅ 各项目的依赖版本统一❌ 依赖升级困难,所有项目都使用同一依赖,升级的成本、风险很高。 | ✅ 单个项目升级依赖不会影响到其他项目❌ 各项目构建、打包、代码校验都各自维护,不一致时会导致代码差异或构建差异。 | ✅ 各项目依赖版本可以统一,也可以保持不同,相当灵活。 共同依赖可以提升到 root,方便依赖管理✅ 便于统一批量升级各项目依赖 |
| 构建部署 | ❌ 项目增多后,构建、部署速度变得越来越慢❌ 经常发生部署环境占用、覆盖等问题,出现线上问题时回滚困难 | ✅ 单独的部署环境,回滚也比较容易❌ 多个项目间存在依赖,部署时需要手动到不同的仓库根据先后顺序去修改版本及进行部署,操作繁琐效率低。 | ✅ 构建性 Monorepo 工具可以配置依赖项目的构建优先级,可以实现一次命令完成所有的部署。✅ 各项目可以单独部署❌ 部署需要良好的工具支持 |
四、Monorepo 场景
综合如上 Monorepo VS MultiRepo,中大型项目,多模块项目,更适合用 MonoRepo 方式管理代码,在开发、协作效率、代码一致性方面都能受益。
4.1 子项目依赖共享库的几种方式
在 monorepo 结构中,有几种方式可以让子项目依赖 shared-lib 共享库:
本次工程中会用到如下1.使用工作区(Workspace)依赖、3.发布到 npm 注册表 两种方式
1.工具函数:升级后所有子项目全部跟着升级(考虑下是否换成(发布到 npm 注册表)的方式)
3.组件库:每个子项目用的组件版本不一致,跨多个项目或团队共享的包
1. 使用工作区(Workspace)依赖
如果您使用的是支持工作区的包管理器(如 Yarn Workspaces 或 pnpm Workspaces),可以直接在子项目的 package.json 中添加对 shared-lib 的依赖。这种方法不需要将 shared-lib 发布到 npm 注册表。
本工程中shared-lib是工具包,采用的是这种方式,因为它们能够很好地处理内部包之间的依赖关系,通用的工具方法一般是所有子项目公用的,升级后一般是全部子项目都要同时升级,如果用版本控制,如果工具包升级,则每个子项目都要手动升级版本号,会有点麻烦,但是如果采用工作区(Workspace)依赖的方式也有弊端就是万一有的子工程想用上一个版本的话 就没法控制了,但是我们这个工程不会存在这种情况
packages/package-a/package.json:
{
"dependencies": {
"shared-lib": "workspace:^1.0.0",
"ui-components": "workspace:^1.0.0",
}
}
在这里,workspace:* 表示子项目 package-a 依赖于同一 monorepo 中的 shared-lib 工作区。当您运行安装命令时,包管理器会自动链接 shared-lib 到 package-a 的 node_modules 目录。
如需更改:目前已经发包@yyfe/boc-shared-lib@1.0.0 子项目只需要:
{
"dependencies": {
"@yyfe/boc-shared-lib": "1.0.0"
}
}
pnpm install
2. 使用符号链接(Symlinks)
您可以使用 npm link 或 yarn link 命令在全局创建 shared-lib 的符号链接,然后在需要它的子项目中链接到这个全局链接。
首先,在 shared-lib 目录中创建一个全局链接:
cd shared-lib
npm link
然后,在子项目中链接到这个全局链接:
cd ../packages/package-a
npm link shared-lib
这样,package-a 就可以使用 shared-lib 中的代码了。
3. 发布到 npm 注册表
发布到 npm 注册表是最正式的方法,适用于需要跨多个项目或团队共享的包。
packages/ui-components:共享的UI组件库更适合这种方式,因为每个子工程可能用到了不同的组件版本
您可以将 shared-lib 打包并发布到 npm 注册表(公共或私有),然后在子项目中像添加常规 npm 包一样添加依赖。
首先,确保 shared-lib 的 package.json 文件中包含了正确的版本号和入口点,然后运行:
cd shared-lib
npm publish
然后,在子项目中添加依赖:
cd ../packages/package-a
npm install shared-lib
4. 使用 Lerna 管理依赖
如果您使用 Lerna 来管理 monorepo,Lerna 可以帮助您自动处理内部包之间的依赖。您只需在子项目的 package.json 中声明对 shared-lib 的依赖,然后运行 Lerna 的引导命令:
lerna bootstrap
Lerna 会自动链接 monorepo 中的包,使得子项目可以使用 shared-lib。
注意:如果想用这种方式去做到每个子项目引用不同的 shared-lib版本则需要用一下方式:
如果使用 Lerna 管理 monorepo 时,在子项目的 package.json 中声明对 shared-lib 的依赖,并且可以指定不同的版本号,从而使得每个子项目可以使用不同版本的 shared-lib的话,这通常意味着您需要将 shared-lib 的每个版本发布到 npm 注册表(无论是公共的还是私有的),以便 Lerna 能够正确解析和安装这些依赖。
在 monorepo 中,如果您希望使用 Lerna 并保持本地包之间的依赖,而不是依赖于 npm 注册表上的版本,那么所有子项目将共享相同的 shared-lib 版本,因为 Lerna 会创建一个符号链接到本地的 shared-lib 目录。
如果您确实需要在使用 Lerna 的情况下让每个子项目依赖不同的 shared-lib 版本,您应该按照以下步骤操作:
-
将不同版本的
shared-lib发布到 npm 注册表: 为shared-lib的每个版本指定不同的版本号,并将它们发布到 npm 注册表。 -
在子项目的
package.json中指定版本: 在每个子项目的package.json文件中,指定所需的shared-lib版本。packages/package-a/package.json:
{ "dependencies": { "shared-lib": "^1.0.0" } }packages/package-b/package.json:
{ "dependencies": { "shared-lib": "^2.0.0" } } -
使用 Lerna 进行引导: 运行
lerna bootstrap命令,Lerna 会自动解析子项目中指定的版本号,并安装对应的shared-lib版本。
请注意,这种方法需要您将 shared-lib 的不同版本发布到 npm 注册表,而不是仅在本地 monorepo 中管理它们。如果您希望在本地管理不同版本的 shared-lib 而不发布到 npm,那么您需要为每个版本创建不同的包名(如 shared-lib-v1、shared-lib-v2 等),并在 Lerna 的配置中包含这些包。然后,您可以在子项目的 package.json 中指定这些本地包的名称来引用不同版本的 shared-lib。这种方式在 monorepo 中比较少见,因为它增加了管理的复杂性。
5. 使用本地文件路径
您也可以在子项目的 package.json 中直接指定 shared-lib 的文件路径作为依赖:
packages/package-a/package.json:
{
"dependencies": {
"shared-lib": "file:../../shared-lib"
}
}
这种方法会在 node_modules 中创建一个指向 shared-lib 的符号链接。
选择哪种方法取决于您的具体需求和偏好。工作区依赖和 Lerna 管理依赖通常是 monorepo 结构中最方便的方法,因为它们能够很好地处理内部包之间的依赖关系。符号链接和本地文件路径方法在某些场景下也很有用,但可能需要手动管理。发布到 npm 注册表是最正式的方法,适用于需要跨多个项目或团队共享的包。
五、Monorepo 踩坑
5.1、幽灵依赖
问题:npm/yarn 安装依赖时,存在依赖提升,某个项目使用的依赖,并没有在其 package.json 中声明,也可以直接使用,这种现象称之为 “幽灵依赖”;随着项目迭代,这个依赖不再被其他项目使用,不再被安装,使用幽灵依赖的项目,会因为无法找到依赖而报错。
方案:基于 npm/yarn 的 Monorepo 方案,依然存在 “幽灵依赖” 问题,我们可以通过 pnpm 彻底解决这个问题
5.2、依赖安装耗时长
问题:MonoRepo 中每个项目都有自己的 package.json 依赖列表,随着 MonoRepo 中依赖总数的增长,每次 install 时,耗时会较长。
方案:相同版本依赖提升到 Monorepo 根目录下,减少冗余依赖安装;使用 pnpm 按需安装及依赖缓存。
5.3、构建打包耗时长
问题:多个项目构建任务存在依赖时,往往是串行构建 或 全量构建,导致构建时间较长
方案:增量构建,而非全量构建;也可以将串行构建,优化成并行构建。
增量构建:增量构建是指只构建自上次构建以来发生变化的部分,而不是每次都构建整个项目。这可以显著减少构建时间。
vite增量构建:生产环境构建:Vite 目前没有内置的增量构建支持。所以在项目结构上将项目拆分成多个小的 Vite 项目,然后在每个项目中单独运行 vite build,模拟了增量构建的效果。开发环境构建:Vite 是专为开发环境设计的,它使用 ES Modules 的原生浏览器支持来实现按需编译,这在很大程度上已经实现了增量构建的效果。
在使用了 Lerna, pnpm, workspace 和 Vite 的项目中,增量构建的实现需要依赖于各个工具的特性。
目前,Vite 并没有内置的增量构建支持。而 Lerna 和 pnpm 主要用于管理多包项目和依赖项,也没有直接提供增量构建的功能。
但是,你可以尝试以下方式:
- 使用构建缓存:一些构建工具(如 Webpack、Rollup)支持缓存,可以在二次构建时跳过一些已经完成的工作。你需要查看 Vite 是否有类似的插件或配置。
- 利用 Lerna 的 changed 命令:Lerna 提供了一个命令
lerna changed,可以列出自上次发布以来有修改的包。你可以结合这个命令,只构建有改动的包。
例如,在根目录的 package.json 中添加一个脚本:
//在自上次发布以来有更改的包中,依次(不并行)执行 `npm run build` 命令,并实时显示每个包的输出。
{
"scripts": {
"build:changed": "lerna exec --concurrency 1 --stream --since -- npm run build"
}
}
这段配置定义了一个名为 build:changed 的 npm 脚本,它使用 Lerna 来执行在上次发布后有变更的每个包中的 npm run build 命令。下面是对这个脚本的详细解释:
lerna exec:这是 Lerna 的一个命令,用于在每个包的上下文中执行任意命令。--concurrency 1:这个选项指定了命令的并发执行数。在这里,它被设置为1,意味着命令会依次在每个包中串行执行。--stream:这个选项确保来自子包的输出会实时显示在控制台上。--since:这个选项告诉 Lerna 只在自上次发布以来有变更的包中执行命令。-- npm run build:这是 Lerna 将在每个有变更的包中执行的实际命令。
要将这个脚本改成使用 pnpm 的形式,我们需要使用 pnpm 提供的类似功能。pnpm 支持类似的工作流,但语法略有不同。以下是使用 pnpm 的等效配置:
{
"scripts": {
"build:changed": "pnpm --filter \"...[since]\" run build --parallel --stream"
}
}
"build:changed": "pnpm --filter \"...[HEAD~1]\" run build --parallel --stream",
在这个新的脚本中:
pnpm --filter:这是pnpm的一个选项,用于指定应该在哪些包中执行命令。\"...[since]\":这个过滤器告诉pnpm只在自上次发布以来有变更的包中执行命令。注意,这里使用的是pnpm的过滤器语法,它与 Lerna 的--since选项类似。...[since] 选择器应该会选择自上次提交以来发生变化的所有包。然而,这个选择器需要一个有效的 Git 标签或提交哈希作为参考点来确定变化run build:这是pnpm将在每个有变更的包中执行的实际命令。--parallel:这个选项确保命令可以在多个包中并行执行。如果您想要像原始脚本那样串行执行,可以省略这个选项。--stream:这个选项确保来自子包的输出会实时显示在控制台上。
请注意,pnpm 的过滤器功能非常强大,您可以根据需要调整过滤器的表达式。如果您想要保持与原始 Lerna 脚本相同的串行行为,可以去掉 --parallel 选项。
这个脚本会找出自上次发布以来有修改的包,并依次运行它们的 npm run build 命令。
请注意,这只是一种可能的方法,实际效果可能会因项目具体情况而异。如果你的项目有特殊的构建需求,可能需要自定义脚本或使用其他工具。
并行构建:①同时执行多个构建任务。这可以充分利用多核 CPU 的优势,显著减少构建时间。npm 的并行执行工具 npm-run-all 或 concurrently
npm install --save-dev npm-run-all
或者
npm install --save-dev concurrently
package.json 文件中设置多个构建脚本,每个脚本构建项目的不同部分。例如:
"scripts": {
"build:part1": "vite build --config vite.config.part1.js",
"build:part2": "vite build --config vite.config.part2.js",
"build": "npm-run-all --parallel build:*"
}
或者使用 concurrently:
"scripts": {
"build:part1": "vite build --config vite.config.part1.js",
"build:part2": "vite build --config vite.config.part2.js",
"build": "concurrently \"npm:build:*\""
}
npm-run-all --parallel build:* 或 concurrently "npm:build:*" 会并行运行所有以 build: 开头的脚本。
然后,你可以通过运行 npm run build 来并行构建你的项目。并行构建会占用更多的 CPU 和内存资源,如果资源有限,可能并不会带来速度的提升。
②在使用了 Lerna, pnpm, workspace 和 Vite 的项目中,你可以通过以下步骤进行并行构建:
- 确保你已经在项目的根目录下的
lerna.json文件中配置了你的 packages。例如:
{
"packages": [
"packages/*"
],
"version": "0.0.0"
}
- 在项目的根目录下创建一个名为
.npmrc的文件,并添加以下内容:
public-hoist-pattern=*
这将使 pnpm 在所有的 workspace 中公共提升所有的依赖项。
- 在每个 package 的
package.json文件中添加一个build脚本,该脚本将使用 Vite 进行构建。例如:
{
"scripts": {
"build": "vite build"
}
}
- 在项目的根目录下的
package.json文件中添加一个build脚本,该脚本将使用 Lerna 并行运行每个 package 的build脚本。例如:
// --parallel:这是一个可选的标志,用于指示 Lerna 应该并行运行这些脚本,而不是按照顺序一个接一个地运行。这可以加快构建过程,特别是在有很多包的情况下。为了使这个命令能够工作,每个包的 package.json 文件中都需要有一个名为 "build" 的脚本。如果某个包没有这个脚本,Lerna 将会跳过这个包。
{
"scripts": {
"build": "lerna run build --parallel"
}
}
- 然后,你可以通过在项目的根目录下运行
pnpm run build来并行构建你的项目。
请注意,这只是一个基本的设置,并行构建可能需要你根据项目的实际情况进行更详细的配置。并且,并行构建会占用更多的 CPU 和内存资源,如果资源有限,可能并不会带来速度的提升。
六、关于现代包管理器的深度思考——为什么现在我更推荐 pnpm 而不是 npm/yarn?
关于现代包管理器的深度思考——为什么现在我更推荐 pnpm 而不是 npm/yarn?:juejin.cn/post/693204…
七、工程搭建-lerna+pnpm+workspace+vite+ts+react
基于以上前六个的调研,及下方的资料查看调研等,所以此次工程采用lerna+pnpm+workspace+vite进行工程搭建包管理及构建
采用MonoRepo管理单仓库多模块应用的方式,结合pnpm、lerna、Yarn Workspaces、Vite、React和TypeScript的技术栈,可以形成一个高效、模块化和现代化的前端项目。以下是项目的整体设计方案:
一、项目背景
XXX项目X年前搭建采用单仓库巨石应用Monolith, 一个 Git 仓库维护着项目代码,随着迭代业务复杂度的提升,项目代码会变得越来越多,越来越复杂,大量代码构建效率也会降低,最终导致了单体巨石应用
面临着陈旧的架构和过时的组件库,任何微小的升级都可能引发全局性的影响。新技术和功能的应用受限,维护困难,且无法实现有效的统一管理。这些问题都迫切需要我们进行系统的重构。
XXXXX现状分析:
| 痛点 | 分析 | 方案 |
|---|---|---|
| **单仓库巨石应用(Monolith)&&**多仓库多模块应用 | 所有的前端代码都维护在一个工程中,随着时间的推移和业务的增长,代码基础变得庞大和复杂。尽管目前用了iframe的方式,多仓库多模块应用**,**但是iframe嵌入的模块都散落在各个git仓库,难以维护,公共模块也难以公用。 | MonoRepo方案 |
| 技术栈老旧 | 项目采用的是React+TS技术栈,但使用了老旧的架构和过时的组件库,这限制了新技术的采用和现有功能的扩展。 | 每个模块单独部署构建,按需应用自己模块的版本 |
| 代码维护困难 | 历史代码中存在大量的any类型使用,缺乏规范的TypeScript写法,使得类型系统的优势没有得到充分发挥,增加了维护难度和出错概率。 | MonoRepo方案 |
| 性能/交互问题 | 菜单跳转和页面切换不平滑,详情跳到管理页困难,需要点一下其他二级菜单再点击才能回来,资源加载速度慢,影响了用户体验。 | 打包、模块独立 |
| 接口管理不足 | 没有有效利用工具来优化接口管理和自动生成TypeScript类型定义,导致前端与后端接口对接效率低下。 | yapi接口定义完直接转成ts |
升级的必要性:
| 提升点 | 分析 |
|---|---|
| 单仓库多模块应用 | 随着业务复杂度的提升,模块仓库越来越多,MultiRepo这种方式虽然从业务上解耦了,但增加了项目工程管理的难度,随着模块仓库达到一定数量级,会有几个问题:跨仓库代码难共享;分散在单仓库的模块依赖管理复杂(底层模块升级后,其他上层依赖需要及时更新,否则有问题);增加了构建耗时。于是将多个项目集成到一个仓库下,共享工程配置,同时又快捷地共享模块代码,成为趋势,这种代码管理方式称之为 MonoRepo-Mono Repository。 |
| 提高构建效率 | 现有的单体应用构建效率低下,需要通过模块化和微前端技术将应用拆分,实现更快的构建和部署。 |
| 技术栈现代化 | 更新到最新的React版本,采用最新的前端技术和工具,以便更好地应对未来业务的需求和挑战。 |
| 代码质量提升 | 通过引入更严格的TypeScript规范和代码规范工具(如ESLint、Prettier),提升代码质量,减少bug发生率。 |
| 用户体验优化 | 通过性能优化措施(如代码分割、懒加载、资源优化等)改善页面加载速度和交互平滑度,提升用户体验。 |
| 接口管理自动化 | 利用YApi等工具自动生成TypeScript类型定义,提升前后端接口对接的效率和准确性。 |
二、项目范围和目标
解决XXXX项目中历史逻辑混乱、技术栈老旧等问题, 缩短XXXXX项目整体需求交付时长。
简化代码共享、版本控制、构建和部署等方面的复杂性,并提供更好的可重用性和协作性。
建设一个统一工程建设标准、促进跨项目代码复用、从而实现开发效率提升、减少维护成本与简化项目管理、高效、稳定、可持续发展的系统,为后续业务发展和技术架构升级打下基础,具体目标可分为:
- 提高人效
- 提高加载性能
- 提高稳定性
- 可维护可扩展性
三、项目整体方案
1. 项目结构设计
2.技术选型
Monorepo项目技术选型及技术应用配置的一些细节:
| 模块 | 推荐选型 | 说明 |
|---|---|---|
| 包管理和依赖关系 | pnpm+ lerna + Workspaces | 基于 npm/yarn 的 Monorepo 方案,依然存在 “幽灵依赖” 问题,我们可以通过 pnpm 彻底解决这个问题"node": ">=16.0.0","pnpm": ">=8.0.0":为什么现在我更推荐 pnpm 而不是 npm/yarn?juejin.cn/post/693204… |
| 前端框架和语言 | TypeScript+React@18.2.0 | 使用React作为UI框架,构建用户界面。TypeScript作为开发语言,提供静态类型检查和现代JavaScript特性 |
| 库 | UI:Roo-React、RSuite图标:Echarts、AntV G2Plot、ECharts for React、工具函数:Lodash、dayjs拖拽工具:react-dnd | |
| 开发和构建工具 | Vite | 采用Vite作为构建和开发服务器,利用其快速的冷启动和即时模块热更新特性提高开发效率。Vite提供现代化的打包策略和优化,支持ES模块,为生产环境提供高效的构建输出。利用Vite的代码分割和懒加载特性,优化应用的加载时间。 |
| 代码规范和质量控制 | ESLint+Prettier | 引入ESLint、Prettier等工具来确保代码风格一致性和质量。制定严格的代码审查流程和自动化测试策略,确保代码的质量和健壮性。 |
| 持续集成和持续部署(CI/CD) | Talos 2.0 | Talos 2.0 在 Talos 1.0 基础上支持了流水线支持并行,模版共享等功能,对monorepo发布更加友善。配置CI/CD流水线,自动化测试、构建和部署过程。talos部署 |
| 前后端接口管理 | yapi | 使用YApi管理API文档和模拟数据。自动生成TypeScript接口类型定义,确保前后端数据类型一致性。 |
| 安全性和权限控制 | 通用请求 shared-lib | 实施安全最佳实践,如HTTPS、CORS策略和输入验证。设计权限管理系统,确保用户权限和数据访问控制。 |
| 文档和知识共享 | 创建项目文档,包括开发指南、组件文档和API文档。建立知识共享机制,如定期技术分享、代码审查和团队博客。 |
通过上述设计方案,品牌运营中心前端架构将变得更加现代化、高效和可维护。这将有助于团队更快地迭代产品,更容易地扩展业务,并提供更好的用户体验。
3.项目治理与工程建设
①统一工程配置
-
统一代码规范:包括开发流程规范、代码开发规范、格式化规范、代码提交规范等。
-
工具链建设:为了保证代码规范的落地,引入一些工具的支持。其中,eslint、stylelint 是核心 linter,提供代码规范的检查能力;prettier 用于代码格式化校验;
husky 是一个 git hooks 工具,配合 lint-stage、commlint 可以在代码提交到仓库阶段前检查代码、commit 信息是否符合规范。---todo
当然,工具并不能解决所有问题,一些软规范(命名规范、文件结构规范等)的落地需要采取一些其他措施,比如引入严格的 code review 机制。
-
统一的配置化解决方案:为方便各项目接入,对代码规范和工具链进行封装(base-config 包),主要提供如下能力:依赖安装、工程配置、规范检查、一键升级。---配置已经通用但是目前base-config 包待todo
②IDE 支持 monorepo
目的:团队成员分工明确,成员只需要关注自己负责的几个项目
vsCode插件:Multi-root Workspaces--目前没有统一要求使用,目前子工程数量太少,后续多了的时候可以应用-todo
4.跨项目复用
①引用远程模块npm包
A:工具包:@yyfe/shared-lib B:业务组件包:@yyfe/ui-components 通用组件库设计方案ui-components
②使用工作区(Workspace)依赖
③使用符号链接(Symlinks)
④使用 Lerna 管理依赖
⑤使用本地文件路径
⑥模块联邦:vite插件originjs/vite-plugin-federation
其他搭建细节
四、结果与收益
借助 Monorepo 理念和 Lerna 提供的 workspaces管理能力,我们完成了项目管理模式的统一和技术架构的升级。在 Monorepo 架构的基础上,我们可以很好地实践前端工程建设,进行模块联邦、构建优化等技术探索。最重要的是,Monorepo 架构更有利于团队成员交流融合。
-
效率提升
- 开发效率提升。促进了项目代码复用,减少了重复造轮子的情况。
- 运维效率提升。通过项目治理,各项目采用统一的代码规范和开发工具链,减少了重复的工程配置和维护成本。
-
质量管理
- 代码重复率减少 X% ↓。
-
价值体现
- 简化项目管理,实现了技术架构的统一,方便新项目扩展。
- 打破项目孤岛,有利于团队成员的协作与交流,促进了业务融合。
本文产出的工程化学习参考资料如下:
- npm install 原理分析:cloud.tencent.com/developer/a… ---讲解的清晰明了
- Node.js 包管理器发展史:wxsm.space/2021/npm-hi… -- npm yarn pnpm 发展史及每次发展所解决的问题,也就是为什么以上选型选pnpm的原因
- 像架构师一样思考,突破技术成长瓶颈:kaiwu.lagou.com/course/cour… --熟悉大概思路,思考问题的方式
- 应用级 Monorepo 优化方案:segmentfault.com/a/119000004… --
- 关于现代包管理器的深度思考——为什么现在我更推荐 pnpm 而不是 npm/yarn?:juejin.cn/post/693204…
- 带你了解更全面的 Monorepo - 优劣、踩坑、选型:juejin.cn/post/721588…
- Monorepo项目技术选型及技术应用配置的一些细节:juejin.cn/spost/74741…