阅读 7144

目标是最完善的微前端解决方案 - qiankun 2.0

原文地址

距 qiankun 开源已过去了 11 个月,距上次官方 发声 已过去 8 个月。

Announcing qiankun@2.0

2019 年 6 月,微前端框架 qiankun 正式发布了 1.0 版本,在这一年不到的时间内,我们收获了 4k+ star,收获了来自 single-spa 官方团队的问候,支撑了阿里 200+ 线上应用,也成为社区很多团队选用的微前端解决方案。

在今天,qiankun 将正式发布 2.0 版本。

qiankun@2.0 带来了一些新能力的同时,只做了很小的 API 调整,1.x 的用户可以很轻松的迁移到 2.x 版本,详细信息见下方 升级指南 小节。

qiankun 简介

可能有的朋友还不太了解 微前端 和 qiankun 是什么。

微前端是最近一年国内前端领域被频繁提及的关键字,虽然它并不是一个全新的领域/技术,但很显然在当今越来越多的前端应用即将步入第 3 个、第 5 个甚至更久的年头的背景下,如何给 巨石应用/遗产应用 注入新鲜的技术血液已经成为我们不得不正视的问题,而微前端正是解决这类问题的一个非常合适的解决方案。

qiankun 是一个生产可用的微前端框架,它基于 single-spa,具备 js 沙箱、样式隔离、HTML Loader、预加载 等微前端系统所需的能力。qiankun 可以用于任意 js 框架,微应用接入像嵌入一个 iframe 系统一样简单。

更多信息可以查阅我们的 官方站点

定位变化

qiankun 2.0 带来的最大变化便是 qiankun 的定位将由 微前端框架 转变为 微应用加载器

此前 qiankun 的典型应用场景是 route-based 的控制台应用,做为一个微应用的聚合框架而被使用。

image-20200415232001094

如上图所示,在这种场景下,一个负责聚合与切换的主应用 与 多个相互独自的微应用 一起构成了整个大的微前端应用,一般来说页面上活跃着的也往往只有一个微应用。

而这是微前端的场景之一,在另外一些场景下,你应该可以在同一个页面中,加载多个不同的微应用,每个微应用都是主应用的组成部分 或者是 提供一些增强能力,这种场景可以说是微应用粒度的前端组件化。

因此,qiankun@2.0 将跳出 route-based 的微前端场景,提供更加通用的微应用加载能力,让用户可以更加自由的组合微应用来搭建产品。

本次升级带来了什么?

新功能

  • 支持多应用并行及多实例沙箱
  • 支持手动 加载/卸载 微应用
  • 支持 IE11 沙箱兼容
  • 官方的极简微应用通信方案
  • 支持基于 Shadow DOM 的样式隔离

此外我们还做了

  • 升级 single-spa 到 5.x 版本
  • 更灵活的 prefetch 的定制策略
  • 配套的 webpack 插件
  • 更友好的部署场景支持,如自动为微应用注入运行时 publicPath 等
  • 更简单易懂的 API,重构了许多代码,使其更清晰和更具扩展性
  • 修复了一些 bug

另外我们还升级了相应的 umi qiankun plugin,在 umi 场景下你可以这样去加载一个微应用:

import { MicroApp } from 'umi';

function MyPage() {
  return (
  	<div>
      <MicroApp name="qiankun"/>
    </div>
  );
}
复制代码

发布日志

多应用支持

在 qiankun@1.x 中,我们的沙箱、样式隔离等机制只能对单一微应用场景生效,多个微应用共存的支持能力尚不完备。

而在 2.0 版本中,我们终于完善了这一功能,现在,你可以同时激活多个微应用,而微应用之间可以保持互不干扰。

**在多应用场景下,每个微应用的沙箱都是相互隔离的,也就是说每个微应用对全局的影响都会局限在微应用自己的作用域内。**比如 A 应用在 window 上新增了个属性 test,这个属性只能在 A 应用自己的作用域通过 window.test 获取到,主应用或者其他微应用都无法拿到这个变量。

但是注意,页面上不能同时显示多个依赖于路由的微应用,因为浏览器只有一个 url,如果有多个依赖路由的微应用同时被激活,那么大概率会导致其中一个 404。

为了更方便的同时装载多个微应用,我们提供了一个全新的 API loadMicroApp ,用于手动控制微应用:

import { loadMicroApp } from 'qiankun';

/** 手动加载一个微应用 */
const microApp = loadMicroApp(
  {
    name: "microApp",
    entry: "https://localhost:7001/micro-app.html",
    container: "#microApp"
  }
)

// 手动卸载
microApp.mountPromise.then(() => microApp.unmount());
复制代码

这也是 qiankun 作为一个应用加载器的使用方式。

基于这个 api,你可以很容易的封装一个自己的微应用容器组件,比如:

class MicroApp extends React.Component {
  
  microAppRef = null;
  
  componentDidMount() {
    const { name, entry } = this.props;
    this.microAppRef = loadMicroApp({ name, entry, container: '#container' });
  }
  
  componentWillUnmount() {
    this.microAppRef.mountPromise.then(() => this.microAppRef.unmount());
  }
  
  render() {
    return <div id="container"/>;
  }
}
复制代码

兼容 IE11 的沙箱能力

在 qiankun issue 区域呼声最高的就是 IE 的兼容,有不少小伙伴都期待 qiankun 能够在 IE 下使用。

