微前端是指存在于浏览器中的微服务,通常由许多组件组成,并使用类似于 React、Vue 和 Angular 等框架来渲染组件,每个微前端可以由不同的团队进行管理,并可以自主选择框架。
每个微前端都拥有独立的 git 仓库、package.json 和构建工具配置。因此,可以拆分一些巨石应用为多个独立的模块再组合起来,应用间独立维护及上线,互不干扰。
本文通过一些精简代码的方式介绍微前端框架qiankun
的原理及OPPO云
在这上面的一些实践。
注:本文默认读者使用过qiankun
框架,且文中使用的qiankun
版本为:2.0.9
。
1. qiankun 的前身 single-spa
qiankun
是一个基于 single-spa
的微前端实现库,在qiankun
还未诞生前,用户通常使用single-spa
来解决微前端的问题,所以我们先来了解single-spa
。
我们先来上一个例子,并逐步分析每一步发生了什么。
import { registerApplication, start } from "single-spa";registerApplication( "foo", () => System.import("foo"), (location) => location.pathname.startsWith("foo"));registerApplication({
name: "bar", loadingFn: () => import("bar.js"), activityFn: (location) => location.pathname.startsWith("bar"),});start();
appName: string 应用的名字将会在 single-spa 中注册和引用, 并在开发工具中标记
loadingFn: () => 必须是一个加载函数,返回一个应用或者一个 Promise
activityFn: (location) => boolean 判断当前应用是否活跃的方法
customProps?: Object 可选的传递自定义参数
1.1 元数据处理
首先,single-spa
会对上述数据进行标准化处理,并添加上状态
,最终转化为一个元数据数组,例如上述数据会被转为:
[{
name: 'foo', loadApp: () => System.import('foo'), activeWhen: location => location.pathname.startsWith('foo'), customProps: {}, status: 'NOT_LOADED'},{
name: 'bar', loadApp: () => import('bar.js'), activeWhen: location => location.pathname.startsWith('bar') customProps: {}, status: 'NOT_LOADED'}]
1.2 路由劫持
single-spa
内部会对浏览器的路由进行劫持,所有的路由方法
和路由事件
都确保先进入single-spa
进行统一调度。
// We will trigger an app change for any routing events.window.addEventListener("hashchange", urlReroute);window.addEventListener("popstate", urlReroute);// Monkeypatch addEventListener so that we can ensure correct timingconst originalAddEventListener = window.addEventListener;window.addEventListener = function(eventName, fn) {
if (typeof fn === "function") {
if ( ["hashchange", "popstate"].indexOf(eventName) >= 0 && !find(capturedEventListeners[eventName], (listener) => listener === fn) ) {
capturedEventListeners[eventName].push(fn); return; } } return originalAddEventListener.apply(this, arguments);};
function patchedUpdateState(updateState, methodName) {
return function() {
const urlBefore = window.location.href; const result = updateState.apply(this, arguments); const urlAfter = window.location.href; if (!urlRerouteOnly || urlBefore !== urlAfter) {
urlReroute(createPopStateEvent(window.history