# 微前端

# 目标

核心目标是将巨石应用拆解成若干可以自治的松耦合微应用,这样才能确保微应用真正具备独立开发、独立运行的能力(防止巨石应用)

# 介绍

微前端按业务功能将一整块前端应用分解成一系列更小的、更内聚的微前端应用,同时通过明确的交互协议来管理这些应用间的依赖关系,实现不同业务的解耦合。并将每个前端应用交给独立团队负责,各自独立开发、独立部署、充分利用并行性。

  • 微前端是一种架构风格,monorepo 是一种开发策略,模块联邦是一种打包和部署方式
    • 微前端将大型的前端应用分解成一组小的、独立的前端服务,每一个小的前端服务可以由不同的团队独立开发、部署和维护。微前端的目标是提高大型前端项目的可维护性、可扩展性和灵活性
    • monorepo 是一种开发策略(技巧),它将多个项目或服务的代码放在一个单一的版本控制系统仓库中,有助于简化依赖管理、代码共享和团队协作。对于工具库能更好的暴露单独模块。
    • 模块联邦是 Webpack5 引入的一个特性,允许将多个 Webpack 项目远程接入、联合起来,形成一个单一的应用,每个模块可以独立开发和部署,同时共享彼此的代码,模块联邦是一种新的打包和部署前端应用的方式。

# 微前端应用

# EMP

模块联邦技术,微前端构建方案,是基于 webpack 5 module federation 的微前端方案。

特点

  1. webpack 联邦编译可以保证所有子应用依赖解耦;
  2. 应用间去中心化的调用、共享模块;
  3. 模块远程 ts 支持;

不足

  1. 对 webpack 强依赖,老旧项目不友好;
  2. 没有有效的 css 沙箱和 js 沙箱,需要靠用户自觉;
  3. 子应用保活、多应用激活无法实现;
  4. 主、子应用的路由可能发生冲突;

底层原理 这个东西有点类似于拆包,也可以叫模块共享,例如 React 有个模块可以共享给 Vue 项目用 Vue2 的组件可以共享给 Vue3 用。

# single-spa

single-spa 是微前端的基础框架,乾坤框架就是基于 single-spa 来实现的,在 single-spa 的基础上做了一层封装,也解决了 single-spa 的一些缺陷。

原理

  1. single-spa 原理是通过网络请求请求到文件资源(使用 import 导入语法时,需要在 importmap 中注册包名称和地址)
  2. 对于样式隔离,single-spa 推荐的方法有:Scoped CSS 和 shadow DOM

分析

single-spa 实现了一个微前端框架需要具备的各种功能,但是实现的又不够彻底,遗留了很多需要解决的问题。

# 乾坤

乾坤是在 single-spa 的基础之上进行的进一步封装

特点

  1. html entry 的方式引入子应用,相比 js entry 极大的降低了应用改造的成本;
  2. 完备的沙箱方案,js 沙箱做了 SnapshotSandbox、LegacySandbox、ProxySandbox 三套渐进增强方案,css 沙箱做了 strictStyleIsolation、experimentalStyleIsolation 两套适用不同场景的方案;
  3. 做了静态资源预加载能力;

不足

  1. 适配成本比较高,工程化、生命周期、静态资源路径、路由等都要做一系列的适配工作;
  2. css 沙箱采用严格隔离会有各种问题,js 沙箱在某些场景下执行性能下降严重;
  3. 无法同时激活多个子应用,也不支持子应用保活;
  4. 无法支持 vite 等 esmodule 脚本运行;

# 无界

特点

  1. 接入简单只需要四五行代码
  2. 不需要针对 vite 额外处理
  3. 预加载
  4. 应用保活机制

不足

  1. 隔离 js 使用一个空的 iframe 进行隔离
  2. 子应用 axios 需要自行适配
  3. iframe 沙箱的 src 设置了主应用的 host,初始化 iframe 的时候需要等待 iframe 的 location.orign 从 'about:blank' 初始化为主应用的 host,这个采用的计时器去等待的不是很悠亚。

底层原理 使用 shadowDom 隔离 css,js 使用空的 iframe 隔离,通讯使用的是 proxy

# 原理

无界的原理就是使用开启影子 dom 来进行样式的隔离,通过使用 attachShadow 方法开启影子 dom(继承 HTMLElement),获得的 shadow 影子 dom 添加 template(无界的初始模板)为子元素,这样就进行了样式隔离,最后通过 window.customElement.define 注册 dom 元素 wu-jie,使用标签即可。

index.js:

