微前端框架选型以及实现

主应用地址: cloudbase-100032138116.coding.net/p/applet-fe...

创建子应用脚手架:cloudbase-100032138116.coding.net/p/applet-fe...

微前端架构设计:www.garfishjs.org/blog

微前端框架对比

框架 介绍 JS 沙箱 样式隔离 预加载 路由同步 数据通信 优点 缺点 背景
single-spa 最早的微前端框架 支持 支持 不支持 不支持 不支持 - 需要自己去加载子应用- 需要自己设置js隔离和css隔离- 无法预加载
Garfish 基于 SPA 的微前端架构 支持 支持 支持 支持 支持 和 qiankun 的 api 相似,改造成本低。没有基于 single-spa,而是重新实现的,项目的灵活性高,解决了 qiankun 的一些痛点。 字节
qiankun 基于 single-spa 的微前端实现库 支持 支持 支持 支持 支持 使用人数最多,沉淀了很多踩坑经验。 - 无法支持 vite 等 ESM 脚本运行- 样式隔离不够完美 - strictStyleIsolation: true1. 原理是利用 webComponent 的 shadowDOM 实现 可能会影响React事件、弹框样式丢失 - experimentalStyleIsolation: true1. 原理类似于 vue 的 scope-css 1. 目前性能较差,处于实验阶段 阿里
无界 基于 WebComponent 容器 + iframe 沙箱 支持 支持 支持 支持 支持 - 社区不太活跃,解决 bug 的速度较慢。- 调查了一下,看起来坑也挺多的,例子- 对有富文本的项目不友好。复杂的 iframe 到 WebComponent 的代理机制,导致市面上大部分富文本编辑器都无法在无界中完好运行。- 长期维护性一般 腾讯
MicroApp 基于 WebComponent 容器 + iframe 沙箱 支持 支持 支持 支持 支持 内置了两种 JS 沙箱。1. with 代理沙箱 1. 类 qiankun 的 with 代理沙箱,据说相比 qiankun 性能高点1. iframe 沙箱 1. 用于兼容 vite 场景 - 如果是 iframe 沙箱 + WebComponent,痛点和无界类似。 京东
EMP 基于Rspack、 Module Federation 支持 支持 支持 支持 支持 - 目前 Rspack 和 Module Federation 的社区沉淀较少 欢聚时代

总结

每个框架都存在一些问题。我们之前的框架是用的 qiankun,结合我们的业务场景,选了 Garfish。

Garfish 的开发团队,是 Web Infra(字节跳动的网络基础设施团队),该团队在前端开源中做了不少贡献,还是有一定影响力的。

有兴趣的可以看下 Module Federation,也是一种微前端架构(类似于服务端的微服务),感觉以后会成为微前端的主流方案。

Module Federation 的作者是 Webpack 的团队成员,也是 Web Infra 组织和 Rspack的团队成员。Vite 的下一个大版本也会和 web-infra 团队合作,共建 Module Federation。

Vue & Vite:现状与未来 - 尤雨溪

Garfish

快速开始

主应用

javascript 复制代码
// main.tsx
import Garfish from 'garfish'
import microFeApps from '@/config/micro-fe-apps'
import { GarfishCssScope } from '@garfish/css-scope'

Garfish.run({
  basename: '/',
  plugins: [GarfishCssScope({ fixBodyGetter: true })],
  props: {
    msg: 'hello',
  },
  apps: microFeApps,
  beforeLoad(appInfo) {
    console.log('子应用开始加载', appInfo.name)
  },
  afterLoad(appInfo) {
    console.log('子应用加载完成', appInfo.name)
  },
})

createRoot(document.getElementById('root')!).render(<App />)

// src/config/micro-fe-apps.ts
const microFeApps: MicroFeApps = [
  {
    name: 'mp-sub-react-demo/*',
    activeWhen: '/mp-sub-react-demo',
    domGetter: '#mp-sub-react-demo',
    // 子应用开发环境
    entry: 'http://localhost:8081',
    // 子应用生产环境
    // entry: 'https://res-sit.wandacm.com.cn/qianfan-static/demo/20240408164058/',
    cache: true,
  },
]

// src/config/routes.tsx
const routes = [
  {
    path: '/',
    element: <Layout />,
    errorElement: <ErrorPage />,
    children: [
      {
        path: 'mp-sub-react-demo/*',
        element: <div id="mp-sub-react-demo" className="garfish-sub" />,
        name: 'mp-sub-react-demo',
      },
    ],
  },
]

子应用

javascript 复制代码
// src/main.tsx
import RootComponent from '@/Root'
import { reactBridge } from '@garfish/bridge-react-v18'

export const provider = reactBridge({
  el: '#root',
  rootComponent: RootComponent,
})

