微前端

目的是为了拆分项目,有以下几个好处

  1. 使项目更小,打包的更快
  2. 能接入不同的框架
  3. 每个团队可以独立出去维护,减少相互依赖
  4. 对于性能较差且实在无法继续在低代码平台进行优化的页面,可以逐步进行技术栈重构。

优势和劣势

  • 只需要更新子系统的文件,提高了更新的效率
  • 各个团队只需要关注自己的子应用

劣势:每次刷新都需要加载很多资源
代码上会有一些不大相关的代码

原理

  1. Qiankun 是基于 single-spa,路由命中了指定的地址,webpack5
  2. 样式隔离和 js 隔离原理,

为什么不用 lerna

  1. 组件库有一些全局的数据、环境变量、配置等,可能没办法获取

实现

在实际开发项目的过程中,如果项目本身采用 SPA 模式进行开发,则可以通过以下方案进行微前端改造:

  • 基于 NPM 包的微前端:将微应用打包成独立的 NPM 包,然后在主应用中安装和使用;
    • 浏览器中的 JavaScript 由于受到了沙箱限制无法直接访问本地的文件(例如 file:// 路径文件),因此在浏览器中使用模块化进行开发,无法像 Node 应用那样直接在 JavaScript 中通过 require 加载模块,只能通过 HTTP 请求的形式获取
    • 通过模块化的方式引入 lodash 的 isNull 函数,还可以天然实现三方库的按需引入,从而减少 HTTP 的请求体积。这也是Vite的原理
  • 基于代码分割的微前端:在主应用中使用懒加载技术,在运行时动态加载不同的微应用;
  • 基于 Web Components 的微前端:将微应用封装成自定义组件,在主应用中注册使用;
  • 基于 Module Federation 的微前端:借助 Webpack 5 的 Module Federation 实现微前端;
  • 基于动态 Script的微前端:在主应用中动态切换微应用的 Script 脚本来实现微前端;
  • 基于 iframe 的微前端:在主应用中使用 iframe 标签来加载不同的微应用;
  • 基于框架(JavaScript SDK)的微前端:使用 single-spaQianKun、wujie 等通用框架。
  • 自研zswl-adminzswl微前端

微前端原理

除了冷冰冰的代码,我们从这些原理中学到了什么,怎么用到项目之中

灵魂之问

iframe 嵌套下,history 无法生效怎么解决

不使用 Proxy 进行代理,那么 window.history 访问的是 iframe 的 history,此时页面会无法正常工作:

 history 的问题解决方案有很多,这里可以列举多种实现方式:

  • 在执行微应用的代码前修改 contentWindow.history,使其指向主应用的 history
  • 在立即执行的匿名函数中传入第二个形参 history,指向 widnow.proxy.history
  • 对 contentWindow.history 进行单独代理,并传入立即执行的匿名函数

为什么要进行 js 隔离

在 qiankun 的实现中,没有使用浏览器自身的隔离能力实现彻底的隔离。这样就会导致使用全局变量比如winodw时会产生相互覆盖的情况

如何解决微前端中的样式冲突问题?

微前端中的状态管理如何实现?

如何实现微前端中的跨域通信?

如何进行性能优化

  • 缓存优化的实现方法和源码细节
  • 懒加载的实现方法和源码细节。

为什么 qiankun 更加推荐使用 eval 而不是 script 呢?

  • 错误处理:(0, eval) 执行的代码若出现错误,可以通过 try-catch 语句捕获异常进行处理,而 script 标签注入的代码需要通过监听 error 事件来处理异常。
  • 执行时机:使用 script.textContent 插入并执行代码的时机可以被精确控制,因为它发生在你将 script 元素添加到 DOM 的那一刻。而使用 (0, eval) 执行的代码通常是立即执行的,一旦调用,代码就会运行。
  • 性能: 对于大型应用,qiankun 需要动态加载和卸载子应用,script 标签的插入和删除可能会导致性能问题,而 (0, eval) 可以更容易地控制这些操作。
  • 安全性: 使用 script 标签可能会导致某些安全性问题,比如内容安全策略(CSP)的违反,而 (0, eval) 可以更容易地遵守这些策略。
  • 兼容性: 在某些老旧浏览器中,动态插入 script 标签可能会遇到兼容性问题,使用 (0, eval) 可以提供更一致的跨浏览器行为。
  • 调试:
    • <script> 标签内的代码或通过其 src 属性加载的外部脚本在浏览器的开发者工具中通常有更好的调试体验,可以提供源代码映射。
    • (0, eval) 执行的代码在调试时可能更加困难,特别是如果执行的是压缩或动态生成的代码字符串,那么可能难以找到代码出错的具体位置。
    • 使用 <script> 标签插入内联代码可能会遇到 CSP 限制,因为 CSP 可以禁止执行页面上的内联脚本作为一种安全措施。
    • 现在 Chrome 开发工具可以调试 eval() 内的代码,但是必须等代码执行一次后才出现在源面板中。

微前端如何管理生命周期

Vue和 React 都有自己的路由管理,微前端怎么接管

因为框架底层有两种路由方式,hash 和 history,本质都是调用 pushStatereplaceState 两个方式 ,更详细请看 Vue Router

single-spa 通过重写addEventListenerremoveEventListener监听 hashchangepopstate执行 reroute,这样就实现了对框架路由的接管,进行微前端应用的切换和卸载

 // 重写 window.history.pushState 方法,
  // 用于捕获 React 或者 Vue 框架路由变化
  window.history.pushState = patchedUpdateState(
    window.history.pushState,
    "pushState"
  );
  // 重写 window.history.replaceState 方法,
  // 用于捕获 React 或者 Vue 框架路由变化
  window.history.replaceState = patchedUpdateState(
    window.history.replaceState,
    "replaceState"
  );
 

参考文章