window.onload = () => {
    class WuJie extends HTMLElement {
        constructor() {
            super()
            this.init()
            this.getAttr('url')
        }
        init() {
          const shadow =  this.attachShadow({ mode: "open" }) // 开启影子 dom 也就是样式隔离
          const template = document.querySelector('#wu-jie') as HTMLTemplateElement
          console.log(template);
          shadow.appendChild(template.content.cloneNode(true))
        }
        getAttr (str:string) {
           console.log('获取参数',this.getAttribute(str));
        }
        // 生命周期自动触发有东西插入
        connectedCallback () {
           console.log('类似于vue 的mounted');
        }
        // 生命周期卸载
        disconnectedCallback () {
              console.log('类似于vue 的destory');
        }
        // 跟 watch 类似
        attributeChangedCallback (name:any, oldVal:any, newVal:any) {
            console.log('跟vue 的watch 类似 有属性发生变化自动触发');
        }
    }
    window.customElements.define('wu-jie', WuJie)
}

index.html:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script src="./index.js"></script>
</head>
<body>
    <!-- 外层写一个 div 测试隔离 -->
    <div>我是div</div>
    <wu-jie url="xxxxxx"></wu-jie>
    <template id="wu-jie">
       <!--div 的样式是作用于全局的 -->
        <style>
            div {
                background: red;
            }
        </style>
         <div>
            样式隔离的
         </div>
    </template>
</body>
</html>

# 预加载

使用:

需要从 wujie 的实例导出 preloadApp , 参数跟 startApp 一致,预加载必须开启 exec 选项

preloadApp({ name: "vue3", url: "http://127.0.0.1:5174/", exec: true })
preloadApp({ name: "react", url: "http://127.0.0.1:5175/", exec: true })
  • exec 代表是否进行预加载,默认值是 true
  • js 的执行模式,由于子应用的执行会阻塞主应用的渲染线程,当设置为 true 时 js 采取类似于 react fiber 的模式方式间断执行,每个 js 文件的执行都包裹在 requestidlecallback 中,每执行一个 js 文件后就可以返回去响应外部的输入,不会造成浏览器渲染和加载之间的冲突从而造成卡顿,但是这个颗粒度是 js 文件,所以应该保证 js 文件不应过大。

浏览器一帧之内要做的事情:

  • 处理用于输入(事件)
  • 执行定时任务
  • 执行 requestAnimationFrame
  • 执行 dom 的回流和重绘
  • 计算更新涂层的更新指令
  • 绘制指令合并主线程,如果有空余时间执行 requestidlecallbask

react 也有该机制 但是 react 并没有用 requestidlecallback ,说是这个东西经过测试可能会超过 16ms,超过 16ms 绘制就会看起来很卡 所以 react16 是用的 requestAnimationFrame + postMessage 实现的那为什么不用 setTimeOut setTimeOut 及时为 0 也会有一个最小毫秒延迟 4ms,所以是用了 postMessage,react18 又换成了 MessageChannel 实现了队列方式去执行任务。

# micro-app

micro-app 是基于 webcomponent + qiankun sandbox 的微前端方案。

特点

  1. 使用 webcomponet 加载子应用相比 single-spa 这种注册监听方案更加优雅;
  2. 复用经过大量项目验证过 qiankun 的沙箱机制也使得框架更加可靠;
  3. 组件式的 api 更加符合使用习惯,支持子应用保活;
  4. 降低子应用改造的成本,提供静态资源预加载能力;

不足

  1. css 沙箱依然无法绝对的隔离,js 沙箱做全局变量查找缓存,性能有所优化;
  2. 支持 vite 运行,但必须使用 plugin 改造子应用,且 js 代码没办法做沙箱隔离;
  3. 对于不支持 webcompnent 的浏览器没有做降级处理;

底层原理 js 隔离跟 qiankun 类似也是使用 proxy + with,css 隔离自定义前缀类似于 scoped

# 模块联邦

模块联邦是跟 webpack5 强耦合的,是基于 webpack5 内置插件的 无须安装

它相当于是一个去中心化技术,它可以让多个独立构建的应用之间,动态的调用彼此的模块。这种运行机制,可以让我们轻松的拆分应用,真正做到跨应用的模块共享。

配置过程就是在 webpack.config.js 中配置 ModuleFederationPlugin 插件,打完包之后观察会发现其实就是 import 函数动态加载,使用模块联邦的好处就在于之前当多个项目共有的模块改变时,每一个项目都需要重新 install 一下,而使用模块联邦之后,就相当于动态导入,免去了重新 install 的过程(由项目的克隆转为类似软链接的过程)。

更新于 阅读次数

请我喝[茶]~( ̄▽ ̄)~*

dmq 微信支付

微信支付

dmq 支付宝

支付宝