微前端介绍

微前端

微前端介绍

微前端定义

微前端(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只是子应用本身了

相关推荐
随心Coding1 小时前
【零基础入门Go语言】struct 和 interface:Go语言是如何实现继承的?
前端·golang
金州饿霸2 小时前
YARN 架构组件及原理
linux·运维·前端
还这么多错误?!3 小时前
webpack打包要义
前端·webpack
小九九的爸爸3 小时前
浅谈ViewBox那些事(一)
前端·svg
ฅQSω[*邱╭3 小时前
写个自己的vue-cli
前端·javascript·vue.js·学习
阿芯爱编程3 小时前
typescript语法讲解
前端·javascript
Daniel_1873 小时前
Promise-课堂笔记
前端·javascript·笔记
一点一木3 小时前
TensorFlow.js 和 Brain.js 全面对比:哪款 JavaScript AI 库更适合你?
前端·javascript·人工智能
疯狂的沙粒3 小时前
如何更轻松的对React refs 的理解?都有哪些应用场景?
前端·react.js·前端框架