乐趣区

关于javascript:qiankun微前端实践

前言

在介绍具体的接入计划和文档之前,先简略介绍一下,什么是微前端,以及为什么咱们的前端工程要做微前端化的革新?

什么是微前端?

微前端,用一句话来解释就是:将多个独立的前端利用聚合在一起组成一个全新的利用,各个利用间接彼此独立,互不烦扰。也就是说,一个微前端利用给用户的感观就是一个残缺的利用,然而在技术角度上是由一个个独立的利用组合通过某种形式组合而成的。再举个大家都晓得都例子,ifream 就是微前端利用都一种,当初依然沉闷在很多控制台零碎中,然而因为其本身存在的一些缺点,当初业内有了更好的解决方案,这就是前面要重点介绍的内容。

微前端的价值?

为什么要做微前端?这也是要看业务场景的,微前端的价值我认为次要体现在两个方面:1. 解决历史大工程的迭代问题 2. 控制台的利用集成,为用户提供对立入口。 这两方面能够离开进行论述:

历史大工程的迭代问题

这是很常见的业务场景,个别有 2 种十分典型的痛点问题:

1. 一个后管平台迭代了很多版本之后,整个工程的体积很大,本地编译的速度很慢,而且常常是多人别离开发不同模块,不论哪个模块的改变,也须要整个工程从新进行编译、打包、部署。

2. 原先的工程是用旧的技术栈(如 jquery),当初要集成新的功能模块,想要新的技术栈(如 react、vue)进行迭代。或是团队同学技术栈不一样,心愿各自负责的模块开发能够用不同的技术栈进行开发,最终都能集成到原利用中。

控制台的利用集成

这个也很好了解,心愿将各个利用的入口进行收拢,进行对立治理。解决零散的页面入口问题,晋升用户体验。

注释

言归正传,上面结合实际的工程例子,开始具体介绍本次微前端的技术计划和接入文档:

技术选型

在调研了业内多个微前端的框架后,最终定下来基于 “ 乾坤 ” 框架(文档点这里)进行微前端的解决方案革新,选用 “ 乾坤 ” 的起因有 2 点,1. 框架的可靠性和口碑较好,githup(详情点这里)上有 8.2k 的 star。2. 接入简略,不会对原有工程造成很大 ” 毁坏 ”。对于框架的地址和文档自行移步,不再反复介绍。

技术计划

利用分层

因为面向的都是后管零碎,后管零碎有一些通用的特点:须要登录鉴权,有左侧的菜单栏和上方的个人信息导航栏,两头局部是具体的页面内容。基于这个特点,能够把零碎分成主利用(登录 + 菜单 + 导航)和子利用(具体的内容页面)两个局部。

主利用定位

主利用的定位是一个架子,次要是集成登录、菜单、导航,以及提供全局数据和公共办法,具体的功能模块尽量放到子利用中

子利用定位

子利用的设计须要额定思考独立性,如果是在微前端的环境中,子利用是作为整体利用的一部分的,因而会与主利用有着严密的分割。然而子利用自身也应该做到绝对的独立,脱离了主利用也能独自运行,因而在计划的设计中,要思考子利用既能集成在主利用中以微前端的架构运行,也要保障子利用可能脱离主利用独自运行。

配置流程

乾坤利用的配置流程其实很简略,只须要别离在主利用和子利用进行各自须要的配置即可。主利用中须要装置好 qiankun 框架,配置须要挂载的子利用的挂载规定,开启 qiankun 利用。而在子利用中须要在入口文件中暴露出 bootstrap、mount、unmount 三个生命周期钩子,以供主利用在适当的机会调用,接管主利用传递的 props。此外,为了让主利用能正确辨认微利用裸露进去的一些信息,须要在对应的打包工具中减少大量的配置信息。

主利用配置

利用的注册和挂载指的是在主利用中注册微利用,设置好在微利用在主利用中挂载的页面元素,以及触发子利用加载的页面 url。用官网的例子来阐明:

