# 微前端的各种方案

微前端的核心解决的问题 -- 结构巨石应用。

目前我了解到的微前端方案有:iframe、single-SPA、qiankun(基于 single-SPA)、Module Federation(Webpack 5新增的构建功能),眼花缭乱,我们该选择哪一个呢?他们之间有什么区别和优劣呢?

# iframe

为什么在微前端领域,iframe 的使用者很少呢?他明明在易用性和知名度上遥遥领先。那就要从他的优缺点来分析了。

优点:

  • 浏览器原生支持硬隔离
  • CSS隔离、JS隔离做的很彻底
  • 各应用独立部署

缺点:

  • DOM结构不共享。iframe为页面中独立的一个盒子,他里面的弹框只能在自己的盒子里显示,导致无法剧中
  • URL不同步。前进后退按钮无法使用
  • 性能不好。每个iframe都需要加载独立的HTML文档,可能导致额外的网络请求,页面加载时间增加
  • SEO问题。搜索引擎可能不会直接索引iframe中的内容
  • 上下文完全隔离,数据不共享

经过上面的分析,适用iframe的场景,我觉得是:静态页展示、一个页面中只放置iframe、上下文不需要通信,即完全隔离独立的项目来使用。其实这样也符合微前端的概念,各干各的事,互相不打扰。类似于拼积木,每个积木有自己的功能。而不是类似一个总管给手下分发任务的场景,各个积木间就更不应该有信息来往了。

所以iframe不适合用很多小积木拼成一个作品的场景,样式的隔离会使得页面的效果十分难管理和控制。

# single-SPA

single-SPA 的原理是:在主应用中注册子应用,然后通过注册时填写的 URL 异步获取子应用,然后挂载到主应用的指定 DOM 节点上。

这就像,你是个有钱人,你的府上招人,iframe是通过地址URL买了一个录像带,放在电视上,你能看能听,但想跟里面的人通信,需要打电话去联系。而single-SPA是通过地址URL直接把人带过来给你现场跳,你在府上划分个舞台给他跳。

那single-SPA有什么优缺点呢?

优点:

  • CSS隔离(CSS Modules、CSS-in-JS、Scoped CSS)、JS隔离(iframe、Web Workers)
  • 各应用独立部署
    • 懒加载。支持按需加载子应用,只有在需要时才会加载子应用,提高应用的性能。
    • 路由集成。提供了一个全局路由,用于管理整个微前端应用的导航。这样,用户在浏览不同子应用时,URL会保持同步。

缺点:

  • 依赖管理。由于每个子应用都可以使用不同版本的库和框架,可能会引起依赖管理的问题。需要谨慎处理不同子应用之间的依赖关系,以避免冲突和错误。
  • 子应用需要安装依赖。分独立运行和子应用运行两种模式。

如此一来,终于可以拼积木了。

# qiankun

qiankun是基于single-SPA开发的,他相当于封装了single-SPA,增加了易用性,升级了一些特性。

qiankun都做了哪些升级呢?

  • 增加了应用间的通信和状态管理
  • 更便捷的子应用注册和加载方式
  • 子应用不需要安装依赖
  • 在子应用的加载方式上,增加了Fetch HTML、Fetch Source Code 等方式
  • 全局状态隔离。通过内置的 initGlobalState 和 mount 阶段的 props 参数来实现。子应用可以通过这些机制来共享和隔离状态。
  • JS沙箱。有快照沙箱和代理沙箱两种。快照沙箱基于原window对象和diff算法实现,代理沙箱利用proxy实现
  • CSS沙箱。主要基于 shadow DOM 和 Scoped CSS。

如此一来,可以更便捷的拼积木了。

# Module Federation

single-spa 是一种组织微前端路由的方案。Module Federation是的一种基于打包共享代码的方案。它们之间并不冲突,可以相互补充一起使用。

以下示例展示了,Host 应用通过 ModuleFederationPlugin 插件的remotes指定了从 Remote 应用要使用的模块。
Remote 应用通过exposes配置指定了要共享的模块。
在 Host 应用中,可以使用类似 import('remoteApp/Component') 的语法来异步加载 Remote 应用中的模块。

// Host 应用的 webpack 配置
const ModuleFederationPlugin = require('webpack/lib/container/ModuleFederationPlugin');

module.exports = {
  // ...其他配置
  plugins: [
    new ModuleFederationPlugin({
      name: 'host',
      remotes: {
        remoteApp: 'remoteApp@http://localhost:3001/remoteEntry.js',
      },
      shared: ['react', 'react-dom'],
    }),
  ],
};

// Remote 应用的 webpack 配置
const ModuleFederationPlugin = require('webpack/lib/container/ModuleFederationPlugin');

module.exports = {
  // ...其他配置
  plugins: [
    new ModuleFederationPlugin({
      name: 'remoteApp',
      library: { type: 'var', name: 'remoteApp' },
      exposes: {
        './Component': './src/Component',
      },
      shared: ['react', 'react-dom'],
    }),
  ],
};

但是个人觉得这种模式还有提升空间,对于项目之间共享关系的维护比较复杂,增加项目间的耦合关系。适合项目少的时候进行使用。

目前觉得抽离公共组件库和公共方法更合理,但也有代码冗余的问题,可能云函数是未来的趋势吧。

最后,又回到最开始,微前端的思想万变不离其宗,就是为了结构巨石应用,具体使用那种方案,也要根据具体场景具体分析,找到适合的技术方案。