module federation模块联邦与微前端
module federation是什么
webpack5新增了module federation,module federation的作用,将每个构建(build)作为容器(这是一个概念),构建后的资源可以正常部署,同时还具备在运行时对外暴露其中的模块,这就意味着多个构建可以独立完成,独立部署,所需的依赖可以在运行时加载。对于多个构建公共的依赖,可以通过shared来指定,这些依赖也可以在运行时加载,并且只加载一次。
事实上,公共依赖模块也可以通过npm包的形式来实现共享,这种方式的共享不得不依赖于app shell这种容器预先加载共享包。module federation只在第一次加载模块A时加载共享包,加载模块B时共享包已被缓存。
模块与容器的概念有点重叠,容器实际上是一些模块的集合,与构建相关,是我们传统意义上的bundle,只是在加入module federation能力后,容器可以对外导出成员,这一点跟模块比较接近而已。
这种能力刚好与微前端架构所需的前端集成能力一致。前端集成技术,就是应用A将应用B、C等应用的某些页面、组件、片段等等,集成到自己页面里。
加入module federation的构建
传统的构建过程,模块之间如果存在依赖关系,这些模块会在一个构建过程中打包成一个bundle。
而module federation让这种存在依赖关系的模块,各自有各自的构建过程,并各自实现自己的bundle和部署,最终在运行时异步获取依赖模块。这种方式提高了模块的自主性,但可能因为异步的原因,降低了首屏渲染性能、运行时的用户交互体验等等。
有对外导出的容器示例
这里展示一个module federation的示例使用。products项目展示一些产品名称,其工程目录如下
- products/- public/- index.html // 模板- src/- bootstrap.js // 导入faker生成假数据,导出一个mount方法,将html内容挂载到某个DOM节点上- index.js // 入口文件,导入bootstrap.js模块- package.json - webpack.config.js // 配置module federation的配置文件
bootstrap.js的内容如下
// 声明为shared的模块,会被拆分为异步模块,所以需要异步加载
const faker = await import("faker");function mount(el) {let products = "";for (let i = 1; i <= 5; i++) {products += `<div>${faker.commerce.productName()}</div>`;}el.innerHTML = products;
}if (process.env.NODE_ENV === "development") {// 依赖该模块的容器,必须提供一个id属性为'dev-products'的DOM元素const el = document.querySelector("#dev-products");if (el) mount(el);
}export { mount };
构建产物
webpack的module federation相关配置示例
devServer: {port: 8081,},// 省略其他传统的配置plugins: [new ModuleFederationPlugin({name: "products", // 当前容器的名称,其他容器导入该容器时的标识filename: "remoteEntry.js", // 当前容器的入口文件,与output.filename不是一回事exposes: {// 导出成员对应的模块会被拆离为异步模块"./Index": "./src/bootstrap.js", // 指定对外暴露的模块列表,标识符: 模块地址},shared: { // 公共的共享模块,其他容器也会使用faker这个模块,共享模块在构建产物会被作为单独的包存在,由容器异步加载faker: {singleton: true,},},}),
],
在加入module federation的webpack配置下,构建产物发生了一定的变化,除了传统的bundle外,还会有如下产物
- module federation插件产生的容器入口文件,如上面配置的
remoteEntry.js; - 对外暴露的模块,如上面配置的"./Index": "./src/bootstrap.js"的产物
src_bootstrap_js.js; - 共享模块,如上面配置的faker,产物是
vendors-node_modules_faker_index_js.js;
而通过模板生成的index.html中,不仅有传统的bundle产物main.js,也会有remoteEntry.js。对于传统的部署而言,remoteEntry.js是没必要的。main.js与remoteEntry.js有很多重复的webpack胶水代码。
容器对外的入口文件remoteEntry.js
查看由module federation生成的容器入口文件,可以看到与传统的bundle不一样的地方在于,容器入口文件包含一个变量声明var products, 与配置name: "products"一致。
remoteEntry.js会包含一个moduleMap,包含模块标识符’./Index’与src_bootstrap_js.js;
remoteEntry.js包含本地容器的初始化init方法和获取导出成员get方法,被加载后导出了products变量,携带init和get方法。
/***/ // "webpack/container/entry/products":
/*!***********************!*\!*** container entry ***!\***********************/
/*** remoteEntry.js中 "webpack/container/entry/products" 对应的函数内部的eval代码整理如下*/var moduleMap = {"./Index": () => {return __webpack_require__.e("src_bootstrap_js").then(() => () =>__webpack_require__(/*! ./src/bootstrap.js */ "./src/bootstrap.js"));},
};
// container的getter,将container中的module加载,并返回加载后的module
var get = (module, getScope) => {__webpack_require__.R = getScope;getScope = __webpack_require__.o(moduleMap, module)? moduleMap[module](): Promise.resolve().then(() => {throw new Error('Module "' + module + '" does not exist in container.');});__webpack_require__.R = undefined;return getScope;
};
// 初始化容器,通过shareScope来提供对外共享的module,如果声明了shared,每个build都会有shared的module,即便有重复
// 如果共享module已经被使用了,那么该容器的共享module会被忽略,但会作为fallback
var init = (shareScope, initScope) => {if (!__webpack_require__.S) return;var name = "default";var oldScope = __webpack_require__.S[name];if (oldScope && oldScope !== shareScope)throw new Error("Container initialization failed as it has already been initialized with a different share scope");__webpack_require__.S[name] = shareScope;return __webpack_require__.I(name, initScope);
};// This exports getters to disallow modifications
__webpack_require__.d(exports, {get: () => get,init: () => init,
});//# sourceURL=webpack://products/container_entry?;
有导入其他容器的容器示例
通常导入别的容器的容器会作为一个app shell,加载其他容器的模块,这正是微前端的客户端集成方案。这里的container项目,加载products的bootstrap模块,使用mount方法挂载HTML内容。目录示例如下
- container/- public/- index.html // 模板- src/- bootstrap.js // 导入products的bootstrap模块,使用mount方法,将html内容挂载到某个DOM节点上- index.js // 入口文件,导入bootstrap.js模块- package.json - webpack.config.js // 配置module federation的配置文件
其中,bootstrap.js的内容如下
// 这里不使用const { mount: mountProducts } = await import("products/Index")的语法
// 是因为模块本身不导入导出任何成员,webpack不认为是ESM,只有ESM才能使用顶层的await语法
// 在这种情况下,使用import ESM语法,那么index.js必须使用import('./bootstrap.js')的动态导入语法,因为远程模块的导入必须是异步的
import { mount as mountProducts } from "products/Index"
// 模板index.html中已经有<div id="prod-products"></div>
mountProducts(document.querySelector("#prod-products"));
相关的module federation配置如下
devServer: {port: 8080},plugins: [new ModuleFederationPlugin({name: "container", // 容器名称remotes: { // 指定远程模块依赖// 模块别名: '模块别名@模块入口地址',模块别名是要在代码里导入该模块成员时使用products: "products@http://localhost:8081/remoteEntry.js", // 模块名称: 模块地址}}),
container容器应用可以拿到products容器应用的bootstrap模块,使用mount方法来挂载HTML内容了。可以尝试一下,启动8080端口,可以看到页面有一些由products容器应用的bootstrap模块挂载的HTML内容。
构建产物
由于container容器没有对外暴露的模块,因此没有remoteEntry.js这样的入口文件,也没有共享模块,所以container容器的构建产物与传统的构建一致,只有bundle。bundle文件中,有包含products容器的引用和模块加载代码
/***/ "webpack/container/reference/products":
/*!****************************************************************!*\!*** external "products@http://localhost:8081/remoteEntry.js" ***!\****************************************************************/
/***/ ((module, __unused_webpack_exports, __webpack_require__) => {var __webpack_error__ = new Error();
module.exports = new Promise((resolve, reject) => {if(typeof products !== "undefined") return resolve();__webpack_require__.l("http://localhost:8081/remoteEntry.js", (event) => {if(typeof products !== "undefined") return resolve();var errorType = event && (event.type === 'load' ? 'missing' : event.type);var realSrc = event && event.target && event.target.src;__webpack_error__.message = 'Loading script failed.\n(' + errorType + ': ' + realSrc + ')';__webpack_error__.name = 'ScriptExternalLoadError';__webpack_error__.type = errorType;__webpack_error__.request = realSrc;reject(__webpack_error__);}, "products");
}).then(() => (products));/***/ })
模块成员标识符规则
模块对外导出的成员应该如何标识?例如,products对外导出的./Index能不能写成’Index’或者’Hello’?
在导入成员时,使用import xxx from 'products/Index',webpack会转换为’./Index’作为模块标识符,因此products对外导出成员时的标识符不能随意写,要按照规则./[name]的形式来书写;
webpack内部使用了一系列映射关系来确定导出成员,如下面代码所示
var chunkMapping = {/******/ // src/bootstrap.js中导入了products/Index和products/World"src_bootstrap_js": [/******/ "webpack/container/remote/products/Index", /******/"webpack/container/remote/products/World"/******/]/******/};/******/var idToExternalAndNameMapping = {/******/"webpack/container/remote/products/Index": [/******/"default", /******/"./Index", /******/ 导入成员的标识符"webpack/container/reference/products"/******/],/******/"webpack/container/remote/products/World": [/******/"default", /******/"./World", /******/ 导入成员的标识符"webpack/container/reference/products"/******/]/******/};
异步模块有异步依赖时使用异步导入还是同步导入
module federation导致的模块拆分,如果是异步模块A依赖了异步模块B,在A中可以同步导入模块Bimport xxx from 'module-B',因为webpack会使用Promise.all来加载模块A和被A依赖的模块B。所以只要使用动态导入`import(‘module-A’)即可,不需要在A中使用动态导入B了。当然,动态导入B模块也是可以的。
总结
module federation是一种支持当前应用在运行时加载其他运行时应用内部模块的技术,在webpack配置时,当前应用需要用remote指定要加载的应用名称, 其他应用使用exposes指定对外暴露的内部模块,使用shared指定公共的共享模块。应用可以各自独立构建,独立部署,只在运行时产生耦合(加载)。各个应用在开发构建时都是独立的,降低了开发构建时的耦合性。
相关文章:
module federation模块联邦与微前端
module federation是什么 webpack5新增了module federation,module federation的作用,将每个构建(build)作为容器(这是一个概念),构建后的资源可以正常部署,同时还具备在运行时对外暴露其中的模块,这就意味着多个构建…...
日常开发记录分享——C#控件ToolTip实现分栏显示内容
文章目录 需求来源实现思路实施请看VCR等等别走,有优化 需求来源 需要在鼠标浮动到指定位置后提示出详细的信息,一开始使用的tooltip实现,但是里面的内容效果并不理想,需要有条理性,于是就想到能不能将展示的东西分列…...
Kettle下载安装
环境说明 虚拟机:Win7;MySql8.0 主机:Win11;JDK1.8;Kettle 9.4(Pentaho Data Integration 9.4)(下载方式见文末) 安装说明 【1】解压后运行Spoon.bat 【2】将jar包 复…...
最新版Golang pprof使用(引入、抓取、分析,图文结合)
最新版Golang pprof使用 🔥具体实践: Go调试神器pprof使用教程Golang线上内存爆掉问题排查(pprof) Github地址:https://github.com/ziyifast/ziyifast-code_instruction/tree/main/go-demo/go-pprof 引入pprof:import _ “net/http/pprof” …...
vue3学习记录1:emit的写法
emit是用于child组件向parent组件通信的工具,因为vue3的script可以设置为setup,写法同vue2有较大区别。 一、script setup - 直接写 <script lang"ts" setup>const emit defineEmits([close]);function handleClose() {emit(close);}…...
Visual Studio Code + vue快速安装配置Node.js+Vue+webpack+vscode
第一部分:Node.js 第一步:下载Node.js 方法1:链接 下载 | Node.js 中文网 (nodejs.cn) 方法2:百度网盘 链接:https://pan.baidu.com/s/1zIqu8H9rb_I1i-1OWD7swQ?pwdaurk 提取码:aurk --来自百度网盘…...
【Dart 教程系列第 49 篇】什么是策略设计模式?如何在 Dart 中使用策略设计模式
这是【Dart 教程系列第 49 篇】,如果觉得有用的话,欢迎关注专栏。 博文当前所用 Flutter SDK:3.22.1、Dart SDK:3.4.1 文章目录 一:什么是策略设计模式?二:为什么要使用策略设计模式࿱…...
BGP路由反射器
原理概述 缺省情况下,路由器从它的一个 IBGP对等体那里接收到的路由条目不会被该路由器再传递给其他IBGP对等体,这个原则称为BGP水平分割原则,该原则的根本作用是防止 AS内部的BGP路由环路。因此,在AS内部,一般需要每台…...
DolphinDB Web 端权限管理:可视化操作指南
在现代数据库管理中,高效和直观的权限管理对于用户的数据安全是至关重要的。过去 DolphinDB 用户需要依赖系统脚本来管理用户和权限,这对于缺乏技术背景的管理员来说既复杂又容易出错。 为了提升用户体验和操作效率,DolphinDB 目前在 Web 上…...
学习Vue2收藏这一篇就够了(如何创建Vue实例)
什么是Vue? Vue是什么:是一个用于构建用户界面的渐进式框架 什么是构建用户界面:基于数据动态渲染页面 什么是渐进式:循序渐进的学习 什么是框架:一整套完整的项目解决方案 创建Vue实例 核心步骤(4步…...
Mysql数据库第四次作业
mysql> create table student(sno int primary key auto_increment,sname varchar(30) not null unique,Ssex varchar(2) check (Ssex男 or Ssex女) not null,Sage int not null,Sdept varchar(10) default计算机 not null); mysql> create table Course(Con int primar…...
使用Docker搭建MySql的主从同步+ShardingSphere搭建Mysql的读写分离
参考课程 尚硅谷ShardingSphere5实战教程(快速入门掌握核心)_哔哩哔哩_bilibili 主服务器 创建容器 docker run -d \ -p 3306:3306 \ -v /kira/mysql/master/conf:/etc/mysql/conf.d \ -v /kira/mysql/master/data:/var/lib/mysql \ -e MYSQL_ROOT…...
数据结构:数据类型与抽象数据类型
数据类型与抽象数据类型 数据类型基本数据类型构造数据类型指针类型枚举类型 抽象数据类型(ADT)抽象数据类型的组成部分常见的抽象数据类型示例 数据类型与抽象数据类型的区别实现抽象数据类型的具体方式用数组实现栈用链表实现栈 总结 数据类型 数据类…...
西方逻辑史简介
西方逻辑史研究,对形式逻辑实现现代化,对加强西方哲学史研究,对开展科学方法论的研究都有重要意义。西方逻辑史一般被划分成古代、中世纪、现代三个历史时期。本文拟对这三个时期中的七个重要逻辑学家和逻辑学派:亚里士多德、斯多…...
【论文10】复现代码tips
一、准备工作 1.创建一个虚拟环境 conda create --name drgcnn38 python=3.8.18 2.激活虚拟环境 conda activate drgcnn38 注意事项 在Pycharm中终端(terminal)显示PS而不是虚拟环境base 问题如下所示 解决方法:shell路径改成cmd.exe 重启终端显示虚拟环境 3.安装torch …...
分布式缓存获取以及设置
1. 通用代码 public SysUser getCache(String sysUserId) {String cacheKey "litgery:warehouse:" sysUserId;// 尝试从缓存中获取数据CacheData cacheData redisUtils.get(cacheKey);if (null ! cacheData) {if (Boolean.TRUE.equals(cacheData.getExist())) {re…...
SMO算法,platt论文的原始算法及优化算法
platt论文:[PDF] Sequential Minimal Optimization : A Fast Algorithm for Training Support Vector Machines | Semantic Scholar 算法优化:[PDF] Improvements to Platts SMO Algorithm for SVM Classifier Design | Semantic Scholar 包含个人plat…...
2.3 openCv -- 对矩阵执行掩码操作
在矩阵上进行掩模操作相当简单。其基本思想是根据一个掩模矩阵(也称为核)来重新计算图像中每个像素的值。这个掩模矩阵包含的值决定了邻近像素(以及当前像素本身)对新的像素值产生多少影响。从数学角度来看,我们使用指定的值来做一个加权平均。 具体而言,掩模操作通常涉…...
【Django】 js实现动态赋值、显示show隐藏hide效果
文章目录 需要达到的前端效果预览:实现步骤复制bootstrp代码(buttons)复制bootstrp代码(Alert警告框)写js测试效果 需要达到的前端效果预览: {% load static %} <!DOCTYPE html> <html lang"…...
qt--做一个拷贝文件器
一、项目要求 使用线程完善文件拷贝器的操作 主窗口不能假死主窗口进度条必须能动改写文件大小的单位(自适应) 1TB1024GB 1GB1024MB 1MB1024KB 1KB1024字节 二、所需技术 1.QFileDialog 文件对话框 QFileDialog也继承了QDialog类,直接使用静态…...
TDengine 快速体验(Docker 镜像方式)
简介 TDengine 可以通过安装包、Docker 镜像 及云服务快速体验 TDengine 的功能,本节首先介绍如何通过 Docker 快速体验 TDengine,然后介绍如何在 Docker 环境下体验 TDengine 的写入和查询功能。如果你不熟悉 Docker,请使用 安装包的方式快…...
深入浅出:JavaScript 中的 `window.crypto.getRandomValues()` 方法
深入浅出:JavaScript 中的 window.crypto.getRandomValues() 方法 在现代 Web 开发中,随机数的生成看似简单,却隐藏着许多玄机。无论是生成密码、加密密钥,还是创建安全令牌,随机数的质量直接关系到系统的安全性。Jav…...
Qwen3-Embedding-0.6B深度解析:多语言语义检索的轻量级利器
第一章 引言:语义表示的新时代挑战与Qwen3的破局之路 1.1 文本嵌入的核心价值与技术演进 在人工智能领域,文本嵌入技术如同连接自然语言与机器理解的“神经突触”——它将人类语言转化为计算机可计算的语义向量,支撑着搜索引擎、推荐系统、…...
Springcloud:Eureka 高可用集群搭建实战(服务注册与发现的底层原理与避坑指南)
引言:为什么 Eureka 依然是存量系统的核心? 尽管 Nacos 等新注册中心崛起,但金融、电力等保守行业仍有大量系统运行在 Eureka 上。理解其高可用设计与自我保护机制,是保障分布式系统稳定的必修课。本文将手把手带你搭建生产级 Eur…...
数据库分批入库
今天在工作中,遇到一个问题,就是分批查询的时候,由于批次过大导致出现了一些问题,一下是问题描述和解决方案: 示例: // 假设已有数据列表 dataList 和 PreparedStatement pstmt int batchSize 1000; // …...
html-<abbr> 缩写或首字母缩略词
定义与作用 <abbr> 标签用于表示缩写或首字母缩略词,它可以帮助用户更好地理解缩写的含义,尤其是对于那些不熟悉该缩写的用户。 title 属性的内容提供了缩写的详细说明。当用户将鼠标悬停在缩写上时,会显示一个提示框。 示例&#x…...
如何在网页里填写 PDF 表格?
有时候,你可能希望用户能在你的网站上填写 PDF 表单。然而,这件事并不简单,因为 PDF 并不是一种原生的网页格式。虽然浏览器可以显示 PDF 文件,但原生并不支持编辑或填写它们。更糟的是,如果你想收集表单数据ÿ…...
pycharm 设置环境出错
pycharm 设置环境出错 pycharm 新建项目,设置虚拟环境,出错 pycharm 出错 Cannot open Local Failed to start [powershell.exe, -NoExit, -ExecutionPolicy, Bypass, -File, C:\Program Files\JetBrains\PyCharm 2024.1.3\plugins\terminal\shell-int…...
ZYNQ学习记录FPGA(一)ZYNQ简介
一、知识准备 1.一些术语,缩写和概念: 1)ZYNQ全称:ZYNQ7000 All Pgrammable SoC 2)SoC:system on chips(片上系统),对比集成电路的SoB(system on board) 3)ARM:处理器…...
EEG-fNIRS联合成像在跨频率耦合研究中的创新应用
摘要 神经影像技术对医学科学产生了深远的影响,推动了许多神经系统疾病研究的进展并改善了其诊断方法。在此背景下,基于神经血管耦合现象的多模态神经影像方法,通过融合各自优势来提供有关大脑皮层神经活动的互补信息。在这里,本研…...
