[TOC]

乾坤微前端培训

※附件

单体应用改造配置说明文档.md模板微应用配置说明文档.md微应用部署说明文档.md

一. 前言

一个完整的产品往往都是从一个个小的功能模块进行开发,然后经过一系列的迭代更新。功能模块越来越多,所需要的依赖包也会越来越多,这时候的产品就会越来越庞大。从而对我们开发、部署等都带来了一系列问题,请大家思考一下下面的问题。

  • 随着功能模块越来越多,各种npm包也会越来越多,dev-server 占用内存大,项目启动时间非常漫长,如何做到分模块独立开发?
  • 构建效率低下,项目只更新了一个模块,但却需要将所有模块完整的打包并整体重新部署,无法增量升级部署部分模块,版本回退也有同样问题。
  • 维护成本过大,不利于新同事开发,假设有新同事接收项目,只需要开发一个简单的模块,但是往往需要阅读众多项目中无关的代码熟悉后才能进行开发。
  • 模块难以复用,假设我们需要新增一个功能模块,这个功能模块我们已经在另外一个独立的项目中实现了。我们如何将这个功能从这个项目中迁移到我们的产品之中?
  • 不利于团队配合,同一个项目中几乎不可能采用不同的框架,所以就一要求我们负责各个模块的小组统一技术栈,不利于充分发挥各个小组的开发水平。
  • 不利于技术更新迭代,目前前端技术还在飞速发展,假设3、5年后VueReact就像曾经的jQuery不流行了,出现了更优质、性能更好的框架。而我们的项目还在维护升级,我们该如何使用最新的技术呢?除非重写推倒重新开发,但显然耗费的时间及人力成本都十分巨大。
    image.png

二. 介绍

1. 什么是微前端

微前端是一种多个团队通过独立发布功能的方式来共同构建现代化 web 应用的技术手段及方法策略。

通俗来说就是将产品中的各个功能模块作为一个个独立的应用系统,然后将这些模块再整合起来组装成完整的产品。这就是最基础的微前端的概念。

类似于后端上的微服务。微服务是为了解决庞大的后端服务带来的变更与拓展方面的限制,而将一个大型的服务应用分割成若干个颗粒度较小的可独立开发、测试及部署的单个子应用。微前端的理念和微服务很相似。本质上都是为了将复杂庞大的前端应用拆解成一个个可以独立开发的部署的功能模块,并通过暴露相关的接口和功能来将各个功能模块进行连接。

2. 微前端的特点及优势
  • 技术栈无关 主框架不限制接入应用的技术栈,子应用具备完全自主权
  • 独立开发/部署 子应用仓库独立,前后端可独立开发,部署完成后主框架自动完成同步更新
  • 增量升级 当一个应用庞大之后,技术升级或重构相当麻烦,而微应用具备渐进式升级的特性
  • 状态隔离 微应用之间运行时互不依赖,有独立的状态管理
  • 提升效率 应用越庞大,越难以维护,协作效率越低下。微应用可以很好管理,提升效率

3. 微前端的适用场景
  • 开发周期长的大型平台类项目前端技术架构选型,或大型平台类项目前端技术架构改造。
  • 旧的系统不能下,新的需求还在来,想要在项目中使用新技术。

4. 微前端的技术方案有哪些

(1)基于 **iframe** 完全隔离的方案

如果不考虑体验问题, iframe 几乎是最完美的微前端解决方案了,使用iframe可以将大型项目拆分为多个小项目并集成到一起,但iframe的最大问题在于他的隔离性无法被突破,导致应用间上下文无法被共享,随之带来的开发体验、产品体验的问题。

  • url 不同步。浏览器刷新 iframe url 状态丢失、后退前进按钮无法使用。
  • Dom 结构不共享。最直观的问题就是iframe内的项目开启遮罩层时无法占满浏览器窗口。
  • 全局上下文完全隔离,内存变量不共享。iframe 内外系统的通信、数据同步等需求,主应用的 cookie 要透传到根域名都不同的子应用中实现免登效果。

