阿里qiankun微前端框架实践

21,756 阅读3分钟

假如你接手了一个技术栈又老体积又大而同时又不能停下进行更新迭代的项目,如果你要进行重构你会怎么办?微前端可以说是这个问题的满分答案。

什么是微前端

微前端架构具备以下几个核心价值

  • 技术栈无关:主框架不限制接入应用的技术栈,子应用具备完全自主权

  • 独立开发、独立部署:子应用仓库独立,前后端可独立开发,部署完成后主框架自动完成同步更新

  • 独立运行时:每个子应用之间状态隔离,运行时状态不共享

    总结:结合开头的问题,可知微前端是为了解决中后台项目随着迭代体积不断膨胀导致的各种问题(例如:部署,存储)。

微前端解决方案

目前比较成熟的解决方案:

  • NPM式:子工程以NPM包的形式发布源码;打包构建发布还是由基座工程管理,打包时集成。
  • iframe式:子工程可以使用不同技术栈;子工程之间完全独立,无任何依赖;基座工程和子工程需要建立通信机制;无单页应用体验;路由地址管理困难。
  • 通用中心路由基座式:子工程可以使用不同技术栈;子工程之间完全独立,无任何依赖;统一由基座工程进行管理,按照DOM节点的注册、挂载、卸载来完成。
  • 特定中心路由基座式:子业务线之间使用相同技术栈;基座工程和子工程可以单独开发单独部署;子工程有能力复用基座工程的公共基建。

关于qiankun框架

qiankun ——— 一套完整的微前端解决方案https://github.com/umijs/qiankun

实践

如图所示,在qiankun框架中,有主程序与子程序。主程序会留出指定的DOM作为子程序的容器,并且通过主程序里的路由转发加载子应用。

主程序的处理:

修改主程序main.js注册子应用

import Vue from "vue"
import App from "./App.vue"
import router from "./router"

import { registerMicroApps, setDefaultMountApp, start } from "qiankun"
Vue.config.productionTip = false
let app = null;
/**
 * 渲染函数
 * appContent 子应用html内容
 * loading 子应用加载效果,可选
 */
function render({ appContent, loading } = {}) {
	if (!app) {
		app = new Vue({
			el: "#app",
			router,
			data() {
				return {
					content: appContent,
					loading
				};
			},
			render(h) {
				return h(App, {
					props: {
						content: this.content,
						loading: this.loading
					}
				});
			}
		});
	} else {
		app.content = appContent;
		app.loading = loading;
	}
}

/**
 * 路由监听
 * @param {*} routerPrefix 前缀
 */
function genActiveRule(routerPrefix) {
	return location => location.pathname.startsWith(routerPrefix);
}

function initApp() {
	render({ appContent: '', loading: true });
}

initApp();

// 传入子应用的数据
let msg = {
	data: {
		auth: false
	},
	fns: [
		{
			name: "_LOGIN",
			_LOGIN(data) {
				console.log(`父应用返回信息${data}`);
			}
		}
	]
};
// 注册子应用
registerMicroApps(
	[
		{
			name: "app2",
			entry: "//localhost:8091",
			render,
			activeRule: genActiveRule("/app2"),
			props: msg
		},
		{
			name: "app1",
			entry: "//localhost:8092",
			render,
			activeRule: genActiveRule("/app1"),
			props: msg
		}
	],
	{
		beforeLoad: [
			app => {
				console.log("before load", app);
			}
		], // 挂载前回调
		beforeMount: [
			app => {
				console.log("before mount", app);
			}
		], // 挂载后回调
		afterUnmount: [
			app => {
				console.log("after unload", app);
			}
		] // 卸载后回调
	}
);

// 设置默认子应用,与 genActiveRule中的参数保持一致
setDefaultMountApp("/app1");

// 启动
start();

修改主程App.vue注册子应用的容器

<template>
  <div id="main-root">
    <!-- loading -->
    <div v-if="loading">loading</div>
    <!-- 子应用盒子 -->
    <div id="root-view" class="app-view-box" v-html="content"></div>
  </div>
</template>

<script>
export default {
  name: "App",
  props: {
    loading: Boolean,
    content: String
  }
};
</script>

子程序处理:

main.js

import Vue from "vue";
import VueRouter from "vue-router";
import App from "./App.vue";
import "./public-path";
import routes from "./router";

Vue.config.productionTip = false;

// 声明变量管理vue及路由实例
let router = null;
let instance = null;

// 导出子应用生命周期 挂载前
export async function bootstrap(props = {}) {
  console.log('从父应用继承的数据',props);
  
  Array.isArray(props.fns) && props.fns.map(i => {
    Vue.prototype[i.name] = i[i.name]
  });
}

// 导出子应用生命周期 挂载前 挂载后
export async function mount() {
  
  router = new VueRouter({
    base: window.__POWERED_BY_QIANKUN__ ? "/app1" : "/",
    mode: "history",
    routes: routes.options.routes
  });
  instance = new Vue({
    router,
    render: h => h(App)
  }).$mount("#app");
  console.log('mountmountmount');

  console.log(window.__POWERED_BY_QIANKUN__ ,'window.__POWERED_BY_QIANKUN__ window.__POWERED_BY_QIANKUN__ window.__POWERED_BY_QIANKUN__ ');

}

// 导出子应用生命周期 挂载前 卸载后
export async function unmount() {
  instance.$destroy();
  instance = null;
  router = null;
}

// 单独开发环境
window.__POWERED_BY_QIANKUN__ || mount();

注意:记得打开子应用的跨域配置!!!!其他配置如vue.config.js在Demo中

Demo:github.com/justworkhar…

父子通信

父传子

// 传入子应用的数据
let msg = {
	data: {
		auth: false
	},
	fns: [
		{
			name: "_LOGIN",
			_LOGIN(data) {
				console.log(`父应用返回信息${data}`);
			}
		}
	]
};
// 注册子应用
registerMicroApps(
	[
		{
			name: "app1",
			entry: "//localhost:8092",
			render,
			activeRule: genActiveRule("/app1"),
			props: msg
		}
	],
	{
		beforeLoad: [
			app => {
				console.log("before load", app);
			}
		], // 挂载前回调
		beforeMount: [
			app => {
				console.log("before mount", app);
			}
		], // 挂载后回调
		afterUnmount: [
			app => {
				console.log("after unload", app);
			}
		] // 卸载后回调
	}
);
// 导出子应用生命周期 挂载前
export async function bootstrap(props = {}) {
  console.log('从父应用继承的数据',props);
  
  Array.isArray(props.fns) && props.fns.map(i => {
    Vue.prototype[i.name] = i[i.name]
  });
}

与传统的父子组件通信一样,父程序通过props向子程序传递信息。子程序通过回调函数向父程序传递信息。

总结

qiankun框架说白了就是通过在主程中添加一个展示子程序的DOM,经过路由判断做转发加载子程序。