2022-08-29 | 毁人不倦 | UNLOCK | 更新时间:2022-8-29 15:21

乾坤微应用的强制性入门

前言

微应用,简单的将就是因为项目太多,想要集成成一个项目,但是又不能重构,或者项目太大,为了版本管理不会因为某个功能回滚影响全体项目的版本更新,以前我们会使用iframe进行项目的简单嵌套,然后跨域传值。现在就可以使用微应用,同样是项目嵌套,但是不会有各种事件传递的问题等等;

准备工作

首先,你得有个项目,它是用来被集成的项目;也就是微应用(子应用);然后你需要有一个包裹它的项目,也就是主应用。当然也可以临时搭建主应用。让我们从主应用开始。我先用Vue项目进行举例。

主应用

在主应用安装乾坤库,并导入到项目中

$ npm install qinkun && yarn add qiankun

main.js
// 下面是在入口文件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(); // 启动
router.js
import VueRouter from 'vue-router' // 临时搭建的空架子主应用,但要尤为注意 mode,主应用子应用不能是不同的路由模式 export default new VueRouter({ mode:"history", routes:[ ] })
App.vue
<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设置跨域

vue.config.js
module.exports = { transpileDependencies: true, devServer: { port: 8083, //主应用启动端口号,视情况而定 headers: { "Access-Control-Allow-Origin": "*", // 允许跨域访问子应用页面 }, }, }

主应用的东西添加完了,那就开始微应用项目的改造了

微应用

src/ 目录下添加文件 public-path.js,同时在package.json设置好全局变量;

public-path.js
if (window.__POWERED_BY_QIANKUN__) { __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__; }
package.json
//... "eslintConfig": { //... "globals": { "__webpack_public_path__": true }, },

修改入口文件main.js,同时保证容器ID不与主应用冲突

main.js
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");
router.js
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中设置微应用跨域

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 文件

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 }

同时在需要的地方进行引用

login.vue
// ... import { actions } from "@/action"; // ... actions.setGlobalState({globalToken: this.Token});
App.vue
import { actions } from "@/micros"; // ... mounted () { actions.onGlobalStateChange(state => { // 监听全局状态,子应用更新主应用数据后触发 console.log(state) }) }

在微应用根据需求修改main.js

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); }

同时在需要的地方进行数值修改,主应用会监测数值修改

login.vue
// 子应用修改数值,主应用监听数据 Vue.actions.setGlobalState({name:"asdadas"})

项目部署

还不确定,先记录一下别人的方案

项目部署目录
├── main │ └── index.html └── subapp ├── sub-react │ └── index.html └── sub-vue └── index.html
nginx
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传值。