在讲解微前端之前先给大家看个示例(https://www.ucloud.cn/):
UCloud是一个云计算服务平台,是比较典型的中后台应用,像这种应用往往面临着当业务发展之后,由刚刚开始的单体应用进化成巨石应用的问题,比如UCloud有很多的产品,如下图
如果按照我们通常的开发单页面后台应用的规则去开发的话,往往会面临几个问题:
为解决以上问题,我们引入微前端的概念:
那么到底什么是微前端,借助qiankun官网的说法,微前端是一种多个团队通过独立发布功能的方式来共同构建现代化 web 应用的技术手段及方法策略。简单来说将前端应用分解成一些更小、更简单的能够独立开发、测试、部署的小块,而在用户看来仍然是内聚的单个产品
我们先看一个基本的微前端框架构成,微前端由主应用和子应用构成,主应用就是最外面的框架,子应用可以是任何语言来写,可以单独运行,也可以放到主框架中进行运行
根据上图可知,微前端架构具备以下几个核心价值:
技术栈无关
主框架不限制接入应用的技术栈,可以随意选择vue,react,angular等框架接入其中
独立开发、独立部署
微应用仓库独立,前后端可独立开发,部署完成后主框架自动完成同步更新
增量升级
在面对各种复杂场景时,我们通常很难对一个已经存在的系统做全量的技术栈升级或重构,而微前端是一种非常好的实施渐进式重构的手段和策略
独立运行时
每个微应用之间状态隔离,运行时状态不共享,将巨石应用拆解成若干可以自治的松耦合微应用
官网地址:https://qiankun.umijs.org/zh/guide
简介:qiankun 是蚂蚁金服基于single-spa 的微前端解决方案
我们借用vue的hash模式进行开发
//main.js文件
import Vue from "vue";
import App from "@/App.vue";
import store from "@/store";
import router from "@/router";
import setupQk from "@/setup/qiankun/index"
new Vue({
store,
router,
render: h => h(App)
}).$mount("#app");
setupQk() //安装乾坤
除了上面基本的路由,vuex配置,我们主要看下setupQk,主要安装逻辑在这里
// @/setup/qiankun/index.js文件
import microAppConfig from "./config.js"; //此文件代码在下方
import {start,registerMicroApps,initGlobalState} from "qiankun";
//乾坤配置 https://qiankun.umijs.org/zh/api
const qkConfig = {
sandbox: { strictStyleIsolation: true } //开启shadow dom沙箱隔离
}
let actions =null;
/**
* @description: 添加监测状态
*/
function addState(state) {
if(!actions){
/**
* @description: 乾坤初始化全局监听状态
* @param state要初始化的值
*/
actions = initGlobalState(state);
actions.onGlobalStateChange(
(value, prev) => {;
console.log(`[主应用接收到值变化 - ${actions}]:`, value, prev);
}
);
}else{
/**
* @description: 乾坤设置state值
* @param state要更新的值
*/
actions.setGlobalState(state);
}
}
/**
* @description: 注册子应用
*/
function registerApp(){
/**
* @description: 乾坤注册子应用
* @param1 子应用配置
* @param2 打开子应用时候触发的生命周期
*/
registerMicroApps(microAppConfig, {
beforeLoad: [
app => {
console.log("[主应用] before load %c%s", "color: green;", app.name);
}
],
beforeMount: [
app => {
console.log("[主应用] before mount %c%s", "color: green;", app.name);
}
],
afterMount: [
app => {
console.log("[主应用] afterMount mount %c%s","color: green;",app.name);
}
],
afterUnmount: [
app => {
console.log("[主应用] after unmount %c%s", "color: green;", app.name);
}
]
});
}
/**
* @description: 挂载乾坤框架
*/
function setupQk() {
let state = {a:123}
registerApp()
addState(state)
/**
* @description: 乾坤开启服务
* @param1 全局配置
*/
start(qkConfig);
}
export default setupQk;
所有配置子应用的配置都在配置文件config.js中
import store from "@/store";
const loader = function(_status) {
store.dispatch("setApplicationState", _status);
};
const getActiveRule = hash => location => location.hash.startsWith(hash);
export default [
{
name: "quality", //子应用名称
entry: " http://localhost:8084", //子应用地址,假设此项目名称为智能质检应用(下面演示生命周期的时候会用到)
container: "#subapp-viewport", //子应用要挂载的节点,和vue.$mount("#app")类似;
loader,//切换时候的加载动画,根据自己需求加入
activeRule: getActiveRule("#/quality") //重点:路由命中规则,当浏览器链接有#/quality时候,例如http://localhost:9000/#/quality/* ,就可以自动的把http://localhost:808挂载到本地的subapp-viewport节点下
},
{
name: "training",
entry: "http://localhost:8081",//子应用地址,假设此项目名称为智能陪练应用
container: "#subapp-viewport",
loader,
activeRule: getActiveRule("#/training")
}
];
配置好了乾坤主应用一定要配置路由规则
//路由配置文件
import Main from '@/view/main/main.vue' //代码在下方
const appRouter = [
{
path: "/training/*", //重点:路由匹配规则一定要加/*,因为要匹配子应用路由
name: "training",
meta: {
title: "智能陪练",
icon: "el-icon-goods",
},
component: Main
},
{
path: "/quality/*",
name: "quality",
meta: {
title: "智能质检",
icon: "el-icon-goods",
},
component: Main
}
];
export default appRouter;
//main.vue
...
写到这里主应用基本上已经ok了,然后我们再来写一下子应用的挂载
//main.js
import Vue from "vue";
import App from "@/App.vue";
import store from "@/store";
import router from "@/router";
import setupQk from "@/setup/qiankun/index.js";
function createApp() {
return new Vue({
router,
store,
render: h => h(App)
});
}
if (!window.__POWERED_BY_QIANKUN__) {
/**
* @description: 保证非嵌套在乾坤子应用可以独立运行
*/
createApp().$mount("#app");
} else {
setupQk(createApp);
}
/**
* @description: 重点:抛出子应用生命周期
*/
export { bootstrap, mount, unmount } from "@/setup/qiankun/index.js";
// @/setup/qiankun/index.js
let app = null;
let createApp = null;
let appEmitIns = null;
/**
* @description: 监测数据变化
*/
function Monitor(props) {
appEmitIns = props;
appEmitIns.onGlobalStateChange((value, prev) => {
console.log(`[陪练应用接收到值变化 - ${props.name}]:`, value, prev);
}, true);
}
/**
* @description: 修改应用的值,注意:子应用中只能修改已存在的一级属性
*/
export function appEmit() {
if (appEmitIns) {
appEmitIns.setGlobalState({
a: 444444
});
}
}
/**
* @description: 子应用初始化
*/
export async function bootstrap() {
console.log("[陪练应用] bootstrap");
}
/**
* @description: 子应用挂载
*/
export async function mount(props) {
console.log("[陪练应用] mount", props);
if (!app) {
const { container } = props;
app = createApp();
app.$mount(container.querySelector("#app"));
}
Monitor(props);
}
/**
* @description: 子应用卸载
*/
export async function unmount() {
console.log("[陪练应用] unmount");
app.$destroy();
app.$el.innerHTML = "";
app = null;
appEmitIns = null;
}
/**
* @description: 挂载乾坤子应用
*/
function setupQk(_createApp) {
// qiankun 将会在微应用 bootstrap 之前注入一个运行时的 publicPath 变量,你需要做的是在微应用的 entry js 的顶部添加
__webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
createApp = _createApp;
}
export default setupQk;
//vue.config.js
module.exports ={
...
configureWebpack: config => {
// 把子应用打包成 umd 库格式
config.output.library = `${name}-[name]`;
config.output.libraryTarget = "umd";
config.output.jsonpFunction = `webpackJsonp_${name}`;
...
}
...
}
当我第一次打开陪练项目时候,生命周期如下图
当我切换子应用为智能质检项目的时候,会触发下图的生命周期
我们已经在主应用中设置了默认值,再次回顾下主应用代码
// /setup/qiankun/index.js
function setupQk() {
let state = {a:123}
registerApp();
addState(state); //初始化为{a:123}
start(qkConfig);
}
先看下子应用监测数据变化函数,onGlobalStateChange接受两个参数,第一个在当前应用监听全局状态,有变更触发 callback,第二个参数 true 代表立即触发 callback
/**
* @description: 监测数据变化
*/
function Monitor(props) {
appEmitIns = props;
appEmitIns.onGlobalStateChange((value, prev) => {
console.log(`[质检应用接收到值变化 - ${props.name}]:`, value, prev);
}, true);
}
/**
* @description: 修改应用的值,注意:子应用中只能修改已存在的一级属性
*/
export function appEmit() {
if (appEmitIns) {
appEmitIns.setGlobalState({
a: 444444,
b:333
});
}
}
当我点击页面上的测试时候会触发appEmit函数,看下效果
由效果可见,会先触发主应用的监听,再触发子应用的监听,但是b赋值给333是不起作用的,因为子应用中只能修改已存在的一级属性