/** 这能够让子应用独立运行起来,以保证后续子应用能脱离主应用独立运行,方便调试、开发 **/
if (!window.__GARFISH__) {
  createRoot(document.getElementById('root')!).render(
    // @ts-expect-error
    <RootComponent basename="/" />,
  )
}

// src/Root.tsx
import { createBrowserRouter, RouterProvider } from 'react-router-dom'
import { type PropsInfo } from '@garfish/bridge-react-v18'

function RootComponent(appInfo: PropsInfo) {
  return (
    <RouterProvider
      router={createBrowserRouter(routes, {
        basename: appInfo.basename,
      })}
    />
  )
}

// src/config/routes.tsx
const routes = [
  {
    path: '/',
    element: <Layout />,
    errorElement: <ErrorPage />,
    children: [
      {
        index: true,
        element: <Home />,
      },
      {
        path: 'test',
        element: <Test />,
      },
    ],
  }
]

Webpack 配置

arduino 复制代码
const isDev = process.env.NODE_ENV === 'development'
const port = 8081

export default {
  mode: process.env.NODE_ENV,
  entry: path.join(srcDir, 'main.tsx'),
  output: {
    clean: !isDev,
    // 需要配置成 umd 规范
    libraryTarget: 'umd',
    // 修改不规范的代码格式,避免逃逸沙箱
    globalObject: 'window',
    // 保证子应用的资源路径变为绝对路径
    publicPath: isDev
      ? `http://localhost:${port}/`
      : `//res-sit.wandacm.com.cn/qianfan-static/demo/20240408164058/`,
  },
  devServer: {
    open: true,
    host: 'localhost',
    port,
    historyApiFallback: true,
    headers: {
      // 保证子应用的资源支持跨域,在上线后需要保证子应用的资源在主应用的环境中加载不会存在跨域问题(**也需要限制范围注意安全问题**)
      'Access-Control-Allow-Origin': '*',
    },
  },
  // 生产环境下,将 react 和 react-dom 作为外部依赖,由主应用提供
  externals: isDev
    ? undefined
    : {
        react: 'React',
        'react-dom': 'ReactDOM',
      },
}

如何接入一个子应用

  1. 创建子应用
lua 复制代码
pnpm create mp-sub
  1. 在主应用中注册子应用
arduino 复制代码
// src/config/micro-fe-apps.ts
const microFeApps: MicroFeApps = [
  // ...
  {
    name: 'mp-sub-test',
    activeWhen: '/mp-sub-test',
    domGetter: '#mp-sub-test',
    // 子应用开发环境
    entry: 'http://localhost:8081',
    // 子应用生产环境
    // entry: 'https://res-sit.wandacm.com.cn/qianfan-static/demo/20240408164058/',
    cache: true,
  },
]
  1. 在主应用的路由中添加子应用的根路由
javascript 复制代码
// src/config/routes.tsx
const routes = [
    // ...
    {
        path: 'mp-sub-test/*',
        element: <div id="mp-sub-test" className="garfish-sub" />,
        name: 'mp-sub-test',
    },
 ]

使用过程中遇到的问题

Vite 子应用开发环境报错 TypeError: Cannot read properties of undefined (reading 'GARFISH_EXPORTS')

TL;DR:当子应用为 ESModule 并且开启 vm 沙箱时 会出现错误。解决办法:子应用最好用 Webpack

因为 Vite 开发环境会进行依赖预构建,Vite 的开发服务器将所有代码视为原生 ES 模块。而 Garfish 支持 esModule时,需要关掉 vm 沙箱或者为快照沙箱时,才能够使用。关闭 vm 沙箱后,不支持主子应用隔离。

如果需要在 vm 沙箱下开启 ESModule 的能力,可以使用 @garfish/es-module 插件。但会带来严重的首屏性能问题。

详情:www.garfishjs.org/issues/#esm...

相关推荐
韩楚风8 分钟前
【linux 多进程并发】linux进程状态与生命周期各阶段转换,进程状态查看分析,助力高性能优化
linux·服务器·性能优化·架构·gnu
莹雨潇潇8 分钟前
Docker 快速入门(Ubuntu版)
java·前端·docker·容器
Jiaberrr17 分钟前
Element UI教程:如何将Radio单选框的圆框改为方框
前端·javascript·vue.js·ui·elementui
Tiffany_Ho1 小时前
【TypeScript】知识点梳理(三)
前端·typescript
安冬的码畜日常2 小时前
【D3.js in Action 3 精译_029】3.5 给 D3 条形图加注图表标签(上)
开发语言·前端·javascript·信息可视化·数据可视化·d3.js
小白学习日记3 小时前
【复习】HTML常用标签<table>
前端·html
丁总学Java3 小时前
微信小程序-npm支持-如何使用npm包
前端·微信小程序·npm·node.js
yanlele3 小时前
前瞻 - 盘点 ES2025 已经定稿的语法规范
前端·javascript·代码规范
懒羊羊大王呀4 小时前
CSS——属性值计算
前端·css