微前端--single-spa

news2024/12/28 17:53:17

微前端

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
使用微前端的挑战: 子应用切换,应用相互隔离,互补干扰,子应用之前的通信,多个子应用并存,用户状态的存储,免登。

常用技术方案

路由分发式微前端

通过http服务的反向代理

http {
    server {
        listen 80;
        server_name xxx.xxx.com;
        location /api/ {
            proxy_pass http://localhost:3001/api    
        }
        location /web/admin {
            proxy_pass http://localhost:3002/api
        }
        location / {
            proxy_pass /;
        }
    }
}

实现简单,不需要对现有应用进行改造,和技术栈无关。
切换应用的时候,浏览器都需要重新加载页面。

iframe

html的标签
实现简单,css和js隔离,互不干扰。全局上下文完全隔离,内存变量不共享,子应用之间的通信,数据同步过程比较复杂,对seo不友好。切换应用的时候,浏览器都需要重新加载页面。

single-spa

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

// 基座
import Vue from 'vue'
import App from './App.vue'
import VueRouter from 'vue-router';
const  { registerApplication, start } =  require('single-spa');

Vue.use(VueRouter)

Vue.config.productionTip = false

// 接入 single-spa 的标志
window.__SINGLE_SPA__ = true

const router = new VueRouter({
  mode: 'history',
  routes: []
});

// 远程加载子应用
function createScript(url) {
  return new Promise((resolve, reject) => {
    const script = document.createElement('script')
    script.src = url
    script.onload = resolve
    script.onerror = reject
    const firstScript = document.getElementsByTagName('script')[0]
    firstScript.parentNode.insertBefore(script, firstScript)
  })
}
// 加载子应用
function loadApp(url, globalVar, entrypoints) {
  return async () => {
    for(let i = 0; i < entrypoints.length; i++) {
      await createScript(url + entrypoints[i])
    }
    return window[globalVar]
  }
}
// 子应用路由注册表
const apps = [
  {
    // 子应用名称
    name: 'app1',
    // 子应用加载函数
    app: loadApp('http://localhost:8081', 'app1', [ "/js/chunk-vendors.js", "/js/app.js" ]),
    // 当路由满足条件时(返回true),激活(挂载)子应用
    activeWhen: location => location.pathname.startsWith('/app1'),
    // 传递给子应用的对象
    customProps: {}
  },
  {
    name: 'app2',
    app: loadApp('http://localhost:8082', 'app2', [ "/js/chunk-vendors.js", "/js/app.js" ]),
    activeWhen: location => location.pathname.startsWith('/app2'),
    customProps: {}
  },
  {
    // 子应用名称
    name: 'app3',
    // 子应用加载函数
    app: loadApp('http://localhost:3000', 'app3', ["/main.js"]),
    // 当路由满足条件时(返回true),激活(挂载)子应用
    activeWhen: location => location.pathname.startsWith('/app3'),
    // 传递给子应用的对象
    customProps: {}
  }
]

// 注册子应用
for (let i = apps.length - 1; i >= 0; i--) {
  registerApplication(apps[i])
}

new Vue({
  router,
  render: h => h(App),
  mounted() {
    // 启动
    start()
  },
}).$mount('#app')
  • name: 子应用的唯一表示
  • activeWhen: 子应用激活的条件,当url发生变化的升级后,会遍历执行注册的子应用的activeWhen方法,当activeWhen返回的是true,对应的子应用就会被激活
  • app: 用户获取子应用提供给基座应用的生命周期,bootstrap mount unmount等。基座应用切换子应用时,也是同样的操作,即先执行上一个子应用的 unmount 操作,然后再执行下一个子应用的 mount 操作。因此就需要子应用提供 mount、unmount 等生命周期方法,供基座应用调用。和单页应用的懒加载一样,基座应用在激活子应用时,如果子应用是首次激活,就会执行 app 方法,动态去加载子应用的入口 js 文件,然后执行,得到子应用的生命周期方法。
  • customProps: 子应用激活的时候,可以传递给子应用的自定义属性,是一个对象
// index.js

import Vue from 'vue'
import App from './App.vue'

Vue.config.productionTip = false

const appOptions = {
  render: (h) => h(App)
};

let vueInstance;

// 子应用没有接入 single-spa
if (!window.__SINGLE_SPA__) {
  new Vue(appOptions).$mount('#app')
}