qiankun 1.x 在 IE 使用的主要阻碍就是 qiankun 的沙箱使用了 ES6 的 Proxy,而这无法通过 ployfill 等方式弥补。这导致 IE 下的 qiankun 用户无法开启 qiankun 的沙箱功能,导致 js 隔离、样式隔离这些能力都无法启用。

为此,我们实现了一个 IE 特供的快照沙箱,用于这些不支持 Proxy 的浏览器;这不需要用户手动开启,在代理沙箱不支持的环境中,我们会自动降级到快照沙箱。

注意,由于快照沙箱不能做到互相之间的完全独立,所以 IE 等环境下我们不支持多应用场景, singlur 会被强制设为 true。

基于 shadow DOM 的样式隔离

样式隔离也是微前端面临的一个重要问题,在 qiankun@1.x 中,我们支持了微应用之间的样式隔离(仅沙箱开启时生效),这尚存一些问题:

  1. 主子应用之间的样式隔离依赖手动配置插件处理
  2. 多应用场景下微应用之间的样式隔离亟待处理

为此,我们引入了一个新的选项, sandbox: { strictStyleIsolation?: boolean }

在该选项开启的情况下,我们会以 Shadow DOM 的形式嵌入微应用,以此来做到应用样式的真正隔离:

import { loadMicroApp } from 'qiankun'

loadMicroApp({xxx}, { sandbox: { strictStyleIsolation: true } });
复制代码

Shadow DOM 可以做到样式之间的真正隔离(而不是依赖分配前缀等约定式隔离),其形式如下:

image.png

图片来自 MDN

在开启 strictStyleIsolation 时,我们会将微应用插入到 qiankun 创建好的 Shadow Tree 中,微应用的样式(包括动态插入的样式)都会被挂载到这个 Shadow Host 节点下,因此微应用的样式只会作用在 Shadow Tree 内部,这样就做到了样式隔离。

但是开启 Shadow DOM 也会引发一些别的问题:

一个典型的问题是,一些组件可能会越过 Shadow Boundary 到外部 Document Tree 插入节点,而这部分节点的样式就会丢失;比如 antd 的 Modal 就会渲染节点至 ducument.body ,引发样式丢失;针对刚才的 antd 场景你可以通过他们提供的 ConfigProvider.getPopupContainer API 来指定在 Shadow Tree 内部的节点为挂载节点,但另外一些其他的组件库,或者你的一些代码也会遇到同样的问题,需要你额外留心。

此外 Shadow DOM 场景下还会有一些额外的事件处理、边界处理等问题,后续我们会逐步更新官方文档指导用户更顺利的开启 Shadow DOM。

所以请根据实际情况来选择是否开启基于 shadow DOM 的样式隔离,并做好相应的检查和处理。

官方的极简通信方案

微前端场景下,我们认为最合理的通信方案是通过 URL 及 CustomEvent 来处理。但在一些简单场景下,基于 props 的方案会更直接便捷,因此我们为 qiankun 用户提供这样一组 API 来完成应用间的通信:

主应用创建共享状态:

import { initGloabalState } from 'qiankun';

initGloabalState({ user: 'kuitos' });
复制代码

微应用通过 props 获取共享状态并监听:

export function mount(props) {
  props.onGlobalStateChange((state, prevState) => {
    console.log(state, prevState);
  });
};
复制代码

更详细的 API 介绍可以查看官方文档

我们会继续为大家带来什么

除了基本的日常维护、bugfix 之外,我们还会尝试走的更远:

  1. 官方支持的 qiankun webpack 插件,解决一些由于配置不当出现的问题
  2. 自定义的沙箱规则
  3. 微应用嵌套支持
  4. 更友好的调试体验
  5. 与 Webpack5 Module Federation 的结合,提供官方的使用指导或插件
  6. 更多的实验性(experimental)尝试,如基于原生 Portal 标签的微应用渲染,基于运行时的更轻量的样式隔离方案。

升级指南

2.0 版本 调整了相当多的内部 API 名字,但大家使用的外部 API 变化并不大(基本完全兼容 1.x),你可以在十分钟内完成升级。

render 更改为 container

import { registerMicroApps } from 'qiankun'

registerMicroApps(
  [
    {
      name: 'react16',
      entry: '//localhost:7100',
-     activeRule: location => location.pathname.startsWith('/react'),
+     activeRule: '/react',
-     render: renderFn,
+     container: '#subapp-viewport',
    },
  ]
)
复制代码

现在你可以简单的指定一个挂载节点即可,而不用自己手写对应的 render 函数了。简单场景下 activeRule 配置也不需要再手写函数了(当然还是支持自定义函数),只需要给出一个前缀规则字符串即可,同时支持 react-router 类的动态规则,如 /react/:appId/name (来自 single-spa 5.x 的支持)。

同时,微应用收到的 props 中会新增一个 container 属性,这就是你的挂载节点的 DOM,这对处理动态添加的容器以及开启了 Shadow DOM 场景下非常有用。

注意,旧的 render 配置依然可以使用,我们做了兼容处理方便不想升级的用户;但 render 存在时,container 就不会生效。

start 的配置变化

因为我们引入了一些新的能力,因为 start 的配置也发生了一些变化:

import { start } from 'qiankun'

start({
-  jsSandbox: true,
+  sandbox: {
+    strictStyleIsolation: true
+  }
})
复制代码

新的 API loadMicroApp

这个 API 用于手动挂载一个微应用

/** 用于加载一个微应用 */
loadMicroApp(app: LoadableApp, configuration?: FrameworkConfiguration)
复制代码

使用详情可见上面 多应用支持 小节。