巨石瓦解!我把Vue3巨石项目拆成了十几个微应用~
📖阅读本文,你将
可能什么也学不到。
了解一个
vue3
巨石项目落地为react
容器 +vue3
子应用微应用方案的落地全过程。
你问我为啥要切换到
React
技术栈?公司决定统一技术路线,选了React
,冒办法啊...
了解一些已经实际落地的工程化方案;
一、背景
复杂的方案。是为了解决复杂的问题。
假设某产品具备 “Sass
化”、“多业态支持”的模式。
在此模式指导下,可以设想的使用场景为:在同一套后台管理平台中,不仅可以按需给用户分配可用模块、还必须支持同一模块在不同业态下的“区分性”和“定制性”。如图:
上述描述的方案,如果继续采用 SPA
显然已经不在合适,需要将前端项目进行一些改造,以适配最新的业务形态。
SPA
不适合的原因:
业务代码杂糅在一个仓库内,多业态/纯订制需求会导致代码仓库异常臃肿,难以维护;
有任何模块更新都会导致项目全量更新,可能导致无关业务;
所以我们需要前端项目具备一种新的形态:
业态与业态、项目与项目之间独立构建、发布,可增量部署; 业态与业态、项目与项目之间的差异性代码独立管理; 公共代码依然可以复用;
因此,经过讨论论证,我们选择了 “微前端” 方案来进行前端调整。
二、微前端是什么?
关于微前端,qiankun.js
官网有一段非常清晰的描述。https://qiankun.umijs.org/zh/guide
Techniques, strategies and recipes for building a modern web app with multiple teams that can ship features independently. -- Micro Frontends
微前端是一种多个团队通过独立发布功能的方式来共同构建现代化 web 应用的技术手段及方法策略。
微前端架构具备以下几个核心价值:
技术栈无关
主框架不限制接入应用的技术栈,微应用具备完全自主权独立开发、独立部署
微应用仓库独立,前后端可独立开发,部署完成后主框架自动完成同步更新增量升级
在面对各种复杂场景时,我们通常很难对一个已经存在的系统做全量的技术栈升级或重构,而微前端是一种非常好的实施渐进式重构的手段和策略独立运行时
每个微应用之间状态隔离,运行时状态不共享
三、技术选型
决定采用微前端架构后,我做了如下技术选型的对比:
single-spa(11.2k star)
仓库地址:https://github.com/single-spa/single-spa
微前端实践的先行者,它定义了一套微前端生命周期,并提供了维护整套声明周期的方法。qiankun.js(12.6K star)
官网:https://qiankun.umijs.org/阿里巴巴开源框架,在single-spa
框架的基础上进行了二次封装,让使用变得更加容易。mirco-app(2.6K star)
官网:https://micro-zoe.github.io/micro-app/来自京东零售团队的开源框架,基于类WebComponent进行渲染。webpack5 Module Federation
文档地址:https://webpack.docschina.org/concepts/module-federation
webpack 5.0 推出的重大更新之一,它提供了一套应用间互相依赖的加载规范。
通过权衡,最终选择了 qiankun.js
作为微前端框架。
理由如下:
single-spa
本身做的事情太少,需要从 0 - 1地做太多工作。micro-app
的社区热度相对较低,踩坑可能性较大。webpack 5 模块联邦方案需要构建的每个微应用都以模块联邦形式构建,存量代码成本较高; 公司推行的 Antd
脚手架采用umijs
构建项目,与qiankun.js
之间生态吻合度较高;qiankun.js
使用者较多,且社区活跃,封装程度较高,适合在没有太多深度订制的情况话采用;
四、容器与微应用拆分
在 qiankun.js
的框架中,有两个基础概念:“容器” 与 “微应用”;
容器:指加载网页后会首先加载和渲染的内容,负责后续管理微应用声明周期、全局状态 等工作。 微应用:指具备独立生命周期、独立状态、独立构建的子应用;
微前端的基本结构如上图所示。(实际情况可能比这要复杂,因为微应用与微应用之间也是可以互相引用的)
因此,在正式开始编码之前,我们从 "功能"、"业务"、"实现" 等多个方面进行考量后按如下粒度进行业务拆分:
容器具备以下能力:
微应用管理 菜单、导航 等管理 全局用户状态管理 全局请求拦截 修改密码、消息中心、样式选择 等全局功能 微应用则拆分为四大类:
超管微应用 基础应用
2.1 登录
2.2 系统管理核心业务应用
3.1 核心业务1
3.2 核心业务2
...非核心业务应用
五、在 umi
脚手架上使用 @umi/plugin-qiankun
本节为具体迁移步骤:
5.1 按官网文档进行umi构建时配置
.umirc.ts
{
...,
mountElementId: 'root', // 指定根元素
qiankun: {
master: {
apps: [] // 无需在配置时增加,为了动态加载,均采用运行时添加微应用
}
}
}
5.2 在 app.tsx
导出 qiankun
选项
app.tsx
const fetchQiankunConfig = async () => {
return {
apps: [
{
name: 'system',
entry: '/micro-apps/system/',
},
{
name: 'login',
entry: '/micro-apps/login/',
props: {
onLoginSuccess: async (
) => {
// 登录后跳转逻辑
},
},
},
],
routes: [
{
path: '/login',
microApp: 'login',
exact: true,
},
{
path: '/',
component: PageWithHeaderAndSider,
routes: [
{
path: '/system',
microApp: 'system',
}
]
}
],
prefetch: false, // 关闭预加载
}
}
export const qiankun = fetchQiankunConfig();
5.3 微应用调整(以 system
子应用为例)
修改 package.json
属性name
为system
;修改 webpack
配置,增加如下内容,使构建为umd
格式:
const pkg = require(resolve('./package'))
{
output: {
library: `${pkg.name}-[name]`,
libraryTarget: 'umd',
jsonpFunction: `webpackJsonp_${pkg.name}`
}
}
修改
webpack
的publicPath
为:'/micro-apps/system/'修改项目的
route
模式为history
模式(vue项目);其根路径修改为:/system
const history = createWebHistory('/system')
createRouter({
history,
...
})
5.4 给子应用增加生命周期
main.js
const { bootstrap, mount, unmount } = initMicroApp({
entry: App, // 入口
beforeMount, // 初始化之前的逻辑
beforeUnmount, // 卸载前逻辑
afterMount // 挂载后逻辑
})
export { bootstrap, mount, unmount }
代码略;
5.7 子应用使用容器的 axios
实例
其实思路很简单,容器初始化 axios
实例后,挂载到 window.SDK.request
上。
然后在子应用 src/utils/request.js
里定义:
export default request = window.SDK.request
这样就能让子应用的请求被统一拦截处理了。
5.8 其他逻辑细节
由于迁移逻辑细节太多,不在此赘述,大概陈列一下所做的工作:
登录状态、token 管理 菜单、路由、权限等管理 全局用户信息管理 全局权限状态提供 项目 vuex
状态管理拆分页面拆分 等等...
六、通过代理层获取资源
qiankun.js
官方用例使用端口区分 微应用
和 容器
,在实际开发中这显然开销过大。
开发期可能需要起一堆应用🤣。(如图)
当然,不必惊慌,方法总比困难多。
当你需要 “容器” 或 “其他微应用” 的资源时,完全不必局限在本地端口中获取,也可以在某个开发环境上直接拉取。
此时,一个独立轻量的 “代理层” 可能就会显得 恰如其分。
虽然也可以在每个微应用内设置代理,但是很显然,一个轻量的代理层会让整个结构变得更加便捷,灵活。
为此,我专门写了另一篇实践文章:《充分且简单!打造专属“轻量代理神器”》,地址:https://juejin.cn/post/7094067487762808863
七、“微应用拆分” 与 “代码共用”
在 SPA
项目里,因为所有代码都在一个项目里,代码共用是一件简单且自然的事情。
但是一旦把业务拆分成 N
个微应用之后,就变得复杂起来。
如果把所有公共代码打包成 npm
组件显然是无法接受的,因为成本太高。
因此我们采取了如下方案:
yarn workspace + git submodule
新建一个名为
smart-vue3
的仓库,其package.json
中name
命名为@chunge/smart-vue3
在业务仓库内执行:
git submodule add <url> src/smart-vue3
这样就可以创造一个软连接指向目标仓库。
在业务仓库内的 pacage.json
里添加属性:
{
"workspaces": [
"src/smart-vue3"
],
}
执行 yarn
这样就可以在node_modules
里创造一个名为@chunge/smart-vue3
的项目。
通过这种方式,我们可以将需要复用的代码存放其中,完成低成本的“跨应用代码复用”;
通过以下方式,可轻松引入复用代码:
import { xxx } from '@chunge/smart-vue3/components/table'
代码略;
八、使用 webpack externals
特性进行微应用瘦身
本次拆分中,因为几乎所有子应用都是用了 vue
/element-plus
/echarts
等依赖。所以子应用大量的体积是可以优化的。
在容器的 src/pages/document.ejs
文件中,增加以下标签到 head
中:
<script src="//unpkg.com/vue@3.2.33"></script>
<script src="//unpkg.com/element-plus@1.1.0-beta.24"></script>
<link rel="stylesheet" href="//unpkg.com/element-plus@1.1.0-beta.24/dist/index.css" />
<script src="//unpkg.com/echarts@4.2.1/dist/echarts.min.js"></script>
然后在子应用的 webpack
配置中增加如下内容:
chainWebpack(config) {
config.externals([
{
vue: 'Vue',
'element-plus': 'ElementPlus',
echarts: 'echarts'
},
/^(echarts|\$)$/i
])
}
这样可以达到让每个微应用的体积降低 1.2M
左右;
九、构建脚本
微前端的构建通常有两种思路:
直接构建
这种思路是在一次构件中,将所有需要的微应用依次构建,然后按需要组织构建物,最终形成一个前端制品提供给用户。先构建制品,再组装制品 这种方式是分别给每个容器、微应用设置版本号,打包成前端制品。在按需拉取打包后的制品,按需组装成最终的前端制品。
方案2显然是更适合大型项目的管理方法,也可以达到“制品级”复用的效果的。
但也有很多管理上成本的开销,一般在项目较为成熟时使用。
在产品成熟之前,采用方案1开销更小,少造轮子。
具体实施方案如下:
新增一个 builder
项目。builder
通过git submoudle
关联到所有微应用和容器。通过脚本依次构建 通过脚本搬运构建物,组成制品。
代码略;
十、展望
“背景”介绍了本项目“微前端”化的背景,是 SASS
化思路下的产物,也是巨石应用的救星。
展望:在企业信息化、服务化发展进程中,“微前端”方案具备非常大的竞争力与优势,“制品级复用”、“巨石项目拆分” 等都是提效和降低风险的有效举措。
往期推荐
最后
欢迎加我微信,拉你进技术群,长期交流学习...
欢迎关注「前端Q」,认真学前端,做个专业的技术人...