Skip to main content

项目合并技术•微前端

single-spa

初始化状态

  • 未加载 -> 加载中 -> 已加载
  • 未启动 -> 启动中 -> 已启动
  • 未挂载 -> 挂载中 -> 已挂载
  • 卸载中
  • 销毁中
  • 运行出错

路由劫持

window.addEventListener('hashchange', urlReroute);
window.addEventListener('popstate', urlReroute);

重写侦听器方法

const routingEventsListeningTo = ["hashchange", "popstate"];
const capturedEventListeners = {
hashchange: [],
popstate: [],
};
// 保存当前监听器
const originalAddEventListener = window.addEventListener;
const originalRemoveEventListener = window.removeEventListener;
// 重写监听器
window.addEventListener = function (eventName, fn) {
if (
routingEventsListeningTo.indexOf(eventName) >= 0 &&
!capturedEventListeners[eventName].some((listener) => listener == fn)
) {
capturedEventListeners[eventName].push(fn);
return;
}
return originalAddEventListener.apply(this, arguments);
};
window.removeEventListener = function (eventName, fn) {
if (routingEventsListeningTo.indexOf(eventName) >= 0) {
capturedEventListeners[eventName] = capturedEventListeners[
eventName
].filter((fn) => fn !== listenerFn);
return;
}
return originalAddEventListener.apply(this, arguments);
};

重写事件方法

window.history.pushState = patchedUpdateState(window.history.pushState);
window.history.replaceState = patchedUpdateState(window.history.replaceState);

关键状态初始化(是否启动基座、是否切换完成、队列中的任务、保存注册的应用)

startd = false
appChangeUnderway = false
peopleWaitingOnAppChange = []
apps = []

导出方法

exports.registerApplication = registerApplication;
exports.start = start;
Object.defineProperty(exports, '__esModule', { value: true });

核心方法

// 注册子应用
function registerApplication(appName, loadApp, activeWhen, customProps) {
apps.push({
name: appName,
loadApp,
activeWhen,
customProps,
status: NOT_LOADED, // 默认应用为未加载
});
reroute();
}
// 启动基座
function start() {
started = true;
reroute();
}
// 子应用管理
function reroute(pendingPromises = [], eventArguments) {
// 已经切换完成
if (appChangeUnderway) {
return new Promise((resolve, reject) => {
peopleWaitingOnAppChange.push({
resolve,
reject,
eventArguments,
});
});
}
// 将注册的应用按状态分为三个数组
const { appsToLoad, appsToMount, appsToUnmount } = getAppChanges();
// 如果调用了start()方法
if (started) {
appChangeUnderway = true;
// 启动、挂载应用
return performAppChanges();
} else {
// 加载应用
return loadApps();
}
}

隔离方案三要素

  • 无技术栈限制
  • 应用单独开发
  • 多应用整合

方案分析

  • iframe(优点)

    • 原生的硬隔离方案,完美解决 css 隔离、js 隔离问题
    • 适用于接入第三方页面
  • iframe(问题)

    • 浏览器刷新导致 url 状态丢失,后退、前进按钮无法使用(可解决)
    • 全局上下文完全隔离,主应用的 cookie 要透传到各个子应用中实现免登效果(难解决)
    • 子应用加载速度慢,每次进入子应用都是一次浏览器上下文重建、资源重新加载的过程(无法解决)
    • ui 不同步问题,如 loading 效果(无法解决)
  • 微前端(优点)

    • 子应用加载速度快
  • 微前端(问题)

    • 应用的加载与切换:路由问题、应用入口、应用加载
    • 应用的隔离与通信:JS隔离、CSS样式隔离、应用间通信
    • qiankun:解决了协同工作的问题,封装了应用加载方案(import-html-entry),并给出了应用的隔离与通信的解决方案,同时提供了预加载功能
  • ES Module(问题)

    • 兼容性问题,但可以通过编译工具解决
  • Web Components(问题)

    • 浏览器的新特性,兼容性问题
  • EMP(优点)

    • 基于webpack5 module Federation(mf)
    • 跨应用状态共享
    • 跨框架组件调用
    • 第三方依赖共享
  • EMP(问题)

    • 目前无法涵盖所有框架

微前端模式

  • 路由问题

    • 监听hashChange和popState两个原生事件
    • 调用reroute函数
    • 通过参数activeWhen判断需要加载的应用
      • 如果应用已加载 ,则进行应用的加载或切换
      • 如果应用未加载,则加载对应的应用
    • 劫持pushState和replaceState两个原生方法,避免第三方库调用该方法时触发hashChange事件,single-spa意外重载
      • singleSpa.start({ urlRerouteOnly: true})
  • 应用入口

    • 采用协议入口,要求应用入口必须暴露三个生命周期钩子函数,且必须返回Promise

    • bootstarp:挂载前的准备工作

    • mount:应用挂载

    • unmount:应用卸载

    • 其他生命周期:load/unload/update

    • 弊端:手动实现应用加载逻辑,只能以JS文件为入口,无法直接以HTML文件为入口,因此不能直接加载JQuery应用

  • 应用加载

    • qiankun通过import-html-entry请求url,得到对应的HTML文件
    • 解析内部所有的script和style标签,依次下载并替换到模板内
    • 返回一个对象,对象内包含处理后的模板以及几个核心方法
      • getExternalScripts
      • getExternalStyleSheets
      • execScripts
  • JS隔离

    • 将子应用全局变量挂载到window.proxy上
    • 如果子应用代码内直接使用 window.name = 'test' 生成全局变量,则无法隔离JS污染
    • 因为IE不支持proxy,所以IE下的快照策略无法支持多实例模式
  • CSS隔离

    • shadowDom样式隔离:子应用根节点创建一个shadow root
      • 某些UI框架弹出框直接挂载到document.body下,对全局造成污染
    • 类似于scoped属性的样式隔离:子应用的根节点添加一个特定的随机属性
      • 不支持@keyframes、@font-face、@import、@page
  • 应用通信

    • 由主应用创建一个globalState的全局对象,内部包含一组用于通信的变量
    • 两个分别用于修改变量值和监听变量变化的方法:setGlobalStateonGlobalStateChange