微前端(What)

如果你还在为"巨石应用"或"跨技术栈集成"犯愁,不妨了解一下微前端吧。微前端借鉴了微服务的理念,将一个单体SPA(单页面应用)拆分为多个小型SPA,微应用之间独立开发、独立部署、独立运行。

一、Web架构演进

Web应用架构的演进可以分为三个阶段:

  1. 单体应用

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

  1. 前后端分离

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

  1. 微服务

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

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

二、前端困局

  • 巨石应用

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

  • 跨技术栈集成

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

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

三、微服务和微前端

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

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

微前端借鉴了微服务的理念,将一个单体SPA拆分为多个小型SPA,并通过路由分发的方式组织起来,微应用之间独立开发、独立部署、独立运行,这就是微前端。

了解了什么是微前端,接下来我们将介绍几个主流的微前端框架,包括使用方法和各自的特点。

四、single-spa

single-spa是微前端框架的开山鼻祖,它开创性地把微应用当做react/Vue组件来管理。每个微应用需要导出挂载、卸载等生命周期函数,single-spa会根据url变化调用这些函数控制微应用的挂载和卸载。

1. 使用方法

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

registerApplication({
  name: 'app1',
  app: () => import('src/app1/main.js'),
  activeWhen: '/app1',
  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会根据url变化调用这些函数控制微应用的加载和卸载。。

2. 局限性

single-spa实现了路由转发和微应用生命周期管理等核心功能,但是也存在以下局限性。

  • 基于js entry的资源加载模式

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

  • 缺乏应用隔离机制

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

五、qiankun

1. 使用方法

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了。

2. 优点

  • 基于html entry的资源加载模式

在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() // 启动基座应用

只有实现了动态加载,微应用才能独立部署。另外也不再需要关注入口js文件,html entry能够根据入口地址自动加载html、js、css资源。

  • js沙箱

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

  • css隔离

通过shadow dom实现css隔离,这是浏览器原生支持的。为每个微应用创建一个shadow dom的父节点,shadow dom内部的dom和css不会影响外部,外部也不会访问到内部的数据。

还有一种方案就是scoped css,在css选择器里加一个前缀,不同微应用的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踩坑记录

  • 微应用资源跨域

默认情况下,浏览器用<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;
   }
}
  • 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,  
  }  
})
  • 微应用hash路由不生效

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

七、为什么不用iframe

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

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

八、总结

前端在面临"巨石应用"和"跨技术栈集成"等困局时,借鉴了微服务的理念,将一个单体SPA拆分为多个小型SPA,微应用之间独立开发、独立部署、独立运行,这就是微前端。然后,我们也介绍主流微前端框架single-spa和qiankun的使用方法,以及局限性和优点。最后,也分享了qiankun的相关实践以及iframe的对比。后续,我们也会有文章继续探索微前端的实现原理。

参考资料

一个js库就把你的网页的底裤🩲都扒了------import-html-entry

single-spa官网

qiankun官网

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

HTML Entry 源码分析

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

Garfish 微前端实现原理

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

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

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

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

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

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

一文看透 Module Federation

相关推荐
paopaokaka_luck22 分钟前
基于SpringBoot+Uniapp的健身饮食小程序(协同过滤算法、地图组件)
前端·javascript·vue.js·spring boot·后端·小程序·uni-app
患得患失9491 小时前
【前端】【vscode】【.vscode/settings.json】为单个项目配置自动格式化和开发环境
前端·vscode·json
飛_1 小时前
解决VSCode无法加载Json架构问题
java·服务器·前端
YGY Webgis糕手之路3 小时前
OpenLayers 综合案例-轨迹回放
前端·经验分享·笔记·vue·web
90后的晨仔4 小时前
🚨XSS 攻击全解:什么是跨站脚本攻击?前端如何防御?
前端·vue.js
Ares-Wang4 小时前
JavaScript》》JS》 Var、Let、Const 大总结
开发语言·前端·javascript
90后的晨仔4 小时前
Vue 模板语法完全指南:从插值表达式到动态指令,彻底搞懂 Vue 模板语言
前端·vue.js
你的人类朋友4 小时前
❤️‍🔥微服务的拆分策略
后端·微服务·架构
德育处主任4 小时前
p5.js 正方形square的基础用法
前端·数据可视化·canvas
烛阴4 小时前
Mix - Bilinear Interpolation
前端·webgl