2023微前端技术方案选型

31,909 阅读10分钟

前言

目前接触过很多SaaS系统。随着业务量的升级,项目不得不做拆分。保证系统稳定性的前提,优化开发人员的体验。

目前在做这块的升级,所以调研了一些主流方案,文章也会分篇写

  • 技术选型
  • 拆分细则
  • 实际落地等
  • 本章主要介绍微前端主流的实现原理、当下比较火的技术方案,以及基于React +Vite,我会采用什么样的方案。后续在内部落地后,继续分享相关拆分细则,有兴趣的小伙伴可加V讨论 or 关注一下:V798595965,点击扫码加v

一、微前端现状

1.1 来源

微前端的概念是由ThoughtWorks在2016年提出的,它借鉴了微服务的架构理念,核心在于将一个庞大的前端应用拆分成多个独立灵活的小型应用,同时可以解决一些iframe的潜在问题。

1.2 意义

不可否认,微前端的设计起源于后端微服务,很多前端比较新的概念都来自于后端,比如Typescript,DDD领域驱动设计等等。

搞前端嘛,不折腾也不叫前端

微前端解决方案也给我们提供如下特性:

  • 单个前端部分可独立开发、测试和部署;
  • 无需重新构建即可添加、移除或替换单个前端部分;
  • 不同的前端部分可使用不同的技术构建;
  • 解决iframe硬隔离的一些问题

目前工作中遇到的项目有50%都已经应用上了该技术,这也体现了微前端带来的价值。

1.3 架构万能图

image.png

二、主流实现方案

2.1 qiankun为代表

主应用

  • 只有主应用需要安装qiankun,子应用不需要
  • name,entry,container,activeRule,当判断页面路由匹配到activeRule时,就去动态创建script,把entry中的文件加载出来,因为子应用mount生命周期判断了渲染的#app,所以就可以把内容渲染到自定义的container中
  • activeRule 则是和 window.location.pathname匹配,通过一级路由标识子应用

子应用接入

  • 判断是否有传入的container,来判断是要渲染到主应用的#app,还是自己的#app
  • 需要打包成一个umd格式的库:为了能通过window['app-name1']拿到子应用声明的生命周期,配合子应用的export的生命周期

实现原理

1、监视路由

  • window.location.pathname等相关变化,触发接下来的匹配逻辑

2、匹配子应用

  • 重写路由window.popState, replaceState,在保留原有功能的基础上,增加子应用entry映射相关逻辑
  • 针对变化的路由,匹配子应用

3、加载子应用

  • import-html-entry 解析入口文件中的html 和 script
  • 动态创建script去执行jsCode
  • 通过umd模块获取子应用,调用子应用mount方法,render子应用

4、渲染子应用

  • 把js和html,渲染到提前预留的#app容器中

2.1 以无界为代表

image.png

webComponent

  • 浏览器原生API,实现方式这里copy一下小满的:了解webComponents
  • 通过webComponent动态加载子应用,目前主流用同样方案的是无界和Micro App
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)
}

沙箱

  • 和qiankun sanbox沙箱不同的是,无界采用iframe做为沙箱方案,micro-app 0.x版本采用with沙箱,1.x支持vite后,支持和无界同样的ifame沙箱。
  • 听说有大佬用v8封装了个沙箱?面试和一个大佬聊的,没太懂,感觉很牛逼

2.3 以webpack-module-federation为代表

模块联邦主要是一种去中心化的思想,也可以用来做服务拆分, 实现原理比较复杂,主要涉及到以下几个方面:

1. 模块接口定义

在需要共享的模块中,通过 module.exports 或 export 将需要共享的模块封装成一个模块接口,并将其在模块系统中注册。

2. 共享模块的描述信息

在需要共享模块的应用程序中,通过使用 ModuleFederationPlugin 插件,将需要共享的模块的描述信息以 JSON 格式写入配置中。描述信息包括需要共享的模块名称、模块接口、提供共享模块的应用程序的 URL 等。

3. 共享模块的加载

在需要使用共享模块的应用程序中,通过 webpack 的 container 远程加载共享模块的代码,并将其封装成一个容器。容器在当前应用程序中的作用是在容器中运行共享模块的代码,并按照描述信息将导出的模块接口暴露出来。容器本身是一个 JavaScript 运行时环境,它可以在需要使用共享模块的应用程序中被动或主动加载。

4. 远程模块的执行

在容器中加载共享模块的代码后,容器需要将其执行,并将执行过程中产生的模块接口导出。为了实现这个目的,容器会利用 webpack 打包时在编译过程中生成的一个特殊的运行时代码,即 remoteEntry.js,通过 script 标签远程加载到当前应用程序中。在这个特殊的运行时代码中,会封装一些与容器通信的方法,例如 remote 方法,可以用于按需加载模块、获取模块接口等。

综上,webpack-module-federation 基于这些原理,实现了多个独立的应用程序之间的模块共享和远程加载,从而可以实现高度解耦、可扩展的架构。

三、各大框架

3.1 Single-Spa

