背景
日常工作中,我们可能会遇到以下某个场景:
- 历史项目使用了旧的技术栈,老系统不能抛弃,重构整个系统成本太高,新技术得不到使用
- 一个庞大的系统需要拆分给不同团队去开发维护
- A系统需要引用B系统的几个页面或整个服务,如何低成本的引用?
微前端架构旨在解决单体应用在一个相对长的时间跨度下,由于参与的人员、团队的增多、变迁,从一个普通应用演变成一个巨石应用(Frontend Monolith)后,随之而来的应用不可维护的问题。这类问题在企业级 Web 应用中尤其常见。
什么是微前端
Techniques, strategies and recipes for building a modern web app with multiple teams that can ship features independently. -- Micro Frontends
微前端是一种多个团队通过独立发布功能的方式来共同构建现代化 web 应用的技术手段及方法策略。
微前端的价值
微前端架构具备以下几个核心价值:
-
技术栈无关
主框架不限制接入应用的技术栈,微应用具备完全自主权 -
独立开发、独立部署
微应用仓库独立,前后端可独立开发,部署完成后主框架自动完成同步更新 -
增量升级
在面对各种复杂场景时,我们通常很难对一个已经存在的系统做全量的技术栈升级或重构,而微前端是一种非常好的实施渐进式重构的手段和策略 -
独立运行时
每个微应用之间状态隔离,运行时状态不共享
方案实践
qiankun(乾坤)是由蚂蚁金服推出的基于Single-Spa实现的前端微服务框架,它提供了更加开箱即用的 API,做到了技术栈无关,接入十分简单。
本文将基于qiankun(乾坤)介绍如何搭建主应用基座,然后接入不同技术栈的微应用,完成微前端架构的从0到1。
本次实践 Demo 的整体布局如下图所示,采用常见的T型布局,顶部可放置系统标志等信息,下方左侧是菜单区域,下方右侧是页面展示区域,根据不同路由展示不同应用的页面。本 Demo 以 Vue 项目作为主应用基座,然后分别接入 Vue、React、Angular的子应用。
构建主应用基座
通过 Vue-cli 初始化一个 Vue 项目,作为主应用,主应用暂且命名为 micro-app-main 。简单改造后,得到一个T型布局如下图:
接下来需要将micro-app-main改造成 qiankun 的主应用基座。
1.安装qiankun:
$ npm i qiankun -S
2.注册子应用并启动
首先,在主应用中创建一个子应用的承载容器,只需要提供一个容器 DOM 即可,该容器规定了子应用的显示区域,子应用将会在该容器内渲染并显示。创建容器时需保证其在主应用中的唯一性,这里创建了一个id 为 micro-app-container 的容器。可以看到,主应用的组件和微应用是显示在同一内容区域。
然后修改入口文件 main.js ,通过 qiankun(乾坤) 的 registerMicroApps 和 start 方法注册子应用并启动。实现代码如下:
// micro-app-main/src/main.js
import Vue from 'vue'
import App from './App'
import router from './router'
import { registerMicroApps, start } from 'qiankun';
Vue.config.productionTip = false
const vueApp = new Vue({
router,
render: h => h(App),
}).$mount('#app')
/* 确保装载子应用的容器已创建,等DOM加载完成后启动子应用 */
vueApp.$nextTick( () => {
/* 注册子应用 */
registerMicroApps([
/**
* name: 子应用名称 - 子应用之间必须确保唯一
* entry: 子应用入口 - 通过该地址加载微应用
* container: 子应用挂载节点 - 子应用加载完成后将挂载在该节点上
* activeRule: 子应用触发的路由规则 - 触发路由规则后将加载该子应用
*/
]);
// 启动子应用
start();
})
需要注意的是,registerMicroApps 方法第一个参数是一个数组,主要是子应用的一些注册信息。其中,name 是子应用的名称,需保证其唯一性; entry 是子应用的入口;container 是子应用的容器节点的选择器或者 Element 实例;activeRule 是子应用的激活规则。
当匹配到 activeRule 对应的路由时, 对应 name 的子应用,将从其 entry 入口将子应用渲染到 container 区域。
这里尚未注册子应用,是个空数组,留待后续逐个补充。
接入Vue子应用
初始化一个 Vue 子应用 micro-app-vue ,为了区别于主应用,这里更改其端口为 9523,如下图。
1.注册Vue子应用
修改主应用 main.js 文件,补充 registerMicroApps 方法缺省参数:
通过图中的代码,完成 Vue 子应用的注册。当主应用中匹配到 /app-vue 路由时,子应用就会挂载到主应用的 micro-app-container 容器中。
接着,在主应用的菜单列表中新增一个 “Vue页面” 的菜单,并配置路由为 /app-vue,具体实现省略。
2.配置Vue子应用
首先,在 Vue 子应用的 src 目录下新增 public-path.js ,代码如下:
if (window.__POWERED_BY_QIANKUN__) {
__webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}
然后,修改入口文件 main.js ,具体配置如下:
分析图中代码:
第1行 引入 public-path.js。webpack 默认的 publicPath 是空字符串,会基于当前路径来加载资源。在主应用中加载子应用时需要重新设置 publicPath。
第12行 render函数,是子应用渲染函数,有两种执行情况:主应用中运行时将在 mount 生命周期钩子函数中调用;子应用独立运行时,直接执行。
第26行 当子应用独立运行时,直接执行render函数进行挂载。主应用会在 window 上添加 POWERED_BY_QIANKUN 属性,用于子应用区分当前是否被主应用加载,或是独立运行。
第30行 子应用导出的生命周期钩子函数 bootstrap。
第34行 子应用导出的生命周期钩子函数 mount。
第39行 子应用导出的生命周期钩子函数 unmount。
为了让主应用能正确识别子应用暴露出来的信息,还需要修改 Vue 子应用webpack配置,主要修改内容如下:
const { name } = require('../package');
module.exports = {
devServer: {
headers: {
'Access-Control-Allow-Origin': '*', // 允许跨域
},
},
configureWebpack: {
output: {
library: `${name}-[name]`,
libraryTarget: 'umd', // 把子应用打包成 umd 库格式
jsonpFunction: `webpackJsonp_${name}`,
},
},
};
至此,Vue 子应用 micro-app-vue 就接入完成。重启应用,具体效果如下图:
可以看到,在主应用中点击 “Vue页面” 菜单,路由跳转至 “/app-vue”,内容显示区域相应的会显示micro-app-vue 子应用的页面;当单独访问子应用地址时也可以正确显示子应用。
通过查看主应用的页面渲染区域的dom元素,可以看到,子应用已挂载到主应用的承载容器 micro-app-container 下,如图:
接入React子应用
初始化一个 React 子应用 micro-app-react,端口为3000。本 Demo 是以 create-react-app 生成的一个 react 16 项目为例,如图:
1.注册React子应用
在主应用中注册 React 子应用,修改主应用到入口文件 main.js :
这里设置 React 子应用的匹配路由规则为 /app-react。然后在主应用中新增一个 “React页面” 的菜单,并设置其路由为 /app-react,菜单部分的具体实现略去。
2.配置React子应用
首先,在 React 子应用的 src 目录下新增一个 public-path.js 文件:
if (window.__POWERED_BY_QIANKUN__) {
__webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}
其次,修改 React 子应用入口文件 index.js:
分析图中代码:
第1 行 引入 public-path.js 文件。当在主应用中加载子应用时需要重新设置 publicPath。
第6 行 子应用渲染函数。
第12行 子应用独立运行时,调用 reader 函数,传空参。
第17行 子应用导出的生命周期钩子函数 bootstrap。
第22行 子应用导出的生命周期钩子函数 mount,调用 render 函数渲染子应用。
第28行 子应用导出的生命周期钩子函数 unmount。
接着,设置路由命名空间,确保主应用能正确加载子应用:
然后,需要修改 webpack 配置,使 index.js 导出的生命周期钩子函数可以被 qiankun(乾坤)识别获取。这里通过安装插件 react-app-rewired 来修改,也可以用其他的插件,如 @rescripts/cli。
npm install react-app-rewired -s
安装完 react-app-rewired 后,需要修改 package.json 的 scripts 选项,修改为由 react-app-rewired 启动应用,如图:
然后在 React 子应用根目录下新增 config-overrides.js 文件来配置 webpack ,具体代码如下图:
至此,React 子应用 micro-app-react 就完成接入。重启应用后,效果如图:
接入Angular子应用
初始化一个 Angular 子应用,命名为 micro-app-angular ,端口号为 4200 。本 Demo 是以 Angular CLI 9.1.15 生成的 angular 9 项目为例。
1.注册Angular子应用
在主应用的 main.js 文件中注册 Angular 子应用:
需要注意的是,由于 Angular 运行依赖于 zone.js ,且 qiankun(乾坤)框架是基于 single-spa 实现的,而 single-spa 明确指出一个项目的 zone.js 只能存在一份实例,所以需要在主应用注入 zone.js:
npm install --save zone.js
这里设置 Angular 子应用的路由匹配规则为 /app-angular 。然后在主应用中新增一个 “Anguarl页面” 的菜单,并设置其路由为 /app-angular。
2.配置Angular子应用
首先,在 Angular 子应用的 src 目录下新增一个 public-path.js 文件:
if (window.__POWERED_BY_QIANKUN__) {
__webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}
其次,设置 history 模式路由的 base,修改 src/app/app-routing.module.ts 文件:
然后修改子应用入口文件 src/main.ts :
分析图中代码:
第1 行 引入 public-path.js 文件。主应用中加载子应用时需要重新设置 publicPath。
第13行 子应用渲染函数。
第19行 子应用独立运行时,调用 reader 函数。
第24行 子应用导出的生命周期钩子函数 bootstrap。
第29行 子应用导出的生命周期钩子函数 mount,调用 render 函数渲染子应用。
第34行 子应用导出的生命周期钩子函数 unmount,卸载子应用。
接着需要修改 webpack 的打包配置。安装 @angular-builders/custom-webpack 插件:\
npm i @angular-builders/custom-webpack@9.2.0 -s
并在子应用的根目录下新增 custom-webpack.config.js 文件,如下图:
随后需修改 angular.json,将 architect > build > builder 和 architect > serve > builder 的值改为所安装的插件,并将打包配置文件加入到 architect > build > options 中,如下图:
至此,就完成了Angular子应用 micro-app-angular 的接入。重启应用后,效果如图:
小结
本文主要总结了如何使用 qiankun(乾坤)搭建主应用基座,并接入不同技术栈的子应用。其原理就是,通过在主应用中引入每个子应用的入口文件(main.js),进行解析,指定渲染的容器(DOM),其次每个子应用设置打包的文件为 UMD,并允许跨域,然后在入口文件main.js中暴露(export)生命周期方法(bootstrap、mount,unmount),并在其 mount 生命周期中进行渲染应用,在unmount 生命周期中执行destory 销毁应用。
通过本次实践,最大的感受就是 qiankun(乾坤)框架的开箱可用性非常强,需要更改的项目配置也很少,真正做到了技术栈无关、样式隔离,它同时也支持应用间通信,由于篇幅原因,应用间的通信此处没有记录,留待后续分享。