(2)基于 **single-spa** 路由劫持方案

single-spa 通过劫持路由的方式来做子应用之间的切换,但接入方式需要融合自身的路由,有一定的局限性,并且主应用加载子应用会存在样式污染和js污染等。

(3)京东 **micro-app**

micro-app 是一个京东推出的一个微前端新星框架,在使用上比较简洁易懂,配置方面也无需太多操作,类似 Vue 风格的 API 对新手算是十分友好了,相比qiankun更易上手一些。 它是21 年 7 月才推出的,现在版本(2022年1月28号)为 0.8.4,还没到 1.0,所以想要在生产环境上使用的话,建议还是要谨慎一点。

(4)蚂蚁金服 **qiankun** 方案

qiankun 它对 single-spa 做了一层封装。主要解决了 single-spa 的一些痛点和不足。执行环境的修改,它实现了 JS 沙箱样式隔离 等特性,是目前社区主流微前端方案,有一定用户群体和生态。

总结:综合以上,qiankun 虽然沙箱机制还有一些问题,但是它有着 3 年的微前端使用场景以及 Issue 经验积累,在解决问题方面更稳定一点,所以我们选择了乾坤微前端解决方案。

三. 乾坤微前端

1. 设计理念
  • 🥄 简单
    由于主应用微应用都能做到技术栈无关,qiankun 对于用户而言只是一个类似 jQuery 的库,你需要调用几个 qiankunAPI 即可完成应用的微前端改造。同时由于 qiankunHTML entry 及沙箱的设计,使得微应用的接入像使用 iframe 一样简单。
  • 🍡 解耦/技术栈无关
    微前端的核心目标是将巨石应用拆解成若干可以自治的松耦合微应用,而 qiankun 的诸多设计均是秉持这一原则,如 HTML entry沙箱应用间通信等。这样才能确保微应用真正具备 独立开发、独立运行 的能力。

2. 特性
  • 📦 基于 single-spa 封装,提供了更加开箱即用的 API。
  • 📱 技术栈无关,任意技术栈的应用均可 使用/接入,不论是 React/Vue/Angular/JQuery 还是其他等框架。
  • 💪 HTML Entry 接入方式,让你接入微应用像使用 iframe 一样简单。
  • 🛡 样式隔离,确保微应用之间样式互相不干扰。
  • 🧳 JS 沙箱,确保微应用之间 全局变量/事件 不冲突。
  • ⚡️ 资源预加载,在浏览器空闲时间预加载未打开的微应用资源,加速微应用打开速度。
  • 🔌 umi 插件,提供了 @umijs/plugin-qiankun 供 umi 应用一键切换成微前端架构系统。

3. 原理

(1)应用加载

乾坤实现了 import-html-entry 插件,应用加载方案主要思路是允许以html文件为应用入口,然后通过一个html解析器从文件中提取jscss依赖,并通过fetch下载依赖,于是在qiankun中你可以这样配置入口:

const MicroApps = [{
  name: 'app1',
  entry: 'http://localhost:8080',
  container: '#app',
  activeRule: '/app1'
}]

qiankun会通过import-html-entry请求http://localhost:8080,得到对应的html文件,解析内部的所有scriptstyle标签,依次下载和执行它们,这使得应用加载变得更易用。

image.png

image.png

(2)css隔离

