微前端介绍

微前端

微前端介绍

微前端定义

微前端(Micro-Frontends)是一种类似于微服务的架构,它将微服务的理念应用于浏览器端,即将 Web 应用由单一的单体应用转变为多个小型前端应用聚合为一的应用。

简单的说:微前端就是在一个Web应用中独立运行其他的Web应用

微前端特点

  1. 技术栈无关:主框架不限制接入应用的技术栈,微应用具备完全自主权;
  2. 独立开发、独立部署:微应用仓库独立,前后端可独立开发,部署完成后主框架自动完成同步更新;
  3. 增量升级:在面对各种复杂场景时,我们通常很难对一个已经存在的系统做全量的技术栈升级或重构,而微前端是一种非常好的实施渐进式重构的手段和策略;
  4. 独立运行时:每个微应用之间状态隔离,运行时状态不共享;
  5. 环境隔离:应用之间 JavaScript、CSS 隔离避免互相影响;
  6. 消息通信:统一的通信方式,降低使用通信的成本;
  7. 依赖复用:解决依赖、公共逻辑需要重复维护的问题;

微前端常见框架

路由分发式微前端

路由分发式微前端,即通过路由将不同的业务分发到不同的独立前端应用上。最常用的方案是通过 HTTP 服务的反向代理来实现。

下面是一个基于路由分发的 Nginx 配置:

javascript 复制代码
 http {
        server {
            listen 80;
            server_name  xxx.xxx.com;
            location /api/ {
                proxy_pass http://localhost:3001/api;
            }
            location /web/admin 

 - List item

{
                proxy_pass http://localhost:3002/api;
            }
            location / {
                proxy_pass /;
            }
        }
    }

优点:

  • 实现简单;
  • 不需要对现有应用进行改造;
  • 完全技术栈无关;

缺点:

  • 用户体验不好,每次切换应用时,浏览器都需要重新加载页面;
  • 多个子应用无法并存;
  • 局限性比较大;
  • 子应用之间的通信比较困难;
  • 子应用切换时需要重新登录;

iframe

iframe 作为一项非常古老的技术,也可以用于实现微前端 。通过 iframe,我们可以很方便的将一个应用嵌入到另一个应用中,而且两个应用之间的 css 和 javascript 是相互隔离的,不会互相干扰。

优点:

实现简单;

  • css 和 js 天然隔离,互不干扰;
  • 完全技术栈无关;
  • 多个子应用可以并存;
  • 不需要对现有应用进行改造;

缺点:

  • 用户体验不好,每次切换应用时,浏览器需要重新加载页面;
  • UI 不同步,DOM 结构不共享;
  • 全局上下文完全隔离,内存变量不共享,子应用之间通信、数据同步过程比较复杂;
  • 对 SEO 不友好;
  • 子应用切换时可能需要重新登录,体验不好;

single-spa

single-spa 提供了新的技术方案,可以帮忙我们实现类似单页应用的体验。

single-spa 方案中,应用被分为两类:基座应用子应用 。其中,子应用 就是文章上面描述的需要聚合的子应用;而基座应用 ,是另外的一个单独的应用,用于聚合子应用

和单页应用的实现原理类似,single-spa 会在基座应用 中维护一个路由注册表每个路由对应一个子应用。基座应用启动以后,当我们切换路由时,如果是一个新的子应用,会动态获取子应用的 js 脚本,然后执行脚本并渲染出相应的页面;如果是一个已经访问过的子应用,那么就会从缓存中获取已经缓存的子应用,激活子应用并渲染出对应的页面。

优点:

  • 切换应用时,浏览器不用重载页面,提供和单页应用一样的用户体验;
  • 完全技术栈无关;
  • 多个子应用可并存;
  • 生态丰富;

缺点:

  • 需要对原有应用进行改造,应用要兼容接入 sing-spa 和独立使用;
  • 有额外的学习成本;
  • 使用复杂,关于子应用加载、应用隔离、子应用通信等问题,需要框架使用者自己实现;
  • 子应用间相同资源重复加载;
  • 启动应用时,要先启动基座应用;

qiankun

single-spa 一样,qiankun 也能给我们提供类似单页应用 的用户体验。qiankun 是在 single-spa 的基础上做了二次开发 ,在框架 层面解决了使用 single-spa 时需要开发人员自己编写子应用加载、通信、隔离等逻辑 的问题,是一种比 single-spa 更优秀的微前端方案。

优点:

  • 切换应用时,浏览器不用重载页面,提供和单页应用一样的用户体验;
  • 相比 single-spa,解决了子应用加载、应用隔离、子应用通信等问题,使用起来相对简单;
  • 完全和技术栈无关;
  • 多个子应用可并存;

缺点:

  • 需要对原有应用进行改造,应用要兼容接入 qiankun 和独立使用;
  • 有额外的学习成本;
  • 相同资源重复加载;
  • 启动应用时,要先启动基座应用;

webpack5:module federation

webpack5,提供了一个新的特性 - module federation 。基于这个特性,我们可以在一个 javascript 应用中动态加载并运行另一个 javascript 应用的代码,并实现应用之间的依赖共享。

通过 module federation,我们可以在一个应用里面动态渲染另一个应用的页面,这样也就实现了多个子应用的聚合。

