微前端是什么?

一、Web应用架构演进

微前端作为一种Web应用架构,是在解决应用复杂度的过程中不断演进的结果。

这张图,展示了Web应用架构演进的三个阶段:

  • 单体应用

在前期的web应用中,前后端代码混是合在一起的(例如JSP)。后端查询数据库,并把数据套入html模板返回给浏览器。缺点很明显,用户每次输入都要刷新html才能看到结果,更适合展示静态网页。

  • 前后端分离

随着AJAX技术的出现,使得网页在不刷新html的情况下获取后端数据,并布局刷新页面。至此,前后端分离,前端更专注于用户交互并通过XMLHttpRequeat和后端数据交互,SPA应用就是这个阶段的代表作。但是,随着业务的发展和代码的堆积,应用复杂度变得不可控,产生了巨石应用。

  • 微服务

后端应用根据业务领域拆分为多个独立的微服务,服务之间通过远程调用进行交互,从而实现复杂度可控。前端调用后端接口时,一般会经过API网关或BFF路由到具体的微服务。

面对业务快速发展带来的应用复杂度,后端通过微服务进行拆解分治,而前端还处于传统的SPA单体应用,又该如何解决这个问题呢?

二、前端应用困局

  • 巨石应用

用户体验需求升级和前端技术演进,让前端越来越重,应用复杂度越来越高。例如,修改代码牵一发而动全身;代码构建和资源加载慢,影响UX(user experience)和DX(developer experience)。

  • 跨技术栈应用集成

历史应用重构无法一次性完成,逐步重构过程中新老应用共存;历史应用整合,把不同技术栈的业务应用集成在一起。问题是不同技术栈,无法统一进行代码开发和应用部署

上面两个问题,一个是拆分应用,另一个是集成应用。表面看是"八竿子打不着边",但本质上都是"系统解耦"的问题,因此可以借鉴"微服务"的解决思路。

三、微服务和微前端

微服务是一种应用架构,它将一个大型单体应用拆分为多个独立的小型服务,并通过轻量级的通信协议组织起来。重点在于独立 二字,即独立开发、独立部署、独立运行。微服务具备以下优势:

  • 系统解耦:每个服务聚焦单一的业务功能,不互相依赖,系统复杂度可控。
  • 快速迭代:新增功能或修复bug时,只要更新对应的服务,不用覆盖整个系统。
  • 故障隔离:单个服务故障不会导致整个系统崩溃。
  • 多技术栈共存:不同服务可以用不同技术栈开发,解决了逐步重构的多技术栈共存难题。

微前端是一种类似微服务的架构,它将微服务的理念应用于SPA(前端单页面应用),将一个单体SPA拆分为多个小型SPA,并通过路由分发的方式组织起来

目前主流的微前端方案有:single-spa、qiankun、wujie。

四、single-spa

在single-spa中,微应用被当做react组件来管理,有挂载、卸载等生命周期。single-spa会在url变化时,把对应的微应用挂载到DOM树。使用方法如下:

  1. 在基座应用中注册微应用
javascript 复制代码
import { registerApplication } from 'single-spa';