目前qiankun主要提供了两种样式隔离方案,一种是基于shadowDom的;另一种则是实验性的,思路类似于Vue中的scoped属性,给每个子应用的根节点添加一个特殊属性,用作对所有css选择器的约束。

  • ShadowDom
    当启用基于 ShadowDOM 的严格样式隔离 strictStyleIsolation时,qiankun将采用shadowDom的方式进行样式隔离,即为子应用的根节点创建一个shadow root。最终整个应用的所有DOM将形成一棵shadow treeshadowDom的特点是,它内部所有节点的样式对树外面的节点无效,因此自然就实现了样式隔离。
    但是这种方案是存在缺陷的。因为某些UI框架可能会生成一些弹出框直接挂载到document.body下,此时由于脱离了shadow tree,所以它的样式仍然会对全局造成污染。
    shadowDom代码示例 ``javascript // 通过 fetch 请求到的子应用字符串 let fetchAppContent =
     <style>
       div{color:red}
     </style>
     <div>我是子应用</div>
    
    `;

// 创建空div,将子应用填充到div中 const containerElement = document.createElement(‘div’); containerElement.innerHTML = fetchAppContent;

// document.body.appendChild(containerElement)

// 找到子应用的根节点 const appElement = containerElement.querySelector(‘#qiankun’); // 保存子应用的 innerHTML 字符串 let html = appElement.innerHTML; // 必须先清空子应用的 innerHTML 字符串,否则样式等会逃逸出来 appElement.innerHTML = ‘’;

// 开启沙箱 let shadow = appElement.attachShadow({ mode: ‘closed’ }); // 将子应用的 innerHTML 字符串添加到沙箱中 shadow.innerHTML = html;

// 将沙箱添加到主应用中 document.body.appendChild(appElement);



-  试验性的样式隔离<br />`qiankun`也在探索类似于`scoped`属性的样式隔离方案,可以通过`experimentalStyleIsolation`来开启。这种方案的策略是为子应用的根节点添加一个特定的随机属性,如: 
```html
<div
  data-qiankun-asiw732sde
  id="__qiankun_microapp_wrapper__"
  data-name="module-app1"
>


经过上述替换,这个样式就只能在当前子应用内生效了。虽然该方案已经提出很久了,但仍然是实验性的,因为它不支持@ keyframes,@ font-face,@ import,@ page(即不会被重写),以及存在其他问题。

(2)js隔离

为什么要实现js隔离?因为我们无法预知子应用会对 **window**上的全局属性做哪些修改,以及是否会因此影响到其他应用。所以我们需要保证挂在 **window** 上的全局方法/变量(如 setTimeout、滚动等全局事件监听等)在子应用切换时的清理和还原。