// 提供 bootstrap 生命周期方法
export function bootstrap () {
  console.log('app1 bootstrap')
  return Promise.resolve().then(() => {

  });
}
// 提供 mount 生命周期方法
export function mount (props) {
  console.log('app1 mount', props)
  return Promise.resolve().then(() => {
    vueInstance = new Vue(appOptions)
    vueInstance.$mount('#microApp')
  })
}

// 提供 unmount 生命周期方法
export function unmount () {
  console.log('app1 unmount')
  return Promise.resolve().then(() => {
    if (!vueInstance.$el.id) {
      vueInstance.$el.id = 'microApp'
    }
    vueInstance.$destroy()
    vueInstance.$el.innerHTML = ''
  })
}

// 提供 update 生命周期方法
export function update () {
  console.log('app1 update');
}

通常通过webpack构建工具生成的js脚本,表现形式都是iiff,就是立即执行函数表达式。各子应用对应的脚本执行的时是相互隔离的,如果是这样,基座应用在激活子应用的时候,是无法获取到子应用的生命周期方法的,也无法挂载子应用。添加libaray,libarayTarget配置项,将子应用入口文件的返回值就是生命周期方法暴露给window,这样基座应用就可以从window中获取子应用的生命周期的方法。

// 项目的构建脚本
module.exports = {
    configureWebpack: {
        ...
        publicPath: 'http://localhost:8081'
        output: {
            library: 'app1',   
            libraryTarget: 'var'
        }    
    }
}
  • 单页应用的路由切换功能是基于window.history(window.location.hash)实现。在单页面应用中,会给window对象注册popstate(hashchange)事件,在callback中,添加页面切换的逻辑,当通过执行 pushState(replaceState) 方法、修改 hash 值、使用浏览器前进后退(go、back、forward)功能改变 url 时,会触发 popstate(hashchange) 事件,然后切换页面。
  • 基座应用加载执行 single-spa 时,也会给 window 对象注册 popstate (hashchange) 事件, popstate(hashchange) 的 calback 中,就是激活子应用的逻辑。当基座应用通过执行pushState(replaceState)、修改 hash、使用浏览器前进后退(go、back、forward)功能的方式修改 url 时,popstate(hashchange) 就会触发,相应的子应用的激活逻辑就会执行。
// 通过原生构造函数 - popStateEvent 创建一个popstate事件对象
function createPopStateEvent(state, originalMethodName) {
    var evt;
    try {
        evt = new PopStateEvent("popstate", {
            state: state    
        })
    } catch(err) {
        evt = document.createEvent('popstateevent')
        evt.initPopStateEvent("popstate", false, false, state)
    }
    evt.singleSpa = true
    evt.singleSpaTrigger = originalMethodName
    return evt
}
// 重写 updateState、replaceState 方法,通过 window.dispatchEvent 方法,手动触发 popstate 事件
function patchedUpdateState(updateState, methodName) {
    return function () {
      var urlBefore = window.location.href;
      var result = updateState.apply(this, arguments);
      var urlAfter = window.location.href;

      if (!urlRerouteOnly || urlBefore !== urlAfter) {
        window.dispatchEvent(createPopStateEvent(window.history.state, methodName));
      }
      return result;
    };
}
// 重写 pushState 方法
window.history.pushState = patchedUpdateState(window.history.pushState, "pushState");
// 重写 replaceState 方法
window.history.replaceState = patchedUpdateState(window.history.replaceState, "replaceState");
...
const router = new VueRouter({
  mode: 'history',
  base: '/app1',
  routes: [{
    path: '/foo',
    name: 'foo',
    component: {
      ...
    }
  }, {
    path: '/bar',
    name: 'bar',
    component: {
      ...
    }
  }]
})
...

application 模式下,single-spa 的工作流程,application 模式下,我们需要先通过registerApplication 注册子应用,然后在基座应用挂载完成以后执行 start 方法, 这样基座应用就可
以根据 url 的变化来进行子应用切换,激活对应的子应用。
在这里插入图片描述

parcel模式下,single-spa的工作流程。mountRootParcel 方法会返回一个parcel实例对象,内部包含update、unmount 方法。当我们需要更新组件时,直接调用parcel对象的update方法,就可以触发组件的update生命周期方法;当我们需要卸载组件时,直接调用parcel对象的unmount方法。在执行mountRootParcel 方法时,传入的第二个参数,会作为组件 mount 生命周期方法的入参;在执行 parcel.update 方法时,传入的参数,会作为组件 update 生命周期方法的入参。