优点:

  • 不需要对原有应用进行改造,只需改造打包脚本;
  • 切换应用时,浏览器不用重载页面,提供和单页应用一样的用户体验;
  • 多个子应用可并存;
  • 相同资源不需要重复加载;
  • 开发技术栈无关;
  • 应用启动后,无需加载与自己无关的资源;
  • 免登友好;

缺点:

  • 构建工具只能使用 webpack5;
  • 有额外的学习成本;
  • 对老项目不友好,需要对 webpack 进行改造;

Web Component

基于 Web ComponentShadow Dom 能力,我们也可以实现微前端,将多个子应用聚合起来。

Shadow Dom 的用法如下:

javascript 复制代码
const shadow = document.querySelector('#hostElement').attachShadow({mode: 'open'});
// url 为应用的地址,基于 fetch,我们可以获取到应用的 html 模板,添加到指定节点下
fetch(url).then(res => {
    shadow.innerHTML = res
});

优点:

  • 实现简单;
  • css 和 js 天然隔离,互不干扰;
  • 完全技术栈无关;
  • 多个子应用可以并存;
  • 不需要对现有应用进行改造;

缺点:

  • 主要是浏览器兼容性问题;
  • 开发成本较高;

微前端问题总结

微前端框架中,js隔离样式隔离元素隔离是必须解决的三个问题,下面我们就来分别说说这三个问题是什么?怎么解决?

js隔离

问题:

情况1:都对全局变量赋值

应用A,写 window.r = 1;然后有应用B,又写 window.r = 2,这就乱套了

情况2:都设置事件

应用A,window.addEventListener('click',()=>console.log('A'));

应用B,window.addEventListener('click',()=>console.log('B'));,这就乱套了

解决:

方法一用 Proxy 代理

es2015 Reflect属于一个静态类或者设置属性等用法

javascript 复制代码
const rawWindow = window
const proxyWindow = new Proxy({},{
    get: (target, key): unknown => {
        // 原 target 上有就返回,否则返回 rawWindow 属性
        return Reflect.has(target, key) ? Reflect.get(target, key) : Reflect.get(rawWindow, key)
    },
    set: (target, key, value): boolean => {
        if(!Object.prototype.hasOwnProperty.call(target, key) && Object.prototype.hasOwnProperty.call(rawWindow, key)){
            const descriptor = Object.getOwnPropertyDescriptor(rawWindow, key)
            const { configurable, enumerable, writable, set } = descriptor!
            // set value because it can be set
            rawDefineProperty(target, key, {
              value,
              configurable,
              enumerable,
              writable: writable ?? !!set,
            })
        } else {
            Reflect.set(target, key, value)
        }
    }
})

window.r = 1window.addEventListener('click',()=>console.log('A')) 包括到自执行函数里面

A和B互不干扰

javascript 复制代码
;(function(window){
    window.r = 1
    window.addEventListener('click',()=>console.log('A'))
})(proxyWindowA)
javascript 复制代码
;(function(window){
    window.r = 2
    window.addEventListener('click',()=>console.log('B'))
})(proxyWindowB)

法二 用快照

快照隔离有个前提条件是,当前还有一个应用显示,不能出现多个应用并存显示在界面上,应用A,B切换时,比如当前应用是A,现在要切入到应用B

  • 暂存起来应用A的全局变量和事件
  • 恢复全局变量和事件到应用A之前
  • 检查之前是否保持有应用B的全局变量和事件,如果有,则载入

样式隔离

问题

同理,各个应用之前可能相互设置标签样式,会相互影响,或者影响全局样式,比如应用A给body设置样式,应用B也给body设置样式

方法一 样式增加不同前缀

每个应用通过前缀独立区分开,京东micro-app默认是采用的这个策略,唯一注意的一个小点是,基座样式会影响子应用的样式,所以需要注意基座中不要写太多样式


方法二 ShadawDom

大多数Html标签都有 attachShadow() 方法给指定的元素挂载一个 Shadow DOM。参数是openclosed

ShadawDom 样式绝对隔离,不用加前缀,如下图

javascript 复制代码
//open 是外界可以访问到Element.shadowRoot再访问到内部元素,closed就是完全不能访问内部元素
var shadowroot = element.attachShadow('open|closed')  


元素隔离

元素隔离是 基座应用和子应用都有一个元素<div id='root'></div>,此时子应用通过document.querySelector('#root'),因为js隔离已经做了代理,此时document.querySelector只是子应用本身了

相关推荐
Pedantic2 小时前
SwiftUI 手势层级(Gesture Hierarchy)详解
前端
飘尘2 小时前
前端转型全栈(Java后端)的快速上手指引
前端·后端·全栈
一颗烂土豆3 小时前
Meshopt 压缩深度解析,为什么它比 Draco 更快
前端·javascript·webgl
浏览器工程师4 小时前
AI Agent 接浏览器任务,先别让它一路点到底
前端·后端
雨季mo浅忆4 小时前
VSCode自动格式化三要素
前端
爱勇宝4 小时前
深扒 Anthropic 1680 位工程师简历:应届生几乎没机会,AI 公司最缺的不是博士
前端·后端·程序员
kyriewen5 小时前
同事每天催我 Code Review,我写了个脚本让 AI 替我 review PR——现在他反过来催 AI 了
前端·javascript·ai编程
user20585561518137 小时前
Windows 项目安装时报 `node-sass` 错误,如何快速处理
前端
LiaCode7 小时前
Redis 在生产项目的使用
前端·后端