乾坤中关于js的隔离方式分别有下面两种:
  • 快照沙箱
    它的大致思路是,在加载应用前,将window上的所有属性保存起来(即拍摄快照);等应用被卸载时,再恢复window上的所有属性,这样可以防止全局污染。 ```javascript // 快照沙箱 class SnapshotSandbox { constructor() {

     this.proxy = window;
     // 记录在window上的修改
     this.modifyPropsMap = {};
     // 调用 激活 方法
     this.active();
    

    }

    // 激活 active() {

     // 创建window的快照
     this.windoowSnapshot = {};
     for (let key in window) {
         if (window.hasOwnProperty(key)) {
             this.windoowSnapshot[key] = window[key];
         }
     }
    
     // 恢复上一次沙箱失活时记录的沙箱运行过程中对 window 做的状态改变,也就是上一次沙箱激活后对 window 做了哪些改变,现在也保持一样的改变。
     Object.keys(this.modifyPropsMap).forEach((key) => {
         window[key] = this.modifyPropsMap[key];
     });
    

    }

    // 失活 inactive() {

     for (let key in window) {
         if (window.hasOwnProperty(key)) {
             // 比对失活时的 winodw 和激活时创建的的 window 快照
             if (window[key] !== this.windoowSnapshot[key]) {
                 // 如果不一样,则记录修改的属性到 modifyPropsMap 中供激活时恢复
                 this.modifyPropsMap[key] = window[key];
                 // 清除沙箱在激活之后在 window 上改变的状态
                 window[key] = this.windoowSnapshot[key];
             }
         }
     }
    

    } }

let sandbox = new SnapshotSandbox(); ((window) => { window.a = ‘a’; window.b = ‘b’;

btnLog.onclick = () => {
    console.log(window.a, window.b);
};
btnActive.onclick = () => {
    sandbox.active();
};
btnInactive.onclick = () => {
    sandbox.inactive();
};

})(sandbox.proxy);

<br />**快照沙箱存的重要的问题是:不能支持多个微应用** 

-  Proxy代理沙箱<br />[Proxy(代理)](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Proxy) 是 `ES6`中新增的一个特性。`Proxy`让我们能够以简洁易懂的方式控制外部对对象的访问。<br />**注意:在IE下,**`**proxy**`**不被支持,并且没有可用的 **`**polyfill**`<br />![image.png](https://cdn.nlark.com/yuque/0/2022/png/22000767/1661420870108-b3b1610a-3c94-4a64-87c3-f542c89d94ca.png#clientId=u4f0460af-9cfc-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=776&id=u9733e6cc&name=image.png&originHeight=776&originWidth=1097&originalType=binary&ratio=1&rotation=0&showTitle=false&size=208332&status=done&style=none&taskId=u46686002-e2a5-4e36-ae67-5debe3925a4&title=&width=1097)
```javascript
class ProxyShaBox {
    constructor() {
        const realWindow = window;
        // 变化存储库记录在历史修改
        const modifyPropsMap = {};

        // 创建 proxy 实例
        this.proxy = new Proxy(modifyPropsMap, {
            // 写入属性
            set(obj, key, value) {
                obj[key] = value;
                return true;
            },
            //  读取属性
            get(obj, key) {
                // 如果变化存储库里面有,就用里面的, 如果没有就用原始的 window 的属性
                return obj[key] || realWindow[key];
            }
        });
    }
}

let proxy1 = new ProxyShaBox();
let proxy2 = new ProxyShaBox();

((window) => {
    window.a = 10;
    setInterval(() => {
        console.log('proxy1', window.a);
    }, 1000);
})(proxy1.proxy);

((window) => {
    window.a = 20;
    setInterval(() => {
        console.log('proxy2', window.a);
    }, 1000);
})(proxy2.proxy);

setInterval(() => {
    console.log('原始window', window.a);
}, 1000);

四. 案例

乾坤提供了两种加载微应用的方式,分别为基于路由配置手动加载微应用。我们以基于路由配置的方式以 hash 路由的方式,来实现一个简单的集成乾坤微应用的主应用和子应用模板。

1. 创建、配置乾坤主应用

(1)根据 《前端vue项目构建流程指导规范》构建基础项目,步骤略。

(2)安装依赖

yarn add qiankun

(3)固定本地开发时项目运行端口(可选)

module.exports = defineConfig({
  devServer: {
    port: 9080
  },
});

(4)配置乾坤

src/qiankun/index.js中导出 registerApps方法,用于注册、启动微应用。

import { registerMicroApps, start, addGlobalUncaughtErrorHandler } from 'qiankun'
import Vue from 'vue'

// 监听微应用的生命周期
const microHandler = {
  beforeLoad: [
    (app) => {
      console.log('%c before load', 'background:#3a5ab0 ; padding: 1px; border-radius: 3px;  color: #fff', app)
    }
  ], // 预加载
  beforeMount: [
    (app) => {
      console.log('%c before mount', 'background:#7d9553 ; padding: 1px; border-radius: 3px;  color: #fff', app)
    }
  ], // 挂载前回调
  afterMount: [
    (app) => {
      console.log('%c after mount', 'background:#7d7453 ; padding: 1px; border-radius: 3px;  color: #fff', app)
    }
  ], // 挂载后回调
  beforeUnmount: [
    (app) => {
      console.log('%c before unmount', 'background:#7dd253 ; padding: 1px; border-radius: 3px;  color: #fff', app)
    }
  ], // 卸载前回调
  afterUnmount: [
    (app) => {
      console.log('%c after unmount', 'background:#d2525c ; padding: 1px; border-radius: 3px;  color: #fff', app)
    }
  ] // 卸载后回调
}

