前言
随着技术栈越来越广泛,许多前端框架也层出不穷,使得我们在构建 web 应用时,由于技术栈之间的差异,难以协同开发。而目前其中一个主流方案就是使用微前端,让不同技术栈、框架能独立开发,独立部署子模块,再共同嵌入主模块,形成一个完整的大型 web 应用。微前端是一种多个团队通过独立发布功能的方式来共同构建现代化 web 应用的技术手段及方法策略。
本文主要介绍微前端实现方案之一:qiankun。
正文
一、介绍
微前端
微前端是一种多个团队通过独立发布功能的方式来共同构建现代化 web 应用的技术手段及方法策略。
微前端架构具备以下几个核心价值:
- 技术栈无关 主框架不限制接入应用的技术栈,微应用具备完全自主权
- 独立开发、独立部署 微应用仓库独立,前后端可独立开发,部署完成后主框架自动完成同步更新
- 增量升级在面对各种复杂场景时,我们通常很难对一个已经存在的系统做全量的技术栈升级或重构,而微前端是一种非常好的实施渐进式重构的手段和策略
- 独立运行时 每个微应用之间状态隔离,运行时状态不共享
微前端架构旨在解决单体应用在一个相对长的时间跨度下,由于参与的人员、团队的增多、变迁,从一个普通应用演变成一个巨石应用(Frontend Monolith)后,随之而来的应用不可维护的问题。这类问题在企业级 Web 应用中尤其常见。
qiankun
qiankun 是一个基于 single-spa 的微前端实现库,旨在帮助大家能更简单、无痛的构建一个生产可用微前端架构系统。
qiankun 孵化自蚂蚁金融科技基于微前端架构的云产品统一接入平台,在经过一批线上应用的充分检验及打磨后,我们将其微前端内核抽取出来并开源,希望能同时帮助社区有类似需求的系统更方便的构建自己的微前端系统,同时也希望通过社区的帮助将 qiankun 打磨的更加成熟完善。
目前 qiankun 已在蚂蚁内部服务了超过 2000+ 线上应用,在易用性及完备性上,绝对是值得信赖的。
qiankun 的核心理念:
- 简单由于主应用微应用都能做到技术栈无关,qiankun 对于用户而言只是一个类似 jQuery 的库,你需要调用几个 qiankun 的 API 即可完成应用的微前端改造。同时由于 qiankun 的 HTML entry 及沙箱的设计,使得微应用的接入像使用 iframe 一样简单。
- 解耦/技术栈无关微前端的核心目标是将巨石应用拆解成若干可以自治的松耦合微应用,而 qiankun 的诸多设计均是秉持这一原则,如 HTML entry、沙箱、应用间通信等。这样才能确保微应用真正具备 独立开发、独立运行 的能力。
特性:
- 基于 single-spa 封装,提供了更加开箱即用的 API。
- 技术栈无关,任意技术栈的应用均可 使用/接入,不论是 React/Vue/Angular/JQuery 还是其他等框架。
- HTML Entry 接入方式,让你接入微应用像使用 iframe 一样简单。
- 样式隔离,确保微应用之间样式互相不干扰。
- JS 沙箱,确保微应用之间 全局变量/事件 不冲突。
- 资源预加载,在浏览器空闲时间预加载未打开的微应用资源,加速微应用打开速度。
- umi 插件,提供了 @umijs/plugin-qiankun 供 umi 应用一键切换成微前端架构系统。
二、快速上手
主应用
① 安装 qiankun
$ npm i qiankun -S # 或者 yarn add qiankun
② 在主应用中注册微应用
import { registerMicroApps, start } from 'qiankun';
registerMicroApps([
{
name: 'vue-app',
entry: '//localhost:8080',
container: '#container',
activeRule: '/activeRule',
},
]);
start();
当微应用信息注册完之后,一旦浏览器的 url 发生变化,便会自动触发 qiankun 的匹配逻辑,所有 activeRule 规则匹配上的微应用就会被插入到指定的 container 中,同时依次调用微应用暴露出的生命周期钩子。
微应用
微应用不需要额外安装任何其他依赖即可接入 qiankun 主应用。
① 导出相应的生命周期钩子
微应用需要在自己的入口 js (通常就是你配置的 webpack 的 entry js) 导出
bootstrap
、
mount
、
unmount
三个生命周期钩子,以供主应用在适当的时机调用。
/**
* bootstrap 只会在微应用初始化的时候调用一次,下次微应用重新进入时会直接调用 mount 钩子,不会再重复触发 bootstrap。
* 通常我们可以在这里做一些全局变量的初始化,比如不会在 unmount 阶段被销毁的应用级别的缓存等。
*/
export async function bootstrap() {
console.log('app bootstraped');
}
/**
* 应用每次进入都会调用 mount 方法,通常我们在这里触发应用的渲染方法
*/
export async function mount(props) {
// 渲染方法等
}
/**
* 应用每次 切出/卸载 会调用的方法,通常在这里我们会卸载微应用的应用实例
*/
export async function unmount(props) {
}
/**
* 可选生命周期钩子,仅使用 loadMicroApp 方式加载微应用时生效
*/
export async function update(props) {
console.log('update props', props);
}
② 配置微应用的打包工具
除了代码中暴露出相应的生命周期钩子之外,为了让主应用能正确识别微应用暴露出来的一些信息,微应用的打包工具需要增加如下配置:
webpack:
const packageName = require('./package.json').name;
module.exports = {
output: {
library: `${packageName}-[name]`,
libraryTarget: 'umd',
jsonpFunction: `webpackJsonp_${packageName}`,
},
};
三、项目实战
以下例子中我创建两个 vue2 项目,一个为主应用,一个为微应用(当然你也可以使用其他框架)。话不多说,直接实战!
主应用
主应用不限技术栈,只需要提供一个容器 DOM,然后注册微应用并
start
即可。这里我的主应用是 vue2 项目,项目的搭建过程不在此赘述,直接进入 qiankun 使用阶段。
项目视图如下:
① 安装 qiankun
npm i qiankun -S
② 注册微应用并启动
为了结构清晰,我在 /src 下新建一个 micro 目录,里面新建一个文件 index.js
index.js
import { registerMicroApps, start } from 'qiankun';
// 注册微应用
registerMicroApps([
{
name: 'appVue2.7', // 自定义微应用名称
entry: '//localhost:8081/', // 微应用的入口地址,即微应用运行起来的地址
container: '#container', // 挂载微应用内容的dom节点(此处为主应用的dom)
activeRule: '/#/app-vue',
// 匹配微应用的路由前缀(/#/,主应用为hash模式,在url命中时,即加载对应的微应用)
},
]);
// 启动 qiankun
start();
③ 主应用添加路由
由于我们在主应用使用了左侧菜单栏,且设置了路由展示,因此我们需要在主应用中配置微应用对应的路由 path,确保点击菜单栏能切换到对应的路由地址。
注意:在注册微应用前,确保我们的微应用启动正常。
此时我已经另外新建一个 vue2.7 的项目当成微应用(当然你也可以使用 react、angular 等框架)并且已经成功启动,地址为 localhost:8081(即主应用注册微应用配置中的 entry)。
微应用视图展示:
接下来就是微应用的配置。
微应用
微应用分为有
webpack
构建和无
webpack
构建项目,有
webpack
的微应用(主要是指 Vue、React、Angular)需要做的事情有:
① 新增
public-path.js
用于修改运行时的
publicPath
。什么是运行时的 publicPath ?。
if (window.__POWERED_BY_QIANKUN__) {
window.__webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}
注意:运行时的 publicPath 和构建时的 publicPath 是不同的,两者不能等价替代。
② 建议使用
history
模式
需要设置路由
base
,值和它的
activeRule
是一样的。该例子微应用暂不使用路由(本文“扩展”部分添加路由使用部分,不容错过哟)。
③ 引入
public-path.js
修改并导出三个生命周期函数。
// main.js
import './public-path'; // 引入
import Vue from 'vue';
import App from './App.vue';
Vue.config.productionTip = false;
let instance = null;
function render(props = {}) {
const { container } = props;
instance = new Vue({
render: (h) => h(App),
}).$mount(container ? container.querySelector('#app') : '#app');
}
// 独立运行时
if (!window.__POWERED_BY_QIANKUN__) {
render();
}
export async function bootstrap() {
console.log('[vue] vue app bootstraped');
}
export async function mount(props) {
console.log('[vue] props from main framework', props);
render(props);
}
export async function unmount() {
instance.$destroy();
instance.$el.innerHTML = '';
instance = null;
}
④ 修改
webpack
允许开发环境跨域和
umd
打包。
// vue.config.js
const { defineConfig } = require('@vue/cli-service')
const { name } = require('./package');
module.exports = defineConfig({
transpileDependencies: true,
devServer: {
headers: {
'Access-Control-Allow-Origin': '*',
},
},
configureWebpack: {
output: {
library: `${name}-[name]`,
libraryTarget: 'umd', // 把微应用打包成 umd 库格式
//jsonpFunction: `webpackJsonp_${name}`,
},
},
})
主要的修改就是以上四个,可能会根据项目的不同情况而改变。例如,你的项目是
index.html
和其他的所有文件分开部署的,说明你们已经将构建时的
publicPath
设置为了完整路径,则不用修改运行时的
publicPath
(第一步操作可省)。
无
webpack
构建的微应用直接将
lifecycles
挂载到
window
上即可。
整合效果
在主应用、微应用都配置完毕之后,接下来就是见证奇迹的时刻了。
我们在主应用切换到微应用匹配的 url,即在注册微应用时
activeRule: '/#/app-vue',
我们在主应用切换菜单
此时地址为 ’/#/app-vue’(hash 模式),但是子应用却没展示出来。经过排查,我们发现在注册微应用时,我们配置了一个容器用于展示微应用
container: '#container',
但是在主应用中并没有找到对应容器,于是重新修改代码
此时刷新,微应用成功展示
主应用端口为 8080,微应用端口为 8081,且主应用顺利挂载微应用,微前端方案成功实现!
四、扩展
回过头来看,我们主应用路由使用的模式是 hash 模式,那么改成 history 模式又该如何修改?
其实很简单,只需要修改两处地方:
修改主应用路由模式
修改注册微应用配置
修改完毕,回到页面,查看效果,history 模式显示正常
为微应用添加路由
前面我们提到,微应用路由建议使用 history 模式,并设置路由
base
,值和它的
activeRule
是一样的。
import Vue from 'vue'
import Router from 'vue-router'
import HelloWorld from '@/components/HelloWorld'
import About from '@/components/About'
Vue.use(Router)
export default new Router({
// 路由模式
mode: 'history',
// 本项目为微应用时,基础路径为 /app-vue,即与注册微应用时的 activeRule 一致
base: window.__POWERED_BY_QIANKUN__ ? "/app-vue" : "/",
routes: [
{
path:'/',
redirect:'/home'
},
{
path: '/home',
name: 'HelloWorld',
component: HelloWorld
},
{
path: '/about',
name: 'About',
component: About
}
]
})
在 mian.js 中注入路由
import './public-path';
import Vue from 'vue';
import App from './App.vue';
import router from './router';
Vue.config.productionTip = false;
let instance = null;
function render(props = {}) {
const { container } = props;
instance = new Vue({
router, // 添加此行,注入路由,其他配置不变
render: (h) => h(App),
}).$mount(container ? container.querySelector('#app') : '#app');
}
// 独立运行时
if (!window.__POWERED_BY_QIANKUN__) {
render();
}
export async function bootstrap() {
console.log('[vue] vue app bootstraped');
}
export async function mount(props) {
console.log('[vue] props from main framework', props);
render(props);
}
export async function unmount() {
instance.$destroy();
instance.$el.innerHTML = '';
instance = null;
}
到主应用查看效果
微应用中 Home、About 顺利切换与展示,路由添加成功。至此,一个基础的微前端 web 项目搭建起来了,我们可以在这个的基础上继续进行迭代与开发。微应用不限制技术栈与框架,多人协同开发大型web项目,简直YYDS!
更多细节请参考:qiankun - qiankun
写在最后
创作不易,如有帮助,请不要吝啬您的手指,一键三连点一波,感谢支持;如有不恰当之处,还请不吝赐教。
版权归原作者 前端不释卷leo 所有, 如有侵权,请联系我们删除。