子应用是否被挂载

  • NOT_LOADED 未加载/待加载
  • LOAD_SOURCE_CODE 加载源代码
  • NOT_SOURCE_CODE 未启动/待启动
  • BOOTSTRAPPING 子应用启动中
  • NOT_MOUNTRED 为挂载/待挂载
  • MOUNTING 子应用挂载中
  • UNMOUNTING 需要卸载
  • UNMOUNTED 已经卸载
  • LOAD_ERROR 子应用加载失败
传参

父组件和parcel组件的通信
mount 阶段,父组件在执行 mountRootParcel 时,可以将要传递给 parcel 组件的值作为第二个参数,这个参数会作为 parcel 组件 mount 方法执行时的入参,这样 parcel 组件就可以拿到父组件传递的值。update 阶段也一样,父组件执行 parcel.update 时,传入的参数会作为 parcel 组件 update 方法执行时的入参。
基座应用和子组件之间的通信
基座应用在定义路由注册表的时候,会给每个子应用定义一个customProps,这个customoProps会作为子应用mount方法的入参,在子应用中, customProps(或者 customProps 里面的某个值) 可以作为子应用的共享状态(使用 vuex、mobx、redux 等)。这样,当基座应用修改 customProps 时,子应用就可接受到通知,然后更新。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1945276.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

光盘防水嘛 ? DVD+R 刻录光盘泡水实验

首发日期 2024-07-20, 以下为原文内容: 同志们好, 欢迎来到 胖喵穷人实验室 ! 这里专注于 低成本, 低难度, 低风险 的 “三低” 小实验. 胖喵穷人实验室 (PM-PLab-E)正式名称: 紫腹巨蚊 (Toxorhynchites gravelyi) 系列穷人 (Poor people) 实验室风险警告: 低风险并不是零风险…

47.简易电压表的设计与验证(2)

&#xff08;1&#xff09;Verilog 代码&#xff1a; module adc_collect(input clk ,input reset_n ,input [7:0] adc_data ,output clk_adc );wire clk_adc_a ;…

PostgreSQL异常:An I/O error occurred while sending to the backend

在使用PostgreSQL数据库批量写入数据的时候&#xff0c;遇到了一个问题&#xff0c;异常内容如下&#xff1a; Cause: org.postgresql.util.PSQLException: An I/O error occurred while sending to the backend.报错内容 报错提示1 Caused by: org.postgresql.util.PSQLExc…

QTimer::singleShot导致定时器资源耗尽解决方法

参考文章&#xff1a;Qt QTimer::singleShot问题及用法 1. 问题描述 QTimer::singleShot定时器事件超时&#xff0c;如果此时类内对象已经被回收&#xff0c;定时器事件调用已经释放的类内资源时会引起崩溃。 这通常是因为定时器的回调函数&#xff08;槽函数&#xff09;在执…

网工能保住饭碗的小技能-划分VLAN

网络工程师&#xff0c;咱也是有一定的专业的知识和技能&#xff0c;今天&#xff0c;我们就来聊聊网络工程师的一项核心技能——VLAN划分&#xff0c;以及它如何成为网络工程师职业生涯中的“铁饭碗”。 VLAN&#xff0c;全称Virtual Local Area Network&#xff08;虚拟局域…

MVCC数据库并发控制技术

一、引言 MVCC&#xff08;Multi-Version Concurrency Control&#xff09;是一种广泛使用的数据库并发控制技术&#xff0c;它允许数据库读操作和写操作并发执行&#xff0c;而无需加锁整个表或行&#xff0c;从而大大提高了数据库的并发性能和吞吐量。MVCC主要被应用于支持事…

Java学习 - Spring Boot整合 Thymeleaf 实例

什么是 Thymeleaf Thymeleaf 是新一代的 Java 模板引擎&#xff0c;类似于 Velocity、FreeMarker 等传统引擎&#xff0c;其语言和 HTML 很接近&#xff0c;而且扩展性更高&#xff1b; Thymeleaf 的主要目的是将优雅的模板引入开发工作流程中&#xff0c;并将 HTML 在浏览器中…

IEEE官方列表会议 | 第三届能源与环境工程国际会议(CFEEE 2024)

会议简介 Brief Introduction 2024年第三届能源与环境工程国际会议(CFEEE 2024) 会议时间&#xff1a;2024年12月2日-4日 召开地点&#xff1a;澳大利亚凯恩斯 大会官网&#xff1a;CFEEE 2024-2024 International Conference on Frontiers of Energy and Environment Engineer…

信创改造叠加等保/密评合规,服务器定期改密难落实?

信创国产化改造是我国党政、金融、电信、电力等行业信息安全建设的主旋律。在信创改造背景下还叠加了等保、密评等合规性要求&#xff0c;因此党政、金融等行业单位在建设信创应用、终端、服务器时必须满足等保、密评要求。 在数据中心运维场景&#xff0c;受行业监管、密评、…