作为比较早的微前端框架,single-spa只是实现了加载器、路由托管。沙箱隔离并没有实现。最早在第一家公司,项目就采用基于single-spa定制的一套自己框架,主要是也是通过proxySandBox实现沙箱隔离。

3.2 QianKun

QianKun 基于 single-spa ,阿里系开源的微前端框架,应该也是大家接触最多的了,社区比较活跃,这点比较重要。

QianKun对single-spa方案进行完善,主要的完善点:

  • 子应用资源由 js 列表修改进为一个url,大大减轻注册子应用的复杂度
  • 实现应用隔离,完成js隔离方案  (window工厂)  和css隔离方案  (类vuescoped
  • 增加资源预加载能力,预先子应用htmljscss资源缓存下来,加快子应用的打开速度

3.3 Mirco-App

Micro App 是京东出的一款基于 Web Component 原生组件进行渲染的微前端框架,不同于目前流行的开源框架,它从组件化的思维实现微前端,旨在降低上手难度、提升工作效率。

  • 官方demo:Micro App
  • 正式版本0.8版本, 1.0版本还是beta阶段,但是维护者在issue比较活跃
    • 0.x版本 vite支持不是很好,使用的时候需要关闭沙箱
    • 1.x版本 支持vite,需要采用iframe沙箱模式,这点和wujie的方案一样了,都是webComponent + iframe
  • MicroApp - 对vite支持的相关说明

image.png

接入

// 主 main.js

import microApp from "@micro-zoe/micro-app";

microApp.start();

// 加载子应用 a.js

import React from "react";

export default function AReact() {

return <micro-app name="ARact" url="http://127.0.0.1:4173/" iframe></micro-app>;
}

3.4 无界

无界是腾讯推出的一款微前端解决方式。它是一种基于 Web Components + iframe 的全新微前端方案,继承iframe的优点,补足 iframe 的缺点,让 iframe 焕发新生。

Web Components 是一个浏览器原生支持的组件封装技术,可以有效隔离元素之间的样式,iframe 可以给子应用提供一个原生隔离的运行环境,相比自行构造的沙箱 iframe 提供了独立的 window、document、history、location,可以更好的和外部解耦

接入

  • 安装WujieReact即可,引入组件即可,和micro-app类似
<WujieReact
  width="100%"
  height="100%"
  name="xxx"
  url={xxx}
  sync={true}
  fetch={fetch}
  props={props}
  beforeLoad={beforeLoad}
  beforeMount={beforeMount}
  afterMount={afterMount}
  beforeUnmount={beforeUnmount}
  afterUnmount={afterUnmount}
></WujieReact>

3.5 Garfish

Garfish 字节跳动出品,当时出现的时候还是比较火热的。但是感觉有点半成品,没啥issue,没啥人用

3.6 EMP

3.7 module-federation

image.png

  • Module Federation | webpack 中文文档
  • 需要加载打包后的产物,启动preview模式
  • JS沙箱、样式隔离需要自己做
  • 按照如下方式构建后,加载子应用,就和加载一个组件一毛一样
  • 使用@originjs/vite-plugin-federation也能在vite架构下实现模块联邦

体验非常好,完全的spa切换体验,非常丝滑

remote

  • 路由直接引入远程组件加载 const AReact = lazy(() => import('remote_app_a/AReact'));
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react-swc';
import federation from '@originjs/vite-plugin-federation';

export default defineConfig({
  plugins: [
    react(),
    federation({
      name: 'remote-app-a',
      filename: 'remoteEntry.js',
      // 需要暴露的模块
      exposes: {
        './AReact': './src/App.tsx',
      },
      shared: ['react'],
    }),
  ],
  build: {
    target: 'esnext',
  },
});


host

import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react-swc';
import federation from '@originjs/vite-plugin-federation';

export default defineConfig({
  plugins: [
    react(),
    federation({
      name: 'host-app',
      filename: 'remoteEntry.js',
      remotes: {
        remote_app_a: "http://127.0.0.1:4174/assets/remoteEntry.js",
      },
      shared: ['react'],
    }),
  ],
  build: {
    target: 'esnext',
  },
});

3.8 其他

  • ice-lab/icestark 飞冰的微前端方案,公司有个2年前的项目已经在用了
  • teambit/bit 无中文文档,国外挺火

四、如何选择

  • 自由度更高:module-federation
    • 需要自定义实现css隔离、js沙箱、路由劫持等功能
  • 用的最多:qiankun
    • 相对比较成熟,社区活跃
    • webpack体系、接入相对比较重
  • 接入更流畅:wujie、micro-app
  • 基于react +vite技术栈,我们最终选择更新更活跃,文档更丰富的micro-app

更活跃的社区

image.png

更详细的文档

image.png

五、后续

  • 技术选项只是开始,后期还有很多要做的事
  • 前期准备用monorepo + pnpm搭建参考,开始拆分第一个子应用
  • 中间比较详细的部署,拆分,组件库,工具库等等
  • 总之:一步步来,有条理的推进

六、往期

image.png