Skip to content

微前端

微前端实现-路由分发

  • 单页面应用的路由控制都是在前端进行,当存在多个项目的时候,就算技术栈一样,A 项目也指挥不了 B 里面的路由跳转。
  • 但是如果我们将路由跳转交给服务端,当访问一个路由的时候,后端进行重定向等操作,这样就会将我们的应用隔离开。
  • 由于不存在跨域,可通过 cookie、localstorage 等技术进行信息共享。而且每次路由匹配到的话,都会进行刷新,因此 JS,CSS 不会互相污染。

    缺点:每次跳转都相当于重新刷新了一次页面,不是页面内跳转,体验较差
    优点:配置简单,可快速部署

微前端实现-iFrame 方案

  • 通过创建一个父程序,在父程序中监听路由的变化,卸载或加载相应的子程序 iframe。因每一个 iframe 就相当于一个单独的页面,所以 iframe 具有天然的 JS 和 css 隔离。在信息共享方面,可以使用 postMessage 或者 contentWindow 的方式进行

    缺点:iframe 样式兼容问题。分别为功能性兼容性以及业务性兼容性的问题。并且可能会存在一些安全问题

    主应用劫持快捷键操作
    事件无法冒泡到顶层,针对整个应用统一处理时效
    iframe 内元素会被限制在文档树中,视窗宽高限制问题
    无法共享基础库进一步减少包体积
    事件通信繁琐且限制多 事件整理参考

    优点:实现简单,自带沙盒特性

微前端实现-Web Components

  • 将每个子应用采用 Web Components 进行开发。纯 web-components 相当于自定义了一个 html 标签,我们就可以在任何的框架中进行使用此标签

    缺点:之前的子系统都要进行改造,工作量大,并且通信方面较为复杂。原生组件,普及度不高,未来充满不确定性,容易翻车

    优点:每个子应用拥有独立的 script 和 css,也可单独部署

微前端实现- 组合式应用路由

  • 每个子应用单独打包,部署和运行。不过需要基于父应用进行路由管理。例如:有子应用 A 的路由是/testA,子应用 B 的路由是/testB,那么父应用在监听到/testA 的时候,如果此时处于/testB,那么首先会直接卸载子应用 B,在去加载子应用 A。

缺点:需要解决样式冲突,JS 污染问题,需要有通信技术等

优点:纯前端改造,相比于路由式,无刷新,体验更好

微前端实现-模块联邦

  • 基于 Webpack 5 的一种微前端方案,使用其 Module federation 插件。
  • 它的主要功能是可以将项目中的部分组件或全部组件暴露给外部使用,我们也可以引用其他项目中暴露的组件,从而实现模块的复用。
  • 这和发布 npm 包然后在其他项目中引用听起来有些相似,本质区别在于,npm 的形式如果 a 包依赖 b 包,那么 b 更改发布后需要在 a 当中 install 新 b 包,重新发布 a 才能应用 b 的更改,而模块联邦只需要更新 b 即可

微前端实现-Single-SPA

Single-SPA 是一个流行的微前端解决方案,它提供了一个灵活的微前端注册系统和生命周期管理机制,使得多个前端应用能够在一个单一页面应用程序中协同工作。 其他框架和库:

问题 - CSS 隔离

  1. 类似于 vue 的 scoped。在打包的时候,对 css 选择器加上响应的属性,属性的 key 值是一些不重复的 hash 值,然后在选择的时候,使用属性选择器进行选择

  2. 可以自定义前缀。在开发子模块之前,需要确定一个全局唯一的 css 前缀,然后在书写的过程中同一添加此前缀,或在根 root 上添加此前缀,使用 less 或 sass 作用域嵌套即可

问题 - JS 的沙盒

diff 方法

当我们的子页面加载到父类的基座中的时候,我们可以生成一个 map 的散列表。在页面渲染之前,我们先把当前的

使用代理

这里需要用到 es6 的新特性:proxy,原理是监听 get 和 set 方法,针对当前路由进行 window 的属性或方法的存取

js
const windowMap = new Map();
const resertWindow = {};

let routerUrl = "";
const handler = {
  get: function (obj, prop) {
    const tempWindow = windowMap.get(routerUrl);
    console.log(windowMap, routerUrl);
    return tempWindow[prop];
  },
  set: function (obj, prop, value) {
    if (!windowMap.has(routerUrl)) {
      windowMap.set(routerUrl, JSON.parse(JSON.stringify(resertWindow)));
    }
    const tempWindow = windowMap.get(routerUrl);
    tempWindow[prop] = value;
    // console.log(obj, prop, value);
  },
};

let proxyWindow = new Proxy(resertWindow, handler);
// 首先是父类的a属性.
proxyWindow.a = "我是父类的a属性的值";

// 改变路由到子类
routerUrl = "routeA";
proxyWindow.a = "我是routerA的a属性的值";

// 改变路由到父类
routerUrl = "";
console.log(proxyWindow.a);

// 改变路由到子类
routerUrl = "routeA";
console.log(proxyWindow.a);

iframe 自带 css 和 js 沙盒隔离

微前端框架

  • Single-spa
  • Qiankun 【阿里基于 single-spa 】
  • Micro App 【京东基于 Web Component】
  • EMP 【欢聚时代基于 Webpack5 Module Federation 】
  • Garfish 【字节跳动】
  • 无界

总结

  • css 隔离 用 webcomponent,纯天然的
  • js 沙箱用 Proxy 代理
  • 另外注意的问题:在微前端场景下,子应用使用 document 查找元素时,由于每个子应用通常被限制在其自身的沙箱环境内运行,直接使用全局 document 对象可能会导致查找范围超出预期的子应用 DOM 树

参考文献

Released under the MIT License.