1、微前端
微前端是一种多个团队通过独立发布功能的方式来共同构建现代化 web 应用的技术手段及方法策略;让不同技术栈、框架能独立开发,独立部署子模块,再共同嵌入主模块,形成一个完整的大型 web 应用;
微前端架构旨在解决单体应用在一个相对长的时间跨度下,由于参与的人员、团队的增多、变迁,从一个普通应用演变成一个巨石应用后,随之而来的应用不可维护的问题;
2、微前端的特定
技术栈无关:主框架不限制接入应用的技术栈,微应用具备完全自主权;
独立开发、独立部署:微应用仓库独立,前后端可独立开发,部署完成后主框架自动完成同步更新;
增量升级:在面对各种复杂场景时,我们通常很难对一个已经存在的系统做全量的技术栈升级或重构,而微前端是一种非常好的实施渐进式重构的手段和策略;
独立运行时:每个微应用之间状态隔离,运行时状态不共享;
qiankun 是一个蚂蚁金融科技提供的基于 single-spa 的微前端实现库,旨在帮助大家能更简单、无痛的构建一个生产可用微前端架构系统。
1、核心设计理念
简单:qiankun 对于用户而言只是一个类似 jQuery 的库,你需要调用几个 qiankun 的 API 即可完成应用的微前端改造;同时由于 qiankun 的 HTML entry 及沙箱的设计,使得微应用的接入像使用 iframe 一样简单;
解耦/技术栈无关:qiankun 的诸多设计如 HTML entry、沙箱、应用间通信等;这样确保微应用真正具备 独立开发、独立运行 的能力;
2、特点
1、基于 single-spa 封装,提供了更加开箱即用的 API。
2、技术栈无关,任意技术栈的应用均可 使用/接入,不论是 React/Vue/Angular/JQuery 还是其他等框架。
3、HTML Entry 接入方式,让你接入微应用像使用 iframe 一样简单。
4、样式隔离,确保微应用之间样式互相不干扰。
5、JS 沙箱,确保微应用之间 全局变量/事件 不冲突。
6、资源预加载,在浏览器空闲时间预加载未打开的微应用资源,加速微应用打开速度。
7、umi 插件,提供了 @umijs/plugin-qiankun 供 umi 应用一键切换成微前端架构系统。
1、安装(主应用+子应用)
npm i qiankun -S
2、在主应用中注册微应用(main.js)
import { registerMicroApps, start } from 'qiankun';
registerMicroApps([
{
name: 'vue-app',
entry: '//localhost:8080',
container: '#container',
activeRule: '/activeRule',
},
]);
start();
当微应用信息注册完之后,一旦浏览器的 url 发生变化,便会自动触发 qiankun 的匹配逻辑,所有 activeRule 规则匹配上的微应用就会被插入到指定的 container 中,同时依次调用微应用暴露出的生命周期钩子;
3、在 src 目录新增 public-path.js(子应用)
if (window.__POWERED_BY_QIANKUN__) {
__webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}
修改子应用路径,保证静态资源正常显示;
4、导出相应的生命周期钩子(子应用)
import './public-path';
import Vue from 'vue';
import VueRouter from 'vue-router';
import App from './App.vue';
import routes from './router';
import store from './store';
Vue.config.productionTip = false;
let router = null;
let instance = null;
function render(props = {}) {
const { container } = props;
router = new VueRouter({
base: window.__POWERED_BY_QIANKUN__ ? '/app-vue/' : '/',
mode: 'history',
routes,
});
instance = new Vue({
router,
store,
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;
router = null;
}
路由模式建议使用 history;
5、修改打包配置
const { name } = require('./package');
module.exports = {
devServer: {
headers: {
'Access-Control-Allow-Origin': '*',
},
},
configureWebpack: {
output: {
library: `${name}-[name]`,
libraryTarget: 'umd', // 把微应用打包成 umd 库格式
jsonpFunction: `webpackJsonp_${name}`,
},
},
};
允许开发环境跨域和 umd 打包;
1、本地存储(localStorage/sessionStorage)
因为在 qiankun 中,主应用是通过 fetch 来拉取子应用的模板,然后渲染在主应用的 dom 上的,所以还是运行在同一个域名上,也就是主应用的域名;
2、官方提供的 props
//主应用
import { registerMicroApps, start } from "qiankun";
import router from '@/router'
const apps = [
{
name: "App1MicroApp",
entry: '//localhost:9001',
container: "#app1",
activeRule: "/app1",
props: {
parentRouter: router
}
}
];
registerMicroApps(apps);
start();
//子应用
export async function mount(props) {
render(props);
}
在主应用 中注册子应用时,将定义好的msg通过props参数传递给子应用;在子应用的 mounted 函数里将接收到的 props 参数内的函数;
3、官方提供的 actions
这种通信方式比较适合业务划分清晰,应用间通信较少的微前端应用场景;
qiankun 内部提供了 initGlobalState 方法用于注册 MicroAppStateActions 实例用于通信,该实例有三个方法,分别是:
setGlobalState:设置 globalState - 设置新的值时,内部将执行 浅检查,如果检查到 globalState 发生改变则触发通知,通知到所有的 观察者 函数。
onGlobalStateChange:注册 观察者 函数 - 响应 globalState 变化,在 globalState 发生改变时触发该 观察者 函数。
offGlobalStateChange:取消 观察者 函数 - 该实例不再响应 globalState 变化。
//主应用
import { initGlobalState, MicroAppStateActions } from 'qiankun';
// 初始化 state
const actions: MicroAppStateActions = initGlobalState(state);
actions.onGlobalStateChange((state, prev) => {
// state: 变更后的状态; prev 变更前的状态
console.log(state, prev);
});
actions.setGlobalState(state);
actions.offGlobalStateChange();
//子应用
// 从生命周期 mount 中获取通信方法
export function mount(props) {
props.onGlobalStateChange((state, prev) => {
// state: 变更后的状态; prev 变更前的状态
console.log(state, prev);
});
props.setGlobalState(state);
}
优点:使用简单、官方支持性高;适合通讯较少的场景;
缺点:
子应用独立运行时,需要额外配置无 Actions 时的逻辑;
子应用需要先了解状态池的细节,再进行通信;
由于状态池无法跟踪,通信场景较多时,容易出现状态混乱、维护困难等问题;
4、使用vuex或redux管理状态,通过shared分享
主应用基于 redux、vuex 维护一个状态池,通过 shared 实例暴露一些方法给子应用使用。同时,子应用需要单独维护一份 shared 实例,在独立运行时使用自身的 shared 实例,在嵌入主应用时使用主应用的 shared 实例,这样就可以保证在使用和表现上的一致性;
// micro-app-main/src/shared/index.ts
import store from "./store";
class Shared {
/**
* 获取 Token
*/
public getToken(): string {
const state = store.getState();
return state.token || "";
}
/**
* 设置 Token
*/
public setToken(token: string): void {
// 将 token 的值记录在 store 中
store.dispatch({
type: "SET_TOKEN",
payload: token
});
}
}
const shared = new Shared();
export default shared;
// micro-app-vue/src/shared/index.js
class Shared {
/**
* 获取 Token
*/
getToken() {
// 子应用独立运行时,在 localStorage 中获取 token
return localStorage.getItem("token") || "";
}
/**
* 设置 Token
*/
setToken(token) {
// 子应用独立运行时,在 localStorage 中设置 token
localStorage.setItem("token", token);
}
}
class SharedModule {
static shared = new Shared();
/**
* 重载 shared
*/
static overloadShared(shared) {
SharedModule.shared = shared;
}
/**
* 获取 shared 实例
*/
static getShared() {
return SharedModule.shared;
}
}
export default SharedModule;
qiankun 有两种运行模式:自动、手动;
官网文档:https://qiankun.umijs.org/zh/api
1、自动:(registerMicroApps + start)注册微应用的基础配置信息。当浏览器 url 发生变化时,会自动检查每一个微应用注册的 activeRule 规则,符合规则的应用将会被自动激活;
registerMicroApps(apps, {
// 加载子应用前,可以用来加载进度条
beforeLoad: (app) => console.log('before load', app.name),
beforeMount: (app) => console.log('before load', app.name),
// 加载子应用后,可以用来关闭进度条
afterMount: (app) => console.log('after load', app.name),
beforeUnmount: [(app) => console.log('before mount', app.name)],
afterUnmount: [(app) => console.log('before mount', app.name)],
},
);
apps 是微应用的一些注册信息;
首次load应用,创建子应用实例,渲染;当切到其他子应用后切回,会重新创建新的子应用实例并渲染;之前的子应用实例 qiankun 直接不要了,即使你没有手动销毁实例;所以说,采用这种模式的话一定要在子应用暴露的 unmount 钩子里手动销毁实例,不然就内存泄漏了;
另外,如果子应用使用 keep-alive 来保存状态,那么从子应用1切到子应用2,再切回子应用1,是不会保存状态的;
2、手动:(loadMicroApp)适用于需要手动 加载/卸载 一个微应用的场景;需要能支持主应用手动 update 微应用,需要微应用 entry 再多导出一个 update 钩子;
//子应用
export async function mount(props) {
renderApp(props);
}
// 微应用中增加 update 钩子以便主应用手动更新微应用
export async function update(props) {
renderPatch(props);
}
//主应用
componentDidMount() {
this.microApp = loadMicroApp({
name: 'app1',
entry: '//localhost:1234',
container: '#container',
props: { brand: 'qiankun' },
});
}
loadMicroApp 的策略是每个子应用都有一个唯一的实例ID,reload时会复用之前的实例;
一般情况下使用自动挡就可以了,如果微应用是一个不带路由的可独立运行的业务组件,可以用手动挡来加载这个子应用;
1、子应用缓存
https://github.com/kuaifengle/qiankun-vue3-tabsPage