// 添加全局的未捕获异常处理器。
addGlobalUncaughtErrorHandler((event) => {
  Vue.prototype.$loading({}).close()
  const { message } = event
  if (message && message.includes('died in status')) {
    Vue.prototype.$notify({
      title: '加载微应用失败',
      message: event.message,
      type: 'error'
    })
  }
})

export const registerApps = (apps) => {
  // 注册微应用
  registerMicroApps(apps, microHandler)
  // 启动微应用
  start({
    prefetch: 'none'
  })
}

(5)配置乾坤

index.vue 调用上面导出的 registerApps 方法

<script>
import { registerApps } from '@/qiankun'
export default {
  // eslint-disable-next-line
  name: 'Index',
  mounted () {
    registerApps([
      {
        // 必选,微应用的名称,微应用之间必须确保唯一。
        name: 'micro-app',
        // 必选,微应用的入口。
        entry: 'http://localhost:9081/child/micro-app/',
        // 必选,微应用的激活规则。
        activeRule: '#/micro-app',
        // 必选,微应用的容器节点的选择器或者 Element 实例。
        container: '#microAppContainer',
        // 可选,主应用需要传递给微应用的数据。
        props: {
          time: new Date().getTime()
        },
        // 可选,loading 状态发生变化时会调用的方法。
        loader: (loading) => {
          if (loading) {
            this.$loading({
              lock: true,
              text: '加载中...',
              background: 'rgba(255, 255, 255, 0.7)'
            })
          } else {
            this.$loading({}).close()
          }
        }
      }
    ])
  }
}
</script>

当微应用信息注册完之后,一旦浏览器的 url 发生变化,便会自动触发 qiankun 的匹配逻辑,所有 activeRule 规则匹配上的微应用就会被插入到指定的 container 中,同时依次调用微应用暴露出的生命周期钩子。

此时访问 http://localhost:9080/#/micro-app/page1页面,会发现 index 页面消失了,因为主应用没有这个路由,所以我们需要修改路由配置用于加载子应用。

{
    path: '/*',
    component: () => import('@/views/index.vue')
}

2. 创建、配置乾坤子应用

子应用不需要额外安装任何其他依赖即可接入 qiankun

(1)根据 《前端vue项目构建流程指导规范》构建基础项目,步骤略。

(2)创建并引入 **public-path.js**

src/qiankun 目录中创建 public-path.js

if (window.__POWERED_BY_QIANKUN__) {
  // eslint-disable-next-line
  __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__
}

main.js 顶部引入 public-path.js

import './qiankun/public-path'

(3)在 **main.js** 封装 **render** 方法创建 **vue** 实例

// 注释掉原有的创建vue实例的方法
// new Vue({
//   router,
//   store,
//   render: (h) => h(App)
// }).$mount('#app')

let instance = null;
function render(props = {}) {
  const { container } = props;
  container &&
    container.setAttribute(
      "style",
      "height:100%;overflow-y: auto;background-color: #ffffff;"
    );
  instance = new Vue({
    router,
    store,
    render: (h) => h(App),
    // 为了避免根 id #app 与其他的 DOM 冲突,需要限制查找范围。 
  }).$mount(container ? container.querySelector("#app") : "#app");
}

// 如果window上的__POWERED_BY_QIANKUN__属性的值为true代表处于乾坤环境,否则是独立运行。
if (!window.__POWERED_BY_QIANKUN__) {
  // 独立运行时,手动创建Vue实例
  render();
}

(4)在 **main.js** 导出相应的生命周期钩子

子应用需要在自己的入口 jsvue 项目就是main.js)导出 bootstrapmountunmount 三个生命周期钩子,以供主应用在适当的时机调用。