registerApplication({
  name: 'app1',
  app: () => import('src/app1/main.js'),
  activeWhen: '/app1',
  customProps: {
    some: 'value',
  }
);

registerApplication({
  name: 'app2',
  app: () => import('src/app2/main.js'),
  activeWhen: '/app2',
  customProps: {
    some: 'value',
  }
);

singleSpa.start() // 启动基座应用

上面是一个简单的例子,有一个微应用叫app1,整个应用的资源被打包成一个main.js文件,当路由跳转到/app1时会执行main.js,把app1渲染到dom树

  1. 导出微应用
javascript 复制代码
import React from 'react';
import ReactDOM from 'react-dom';
import App from './index.tsx'

export const bootstrap = () => {}
export const mount = () => {
  ReactDOM.render(<App/>, document.getElementById('root'));
}
export const unmount = () => {}

微应用要导出bootstrap、mount、unmount等生命周期函数,也就是要让single-spa知道怎么去加载、挂载微应用。

作为一个基础的微前端框架,single-spa存在以下不足:

  • js entry的局限性

默认是在编译时加载微应用资源,无法动态加载,即无法独立部署。另外,整个微应用被打包成一个js文件,无法实现按需加载。最后,还要指定微应用入口文件,一旦打包后的文件名变了,还得同步更改基座应用的配置。

  • 全局变量冲突

当有多个子应用时,存在js全局变量冲突和css冲突。,比如:微应用A声明了一个全局变量 window.a,这时候切换到微应用B,B也有一个全局变量window.a,如何保证访问到正确的值?

  • 应用状态管理

有些场景下,基座应用的状态需要同步给微应用,微应用也要能够修改基座应用的状态,那么应用之间怎么实现状态管理呢?

五、qiankun

qiankun是基于single-spa实现的更为完善的微前端框架,使用方法如下:

  1. 在基座应用中注册微应用
javascript 复制代码
import { registerMicroApps, start } from 'qiankun';

registerMicroApps([
  {
    name: 'app1',
    entry: 'http://domain1/app1',
    container: '#container',
    activeRule: '/app1',
  },
  {
    name: 'app2',
    entry: 'http://domain2/app2',
    container: '#container',
    activeRule: '/app2',
  },
]);

start();

使用方法跟single-spa差异不大,但细心的同学会发现,entry指的是微应用入口地址,而不是js文件。qiankun会去请求入口地址,获取入口html并劫持html解析,然后再构造请求去加载html中的script、style资源 ,这种方案被称为html entry

  1. 导出微应用
javascript 复制代码
import React from 'react';
import ReactDOM from 'react-dom';
import App from './index.tsx'

export const bootstrap = () => {}
export const mount = (props) => {
  ReactDOM.render(<App/>, props.container.getElementById('root'));
}
export const unmount = () => {}
  1. 微应用打包配置
javascript 复制代码
const packageName = require('./package.json').name;  
  
module.exports = {  
output: {  
library: `${packageName}-[name]`,  
libraryTarget: 'umd',  
jsonpFunction: `webpackJsonp_${packageName}`,  
},  
};

微应用打包成一个umd库,并设置库名称为注册微应用时的name,这样qiankun框架就能调用微应用js了。

qiankun主要解决了以下问题:

  • 自动加载微应用资源

在single-spa中要实现动态加载微应用资源,得借助SystemJS工具。

javascript 复制代码
import { registerApplication } from 'single-spa';

registerApplication({
  name: 'app1',
  app: () => System.import('http://domain1/app1/main.js'),
  activeWhen: '/app1',
  customProps: {
    some: 'value',
  }
);

singleSpa.start() // 启动基座应用

动态加载使得微应用能够独立部署,但还得指定入口文件地址。qiankun使用了html entry方案,实现了自动加载微应用资源。

  • js沙箱

qiankun通过js沙箱,解决了js全局变量冲突。具体是通过proxy代理window对象,为每个微应用创建一个window对象副本。当切换应用时,微应用通过proxy访问到对应的window对象副本。

  • css隔离

一说到css隔离,最容易想到的就是scoped css。在css选择器加一个前缀,隔离不同微应用的css命名。

另外一种是浏览器原生技术,shadow dom。给每个微应用创建一个shadow dom的父节点,shadow dom内部的dom和css不会影响外部,外部也不会访问到内部的数据。

  • 信息通信

qiankun提供了设置全局状态和监听全局状态的方法,从而实现基座应用和微应用的信息通信。

主应用

javascript 复制代码
import { initGlobalState, MicroAppStateActions } from 'qiankun';
// 初始化 state
const actions: MicroAppStateActions = initGlobalState(state);
actions.onGlobalStateChange((state, prev) => {
  // state: 变更后的状态; prev 变更前的状态
  console.log(state, prev);
});
actions.setGlobalState(state);
actions.offGlobalStateChange();

微应用

javascript 复制代码
// 从生命周期 mount 中获取通信方法,使用方式和 master 一致
export function mount(props) {
  props.onGlobalStateChange((state, prev) => {
    // state: 变更后的状态; prev 变更前的状态
    console.log(state, prev);
  });

  props.setGlobalState(state);
}

六、qiankun踩坑记录

  1. 微应用资源跨域

默认情况下,浏览器用<script>和<style>加载js、css资源,不受同源策略限制。而qiankun是由框架构造XHR请求获取微应用的js、css资源,存在跨域问题(主应用和微应用的域名不一致)。一般修改微应用的nginx配置即可。

javascript 复制代码
server {
   listen 80;
   server_name app1.com;
   location / {
      add_header Access-Control-Allow-Origin main1.com;
   }
}
  1. dialog组件无法找到body节点

qiankun支持两种沙箱方案,一种是shadow dom,虽然shadow dom可以实现彻底的css隔离,但也会导致微应用无法访问html、body等全局DOM节点,例如dialog、Modal等组件无法找到body节点,进而无法挂在到DOM上。所以一般推荐用另一种方案,即scoped css。

javascript 复制代码
// shadow dom
start({
  sandbox: {
    strictStyleIsolation: true,
  }
})

// scoped css
start({  
  sandbox: {  
    experimentalStyleIsolation: true,  
  }  
})
  1. 微应用hash路由不生效

当基座应用使用history路由,微应用使用hash路由时,会出现在基座应用跳转微应用页面时路由不生效。因为在基座应用是使用的history api,不会触发hash事件。解决方案就是在基座应用路由微应用页面时加上触发hash事件的逻辑。

七、为什么不用iframe

iframe是浏览器原生技术,常用来在网页中嵌入另一个网页,这一点和微前端是不谋而合的。但是也因为它的强隔离性带来了一系列问题:

  • 刷新会丢失路由状态
  • dom割裂,iframe内部的弹窗无法全局展示
  • 跨域,客户端的登录态无法共享,子应用需要重新登录
  • 通信复杂,只能通过postmessage传递消息

八、总结

微前端是一种类似微服务的架构,它将微服务的理念应用于SPA,将一个单体SPA拆分为多个小型SPA,并通过路由分发的方式组织起来。从而解决了巨石应用和跨技术栈应用集成的难题。落地微前端的挑战有,微应用资源加载、js沙箱和css隔离、信息通信等。iframe、qiankun和wujie是常见的方案,其中,qiankun应用较广泛。

参考资料

single-spa官网

qiankun官网

微前端方案 qiankun 只是更完善的 single-spa

HTML Entry 源码分析

qiankun 2.x 运行时沙箱 源码分析

Garfish 微前端实现原理

在腾讯换了新部门,微前端 + 重构 Vue -> React 项目实战落地总结

微前端在小米 CRM 系统的实践

有赞美业微前端的落地总结

字节跳动是如何落地微前端的

前端微服务在字节跳动的打磨与应用

将微前端做到极致-无界方案

一文看透 Module Federation

相关推荐
前端三叶草25 分钟前
静态分析:现代前端脚手架路由梳理
前端·前端框架
Goboy1 小时前
从崩溃到升职:腾讯云EdgeOne Pages MCP拯救了我的996危机
后端·程序员·架构
uhakadotcom1 小时前
Gradio入门:快速构建机器学习交互界面
面试·架构·github
一道微光2 小时前
mac air m系列arm架构芯片安装虚拟机 UTM+debian 浏览器firefox和chrome
arm开发·macos·架构
alksql2 小时前
架构思路法
数据库·架构
Lethehong2 小时前
崖山YashanDB:下一代国产分布式数据库的架构革新与行业实践
数据库·分布式·架构
小蘑菇二号2 小时前
ARM 架构--通用寄存器/状态寄存器/控制寄存器/特殊用途寄存器
arm开发·架构
工业互联网专业2 小时前
基于springcloud微服务架构的巡游出租管理平台
java·vue.js·spring cloud·微服务·毕业设计·源码·课程设计
CloudJourney2 小时前
(万字超详细-网络版本)VXLAN详解:概念、架构、原理、搭建过程、常用命令与实战案例
网络·架构
陌言不会python2 小时前
谷粒微服务高级篇学习笔记整理---thymeleaf
笔记·学习·微服务