Module Federation原理剖析
【转自团队掘金原文: https://juejin.im/post/6895324456668495880】
为什么需要学习webpack5 module Federation原理呢?因为EMP微前端方案正是基于该革命性功能进行的,具有历史突破意义。通过本文,可以让你深入学习webpack5 module Federation原理,掌握EMP微前端方案的底层基石,更好使用和应用EMP微前端方案。
最近webpack5正式发布,其中推出了一个非常令人激动的新功能,即今日的主角——Module Federation(以下简称为mf),下面将通过三个方面(what,how,where)来跟大家一起探索这个功能的奥秘。
一. 是什么
Module Federation中文直译为“模块联邦”,而在webpack官方文档中,其实并未给出其真正含义,但给出了使用该功能的motivation, 即动机,原文如下
Multiple separate builds should form a single application. These separate builds should not have dependencies between each other, so they can be developed and deployed individually. This is often known as Micro-Frontends, but is not limited to that.
翻译成中文即
多个独立的构建可以形成一个应用程序。这些独立的构建不会相互依赖,因此可以单独开发和部署它们。
这通常被称为微前端,但并不仅限于此。
结合以上,不难看出,mf实际想要做的事,便是把多个无相互依赖、单独部署的应用合并为一个。通俗点讲,即mf提供了能在当前应用中远程加载其他服务器上应用的能力。对此,可以引出下面两个概念:
- host:引用了其他应用的应用
- remote:被其他应用所使用的应用
鉴于mf的能力,我们可以完全实现一个去中心化的应用部署群:每个应用是单独部署在各自的服务器,每个应用都可以引用其他应用,也能被其他应用所引用,即每个应用可以充当host的角色,亦可以作为remote出现,无中心应用的概念。
二. 如何使用
配置示例:
const HtmlWebpackPlugin = require("html-webpack-plugin");
const ModuleFederationPlugin = require("webpack/lib/container/ModuleFederationPlugin"); module.exports = {
// 其他webpack配置...
plugins: [
new ModuleFederationPlugin({
name: 'empBase',
library: { type: 'var', name: 'empBase' },
filename: 'emp.js',
remotes: {
app_two: "app_two_remote",
app_three: "app_three_remote"
},
exposes: {
'./Component1': 'src/components/Component1',
'./Component2': 'src/components/Component2',
},
shared: ["react", "react-dom","react-router-dom"]
})
]
}
通过以上配置,我们对mf有了一个初步的认识,即如果要使用mf,需要配置好几个重要的属性:
字段名 | 类型 | 含义 |
---|---|---|
name | string | 必传值,即输出的模块名,被远程引用时路径为${name}/${expose} |
library | object | 声明全局变量的方式,name为umd的name |
filename | string | 构建输出的文件名 |
remotes | object | 远程引用的应用名及其别名的映射,使用时以key值作为name |
exposes | object | 被远程引用时可暴露的资源路径及其别名 |
shared | object | 与其他应用之间可以共享的第三方依赖,使你的代码中不用重复加载同一份依赖 |
三. 构建解析原理
让我们看看构建后的代码:
var moduleMap = {
"./components/Comonpnent1": function() {
return Promise.all([__webpack_require__.e("webpack_sharing_consume_default_react_react"), __webpack_require__.e("src_components_Close_index_tsx")]).then(function() { return function() { return (__webpack_require__(16499)); }; });
},
};
var get = function(module, getScope) {
__webpack_require__.R = getScope;
getScope = (
__webpack_require__.o(moduleMap, module)
? moduleMap[module]()
: Promise.resolve().then(function() {
throw new Error('Module "' + module + '" does not exist in container.');
})
);
__webpack_require__.R = undefined;
return getScope;
};
var init = function(shareScope, initScope) {
if (!__webpack_require__.S) return;
var oldScope = __webpack_require__.S["default"];
var name = "default"
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);
}
可以看到,代码中包括三个部分:
- moduleMap:通过exposes生成的模块集合
- get: host通过该函数,可以拿到remote中的组件
- init:host通过该函数将依赖注入remote中
再看moduleMap
,返回对应组件前,先通过__webpack_require__.e
加载了其对应的依赖,让我们看看__webpack_require__.e
做了什么:
__webpack_require__.f = {};
// This file contains only the entry chunk.
// The chunk loading function for additional chunks
__webpack_require__.e = function(chunkId) {
// 获取__webpack_require__.f中的依赖
return Promise.all(Object.keys(__webpack_require__.f).reduce(function(promises, key) {
__webpack_require__.f[key](chunkId, promises);
return promises;
}, []));
};
__webpack_require__.f.consumes = function(chunkId, promises) {
// 检查当前需要加载的chunk是否是在配置项中被声明为shared共享资源,如果在__webpack_require__.O上能找到对应资源,则直接使用,不再去请求资源
if(__webpack_require__.o(chunkMapping, chunkId)) {
chunkMapping[chunkId].forEach(function(id) {
if(__webpack_require__.o(installedModules, id)) return promises.push(installedModules[id]);
var onFactory = function(factory) {
installedModules[id] = 0;
__webpack_modules__[id] = function(module) {
delete __webpack_module_cache__[id];
module.exports = factory();
}
};
try {
var promise = moduleToHandlerMapping[id]();
if(promise.then) {
promises.push(installedModules[id] = promise.then(onFactory).catch(onError));
} else onFactory(promise);
} catch(e) { onError(e); }
});
}
}
通读核心代码之后,可以得到如下总结:
- 首先,mf会让webpack以
filename
作为文件名生成文件 - 其次,文件中以var的形式暴露了一个名为
name
的全局变量,其中包含了exposes
以及shared
中配置的内容 - 最后,作为
host
时,先通过remote
的init
方法将自身shared
写入remote
中,再通过get
获取remote
中expose
的组件,而作为remote
时,判断host
中是否有可用的共享依赖,若有,则加载host
的这部分依赖,若无,则加载自身依赖。
四. 应用场景
英雄也怕无用武之地,让我们看看mf的应用场景有哪些:
- 微前端:通过shared以及exposes可以将多个应用引入同一应用中进行管理,由YY业务中台web前端组团队自主研发的EMP微前端方案就是基于mf的能力而实现的。
- 资源复用,减少编译体积:可以将多个应用都用到的通用组件单独部署,通过mf的功能在runtime时引入到其他项目中,这样组件代码就不会编译到项目中,同时亦能满足多个项目同时使用的需求,一举两得。
五. 最后
目前仅有EMP微前端方案是基于Module Federation实现的一套具有成熟脚手架和完整生态的微前端方案,并且在欢聚时代公司内部应用了80%的大型项目,通过本文我们也可以认知到EMP微前端方案是具有前瞻性的、可扩展性的、基石可靠的。针对EMP微前端方案的学习,有完整的wiki学习目录供大家参考:
基础知识解析
快速入门
进阶教程
Module Federation原理剖析的更多相关文章
- 写给 Android 应用工程师的 Binder 原理剖析
写给 Android 应用工程师的 Binder 原理剖析 一. 前言 这篇文章我酝酿了很久,参考了很多资料,读了很多源码,却依旧不敢下笔.生怕自己理解上还有偏差,对大家造成误解,贻笑大方.又怕自己理 ...
- ARouter原理剖析及手动实现
ARouter原理剖析及手动实现 前言 路由跳转在项目中用了一段时间了,最近对Android中的ARouter路由原理也是研究了一番,于是就给大家分享一下自己的心得体会,并教大家如何实现一款简易的路由 ...
- Spring 中常用注解原理剖析
前言 Spring 框架核心组件之一是 IOC,IOC 则管理 Bean 的创建和 Bean 之间的依赖注入,对于 Bean 的创建可以通过在 XML 里面使用 <bean/> 标签来配置 ...
- JVM Attach实现原理剖析
本文转载自JVM Attach实现原理剖析 前言 本文旨在从理论上分析JVM 在 Linux 环境下 Attach 操作的前因后果,以及 JVM 为此而设计并实现的解决方案,通过本文,我希望能够讲述清 ...
- ASP.NET Core 运行原理剖析2:Startup 和 Middleware(中间件)
ASP.NET Core 运行原理剖析2:Startup 和 Middleware(中间件) Startup Class 1.Startup Constructor(构造函数) 2.Configure ...
- ASP.NET Core 运行原理剖析1:初始化WebApp模版并运行
ASP.NET Core 运行原理剖析1:初始化WebApp模版并运行 核心框架 ASP.NET Core APP 创建与运行 总结 之前两篇文章简析.NET Core 以及与 .NET Framew ...
- 【Xamarin挖墙脚系列:Xamarin.IOS机制原理剖析】
原文:[Xamarin挖墙脚系列:Xamarin.IOS机制原理剖析] [注意:]团队里总是有人反映卸载Xamarin,清理不完全.之前写过如何完全卸载清理剩余的文件.今天写了Windows下的批命令 ...
- 【Xamarin 跨平台机制原理剖析】
原文:[Xamarin 跨平台机制原理剖析] [看了请推荐,推荐满100后,将发补丁地址] Xamarin项目从喊口号到现在,好几个年头了,在内地没有火起来,原因无非有三,1.授权费贵 2.贵 3.原 ...
- iPhone/Mac Objective-C内存管理教程和原理剖析
http://www.cocoachina.com/bbs/read.php?tid-15963.html 版权声明 此文版权归作者Vince Yuan (vince.yuan#gmail.com)所 ...
随机推荐
- Linux系统编程 —线程同步概念
同步概念 同步,指对在一个系统中所发生的事件之间进行协调,在时间上出现一致性与统一化的现象. 但是,对于不同行业,对于同步的理解略有不同.比如:设备同步,是指在两个设备之间规定一个共同的时间参考:数据 ...
- kubernetes:用kubeadm管理token(kubernetes 1.18.3)
一,token的用途: 1,token是node节点用来连接master节点的令牌字串, 它和ca证书的hash值是把一台node节点加入到kubernetes集群时要使用的凭证 2, 通过kubea ...
- linux(fedora30):安装/配置maven(maven3.6.1)
一,maven的用途 1,用途 Maven 是一个项目管理工具,主要用于项目构建,依赖管理,项目信息管理 maven可以用来: 帮用户下载jar包, 有依赖包时自动下载所需的依赖包 打包war包或ja ...
- node服务器基本搭建
const http = require('http') // 引入http模块 http.createServer(function(req,res){ // 创建一个http服务器 // 这里是一 ...
- JS的各种数据类型
Number js与其他编程不一样,不管是整数还是浮点,都称为数字类型(Number) 例:123,1.11111,-960 当该类型结果不存在时,即表示为 NaN (Not a Number) In ...
- mapstruct 快速使用
mapstruct 快速使用 mapstruct 主要的作用则是用来复制对象字段使用,功能非常的强大.在没有使用 mapstruct 之前可能都在使用 BeanUtils ,但是 BeanUtils ...
- open_spiel 随笔
------------恢复内容开始------------ ------------恢复内容开始------------ 遇到的一些疑惑且已经解决的 1. SPIEL_CHECK_GT()诸如此类的 ...
- Python opencv resize图片并保存原有的图像比例
参考链接:https://www.jianshu.com/p/3092835eab61 现有的图像是高瘦高瘦的,所以直接resize成矩形不合适.改变了整个结构. 所以采用的是先resize再padd ...
- 关于sql的随笔(标识列 即自动增长列)
一.标识列的定义以及特点SQL Server中的标识列又称标识符列,习惯上又叫自增列.该种列具有以下三种特点:1.列的数据类型为不带小数的数值类型2.在进行插入(Insert)操作时,该列的值是由系统 ...
- STM32入门系列-介绍STM32型号与功用
作为STM32初学者,一般会选择购置一块开发板,因为在开发板上有很多已经集成好的模块,如红外模块.按键模块.LED模块.DAC模块.ADC模块.can模块.485模块.以太网模块.WiFi模块.蜂鸣器 ...