Monorepo项目的复盘

80 阅读6分钟

前言

公司的前端代码要从vue2到vue3的升级迭代。前后从2022年7月开始做准备至2023年4月,完成了移动端代码的升级和部分PC端代码的升级。现做一个复盘。

回顾目标

  1. 使用Monorepo的方式组织项目代码。
  2. Vue3+Vite+TS+Pinia+Router4的技术栈迭代。

叙述过程

随着vue团队正式将vue3作为vue框架的默认版本,vue3也凭借着比vue2更好的TypeScript支持,更加灵活和强大的组合式API,更快的渲染速度和更小的代码体积等优势,受到业界的欢迎。

我们也将代码进行迭代升级。一方面迎合前端技术的发展方向,一方面项目设计了新的样式,样式变动大,另一方面在此基础上我们还有更大的愿景,希望能建立一个Monorepo型的项目,将移动端与PC端的逻辑代码抽离到一起,方便维护(可是这也意味着修改一处逻辑,多个项目也要受到影响,在维护代码时需要投入更多精力。)。

Monorepo指单个仓库中管理多个项目,有助于简化代码共享、版本控制、构建和部署等方面的复杂性,并提供更好的可重用性和协作性。Monorepo 提倡了开放、透明、共享的组织文化,这种方法已经被很多大型公司广泛使用,如 Google、Facebook 和 Microsoft 等。

此时,摆在我们面前首当其冲要考虑的是使用哪种Monorepo的方案。市面有多种方案供选择,Lerna,Nx,Rush,PNPM+TurboRepo等等。

在2022年7月中下旬我们开始筛选适合的方案。

我研究的是微软开源的Rush方案。经过半个月的研究,我总结了一份Rush作为选型的研究报告,通过功能全面性,上手难度,代码结构等多个角度综合分析得出它的优缺点。

Rush的一些优点:

  • 可以管理多个仓库还支持构建,发布等。所以使用Rush可以一把梭,是非常全面的构建Monorepo项目的方案。

  • 提供了详细的中文文档和教程。快速开始 | Rush (rushjs.io)

  • 提供了多种命令行工具,用于管理依赖项、版本控制等。

Rush的缺点:

  • 对于新手来说,学习指令和配置过程可能比较困难。

  • 指令只适用于rush平台在其它平台不适用,学习成本比较高。

在8月上旬经过讨论,我们决定选择配置难度和入门难度较低的PNPM+TurboRepo方案。

PNPM 内置了对 Monorepo 的支持,仅仅需要一个简单的配置就可以实现Monorepo 。而TurboRepo在项目构建中提供更快的速度,而且配置也简单。PNPM指令可以用在其它项目中,且它比NPM有着节省磁盘空间,更快安装速度等优点。

技术栈

从vue2升级到vue3,不是简单的仅仅运用vue3的语法重构vue2代码那么简单。可以说vue3是对vue2底层架构的一次彻底的改头换面。Vue团队趁势推出了新的构建工具vite。与Webpack相比,Vite在开发体验、构建速度以及轻量级等方面具有明显优势。还有新的状态管理库pinia,与vuex相比它简单,具有高效、类型安全和扩展性强等优点。以及vue3底层是用TypeScript来写,友好的支持ts。

最后我们确定下来使用Vue3+Vite+TS+Pinia+Router4的技术栈迭代代码。

个人收获

除了对技术栈熟练使用以外,对代码的组织,整体结构和思维上有了新的认识。

思维上的变化

传统写前端项目,先要考虑结构,样式等然后再写业务逻辑或交互效果。但是我们摒弃了这种思维,因为我们要把多个项目共同的逻辑放在一个项目中。pc端与移动端都有自己对应组件库,让它们各自写不同的样式。所以倒逼我们先去想清楚一个页面到底要实现哪些功能或者说是业务逻辑。那么这些要实现的功能我们就应该放在同一个目录里。这就与vue框架中runtime的模式有点像,核心的代码写在runtime-core中,而不同平台有不同的实现方式。浏览器就可以使用runtime-dom。

熟练了类以及TypeScript的使用

在vue2开发中很少使用到类,也从来不使用TypeScript。

我们的思路是把这些公用的功能点写在一个项目中,该怎么写呢?答案就是用类来写。类封装了属性和方法,在加上使用TypeScript,对数据进行一定的约束。也就意味着,类可以写任何你想要实现的功能。

事实上ts页面有很多小方法,小功能。组合在一起就是一个大功能了,多个大功能组合一起就是一个类就是一个ts文件就是一个模块,就完成了一个页面所要的数据和功能。从结构上看也比较清晰,试想这么多的逻辑写在vue页面中,势必与交互代码缠绕在一起,不利于后期维护。

数据结构的重要性

Vue是通过数据来驱动页面变化的,好的数据结构在实现功能就会容易许多。在项目中有个筛选的功能。移动端与PC端在实现筛选功能有不同组件库。所以我们在store中抽离出筛选逻辑的功能代码。而如何盘清逻辑,就需要在类中组织好数据,每一个字段都有其意义。

举例:每一个筛选项都是一个对象,对象中通过type字段来区别单选还是多选还是下拉框,通过value字段来记录用户选中的选项等等。我们所实现的功能无非就是通过类中定义的方法来改变数据。数据结构越完善,实现的方法也越方便。

普遍情况普遍处理,特殊情况特殊处理

普遍情况与特殊情况要分开处理,不要把逻辑都混在一起。

在筛选功能中遇到一个特殊的需求。有一个筛选项它既要包含多选也要包含单选。那么我们就把它的类型设为type=“custom”,遇到自定义的类型就在通用的store文件中不做处理。有这个需求的组件让它自行实现功能即可。