// bootstrap 只会在微应用初始化的时候调用一次,下次微应用重新进入时会直接调用 mount 钩子,不会再重复触发 bootstrap。
export async function bootstrap() {
  console.log("[vue] vue app bootstraped");
}

// 应用每次进入都会调用 mount 方法,通常我们在这里触发应用的渲染方法
export async function mount(props) {
  console.log("[vue] props from main framework", props);
  render(props);
}

// 应用每次 切出/卸载 会调用的unmount方法,通常在这里我们会卸载微应用的应用实例
export async function unmount() {
  instance.$destroy();
  instance.$el.innerHTML = "";
  instance = null;
}

(5)在 **vue.config.js** 修改**webpack**配置

除了代码中暴露出相应的生命周期钩子之外,为了让主应用能正确识别微应用暴露出来的一些信息,微应用的打包工具需要增加如下配置:

  1. 允许跨域让基座加载微应用
  2. 配置打包格式为 umd 打包
const { defineConfig } = require('@vue/cli-service'),
  { name } = require('./package.json')
module.exports = defineConfig({
  // 为了方便后续部署,将子应用的publicPath添加一个固定的前缀
  publicPath: `/child/${name}`,
  devServer: {
    // 推荐固定端口,方便调试(可选)
    port: 9081,
    // 允许跨域让基座加载微应用
    headers: {
      'Access-Control-Allow-Origin': '*'
    }
  },
  configureWebpack: {
    // 配置打包格式
    output: {
      library: `${name}-[name]`,
      libraryTarget: 'umd',
      // webpack5以下使用 jsonpFunction 配置
      // jsonpFunction: `webpackJsonp_${name}`
      // webpack5及以上使用 chunkLoadingGlobal 配置
      chunkLoadingGlobal: `webpackJsonp_${name}`
    }
  }
})

(6)在 **router/index.js** 配置路由根路径

还记得我们在主应用中调用 registerApps 时指定的 activeRule 吗?当路由前缀成功匹配到后就会自动加载子应用,所以我们还需要配置子应用的路由根路径。

{
    // path: '/',
    // 根路由的地址保证能够被在主应用中配置activeRule匹配到
    path: '/micro-app',
    name: 'Index',
    component: () => import('@/views/index.vue')
}

此时切换到主应用就可以成功加载子应用了!

(7)优化 **index.vue**

我们在乾坤环境下只需要显示子应用的 router-view 区域,左侧菜单和顶部导航我们不需要,所以需要判断当前的运行环境。还记得上面提到的 __POWERED_BY_QIANKUN__ 吗?我们这里可以使用这个属性来进行判断。

<template>
  <el-container class="micro-index-page">
    <el-header v-if="!isPoweredByQiankun" class="micro-index-page-header flex-row-vcenter">
      ...
    </el-header>
    <el-container>
      <el-aside v-if="!isPoweredByQiankun" width="200px">
       ...
      </el-aside>
      <el-main class="index-page-main">
        <router-view></router-view>
      </el-main>
    </el-container>
  </el-container>
</template>

<script>
export default {
  // eslint-disable-next-line
  name: 'Index',
  computed: {
    isPoweredByQiankun () {
      return Boolean(window.__POWERED_BY_QIANKUN__)
    }
  }
}
</script>

3. 应用通讯

(1)使用 **initGlobalState** Api