// 主利用的入口文件
import {registerMicroApps, start} from 'qiankun';
registerMicroApps([
  {
    name: 'react app', // 利用名称,必须惟一
    entry: '//localhost:7100', // 子利用的地址
    container: '#yourContainer', // 页面中挂载子利用的元素节点
    activeRule: '/yourActiveRule', // 触发子利用加载的页面 ur
  },
  {
    name: 'vue app',
    entry: {scripts: ['//localhost:7100/main.js'] },
    container: '#yourContainer2',
    activeRule: '/yourActiveRule2',
  },
]);
start(); // 运行乾坤

个别上述的形式曾经能满足大多数常见的利用场景来,如果微利用不是间接跟路由关联或是有须要手动触发子利用加载的场景,比方子利用嵌套的场景,须要在一个子利用中手动去加载另一个子利用。这时候乾坤提供了一个 loadMicroApp 的办法进行子利用的手动加载:

import {loadMicroApp} from 'qiankun';
loadMicroApp(
  { 
    name: 'app', 
       entry: '//localhost:7100',
    container: '#yourContainer', 
  }
);

子利用配置

首先在入口文件中裸露生命周期钩子函数,bootstrap 只会在子利用首次加载时触发一次,在这里能够初始一些子利用的全局数据;mount 会在每次子利用加载时都触发,在这里罕用来触发子利用的渲染办法,同时接管主利用下发的 props,进行父子通信;unmount 会在每次子利用卸载时都触发,在这里卸载微利用的利用实例,清空一些全局数据。

export async function bootstrap() {console.log('app bootstraped');
}

export async function mount(props) {console.log('app mount');
  console.log('props from main framework', props);
}

export async function unmount() {console.log('app unmount')
}

其次,在 webpack 打包配置文件中增加一些辨认的配置信息,这里以官网的 vue 子利用作为例子:

const {name} = require('./package');
module.exports = {
  devServer: {
    headers: {'Access-Control-Allow-Origin': '*', // 开启利用间的跨域拜访},
  },
  configureWebpack: {
    output: {library: `${name}-[name]`, // 名称的匹配
      libraryTarget: 'umd',// 把微利用打包成 umd 库格局
      jsonpFunction: `webpackJsonp_${name}`,
    },
  }
};

主利用和子利用通信

在乾坤中,提供了两种形式进行主利用和子利用的通信:

形式一:提供了公布订阅模式实现父子利用的通信,提供了 initGlobalState 办法定义全局状态,并返回通信办法。主利用和子利用中都能够通过 setGlobalState 按一级属性设置全局状态,并通过 onGlobalStateChange 办法监听全局状态的变动,有变更触发 callback,个别在主利用中进行全局状态的初始化,子利用中进行监听获取。

形式二:提供了基于 props 传参的模式,主利用将须要传递给子利用的属性和办法通过 props 的模式进行传递,子利用在生命周期的钩子函数中进行接管,是一种单向数据流模式。

思考到业务中个别只须要主利用中给子利用进行登录信息、用户信息等相干全局信息的传递,且心愿子利用可能放弃绝对独立,缩小与主利用的耦合,决定采纳形式二的模式进行通信。为了不便全局状态的对立治理,对父子利用的通信模块进行了二次封装,具体的应用形式如下:

第一步:主利用中封装利用通信类

// sharedStore.js
// 上面都是伪代码,提供大抵的设计思路和应用形式,具体的应用形式跟框架无关,前面具体例子中会有具体介绍

import store from './xxx'; // 引入全局 store,依据框架不同形式不一样
import utils from './xxx' // 引入共有工具函数

class SharedStore {

  /**
   * 获取 global model
   */
  getGlobal() {const { global} = store.getState(); // 获取 store 中的 global
    return global || {};}

  /**
   * 子定义方法例子
   */
  fetchDictData(dict) {
    return store.dispatch({
      type: "dict/fetchDictData",
      payload: dict
    });
  }
  
   /**
   * 获取共有工具函数例子
   */
  getSharedUtils() {const { commonErrorHandler, myAjax} = utils;
   return {
           commonErrorHandler,
        myAjax
   }
  }
}

const sharedStore = new SharedStore();
export default sharedStore;

第二步:主利用通过 props 传递

在主利用中注册子利用的配置时,将须要下发给子利用的属性或是办法,通过 props 参数传递给子利用。子利用在生命周期的钩子函数的入参中就能拿到对应的 props,再依据须要自行进行存储应用。在这里,咱们把刚刚在主利用中封装好的 sharedStore 实例传递给子利用。

// 主利用中的入口文件
import sharedStore from '../src/utils/sharedStore.js'; // 主利用通信类文件

...
{
    name: 'xxx'
    entry: 'xxx',
    container: '#xxx',
    activeRule: 'xxx',
    props: {sharedStore}
  },
...

第三步:子利用在生命周期函数中接管 props

子利用生命周期函数中的入参就是 props,间接就能够从入参中拿到传递的属性或者办法,拿到之后依据业务须要进一步进行存储。举例来说,能够挂载到对应的全局实例中或是存在 localstorage 中,也能够混入子利用的 store 中作为初始化的值。在具体组件中须要应用时,能通过失当的形式取到即可。在这里,咱们在子利用中也写一个对应的 sharedStore 类,同时兼容子利用独立运行和在乾坤环境中运行:

// sharedStore.js
import store from './xxx'; // 子利用本身 store,依据框架不同形式不一样
import utils from './xxx' // 子利用本身工具函数
import dictData from './xxx' // 子利用自定义数据对象
import {getDvaApp} from 'umi';
import {get} from './request';

// 独立运行时的数据管理
class selfStore {
  /**
   * 获取 global model
   */
  getGlobal() {const { global} = store.getState(); // 从子利用的 store 中获取
    return global || {};}


    /**
   * 自定义办法例子
   */
  fetchDictData(dict) {
    return {
        dictData: {...dictData}
    }
  }
  
  /**
   * 获取共有工具函数
   */
  getSharedUtils() {
       return {...utils}
  }
}

// 兼容独立运行和 qinkun 子利用的全局数据
class SharedStore {static store = new selfStore();

  /**
   * 重载 store
   */
  static overloadStore(store) {SharedStore.store = store;}

  /**
   * 获取 store 实例
   */
  static getStore() {return SharedStore.store;}
}

export default SharedStore;

而后在子利用生命周期函数中调用 sharedStore 类重载 store 数据:

// app.js
import sharedStore from './utils/sharedStore.js'; // 子利用通信类文件 

export async function mount(props) {sharedStore.overloadStore(props.sharedStore); // props.sharedStore 是主利用中传递的共享数据
}

这样之后,不管子利用是独立运行或是在乾坤的环境中,如果要用到 shareStore 中的属性和办法,都对立从 shareStore 的 getStore 办法中获取就能够了。

各个利用款式隔离

这个问题乾坤框架做了肯定的解决,在运行时有一个 sandbox 的参数,默认状况下沙箱能够确保单实例场景子利用之间的款式隔离,然而无奈确保主利用跟子利用、或者多实例场景的子利用款式隔离。如果要解决主利用和子利用的款式问题,目前有 2 种形式:

  1. 在乾坤种配置 {strictStyleIsolation: true} 时示意开启严格的款式隔离模式。这种模式下 qiankun 会为每个微利用的容器包裹上一个 shadow dom 节点,从而确保微利用的款式不会对全局造成影响。然而基于 ShadowDOM 的严格款式隔离并不是一个能够无脑应用的计划,大部分状况下都须要接入利用做一些适配后能力失常在 ShadowDOM 中运行起来,这个在 qiankun 的 issue 外面有一些探讨和应用教训。
  2. 人为用 css 前缀来隔离开主利用和子利用,在组件层面用 css scoped 进行组件层面的款式辨别,在 css 框架层面能够给 css 组件库加上不同的前缀,比方文档中的 antd 例子:
  3. 配置 webpack 批改 less 变量
{
  loader: 'less-loader',
+ options: {
+   modifyVars: {
+     '@ant-prefix': 'yourPrefix',
+   },
+   javascriptEnabled: true,
+ },
}

b. 配置 antd ConfigProvider

import {ConfigProvider} from 'antd';
   
export const MyApp = () => (
  <ConfigProvider prefixCls="yourPrefix">
    <App />
  </ConfigProvider>
);

公共资源的解决

公共资源的解决,大抵能够分为两类:第三方依赖库、通用办法和组件,乾坤自身目前还没有提供官网的解决方案(文档中说后续会有),所有目前都是使用者本人进行解决的。在方案设计中思考到须要反对子利用在乾坤环境中和独自运行时,上面别离开展说一下各自的解决办法:

第三方依赖库

指的是像 react、lodash 这样的在主利用和子利用中都会用到的通用依赖包,在主利用中引入 cdn 资源,因为在乾坤利用中,所有子利用能拜访到主利用中引入的 cdn 资源,同时乾坤自身对外链资源作了缓存,外链内容申请到之后,会记录到一个全局变量中,下次再次应用,会先从这个全局变量中取,解决了反复加载的问题。同时在子利用中,须要配置 webpack 的 external 参数,当在乾坤环境中,external 中配置第三方依赖库,这样子利用打包时就不会把这部分的 npm 包资源打包到 node_module 中,缩小利用包的体积;当在独立运行时,external 配置为空,这样打包时会失常打包对应的 npm 包资源。

// webpack 配置文件

module.exports = {
  //...
  externals: {react: 'react'}
};

通用办法和组件

指的是在父子利用或是多个子利用中共享的组件或函数办法,对于这种资源的解决有 2 种形式:

  1. 公布到 npm 包平台,须要用到的利用自行装置 npm 包,这是一种宽泛使用的组件共享形式。
  2. 通过父利用 props 下发给子利用,子利用拿到后进行应用。为了通用化这种应用模式,能够在主利用中封装一个插件对立治理须要共享给子利用的组件,将这个插件通过 props 下发给子利用,子利用装置成全局组件后应用即可。以 vue 框架为例,首先在主利用中导出一个插件用来给子利用注册全局组件:
// shareComponent.js, 主利用中的共享组件治理插件

import SideBar from '../components/sideBar' // 自定义公共组件
import TopBar from '../components/topBar' // 自定义公共组件

const shareComponents = [SideBar, TopBar]; // 须要共享给子利用的组件
//vue 插件的 install 办法
const install = function (Vue) {
  shareComponents.forEach(component => {Vue.component(component.name, component); // 注册为 Vue 全局组件
  });
};

export default {install,};

而后将这个插件作为 props 下发给子利用:

// 主利用中的入口文件
import shareComponent from '../src/utils/shareComponent.js'

...
{
    name: 'xxx'
    entry: 'xxx',
    container: '#xxx',
    activeRule: 'xxx',
    props: {shareComponent}
  },
...

接着在子利用的钩子函数中进行接管并装置

// 子利用入口文件
...
export async function mount(props) {console.log('[vue] props from main framework', props);
  Vue.use(props.shareComponent)
}
...

最初在子利用的任意页面中就能够失常应用:

// 子利用的一个组件中
<template>
      <TopBar></TopBar>
</template>

主利用和子利用的路由模式

不论是 vue 还是 react 利用,路由模式都有 history 和 hash 两种模式,主利用和子利用能够自在搭配不同的路由模式,一共会呈现 2 * 2 = 4 种搭配,从应用角度来说,4 种搭配都是能够失常应用,只是不同模式下 主利用下的 activeRule 和 子利用下的 route 的 basePath 会有不同,同时子利用内的路由跳转形式须要做相应的调整。通过比照这几种模式的优缺点,最终采纳的是主利用 history 模式搭配子利用 hash 模式。这么抉择的起因是:

  1. 主利用用 history 模式,能够兼容子利用的 hash 或 history 模式,不须要调整旧我的项目的子利用外部的路由跳转形式
  2. 如果是子利用是新我的项目,用 hash 模式搭配主我的项目的 history 模式,模式清晰,从 url 上就能直观辨别出以后是处在主利用还是子利用的页面。
  3. 自身我的项目中没有子利用相互跳转的场景,子利用的 hash 模式满足了当下需要,如果后续有跳转需要,能够通过调用主利用下发的跳转办法通过主利用来跳转。

子利用错误处理

乾坤提供了全局谬误监听办法,在主利用中开启后,能够监听到所有子利用中抛出的异样,依据业务须要在谬误回调函数中进行自定义解决即可。

// app.js
import {addGlobalUncaughtErrorHandler} from 'qiankun';
addGlobalUncaughtErrorHandler(event => console.error('子利用异样', event));

利用的部署

主利用和子利用能够部署在同一个服务器下,通过目录层级进行辨别;也能够部署在不同的服务器上,这时主利用的就须要通过 nginx 做一次反向代理,把指向子利用的拜访门路转发到子利用的服务入口。部署这一块官网的文档上介绍得非常分明,在这里就不再多说了,详见 乾坤利用的部署

其余

子利用是否运行在乾坤中

标识:window.__POWERED_BY_QIANKUN__

嵌套子利用

这是一种较为简单的场景,临时没有遇到须要的场景,在本地跑了下 demo,通过,此外这篇文章中给出了两种解决方案:qiankun 的嵌套。在 qiankun 的 issue 里也有很多对于子利用嵌套的探讨,整体的论断是 qiankun 是反对的,然而因为工程和业务场景的不同可能会遇到 bug,这点须要结合实际我的项目会有更大的发言权,在这里就不开展了。

接入文档

依据咱们团队现有的前端利用现状,接入文档分成 umi 利用和其余利用(包含 vue 和 react)两大类型来介绍。

umi 利用

umi 利用有个特点是 umi 框架通过插件的模式做了 react – dva – layout 这种从框架层,到 ui 层的高度封装,开发者不须要关注实现的细节,只须要依照标准进行应用即可,而同样地,umi 利用通过 @umijs/plugin-qiankun 插件(文档链接)对乾坤框架进行来集成,因而用法须要独自阐明,上面的 umi 的例子,基于默认路由和侧边栏 layout 布局。

第一步:装置 @umijs/plugin-qiankun 插件

npm install --save @umijs/plugin-qiankun

第二步:主利用配置

  1. 插件注册并安装子利用
  2. 如果配置是写死的,不须要从接口获取,且 props 外面传递的共享数据也跟 store 或是接口无关,能够间接在 umi 的 config.js 文件中进行配置
//config.js 文件
import {defineConfig} from 'umi';

export default defineConfig({
    qiankun: {
    master: {
      // 注册子利用信息
      apps: [
        {
          name: 'app-organization', // 惟一 id
          entry: '//localhost:7001', // html entry
          props: {...}
        },
        {
          name: 'app-platform',
          entry: '//localhost:7002',
           props: {...}
        },
      ],
      
      // 应用路由绑定的形式安装子利用
      // 子利用的 routes,会被合并到主利用的 routes 中
      routes: [ 
        {
          path: '/app-organization',
          microApp: 'app-organization',
          microAppProps: {autoSetLoading: true, // 开启子利用页面主动 loading}
        },
        {
          path: '/app-platform',
          microApp: 'app-platform',
          microAppProps: {autoSetLoading: true,}
        }   
      ]
    },
  },
    ......
})

b. 否则,须要把 乾坤的 配置写在 主利用的 app.js 文件中,在运行时进行加载,运行时加载的参数会合并到 config.js 中。

//app.js
import sharedStore from '../src/utils/sharedStore.js'; // 主利用通信类文件

export const qiankun = {
    apps: [
      {
        name: 'app-organization', // 惟一 id
        entry: '//localhost:8001', // html entry
        props: {sharedStore, // 因为 sharedStore 与主利用 store 相干,所以要写在 app.js 中}
      },
      {
        name: 'app-platform',
        entry: '//localhost:8002',
        props: {sharedStore,}
      },
    ],
}
  1. 封装 sharedStore

    封装的目标和思路在上文中曾经有阐明,在这里给出在 umi 中具体的利用例子:

// sharedStore.js
import {getDvaApp} from 'umi';
import utils from './utils' // 引入共有工具函数

class SharedStore {

  /**
   * 获取 global model
   */
  getGlobal() {const dva = getDvaApp();
    const {global} = dva._store.getState();
    return global || {};}

  /**
   * 更新字典数据
   */
  fetchDictData(dict) {const dva = getDvaApp();
    return dva._store.dispatch({
      type: "dict/fetchDictData",
      payload: dict
    });
  }
  
   /**
   * 获取共有工具函数
   */
  getSharedUtils() {const { commonErrorHandler, myAjax} = utils;
   return {
           commonErrorHandler,
        myAjax
   }
  }
  
}

const sharedStore = new SharedStore();
export default sharedStore;
  1. 批改菜单中子利用页面门路

这里的意思是将左侧菜单中对应的页面门路中对于子利用的要进行解决加上 “/app-organization” 或 “/app-platform” 前缀,这样能力指向子利用。这里的解决能够改接口返回中的门路地址,也能够在前端写一个对立处理函数进行解决。同时如果在页面的业务逻辑中有手动通过 history.push 进行跳转到子利用的中央,也要进行前缀解决。

  1. 批改主利用页面挂载元素

因为 @umijs/plugin-qiankun 插件的默认页面挂载元素的 id 是 root-master,所以如果用了默认的 document.ejs 模版,须要将模版中 body 元素下的挂载节点 id 从 “root”,批改成 “root-master”。同时如果有全局款式 global.css,也须要将其中的款式 “#root” 批改成 “#root-master”。

// document.ejs 文件
<html>
<head>
  <meta charSet="utf-8" />
  <title>Your App</title>
</head>
<body>
  <div id="root-master"></div>
</body>
</html>

5. 利用端口配置(可选)

因为要同时启动主利用和多个子利用,为了端口治理的不便,能够在 src 下新建一个 .env 文件,在文件中写死利用启动的端口,这样每次启动时端口是固定的,不会依据利用启动的先后发生变化。

// .env 文件
PORT=8000

第三步:子利用配置

  1. 插件注册
// config.js
import {defineConfig} from 'umi';
export default defineConfig({
    qiankun: {slave: {}
  }
  ...
})
  1. 配置运行时生命周期钩子
// app.js
import sharedStore from './utils/sharedStore.js'; // 子利用通信类文件 

export const qiankun = {
  // 利用加载之前
  async bootstrap(props) {console.log('app-platform bootstrap', props);
  },
  // 利用 render 之前触发
  async mount(props) {console.log('app2-platform mount', props);
    // 1. 重写类的形式将主利用中传递的实例混入子利用中
    sharedStore.overloadStore(props.sharedStore); // props.sharedStore 是主利用中传递的全局数据
  },
  // 利用卸载之后触发
  async unmount(props) {console.log('app2-platform unmount', props);
  },
};

3. 封装 sharedStore

// sharedStore.js
import {getDvaApp} from 'umi';
import {get} from './request';

// 独立运行时的数据管理
class selfStore {
  /**
   * 获取 global model
   */
  getGlobal() {
    // 从 store 中加载
    const dva = getDvaApp();
    const {global} = dva._store.getState();
    return global || {};}

    /**
   * 更新字典数据
   */
  fetchDictData(dict) {
    return {
        dictData: {
          APP_1: 'SP',
        APP_2: 'SE',
        APP_3: 'SL',
      }
    }
  }
  
  /**
   * 获取共有工具函数
   */
  getSharedUtils() {return {}
  }
}

// 兼容独立运行和 qinkun 子利用的全局数据
class SharedStore {static store = new selfStore();

  /**
   * 重载 store
   */
  static overloadStore(store) {SharedStore.store = store;}

  /**
   * 获取 store 实例
   */
  static getStore() {return SharedStore.store;}
}

export default SharedStore;

4. 利用端口配置(可选)

// .env 文件
PORT=8001

第四步:利用部署

该我的项目中的主利用和子利用的前端部署走的是 oss 拜访的形式,umi 利用打包后会生成一份 index.html、umi.js 和 umi.css 文件,只须要把这份文件搁置在 oss 文件门路下,浏览器拜访对应文件门路下的 index.html 文件可。因而生产环境和本地开发不同的有两点:1. 主利用配置中的 publicPath 要调整到 oss 的根目录(即搁置 umi.js 和 umi.css 的目录),而本地开发时 publicPath 对应的是 ‘/’。2. 主利用中 qiankun 的 app 入口配置要调整成子利用 index.html 对应的 oss。

vue 利用

第一步:主利用装置 qiankun

npm i qiankun -S

第二步:主利用配置

  1. 配置入口文件,挂载子利用
  2. 主利用中应用 vue-router 进行路由治理,如果不须要 vue-router,去掉相干的配置即可
  3. 启动乾坤的 start 办法放到 $nextTick 中调用,为了避免出现主利用还没挂载实现,子利用找不到其页面挂载元素而报错
  4. shareComponent、sharedStore 的计划是通用的,在上文曾经介绍过,不再反复开展。
import Vue from 'vue'
import App from './App.vue'
import VueRouter from 'vue-router'
import routes from './routes'

import  shareComponent from '../src/utils/shareComponent'; // 公享组件
import sharedStore from '../src/utils/sharedStore.js'; // 共享通信类文件
import {registerMicroApps, start} from 'qiankun';


Vue.use(VueRouter)

Vue.config.productionTip = false

const router = new VueRouter({
  routes,
  mode: "history",
})
const vueApp = new Vue({
  router,
  render: h => h(App),
}).$mount('#app')


registerMicroApps([
  {
    name: 'slave-one', // app name registered
    entry: '//localhost:8082',
    container: '#slave-view',
    activeRule: '/qiankun-demo-slave-one',
    props: {
      sharedStore,
      shareComponent,
    }
  },
  {
    name: 'slave-two', // app name registered
    entry: '//localhost:8083',
    container: '#slave-view',
    activeRule: '/qiankun-demo-slave-two',
    props: {
      sharedStore,
      shareComponent,
    }
  },
  {
    name: 'slave-three',
    entry: '//localhost:3000',
    container: '#slave-view',
    activeRule: '/qiankun-demo-slave-three',
    props: {
      sharedStore,
      shareComponent,
    }
  },
]);

vueApp.$nextTick(()=>{start(); // 为了避免出现主利用还没挂载实现,子利用找不到 #slave-view 元素而报错
})
  1. 配置子利用在页面中的加载地位
  2. 页面的布局固定好左侧菜单和上方导航组件,两头的局部进行页面内容的展现
  3. 通过 $route.name 的有无来判断是加载主利用还是子利用的页面,当主利用中的路由中匹配以后 url 则是主利用本身的页面,否则展现子利用页面。
// App.vue
<template>
  <div id="app">
    <div class="left-menu">
       <sideBar></sideBar>
    </div>
    <div class="right-content">
      <topBar></topBar>
      <div class="main-content">
          <div id="master-view" v-if="$route.name">
            <router-view></router-view>
          </div>
          <div id="slave-view" v-else></div>
      </div>
    </div>
    
  </div>
</template>

<script>
import sideBar from './components/sideBar';
import topBar from './components/topBar';

export default {
  name: 'App',
  components: {
    sideBar,
    topBar
  },
}
</script>
...

第二步:子利用配置

  1. 配置 public-path.js 文件
if (window.__POWERED_BY_QIANKUN__) {__webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__; // 这是为了在乾坤环境下动静设置 webpack 的 publicPath,避免资源加载出错}
  1. 配置入口文件
  2. 在入口文件中引入 public-path.js
  3. 子利用如果也用了 vue-router,要留神 new vueRouter() 的 base 在乾坤环境下要加上和主利用中配置的 activeRule 统一的前缀,个别能够约定都用子利用的工程名字。否则子利用外部的路由跳转会出错。
  4. 在子利用的 unmount 钩子中肯定要卸载元素,否则会有多个子利用切换时的 bug,这个在乾坤的 issue 里有提到
import './public-path'
import Vue from 'vue'
import App from './App.vue'
import VueRouter from 'vue-router'
import routes from './routes'

Vue.use(VueRouter)
Vue.config.productionTip = false;

const packageName = require("../package.json").name; // 对立标准,取工程名字,且必须跟主利用配置下的 activeRule 统一
let router = null;
let instance = null;

function render(props = {}) {const { container} = props;
  router = new VueRouter({base: window.__POWERED_BY_QIANKUN__ ? `/${packageName}` : '/',
    mode: 'hash',
    routes,
  });
  instance = new Vue({
    router,
    render: h => h(App),
  }).$mount(container ? container.querySelector('#app') : '#app'); // 挂载时部分进行查找,防止多个子利用的相互影响
}
// 独立运行时
if (!window.__POWERED_BY_QIANKUN__) {render();
}

// 微利用必须裸露的三个加载生命周期 hooks
export async function bootstrap() {console.log('[vue] vue app bootstraped');
}
export async function mount(props) {console.log('[vue] props from main framework', props);
  Vue.use(props.shareComponent) // 注册共享组件
  render(props); // 渲染利用
}
export async function unmount() {instance.$destroy();
  instance.$el.innerHTML = '';
  instance = null;
  router = null;
}

3. 配置 webpack

const {name} = require('./package');
module.exports = {
  devServer: {
    headers: {'Access-Control-Allow-Origin': '*',},
  },
  configureWebpack: {
    output: {library: `${name}-[name]`,
      libraryTarget: 'umd',// 把微利用打包成 umd 库格局
      jsonpFunction: `webpackJsonp_${name}`,
    },
  },
};

第三步:利用部署

主利用和子利用在不同服务器独立部署,具体计划在上文中曾经有阐明,跟着官网文档操作即可。

react 利用

流程跟 vue 利用大同小异,参考上方的 vue 配置流程例子和官网的教程:react 微利用

总结

通过对老我的项目(umi 工程)对微前端理论革新,以及本地新我的项目(vue 工程)对 demo 的测试。借助乾坤框架 + 自定义的封装解决,基本上实现了微前端的利用的安稳接入。总体感触,微前端的确能以一种优雅的形式解决前端工程臃肿和多人合作开发的问题。然而须要留神的是,是否须要微前端要从我的项目理论状况登程,比方简略的单个利用基本不须要采纳,反而会减少开发成本。此外微前端的子利用的拆分形式和力度也须要从理论我的项目登程,并不是越细越好,适宜理论的才是最好的。

参考资料

乾坤官网文档

qiankun issue

@umi/plugin-qiankun 文档

qiankun 微前端计划实际及总结(一)

qiankun 微前端实际总结(二)

让前端走进微时代,微微一弄很哇塞!

退出移动版