微前端完整解读 —— 场景分析及 Qiankun 原理介绍

28,772 阅读5分钟

微前端是近期比较热门的一个概念,有很多关于微前端实践的文章,但是整体看下来还是会有一脸懵逼的感觉,因为场景多,概念也多,技术方案也比较复杂。这里希望能够对微前端的背景和方案做一个整体的梳理。

What?

微前端为什么叫做微前端?主要是相对服务端的微服务来说的,微服务是将大型应用拆分为更小的独立服务,而微前端,可以理解为可以被动态加载的前端模块资源。这个范围比较广,可以认为是广义的微前端。

Why?

为什么要做微前端?或者说微前端是要解决什么问题?

实际上对于这个最基本的问题,答案就不止一个。微前端的鼻祖 Single-spa 最初要解决的问题是,在老项目中使用新的前端技术栈,那到现阶段,蚂蚁金服微前端框架 Qiankun 所声明的微前端想要解决的另一个主要问题是,巨石工程的维护困难和协作开发困难。而这两个目标基本上是一致的,主要目标是拆分,对大型工程进行解耦。这就是我们现在作为技术方案谈论的微前端所要解决的主要问题,也是我所认为的狭义微前端范围。而相对应的广义微前端范围的问题还包括组件或者说模块的动态引用(运行时加载),粒度相对比较小,并不是以系统层面的拆分为目的,反而是以整合为目的。

另外,说到前端系统的拆分和动态加载,一个基础性的问题是“为什么不用 iframe”?对此 Qiankun 官网有专门的解答

How?

微前端怎么做?

我们主要针对狭义微前端的目标,也就是对大型工程进行解耦,这里有两个基本问题:

  1. 子应用之间是否需要隔离,是否支持技术栈不统一?这个问题很关键,目前主流的方案都是选择隔离的,如果选择不隔离,那一般是技术栈统一的,在组件和工具维度做集成。比如美团外卖的方案 ,这种方案主子工程之间耦合程度高(需要路由加载约定,组件动态加载约定,store 加载约定,共享组件和工具等等),维护成本高,从某种程度上来说是违背我们想要降低巨石工程维护困难的初衷的。但这也同样是它的优势,子工程之间的集成和复用程度可以更高。
  2. 子应用加载时机?路由切换或者手动加载?目前大部分的场景是通过切换菜单更改路由来切换子应用,手动加载的情况较少,不过也是有相应场景的,新版的 Qiankun 基于 Single-spa 的 Parcel 做了这方面的支持。

接下来我们看一下典型微前端方案需要回答的三个最关键问题:

  1. 子应用如何定义和使用?
  2. 如何动态加载?
  3. 如何隔离?

Single-spa

几年前面世的 single-spa 解决了第一个问题,我们来看一下它的用法,截图自Single-spa 官网

我们可以看到,关键 api 是 registerApplication,这里需要传入 app,它一般是个函数,返回一个 promise,resolve 值需要包含 mount, unmount 和 bootstrap 等子应用生命周期方法,这个设计是比较有想象空间的,只要符合这个规则,我们可以自己去实现这个函数,加入自己的规则,Qiankun 正是这样做的。

另一个参数 activeWhen 就是根据路由加载子应用的规则,single-spa 会监听路由变化并劫持更改原生方法,应用路由判断逻辑。部分代码如下:

window.addEventListener("hashchange", urlReroute);
window.addEventListener("popstate", urlReroute);
window.history.pushState = patchedUpdateState(
  window.history.pushState,
  "pushState"
);

子应用加载之后,single-spa 会维护子应用的实例,再次激活时会直接使用。

Single-spa 主要解决了三个问题中的第一个,对于第二个和第三个没有给出具体方案,Qiankun 填补了这个空白。

Qiankun

首先看一下它的用法:

关键 api registerMicroApps 与 single-spa 类似,都是注册子应用,区别在于支持传入 html 地址或者前端资源地址的数组作为应用入口,根据约定,子应用需要暴露声明周期方法,Qiankun 会去加载资源然后根据约定拿到方法,这里官方的推荐是通过 webpack 的 umd 输出格式来做。在执行 js 资源时通过 eval,会将 window 绑定到一个 Proxy 对象上,以防污染全局变量,并方便对脚本的 window 相关操作做劫持处理,达到子应用之间的脚本隔离。 下面是截取的一些相关代码,逻辑还算比较容易看懂。
其中子应用代码真正的执行过程在拆分出来的 import-html-entry 模块中:
而样式隔离则是通过在 unmount 时卸载样式表自然地做到。

就这样,Qiankun 解决了剩余的两个问题——加载和隔离,成为了一个完整的微前端方案。

当然,除了这三个主要问题,还有一些次要的问题需要解决,比如:如何共享基础库?子应用如何与主应用联调?等等。 随着实践微前端概念的团队越来越多,Qiankun 也在不断的完善,这里有新版功能和后续计划的介绍

最后我想说,微前端并不是银弹,它只是为了解决特定问题而产生的方案,而且有自己的弊端,比如下图中阿里云的总结。因此如果没有文中提到的痛点,大可不必费力尝试。