initGlobalState 是乾坤提供的通信方式,定义全局状态,并返回通信方法,建议在主应用使用,微应用通过 props 获取通信方法。

  1. 在主应用的 qiankun/actions.js 中定义并导出 qiankunActions方法 ```javascript import { initGlobalState } from ‘qiankun’

export const qiankunActions = initGlobalState({ // 事件触发来源 eventFrom: ‘’, // 事件的标识 eventCode: ‘’, // 事件传递的参数 eventData: {} })



2.  在主应用的 `index.vue` 开启监听 
```vue
<script>
import { qiankunActions } from '@/qiankun/actions';
export default {
  // eslint-disable-next-line
  name: 'Index',
  mounted () {
    // 开启监听,当globalState发生改变时触发
    qiankunActions.onGlobalStateChange((state) => {
      console.log('主应用监听到globalState发生改变', state)
    })
  },
  beforeDestroy () {
    // 销毁监听
    qiankunActions.offGlobalStateChange()
  }
}
</script>
  1. 在子应用的 main.js 获取通信实例

    export async function mount(props) {
    console.log("[vue] props from main framework", props);
    
    Vue.prototype.$setGlobalState = props.setGlobalState;
    Vue.prototype.$onGlobalStateChange = props.onGlobalStateChange;
    
    render(props);
    }
    
  1. 在子应用监听、修改 globalState ```vue



需要注意的是 `setGlobalState` 是按一级属性设置全局状态,且**微应用**中只能修改**已存在的**一级属性(主应用没有此限制)。

并且乾坤会在下一个大版本移除此api。

![image.png](https://cdn.nlark.com/yuque/0/2022/png/22000767/1661420899402-ff24ecf3-6b6d-42a3-8a4d-49a156065372.png#clientId=u4f0460af-9cfc-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=34&id=u715b8b3d&name=image.png&originHeight=34&originWidth=540&originalType=binary&ratio=1&rotation=0&showTitle=false&size=3230&status=done&style=none&taskId=u0c1ae67a-8716-4052-8bcb-98f06690683&title=&width=540)

**(2)使用 **`**eventBus**`** **

1.  在主应用的 `qiankun/actions.js` 中定义并导出 `qiankunEventBus`方法 
```javascript
import Vue from 'vue';

export const qiankunEventBus = new Vue();
  1. 在主应用的 index.vue 开启监听、并将 qiankunEventBus在注册微应用时,在 props 中传给子应用
    <script>
    import { qiankunEventBus } from '@/qiankun/actions';
    export default {
    // eslint-disable-next-line
    name: 'Index',
    mounted () {
    registerApps([
        {
            // 必选,微应用的名称,微应用之间必须确保唯一。
            name: 'micro-app',
            // 必选,微应用的入口。
            entry: 'http://localhost:9081/child/micro-app/',
            // 必选,微应用的激活规则。
            activeRule: '#/micro-app',
            // 必选,微应用的容器节点的选择器或者 Element 实例。
            container: '#microAppContainer',
            // 可选,主应用需要传递给微应用的数据。
            props: {
                time: new Date().getTime(),
                qiankunEventBus
            },
            // 可选,loading 状态发生变化时会调用的方法。
            loader: (loading) => {
                if (loading) {
                    this.$loading({
                        lock: true,
                        text: '加载中...',
                        background: 'rgba(255, 255, 255, 0.7)'
                    })
                } else {
                    this.$loading({}).close()
                }
            }
        }
    ])
    qiankunEventBus.$on('testEvent', (props) => {
      console.log('主应用EventBus监听到testEvent事件', props)
    })
    },
    beforeDestroy () {
    qiankunEventBus.$off()
    }
    }
    </script>
    
  1. 在子应用的 main.js 获取 qiankunEventBus

    export async function mount(props) {
    console.log("[vue] props from main framework", props);
    
    Vue.prototype.$qiankunEventBus = props.qiankunEventBus;
    
    render(props);
    }
    
  1. 在子应用发布事件 ```vue



**(3)使用 **`**localStorage**`** **

不同于`iframe`的是,子应用在乾坤环境下被加载时,和主应用是处于同一个域名、同一个端口下的,因此他们的

`localStorage`、`sessionStorage` 是同一个window下共享的。

**(4)使用 **`**vueX**`** **

相较于上面几种方法,我们更推荐的是使用 `vueX` 来进行通信。接下来我们实现将主应用的一个 `vueX` 模块注册到子应用中,并访问它。

1.  分别在主应用、子应用的 `store/modules` 目录创建 `common.js`并在 `store/index.js` 中引入该模块 
```javascript
function initState () {
  return {
    // 用户信息
    userInfo: { name: '李四' }
  }
}

