基于 qiankun 的微前端实践

前端

微前端的实践越来越受到大家的关注,在集成资源的平台上有比较大的应用价值,新增的需求只需要通过新增加子应用的方式,无需管理体积庞大的,耦合的单体应用。对技术栈的不限制也能够让年代久远的微前端项目能够比较轻松的逐步翻新。对微前端概念和应用价值的描述可以阅读 这篇译文

single-spa

使用 single-spa, 需要子应用抛出生命周期,主应用根据 Location 的变化来对 app 进行动态的 mount 和 unmount 实现子应用的切换。

single-spa 会劫持 location 的变化事件,保证执行完子应用的mount操作之,再由子应用的路由系统接管 url change 事件。

qiankun 在 single-spa 基础上做了什么

html-entry

如何加载子应用的静态资源,一种做法是使用 js-entry

由于每次打包 js 文件的名称不同,社区中的做法是配合 webpack 插件 stats-webpack-plugin 在打包生成的文件中来输出一个 manifest.json 文件。还需要修改打包方式,使得 css 不单独成一个静态文件,也同时放到 js 里。

  plugins: [
    new StatsPlugin('manifest.json', {
      chunkModules: true,
      exclude: [/node_modules[\\\/]react/]
    })
  ]
复制代码

然后通过 ajax 请求这个 manifest.json 得到 assets 中要加载的静态资源的名称和路径

完整的 demo 请参考这里的实现 vue-single-spa

qiankun 保留了这种 JS Entry 的方式

registerMicroApps([{
    name:'subApp1',
    entry:{ html: '', scripts: ['//abc.alipay.com/index.js'], css: ['//abc.alipay.com/index.css']}
}])
复制代码

同时也可以使用 HTML Entry,作者把 HTML Entry 的实现封装在了 import-html-entry

registerMicroApps([
    name:'subApp1', 
    entry: '//abc.alipay.com/index.html'
])
复制代码

子应用需要配合使用 umd 格式打包,umd 打包可以使代码同时运行在 Node 环境和浏览器环境中, name 为 package.json 中子应用的名称。

 configureWebpack:{
     output: {
      // 把子应用打包成 umd 库格式
      library: `${name}-[name]`,
      libraryTarget: 'umd',
      jsonpFunction: `webpackJsonp_${name}`,
  }
 }
复制代码

最后打包出来的文件 window 变量会带上子应用的标记,主应用加载子应用,使用 umd 中的 global export 获取子应用的导出。

样式隔离与 js 隔离

“ 浏览器会对所有的样式表的插入、移除做整个 CSSOM 的重构,从而达到 插入、卸载 样式的目的。HTML Entry 方式,应用卸载后会直接移除去 HTML 结构,从而自动移除了其样式表。“ 从而解决了子应用之间的样式相互影响问题。

子应用全局变量之间的隔离,qiankun 使用 Proxy 做了一个 JS 沙箱,会在 bootstrap 和 mount 时保存全局变量的快照, 然后应用切换时恢复到之前状态,处理全局变量。

父子组件通信

在 registerApplication 中提供了 props 参数,子应用可以在生存周期中获取到 props 值

export async function mount(props) {
  console.log('props from main framework', props);
  render();
}
复制代码

笔者对实现原理和源码的理解比较浅,更详细的 qiankun 实现原理可以参考框架作者的这篇文章:可能是你见过最完善的微前端解决方案,以及 qiankun 的 源码

对qiankun demo的修改

qiankun 提供了 React, Vue, Angular 的 demo,由于目前的技术栈基本是 vue,我把主应用和子应用都是 vue 的demo拿了出来,然后根据实际使用中的一些问题做了些修改,demo地址在这里

publicPath 的修改

使用 vue-cli 重新搭建的项目接入 qiankun 之后,发现 public 下的一些静态资源加载不出来,qiankun 中这样指定 publicPath,但是在 vue 中这个配置未生效。

if (window.__POWERED_BY_QIANKUN__) {
  // eslint-disable-next-line no-undef
  __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}
复制代码

