Skip to content

微前端 #84

Description

@inkjuncom

什么是微前端

微前端其实有点类似于后端的微服务,把一个应用拆分为多个子应用,然后给多个团队去维护,最后统一在一个页面内进行整合

它的优点主要有三个

  1. 子应用的技术栈想用什么就用什么,不需要强制统一
  2. 每一个子应用单独开发,单独上线
  3. 一个模块崩了,不会导致整体崩了

它的缺点也主要有三个

  1. 整体结构变复杂了,排查问题更难
  2. 多套代码一起跑,整体应用占用内存会更多,会更卡
  3. 全局样式、全局变量等会冲突,处理起来更繁琐

与我历史工作经验结合

我回来仔细想了想,从这个思路来看,我们的 C 端项目、小程序其实也可以看作一种微前端。

就拿美团小程序来说,它的内容非常丰富,涵盖外卖、打车、医药、闪购等多个业务板块,每个子模块都由独立团队负责维护,最终统一集成到美团小程序里。

它虽然不像 B 端系统那样,通过 JS 沙箱做运行时隔离,但用了另一种方式,小程序自身的机制来实现模块隔离。

因为微信小程序本身不允许动态执行JavaScript,一旦检测到就会直接封禁,所以很多隔离工作都需要我们手动处理:

  • 全局 storage 的隔离
  • 全局公共方法的处理
  • 打包依赖与路径的处理
  • 通信方法
  • 路由方法处理

我觉得这些思路,本质上和 B 端微前端的设计思想是完全相通的。

微前端的两种实现方式

微前端有两种主要的实现方式:一种是利用 iframe 实现,另外一种是通过 SPA 实现

iframe

  • 优点
    • 隔离极强,样式、JS、全局变量天然互不干扰
  • 缺点
    • 弹窗、路由、高度自适应很难处理,体验割裂
    • 通信麻烦,性能差,多 iframe 容易卡顿
    • 无法共享依赖,资源重复加载,体积大

spa

  • 优点
    • 体验流畅,像同一个应用,无页面割裂感
    • 可共享依赖,减少重复打包,性能更好
    • 路由、状态、全局拦截统一管理,更易维护
  • 缺点
    • 隔离靠沙箱,不如 iframe 彻底,仍可能冲突
    • 改造成本高,需适配构建、生命周期
    • 调试更复杂,问题定位比 iframe 麻烦

自己实现一个微前端

微权大致可以分为三部分:

  1. 主应用
  2. 次应用
  3. 微权能框架

其中要接入前端主应用和子应用,都需要进行改造
一般情况下,需要在主应用中调用微前端框架,对微前端进行注册
此应用里面需要区分一下环境,因为很多时候此应用不仅需要嵌入在微前端框架里面运行,它自己也可以单独运行

export const patchRouter = (globalEvent, eventName) => {
  return function () {
    const e = new Event(eventName);
    globalEvent.apply(this, arguments); // 执行原始路由方法
    window.dispatchEvent(e); // 派发自定义事件
  }
};
export const rewriteRouter = () => {
  // 劫持 pushState/replaceState
  window.history.pushState = patchRouter(window.history.pushState, 'micro_push');
  window.history.replaceState = patchRouter(window.history.replaceState, 'micro_replace');
  
  // 监听自定义事件,触发路由处理
  window.addEventListener('micro_push', turnApp);
  window.addEventListener('micro_replace', turnApp);
  
  // 监听浏览器前进后退
  window.onpopstate = function () {
    turnApp();
  }
};
  1. 劫持路由方法:重写 pushState / replaceState,监听所有前端路由跳转。
  2. 触发统一处理:路由变化时自动派发事件,执行子应用切换逻辑。
  3. 兼容全场景:支持代码跳转、浏览器前进后退,保证路由行为一致。

获取第一个子应用,其实就是从一堆配置中找到当前被激活的配置。

我们可以注册主应用的生命周期。这个生命周期可以分为三段:

  • before load
  • mounted
  • destroyed

这三个生命周期可以注册为一个函数。当我们需要触发生命周期,即触发注册的生命周期时,就可以直接调用相关的函数

微旋端框架的生命周期其实就是在调用主应用的生命周期,同时也在调用子应用的生命周期

在微前端框架中获取 HTML,可以通过 GET 请求,获取 HTML 之后可以通过 document.innerHTML 来插入之前获取的内容

危险温度降到里面,我们需要获取 GS 的相关内容,JS 又分为 ink 里面的 JS,script 里面的 JS其中 script 里的 gs 又分为:通过 src 引入的 gs,以及没有 src 直接在 script 内部的 js

我们需要提炼这些 JS

提炼 gs 之后,我们需要执行 gs

export const performScriptForFunction = (script) => {
  new Function(script).call(window, window)
}

export const performScriptForEval = (script) => {
  eval(script)
}
  1. 两个函数都是动态执行 JS 字符串脚本,用于微前端加载子应用代码。
  2. new Function 方案更安全:全局作用域、隔离性好、变量污染风险低。
  3. eval 方案风险更高:继承当前作用域、易造成变量混乱,生产不推荐。

危险端里面有一个快照杀香的概念。所谓快照杀香,就是在第一个子应用里面的全局变量,并不会在第二个子应用里面生效。

要实现这个快照功能,我们可以用一个 Proxy 来代理这个 Window 对象

当 Window 对应的应用生命周期被激活时,我们可以给快照生成进行复制。当子应用的生命周期结束之后,我们可以重置回这个快照生成

css 样式隔离

方案 时机 原理 优点 缺点
CSS Modules 构建时 类名加哈希:.btn → .btn_abc123 彻底、无兼容问题 要改代码、不兼容老项目
minicss/scoped 运行时 选择器加前缀 / 属性 零改造接入、兼容所有框架 复杂选择器(body/:root)可能漏
Shadow DOM 运行时 原生封闭 DOM 树 最彻底隔离 弹窗 / 覆盖层易出问题、兼容性一般

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions