微前端-qiankun


官网: https://qiankun.umijs.org/zh/

1、概念

1、微前端
微前端是一种多个团队通过独立发布功能的方式来共同构建现代化 web 应用的技术手段及方法策略;让不同技术栈、框架能独立开发,独立部署子模块,再共同嵌入主模块,形成一个完整的大型 web 应用;

微前端架构旨在解决单体应用在一个相对长的时间跨度下,由于参与的人员、团队的增多、变迁,从一个普通应用演变成一个巨石应用后,随之而来的应用不可维护的问题;

2、微前端的特定

技术栈无关:主框架不限制接入应用的技术栈,微应用具备完全自主权;
独立开发、独立部署:微应用仓库独立,前后端可独立开发,部署完成后主框架自动完成同步更新;
增量升级:在面对各种复杂场景时,我们通常很难对一个已经存在的系统做全量的技术栈升级或重构,而微前端是一种非常好的实施渐进式重构的手段和策略;
独立运行时:每个微应用之间状态隔离,运行时状态不共享;

2、qiankun

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 应用一键切换成微前端架构系统。

3、使用(vue)

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 打包;

4、主应用和子应用之间通讯

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;

5、运行模式

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时会复用之前的实例;

一般情况下使用自动挡就可以了,如果微应用是一个不带路由的可独立运行的业务组件,可以用手动挡来加载这个子应用;

6、其他问题

1、子应用缓存
https://github.com/kuaifengle/qiankun-vue3-tabsPage

猜你喜欢

转载自blog.csdn.net/weixin_43299180/article/details/129483695