背景
最近在工作中,接手了个 vue2 的老项目,面对上百个文件项目,而且有一大部分的一个 vue 文件有上万行代码,甚至有几万行代码,让整个前端团队的人都叫苦连连;于是团队决定对这个项目进行一定的升级;
主要问题
面对一个老项目,我们很难做到一步到位,完全升级,不然很可能会造成一些想象不到的问题,对公司造成不必要的损失,俗话说的好能跑的破车,你最好就不要动它;所以只能一步一步的一点点改造;我们先看看老项目面临的问题,及哪些能马上改的,哪些是先不能动的,只能以后长记计议,以后一点一点改;
面临的问题:
- 项目依赖老旧,node还是12的,同样 npm 的包也很老旧;这个问题一来会影响项目性能,二来会导致很多新的包没办法使用,而且其它项目都是node20+,开发的时候每次都要切换 node版本也挺麻烦的
- 项目启动慢,修改代码的编译慢,上线打包慢,严重影响开发效率;
- 新功能开发麻烦,很多社区上的包只支持 vue3了;
- 旧的项目,缺少规划,没有封装通用的组件,工具函数,业务组件;
第一,第二个问题,是可以必须着手解决的,因为也没动到实际的代码,风险也比较低,只要能正常跑起来,一般也不会出现问题;第三点可以让完成新功能在 vue3 中开发,但很多情况可能只是部分新功能,会存在两边要写一样的逻辑的时候,所以要考虑到跟旧项目怎么实现代码复用,不然就徒增开发成本;第四点,可能就只能在业务开发中一点点改进了;
解决方案
下面我将说下我们面对上面问题的解决方案;
升级模块
以我升级的经验来说,升级模块,不能一步到位直接升到 node20,直接升到 20 很大可能性是完全跑不起来,而且报错的模块太多,根本不知从何解决;一面是我升级模块的经验;
第一阶段
node,npm模块小升级
- 先删除不必要的模块,像这种远旧项目往往会存在很多没有用到的模块,但还在 package.josn 中写着,需要把这些模块先删了,以减少后面升级的模块,但这也是体力活,需要一个个去排查;
- 先升级一个中间版本的 node, 这里可以看下 node 每个版本有大的破不性的升级,我们就先升级到前一个版本,我是先升到 node16, 因为 node17和 18 都有比较大的破性升级;过大的版本升级也可以导致 npm包不兼容。
- 对于模块的升级,我们先把所有模块,升到当前大版本的最新版本,比如 1.0.0的升到 1.9.0 而不是升到 2点几,这样可以避免了很多跑不起来的情况;升级完先跑一个,然后再一点一点的解决运行时的报错,这里要感谢AI 的时代,帮我排查了很多问题,在以前可能要查更多的资料和时间去解决;
- 到这里应该先上一个版本,先在 dev 环境中跑一段时间,然后再上正式环境,并做好通知,和随时回滚的准备,如果的项目用的流量大,影响也大的话,也可以考虑做一部分的灰度;
第二阶段
npm模块大升级
在项目稳定的跑一段时间后,我们可以进行下一阶段的升级;
-
这个时候可以尝对 npm 包的大版本升级了,但不要跳级,就是从 1 升到 2 的版本,但不要直接升到 3,因为对于大部分 npm 包是随着 node 的版本一起升级的,过大的版本跳跃可以 node 不支持,也不利于排查问题;
-
对于不再维护的模块升级,在这个大版本升级,可能会面临破坏性升级,或不再维护寻找替代方案,比如我在升级中就面临 node-sass不再维护,要用 dist-sass 替代,这一步也是必须的,因为这里不升,后面 Node也升级不动了;面对这样的升级一定要看下官方的迁移文档,看下注意事项;这里最好的方式还是借助 AI, 把迁移文档喂给 AI让 AI写个脚本修改文件,修改后遇到问题,再把问题喂给 AI 修改脚本,这时还原代码再用脚本修改一次,直至没有问题;
-
升级完后,和上面同样的方式,让其在线上跑一段时间,确定其没有问题;
第三阶段
node大升级
这个时候,就可以把 node 升到最新的版本了,一般这一步应该是最顺利的,我基本没有在这一步遇到什么大问题;修改完一些小问题后,就可以上线让其在线上跑一段时间,我们就以着手下一阶段的性能优化了。
提升性能
以上node和模块的升级都是为了,我们做性能优化,及下面一些优化做一些先决准备,其它在上面node 及 sass 的升级已经带来了一点代码编译速度的提升;
其实性能主要包括两部分,一部分是代码编译的性能的提升,一部分是用户体验上的性能提升,对于用户性能的提升,主要包括赖加载, 通过webpack配置优化,减少代码量等等一些手段;这部分大家讲得比较多,我就不展开说了;
但对于老项目来说另一大问题就是编译的性能,像我们的项目,打包要好几分钟,改完代码要编译几至 10 几秒,严重影响开发效率和发布效率,特别是要发 hotfix 的时候,头发都多掉几根;
对于编译的性能的优化,一开始我是尝试对 webpack 的配置优化,如打开多核编译,排除一些文件的编译等等,这些优化虽然起到了一定的作用,但到量变的结果,对于开发体验还是很差;我们最终决定改用别的编译工具试试,选择有 vite,rspack,rsbuid,让我们先比较一下
特性 | webpack | vite | rspack | rsbuild |
---|---|---|---|---|
语言 | JS | JS | Rust | Rust(基于 rspack) |
构建速度 | 慢 | 开发快,构建一般 | 快 | 快 |
热更新 | 一般 | 极快(原生 ESM) | 快 | 快 |
生态 | 最丰富 | 丰富 | 逐步兼容 webpack | 依赖 rspack |
配置 | 灵活 | 简单/灵活 | 类似 webpack | 零配置 |
适用场景 | 复杂/大型项目 | 现代前端/中小型 | 大型/性能敏感 | 快速开发 |
插件系统 | 成熟 | 丰富 | 兼容 webpack | 兼容 rspack |
从这个表格可以看出,rspack和 rsbuild 是最适合我们的项目升级的,因为他兼容 webpack,速度又快;可以让我们项目改动小,又跑得快;在 rspack 和 rsbuild 尝试后,我们选择了用 rsbuild,一来原先的项目并没有过于复杂的 webpack 配制,rsbuild 可以快速替换,并且rsbuild的@sbuild/plugin-vue2插件,可以直接让 vue2 跑起来,而不用过多的配置调试;
在把 webpack 改为 rsbuild后,项目的打包速度变为了秒级,而修改代码后的编译速度更是毫秒级;完全实现了量变,让 vue2的远古项目,也有点现代项目的感觉了;
用微前端
要想在老项目中,用到新技术的选择最好的方式就是用微前端,可以让完全新的需求写在 vue3上,让旧的项目跑在 vue2上;但对于微前端的选择,我们需要看一是否能满足我们的需求,二是社区活跃度,三是框架的问题多不多,下面我们先来,对比一下各框架的性况
框架/方案 | 技术栈兼容性 | 子应用隔离性 | 路由管理 | 加载性能 | 开发体验 | 社区活跃度 | 典型场景 | 代表企业/组织 |
---|---|---|---|---|---|---|---|---|
single-spa | React/Vue/Angular等 | 中(需手动处理) | 强 | 中 | 中 | 高 | 多团队协作、框架混用 | Canopy |
qiankun | React/Vue/Angular等 | 强(沙箱机制) | 强 | 优 | 优 | |||
iceStark | React/Vue/Angular等 | 强(沙箱机制) | 强 | 优 | 优 | 中 | 企业级、阿里系 | 阿里巴巴 |
micro-app | React/Vue/Angular等 | 强(沙箱机制) | 强 | 优 | 优 | 中 | 轻量级、灵活 | 开源社区 |
这里我们最终选择了micro-app,对于single-spa,配置过于复杂,而我们的项目也没有这么复杂,对于qiankun 需说用的人最多,但库的版本已经两年没更新了,存在很多问题没有解决,遇到问题提issue也没有解决;所以我们最终选择了micro-app,一是用的人相对较多,版本最近也有在更新;
迁移部分模块
虽说我们一般不会去把老的项目的模块,迁移到 vue3 的;因为往往吃力不讨好,但我还是建议可以把一部分迁移了,如首页和登录,注册页及菜单模块,这样能大大的提性能;因为在接入微前端后,我们就以 vue3 项目作为主应用,只要首页和登录,注册页及菜单模块,迁移后,用户进来只需要加载了主应用几k 的代码,就能看到项目内容了;大大提升了,项目的加载性能,之前的 vue2项目,主 js 都有 4m 代码,打开特别慢;
使用模块联邦
什么是模块联邦
模块联邦(Module Federation)是 Webpack 5 引入的一项革命性功能,允许多个独立构建的应用(通常称为"主应用"和"远程应用")在运行时动态共享和加载彼此的模块。这样可以实现微前端架构、代码共享、团队独立开发和部署等目标。
核心特点:
- 动态加载远程模块:主应用可以在运行时从远程服务器加载其他应用暴露的模块。
- 共享依赖:可以配置共享依赖库(如 React、Vue),避免重复加载,减少包体积。
基本原理:
- 暴露(Expose) :远程应用通过配置将部分模块暴露出去。
- 消费(Consume) :主应用通过配置动态加载远程应用暴露的模块。
- 共享(Share) :配置共享依赖,确保依赖只加载一次。
为什么要用模块联邦
最后一步的优化,对于这个项目来说,我们肯定会长期存在 vue2 和 vue3的项目的情况,但很多时候我们又不想一样的业务代码写两遍的情况,通过模块联邦我们可以把业务逻辑写在主应的联邦模块上,然后子应用共享,这样就能实现代码复用;并且还可以配置共享依赖库(如 React、Vue),避免重复加载,减少包体积。这也是为什么一定要先升级旧项目的 node 和 模块的原因;