SQL 注入漏洞详解 - Union 注入

1)漏洞简介 SQL 注入简介 SQL 注入 即是指 Web 应用程序对用户输入数据的合法性没有判断或过滤不严,攻击者可以在 Web 应用程序中事先定义好的查询语句的结尾上添加额外的 SQL 语句,在管理员不知情的情况下实现非法操作,以此来实现欺骗数据库服务器执行非授权的任意查询,…

javaScrip的学习(一)

目录 引言 一、java和JavaScript的联系 二、js中的弹出框 1.alert弹出框 2.confirm带确认取消的按钮弹框 3.prompt带有提示信息且带有输入框的弹框 4.输出到网页中 ​三、js引入方式 1. 放在script标签中 2.放在外部js文件中 四、执行顺序 五、书写规范 1. 语句结…

专业技能(挖坑填坑)——Java核心基础知识,对集合、线程

熟悉Java核心基础知识&#xff0c;对集合、线程等都有了解&#xff0c;能运用模块化、面向对象的方式编程。 1.Java八种基本数据类型 Java的数据类型分为两大类&#xff1a;①基本数据类型 ②引用数据类型 2.面向对象三大特性 封装、继承、多态。 简要介绍一下/谈一下你的…

为什么 FPGA 的效率低于 ASIC?

FPGA是“可重构逻辑”器件。先制造的芯片&#xff0c;再次设计时“重新配置”。 ASIC 不需要“重新配置”。你先设计&#xff0c;把它交给代工厂&#xff0c;然后制造芯片。 现在让我们看看这些芯片的结构是什么样的&#xff0c;以及它们的不同之处。 ● 逻辑单元&#xff1a;F…

(leetcode学习)21. 合并两个有序链表

将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。 示例 1&#xff1a; 输入&#xff1a;l1 [1,2,4], l2 [1,3,4] 输出&#xff1a;[1,1,2,3,4,4]示例 2&#xff1a; 输入&#xff1a;l1 [], l2 [] 输出&#xff1a;[]示例…

Ethernet/IP转ModbusTCP协议转化网关(经典通讯案例)

怎么样把EtherNet/IP和ModbusTCP网络通讯连接起来呢?最近有很多朋友咨询这个问题&#xff0c;在这里统一为大家详细说明一下。其实有一个设备可以很轻松地解决这个问题&#xff0c;名为YC-EIP-TCP&#xff0c;下面是详细信息。 一&#xff0c;设备主要功能 1YC-EIP-TCP工业级…

数据结构(5.3_1)——二叉树的先中后序遍历

先序遍历——根左右——前缀表达式 中序遍历——左根右——中缀表达式 后序遍历——左右根——后缀表达式 二叉树的遍历(手算) 先序遍历代码 struct ElemType {int value; }; //二叉树的结点(链式存储) typedef struct BiTNode {ElemType data;//数据域struct BiTNode *lchil…

数据采集产品 搭建智能水房实现远程控制的案例分享

一、系统简介 随着科技的逐步发达&#xff0c;在各个领域都迫切的希望有一种控制系统能代替传统的操作方式&#xff0c;智能水房控制系统则是一种符合人们要求的系统&#xff0c;他可代替传统的人工机械操控&#xff0c;真正实现控制智能化。通过水房的各种数据采集可以实现24小…

学习记录——day15 数据结构 链表

链表的引入 顺序表的优缺点 1、优点:能够直接通过下标进行定位元素&#xff0c;访问效率高&#xff0c;对元素进行查找和修改比较快 2、不足:插入和删除元素需要移动大量的元素&#xff0c;效率较低 3、缺点:存储数据元素有上限&#xff0c;当达到MAX后&#xff0c;就不能再…

VScode tab不能正常使用

现象不能够在文本编辑器中按下tab键&#xff0c;如果按下了&#xff0c;就焦点会跑到终端或者是其他地方&#xff0c;猜测是因为装了某些插件导致的。 解决方案比较简单&#xff1a; 删除掉其他的按键 比如这样的&#xff0c;保存就可以了

mysql1055报错解决方法

目录 一、mysql版本 二、 问题描述 三、解决方法 1.方法一&#xff08;临时&#xff09; 2.方法二&#xff08;永久&#xff09; 一、mysql版本 mysql版本&#xff1a;5.7.23 二、 问题描述 在查询时使用group by语句&#xff0c;出现错误代码&#xff1a;1055&#xf…