const state = initState(),
  mutations = {
    /**
     * @description 修改用户信息
     * @return {void}
     * @example
     * this.$store.commit('common/setUserInfo')
     */
    setUserInfo (state, payload) {
      state.userInfo = payload
    }
  },
  actions = {
    /**
     * @description 修改用户信息
     * @return {void}
     * @example
     * this.$store.dispatch('common/setUserInfo')
     */
    setUserInfo ({ commit }, payload) {
      commit('setUserInfo', payload)
    }
  },
  getters = {
    /**
     * @description 获取用户信息
     * @return {string}
     * @example
     *  this.$store.getters['common/getToken']
     *  // => {name:'张三'}
     */
    getUserInfo (state) {
      return state.userInfo
    }
  }

export default {
  namespaced: true,
  state,
  mutations,
  getters,
  actions
}
  1. 在主应用的 index.vue 将上面定义的 common模块在注册微应用时,在 props 中传给子应用
    <script>
    import qiankunCommonStore from '@/store/modules/common';
    export default {
    // eslint-disable-next-line
    name: 'Index',
    mounted () {
    registerApps([
        {
            // 必选,微应用的名称,微应用之间必须确保唯一。
            name: 'micro-app',
            // 必选,微应用的入口。
            entry: 'http://localhost:9081/child/micro-app/',
            // 必选,微应用的激活规则。
            activeRule: '#/micro-app',
            // 必选,微应用的容器节点的选择器或者 Element 实例。
            container: '#microAppContainer',
            // 可选,主应用需要传递给微应用的数据。
            props: {
                time: new Date().getTime(),
                qiankunCommonStore
            },
            // 可选,loading 状态发生变化时会调用的方法。
            loader: (loading) => {
                if (loading) {
                    this.$loading({
                        lock: true,
                        text: '加载中...',
                        background: 'rgba(255, 255, 255, 0.7)'
                    })
                } else {
                    this.$loading({}).close()
                }
            }
        }
    ])
    }
    }
    </script>
    
  1. 在子应用的 main.js 获取主应用的 common模块并在 vueX 中注册该模块

    关于vueX模块动态注册的更多信息请查阅官方文档

export async function mount (props) {
 console .log('[vue] props from main framework', props )
//将主应用的common注册的微应用自己的vueX实例上,这样子应用就可以使用自己的vueX实例访问
该模块
//将主应用的common注册的微应用自己的vueX实例上,这样子应用就可以使用自己的vueX实例访问该模块
  if (store && store.hasModule) {
    //判断是否存在该模块,如果不存在,则将该模块注册到vueX上
    if (!store.hasModule('qiankunCommonStore')) {
      //将该模块注册到vuex上
      store. registerModule('qiankunCommonStore', props.qiankunCommonStore)
    }
  }
  1. 在子应用中访问主应用的 vueX 模块
    此时在子应用中,我们就可以像访问自身模块那样访问主应用的comom模块! ```vue

```

五. 乾坤微前端专题

专题介绍文档查看方式:

  • 钉钉工作台 -> 未分组应该 -> 技术中心门户 -> 专题 -> 乾坤微前端架构
  • 浏览器访问 -> 专题 -> 乾坤微前端架构

通过本专题您可以获取到以下产物:

  • 快速得到一套规范化的乾坤基座应用模板,包含了登录、退出、主框架结构、菜单等常用功能;
  • 快速得到一套规范的乾坤子应用模板,基本零配置,直接上手业务开发;
  • 快速得到基于骨架项目权限平台的乾坤子应用项目,快速接入公司的统一权限管理平台;