前言
微应用,简单的将就是因为项目太多,想要集成成一个项目,但是又不能重构,或者项目太大,为了版本管理不会因为某个功能回滚影响全体项目的版本更新,以前我们会使用iframe进行项目的简单嵌套,然后跨域传值。现在就可以使用微应用,同样是项目嵌套,但是不会有各种事件传递的问题等等;
准备工作
首先,你得有个项目,它是用来被集成的项目;也就是微应用(子应用);然后你需要有一个包裹它的项目,也就是主应用。当然也可以临时搭建主应用。让我们从主应用开始。我先用Vue项目进行举例。
主应用
在主应用安装乾坤库,并导入到项目中
$ npm install qinkun && yarn add qiankun
// 下面是在入口文件main.js添加的注册微应用的代码,但之前的代码该有都要有,不能删除
import { registerMicroApps, start } from "qiankun";
registerMicroApps([ // 在主应用中注册子应用
{
name: "vueApp", //子应用名称
entry: "//localhost:8080", //子应用启动的地址
container: "#container", // 子应用在主应用的容器名称,不能和子应用div同名称
activeRule: "/app-vue", // 路由地址,注意你的路由模式是hash # 还是history /
props: {
data: "child子应用",
}, //传参
},
]);
start(); // 启动
import VueRouter from 'vue-router'
// 临时搭建的空架子主应用,但要尤为注意 mode,主应用子应用不能是不同的路由模式
export default new VueRouter({
mode:"history",
routes:[
]
})
<template>
<div id="app">
<p> <router-link to="/">主页/</router-link> </p>
<p> <router-link to="/testapp">子应用test</router-link> </p>
<p> <router-link to="/app-vue">子应用app</router-link> </p>
<div id="container"></div>
</div>
</template>
同时由于不同项目在各资源的调用的时候需要跨域,所以需要在config设置跨域
module.exports = {
transpileDependencies: true,
devServer: {
port: 8083, //主应用启动端口号,视情况而定
headers: {
"Access-Control-Allow-Origin": "*", // 允许跨域访问子应用页面
},
},
}
主应用的东西添加完了,那就开始微应用项目的改造了
微应用
在 src/
目录下添加文件 public-path.js
,同时在package.json
设置好全局变量;
if (window.__POWERED_BY_QIANKUN__) {
__webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}
//...
"eslintConfig": {
//...
"globals": {
"__webpack_public_path__": true
},
},
修改入口文件main.js,同时保证容器ID不与主应用冲突
import Vue from "vue";
import App from "./App.vue";
import router from "./router";
import './public-path'
Vue.config.productionTip = false;
// 修改render函数
let instance = null;
function render(props = {}) {
const { container } = props;
instance = new Vue({
router,
render: (h) => h(App),
}).$mount(container ? container.querySelector("#app-vue") : "#app-vue");
}
// 独立运行时
if (!window.__POWERED_BY_QIANKUN__) {
render();
}
// 导出 bootstrap、mount、unmount 三个生命周期钩子,以供主应用在适当的时机调用
/**
* bootstrap 只会在微应用初始化的时候调用一次,下次微应用重新进入时会直接调用 mount 钩子,不会再重复触发 bootstrap。
* 通常我们可以在这里做一些全局变量的初始化,比如不会在 unmount 阶段被销毁的应用级别的缓存等。
*/
export async function bootstrap() {
console.log("bootstraped");
}
/**
* 应用每次进入都会调用 mount 方法,通常我们在这里触发应用的渲染方法
*/
export async function mount(props) {
console.log("mount");
render(props);
}
/**
* 应用每次 切出/卸载 会调用的方法,通常在这里我们会卸载微应用的应用实例
*/
export async function unmount() {
console.log("unmount")
instance.$destroy();
instance.$el.innerHTML = "";
instance = null;
}
new Vue({
router,
render: (h) => h(App),
}).$mount("#app-vue");
import Vue from 'vue'
import VueRouter from 'vue-router'
import index from './components/index.vue'
const Hello = ()=> import('./components/Hello.vue')
Vue.use(VueRouter)
const routes = [
{
path: '/',
name: 'index',
component: index
},
{
path:"/Hello",
component:Hello
},
]
// 注意路由的mode,需要和主应用保持一致,同时路由的匹配需要注意
const router = new VueRouter({
// 这里和主应用中注册子应用时的activeRule对应
base: window.__POWERED_BY_QIANKUN__ ? "/app-vue" : "/",
mode: "history",
routes,
});
export default router
在vue.config.js
中设置微应用跨域
const { name } = require("./package");
module.exports = {
transpileDependencies: true,
devServer: {
port: 8080, //子应用启动端口号,与上文中父应用注册的子应用端口号对应
headers: {
"Access-Control-Allow-Origin": "*", //跨域配置
},
},
configureWebpack: {
output: {
library: `${name}-[name]`,
libraryTarget: "umd", // 把微应用打包成 umd 库格式
// jsonpFunction: `webpackJsonp_${name}`,
},
},
}
配置完成后,执行两项目,注意端口,然后访问主应用即可;
API
简单讲一下我对它们的个人理解
registerMicroApps(apps,lifeCycles?)
在主应用中注册微应用的API方法,参数为微应用和全局微应用的的生命周期钩子。
start(opts?)
在主应用中启动乾坤的API方法,参数为一些预加载的相关配置,具体可查询官网。
setDefaultMountApp(appLink)
设置主应用启动后默认进入的微应用。
runAfterFirstMounted(effect)
第一个微应用启动后调用的API,用于开启一些比如监控或者埋点脚本之类的操作
loadMicroApp(app, configuration?)
手动加载微应用场景,具体可见官网
prefetchApps(apps, importEntryOpts?)
手动加载微应用静态资源,具体可见官网
addGlobalUncaughtErrorHandler(handler)
添加全局的未捕获异常处理器,具体可见官网
removeGlobalUncaughtErrorHandler(handler)
移除全局的未捕获异常处理器,具体可见官网
initGlobalState(state)
定义全局状态,并返回通信方法,建议在主应用使用,微应用通过 props 获取通信方法
场景
主应用和微应用之间的通信
在主应用创建 action.js
文件
import { initGlobalState } from "qiankun";
// 初始化 state 默认值
const state = {
globalToken:"",
}
const actions = initGlobalState(state);
actions.onGlobalStateChange((state, prev) => {
// state: 变更后的状态; prev 变更前的状态
console.log(state, prev);
});
actions.setGlobalState(state);
actions.offGlobalStateChange();
export { actions }
同时在需要的地方进行引用
// ...
import { actions } from "@/action";
// ...
actions.setGlobalState({globalToken: this.Token});
import { actions } from "@/micros";
// ...
mounted () {
actions.onGlobalStateChange(state => {
// 监听全局状态,子应用更新主应用数据后触发
console.log(state)
})
}
在微应用根据需求修改main.js
// ...
import Vue from 'Vue'
export async function mount(props) {
console.log("mount",props);
props.onGlobalStateChange((state, prev) => {
// state: 变更后的状态; prev 变更前的状态
// 对监听到的数据进行存储操作,比如vuex
console.log(state, prev);
},true);
Vue.actions=props //将主应用传递过的通信方法赋值给vue,后面直接调用Vue进行传值
render(props);
}
同时在需要的地方进行数值修改,主应用会监测数值修改
// 子应用修改数值,主应用监听数据
Vue.actions.setGlobalState({name:"asdadas"})
项目部署
还不确定,先记录一下别人的方案
├── main
│ └── index.html
└── subapp
├── sub-react
│ └── index.html
└── sub-vue
└── index.html
server {
listen 80;
server_name qiankun.fengxianqi.com;
location / {
root /data/web/qiankun/main; # 主应用所在的目录
index index.html;
try_files $uri $uri/ /index.html;
}
location /subapp {
alias /data/web/qiankun/subapp;
try_files $uri $uri/ /index.html;
}
}
问题集合
ElementUi 字体文件 404错误?
路径加载错误,需要在 Vue.Config.js 里配置
const port = xxxx; // xxxx为端口号
module.exports = {
publicPath:`//localhost:${port}`,
//......
}
微应用 ElementUI 样式缺失?
在不开启严格模式的情况下,在主应用导入ElementUi样式,然后在主应用的第三方库设置Class前缀;如开启严格模式,可能会导致部分微应用样式问题;
面试题目
1:简述qiankun的实现原理
qiankun 是一个基于 single-spa
的微前端实现库,能够将多个独立的前端应用聚合成一个整体的应用。通过监听路由变化,动态加载和卸载微应用,同时通过自定义事件系统来实现微应用之间的通信
2:qiankun如何确保主应用跟微应用之间的样式隔离
可以通过设置 sandbox 属性来开启样式隔离,sandbox 属性可以设置为 true 或者一个对象。这样在编译的时候,不同的子应用有不同的class后缀。或者手动给calss类添加前缀的方式来开启。
3:qiankun的主应用和子应用,及子应用和子应用之间如何通讯
主应用和子应用之间可以通过自定义事件和全局变量进行通讯。主应用中配置 apps 时以 props 将数据传递下去,子应用在生命周期接受props传值。