发现 vue-cli 中有这样一个 issues 还没有得到解决,在自己的 demo 中的 vue.config.js 文件中,修改publicPath,让开发态下 public 下资源可以正常访问。

publicPath:'//localhost:7101'
复制代码

主应用和子应用 vue-router 的接入

实际应用中主应用和子应用都需要路由,主应用切换子应用时使用 window.history.pushState,就可以被微前端框架所劫持到进行子应用的挂载处理。

<template>
  <div class="menu" @click="goto('vue app1', '/demo1')">菜单1</div>
</template>

<script>
  methods: {
    goto(title, href) {
      window.history.pushState({}, title, href);
    }
 }
</script>
复制代码

考虑到 portal 中可能某一个子应用会有公共的侧边栏部分,主应用中增加了一个 的处理,可以自定义内容。

const routes = [
  {
    path: '/demo1*',
    name: 'demo1',
    component: () => import(/* webpackChunkName: "about" */ '../components/demo1')
  },
]
复制代码

子应用中没有特殊的路由处理,需要把 devServer 中的 historyApiFallback 置为 true,避免刷新路由时出现404。微前端框架在路由切换时帮我们处理掉的部分是保证子应用的 router 会在子应用 mount 之后执行。

父子应用通信

qiankun 中提供了主应用和子应用之间通信的方法,但是想要实现子应用获取主应用的动态内容,还要多做一些处理。结合了 vuex 来做动态获取主应用的数据。传递的 msg 如下:

let msg = {
  data: {
    userInfo: store.state.userInfo,
    fns:[
      function getMicro(){
        return store.state.micro
       }
    ]
  }
};
复制代码

在子应用中再全局挂载这个方法,然后可以通过 this.$getMicro 来动态获取父应用的数据了。如果有需求要实现子应用和子应用之间的通信,可以通过子应用向上 emit 事件到主应用,然后再下发到另外的子应用。

export async function mount(props) {
  console.log(props.data.userInfo)
  Array.isArray(props.data.fns) && props.data.fns.map(i => {
    console.log(i)
    Vue.prototype[`$${i.name}`] = i
  });
  render();
}
复制代码

更多关于父子之间的资源传递,子应用向父应用传递数据, wl-micro-frontends 中给出了具体的demo。

关于子应用和子应用之间的通信,目前还没有找到比较好的实践,微前端架构体系 中给出了这样一个图例来通过主应用实现通信。

IE下的处理

qiankun 的 demo 在 IE11 下不能正常显示,在 demo 中尝试关闭了 jsSandbox 并且给 fetch 增加了 pollyfill,让IE下界面能正常展示,但是 js 隔离的功能也就关掉了。

蚂蚁金服采用 qiankun 实现的平台没有兼容IE,会提示在其他浏览器下打开。

快速脚手架搭建

考虑到实际使用中可能会有比较多的子应用,可以使用 vue-cli3 的插件来快速的搭建接入了qiankun 的子应用和父应用。参考 vue-cli-plugin-qiankun 实现的 qiankun 的快速接入,自己也做了两个脚手架插件,把对 demo 的修改也包含进去了,搭建一个和上文 demo 一样的项目只需要如下操作:

主应用生成方式

vue create portal
cd portal
vue add vue-cli-plugin-qiankun-portal
复制代码

子应用生成方式

vue create demo1
cd demo1
vue add vue-cli-plugin-qiankun-microapp
复制代码

在给出的 prompt 中输入端口,例如:7101。

分别 npm i,npm run serve 之后就启动好 demo 项目了。

未解决的问题

1.子应用之间通信的方式,子应用如何不手动调用方法获取到主应用中数据的更新

2.主应用与子应用一起调试时 devTools 无法调试子应用的内容

其他

如果你的主应用和子应用用的 Nuxt,Nuxt 接入 qiankun 的方案社区有团队在做,可参考 nuxt-micro-frontend

参考文章及项目

可能是你见过最完善的微前端解决方案

标准微前端架构在蚂蚁的落地实践

微前端架构体系

vue-cli-plugin-qiankun

wl-micro-frontends

文章分类
前端
文章标签