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类,直接使用静态…...
Python爬虫实战:研究MechanicalSoup库相关技术
一、MechanicalSoup 库概述 1.1 库简介 MechanicalSoup 是一个 Python 库,专为自动化交互网站而设计。它结合了 requests 的 HTTP 请求能力和 BeautifulSoup 的 HTML 解析能力,提供了直观的 API,让我们可以像人类用户一样浏览网页、填写表单和提交请求。 1.2 主要功能特点…...

接口测试中缓存处理策略
在接口测试中,缓存处理策略是一个关键环节,直接影响测试结果的准确性和可靠性。合理的缓存处理策略能够确保测试环境的一致性,避免因缓存数据导致的测试偏差。以下是接口测试中常见的缓存处理策略及其详细说明: 一、缓存处理的核…...

铭豹扩展坞 USB转网口 突然无法识别解决方法
当 USB 转网口扩展坞在一台笔记本上无法识别,但在其他电脑上正常工作时,问题通常出在笔记本自身或其与扩展坞的兼容性上。以下是系统化的定位思路和排查步骤,帮助你快速找到故障原因: 背景: 一个M-pard(铭豹)扩展坞的网卡突然无法识别了,扩展出来的三个USB接口正常。…...

初学 pytest 记录
安装 pip install pytest用例可以是函数也可以是类中的方法 def test_func():print()class TestAdd: # def __init__(self): 在 pytest 中不可以使用__init__方法 # self.cc 12345 pytest.mark.api def test_str(self):res add(1, 2)assert res 12def test_int(self):r…...

html css js网页制作成品——HTML+CSS榴莲商城网页设计(4页)附源码
目录 一、👨🎓网站题目 二、✍️网站描述 三、📚网站介绍 四、🌐网站效果 五、🪓 代码实现 🧱HTML 六、🥇 如何让学习不再盲目 七、🎁更多干货 一、👨…...
CRMEB 中 PHP 短信扩展开发:涵盖一号通、阿里云、腾讯云、创蓝
目前已有一号通短信、阿里云短信、腾讯云短信扩展 扩展入口文件 文件目录 crmeb\services\sms\Sms.php 默认驱动类型为:一号通 namespace crmeb\services\sms;use crmeb\basic\BaseManager; use crmeb\services\AccessTokenServeService; use crmeb\services\sms\…...

云原生安全实战:API网关Envoy的鉴权与限流详解
🔥「炎码工坊」技术弹药已装填! 点击关注 → 解锁工业级干货【工具实测|项目避坑|源码燃烧指南】 一、基础概念 1. API网关 作为微服务架构的统一入口,负责路由转发、安全控制、流量管理等核心功能。 2. Envoy 由Lyft开源的高性能云原生…...
深度解析云存储:概念、架构与应用实践
在数据爆炸式增长的时代,传统本地存储因容量限制、管理复杂等问题,已难以满足企业和个人的需求。云存储凭借灵活扩展、便捷访问等特性,成为数据存储领域的主流解决方案。从个人照片备份到企业核心数据管理,云存储正重塑数据存储与…...
IP选择注意事项
IP选择注意事项 MTP、FTP、EFUSE、EMEMORY选择时,需要考虑以下参数,然后确定后选择IP。 容量工作电压范围温度范围擦除、烧写速度/耗时读取所有bit的时间待机功耗擦写、烧写功耗面积所需要的mask layer...

React、Git、计网、发展趋势等内容——前端面试宝典(字节、小红书和美团)
React React Hook实现架构、.Hook不能在循环嵌套语句中使用 , 为什么,Fiber架构,面试向面试官介绍,详细解释 用户: React Hook实现架构、.Hook不能在循环嵌套语句中使用 , 为什么,Fiber架构,面试向面试官介绍&#x…...