Rspack 外部外部外部外部外部外部外部外部外部外部外部外部外部外部外部外部
前言:远程模块不是真实模块
模块联合会:
导入“ 远程/ 按钮 ”
- > 键
- > 键
这和 externals(第 22 篇)的「假模块代替真实模块」一脉相承。区别是 externals 从全局变量/require 取,remote 从远程容器的 get/init 接口取。
校对:Soup
箱/rspack plugin mf/src/contain/contain.rs CH 集装箱Plugin 负责「我暴露什么给别人」。它的使Hook 创建一个集装箱依赖性并 hook 创建一个 hook 创建一个 ccontain enter:
# [plugin hook(集装箱管道的编译器)]
async fn make( &self,汇编:& mut 汇编) - > 结果 < () > {
让 dep = 容器依赖性:new ()
自定义。 options.name.clone ()
自定义。 options.exposes.clone ()
自定义。 options.share scope.clone ()
自我。 选项。 增强
);
编译。添加条目(
框:新建(dep)
条目选项{
名称:Some( self. options. name. clone ())
运行时间:self. options.runtime.clone ()
文件名:自。 options. filename.clone ()
库:Some(self.options.library.clone ())
. 默认:默认()
},
等待吗?
Ok(())
}
集装箱进口进口进口进口进口进口进口进口进口进口进口进口进口进口进口进口进口进口进口进口进口进口进口进口进口进口进口进口进口进口进口进口进口进口进口进口进口进口进口进口进口进口进口进口进口进口进口进口进口进口进口进口进口进口进口进口进口进口进口进口进口进口进口进口进口进口进口进口进口进口进口进口进口进口进口进口进口进口进口进口进口进口进口进口进口进口进口进口进口进口进口进口进口进口进口进口进口进口进口
# [plugin hook(集装箱管道汇编)]
async fn 汇编( &self,汇编,parms) - > 结果 < () > {
编译。 set dependency factory (Dependency Type:: Contener Entry, Arc: new (ContenerEntry ModuleFactory));;;;;;;;;;。
编译。 set dependency factory (Dependency Type:: Contener Exposed, params. 正常 module factory.clone ());编译。 set dependency factory (DependencyType:: Contener Exposed, params.
Ok(())
}
集装箱入口单元:
箱/rspack plugin mf/src/容器/容器 enter module.rs CH Control EntryMudule 建 X 暴露 Q+块:
async fn 构建( mut self: box <self>, building context,: Option QQ汇编>) - > 结果 <BuildResult > {
让哑巴块=vec!
让 mut 依赖性:Vec < BoxDependency > = vec! [;];
//集装箱逻辑
用于( 名称、 选项)。 曝光 {
让 mut 块 = Async 依赖性Block: new ()
自定义
无
一些( 名称)
选项.import.iter ().map (\\\ request\\\\\\ n{
框:new( 容器的依赖性:new( name.clone (), request.clone ()) 作为框<dyn依赖性>
\ ) 收集 ()
无
);
块。 set group options( Group options:: ChunkGroup ())
ChunkGroup 选项:默认 (). name optional (options.name.clone ())
));
block.push(框:新(块));
}
依赖性。 push( 框:new( staticExports dependency: new( 框:))
StaticExportsSpec::Array(vec!["get".into(), "init".into()]),
虚假
)));
// ...
}
* * * 揭露 {Async DependenitysBlock( {} ),}:获取 – 输入。
代码( G): − 获取/init
容器 EntryModule CH代码生成 Q模块映射:
let module map = ExposeModuleMap: new(编译、自我、运行时间 template);
让模块 map str = 模块 map.render(运行时间 template);
将工厂曝光
程序 ExposeModuleMap {
pub fn 新(汇编、容器输入模块、运行时间温度) - > 自我 {
让mut模块 map=vec![;];
用于在容器中的块 ID 模块。 get blocks() {
// ...
让 str = 如果模块 iter.clone (). any (,模块,, ) 模块.is none () {
运行时间 template. throw missing module error block(...)
否则
让块保证=运行时间 template.block promise(Some(block id),编译,"");
让模块=模块 iter.map(,,请求,依赖 id){{
运行时间 template.module raw(汇编、依赖 id、请求、虚假)
}))收集:<Vec< >>().join(,");
让模块 raw = 运行时间 template. return 函数 ()
运行时间 template. return 函数( & modules, "");
格式!(“ return {block promise}}.when({module raw}) )
};
模块 map.push((name.to string (, str)));
}
自定义( 模块映射)
}
}
* 显示 CH 值 CH 值 CH 返回块 Promise.when(模块 QQ () 模块) QQ 块 QQ
生成的 get/init 代码
代码 %% 1:
让源=格式!()
r#"
var 模块Map = {module map str};
var 获得 = 函数( 模块,获取Scope) QQ
{当前 remote get scope} = 获得范围;
获取Scope=(
{has own property}(模块地图,模块)
模块映射 [模块] ()
: promise.resolve ().when({get scope reject}) 存档副本。
);
{当前 remote get scope}}=未定义;
返回获取范围;
}}
var init = 函数(份额范围,initSope) QQ
如果 (!{share scope map}) 返回;
var 名称 = {share scope};
var 旧Scope = {share scope map} [名称];
如果( oldScope QQ 旧Scope! ==============================================================================================================================================================================================================================================
{share scope map}[名称]=shareScope;
返回 {初始化共享} (名称,initScope);
}}
{ define property getters}({导出} ), {}
获取:{导出 get}
输入:{导出 init}
}});"#,
// ...
);
获得 hight 模块Map QQ( QQ), init QQ 共享范围( QX). define property getters left/ init QQ getter.
校对:Soup
容器 reference plugin.rs CH 集装箱ReferencePlugin 将 Hook + 远程/xxxx, pdf 远程模块化。
远程模块:
条框/rspack plugin mf/src/容器/remote module.rs 的的的:
[可解
# [贬低(debug)]
酒吧结构远程模块{
// ...
请求:字符串
外部请求:Vec < String >
酒吧内请求:字符串
酒吧共享( S):字符串
酒吧远程键:字符串
// ...
}
建造:
async fn 构建(mut self: box <self>, build context: buildContext,: Option QQ汇编>) - > 结果 <BuildResult> {
让 mut 依赖性:Vec < BoxDependency > = Vec: new ();
如果属于自身。 external requests.len () == 1 {
让 dep = 远程外部依赖性:new(self.external requests[0].clone ());
// ...
依赖性.push(框:new(dep));
否则
让 dep = 倒背依赖性:new(self.external requests.clone ());
// ...
依赖性.push(框:new(dep));
}
// ...
}
* 远程外部依赖,(10)( 重复) 260。
代码( E):
远程模块 CH 代码生成 Q:
async fn 代码生成( &self,代码生成 context) - > 结果 < CodeGenerationResult > {
让mut codegen=codeGenerationResult::default();
let module graph = 代码生成 context.c 汇编.get module graph ();
let module = module_graph.get_module_by_dependency_id(&self.dependencies[0]);
让 id = module.和 (|m|chunkGraph::get module id(...));
codegen.add(SourceType:Remote, RawString 来源:from static (")). boxed ());.
codegen.data. 插入(代码GenerationDataShareInit {
项:vec! [ShareInitData {
share scope.clone () 常规
进入阶段:20
init: DataInitInfo:External ModuleId(id.cloned()) 互联网档案馆的存檔,存档日期2013-12-21.
}],
});
Ok( 代码)
}
(原始内容存档于2019-09-25). Remodel Module QQ JS, QQ CodeGenerationDataShareInit 分享范围,idQQ 远程 runtime module – 远程Loading.ejs QQQ).
来源类型:remote $ shareInit
远程模块 CH 源类型 QQ:
fn 源代码类型( &self, module graph: &ModuleGraph) - > & [來源Type] {
& [來源請求:讀取,源碼:分享]
}
远程 QQ, ShareInit lair 共享范围 QQ 源类型 QQ 代码生成 QQ
:假设共享模块
箱/rspack plugin mf/src/share/consume share module.rs 的 ConsummeredModule 表示「消费共享模块」(如 shared shared shared shared shared).
+++ 代码生成 ++ 装载器 ++:
让 mut 函数 = 字符串:from("loaders.load");
让mut args = vec!
json 字符串( &self. options.share scope)
json 字符串( &self. options.share key)
];
如果让 Some( version) = &self. options. required version {
如果自定义。 options. print version {
函数“ Q”;
}
如果自定义。 options. singleton {
函数“ singleton ”;
}
让版本 = json stringify( & version.to string ());
args.push(格式!("loaders.parse Range({versive})"));;;;;;。
函数“ VersionCheck”;
\ 如果自选。 singleton {
函数“ singleton ”;
}
tright version-singleton, required version {}装入器:装入器:装入器:装入器:装入器:装入器:装入器:装入器:
假设共享插件:创建 QQ
clus shared plugin.rs ya 将 Hook {{}\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
# [plugin hook(常规模块 Factory Factorize for ConsumeShared Plugin)] (英语).
async fn因子化( &self,数据:&mut ModuleFactoryCreateData) - > 结果 < option<BoxModule}{
// 跳过后退 / 相关依赖
如果匹配!(dep. dependency type (),依赖类型:: ConsumeSharedFallback {...}{
返回 Ok( 无);
}
让请求=数据。 request;
允许消费 = self.get complete cumes();
如果让 Some( 匹配) = 消耗。 unresolved. get( request) {
let module = self.create consume shared module( & data.context, request, matched.clone (,...)).await;存档副本。
返回 OK( Some( module. boxed () ) );
}
// 前缀匹配
用于 & concumes 中的(前缀,选项)。 前缀 {
如果请求。 starts with(前缀) {
// ...
}
}
OK( 无)
}
外在因素化::,:
解析 matched configs: laid 共享 {}
共享QQ请求QQ:
用于配置 {% (request, config)
如果 REQEST.is match( 请求) {
// ./xxx 相对路径 -> resolver 解析
否则,如果ABSOLUTE REQUTE.is match(请求){
// /xxx 绝对路径
} 如果请求。 ends with ('/') {
// 前缀匹配
前缀。 插入( request. to owned (), config.clone ());
否则
// 裸包名,运行时解析
尚未解决。 插入( request. to owned (), config.clone () );
}
}
未解决的 QQ (% react), QQ, QQ 系数化 QQ;解 QQ,走创建模块 QQ)
(原始内容存档于2019-03-01). Rust 18: XXX: QQ
模块联合会:
容器输入模块 - > sourceType::JavaScript + Expose
远程模块 - > sourceType:: remote + ShareInit
组合共享模块 - > 源类型:假设共享
QQ 模块 ^, 12. 1 来源类型 QQ 代码生成,12. 1 set dependent factory → MF →
这一篇应该带走什么
- 集装箱管道,集装箱单元 ++++++++;
- 集装箱参考
- 远程QQ, 212运行时模块+远程Loading.ejs;
- 装填器:
- MF + 依赖工厂
写在最后
MF rust ##[plugin] [plugin hook] ##[可处理] ***, rspack hook $ rspack macros, $ 钩子钩子
本文由 AI图鉴自动抓取并翻译,版权归原作者所有;如需查看原文请点下方链接。
本篇是 Rspack 源码解析系列第三十三篇。第二十二篇 externals 末尾说 remote 和 externals 是同一思路的两种形态。本文进入 rspack_plugin_mf,看 Module Federation 的 RemoteModule(消费远程)和 ContainerEntryModule(暴露本地)如何实现,把「加载远程代码」变成一种模块类型。
前言:远程模块不是真实模块
Module Federation 的核心是:
宿主应用 import 'remote/Button'
-> 运行时从远程容器加载 Button
-> 而不是本地打包 Button
这和 externals(第 22 篇)的「假模块代替真实模块」一脉相承。区别是 externals 从全局变量/require 取,remote 从远程容器的 get/init 接口取。
源码入口:ContainerPlugin 暴露本地
crates/rspack_plugin_mf/src/container/container_plugin.rs 的 ContainerPlugin 负责「我暴露什么给别人」。它的 make Hook 创建一个 ContainerEntryDependency 并 add_entry:
#[plugin_hook(CompilerMake for ContainerPlugin)]
async fn make(&self, compilation: &mut Compilation) -> Result<()> {
let dep = ContainerEntryDependency::new(
self.options.name.clone(),
self.options.exposes.clone(),
self.options.share_scope.clone(),
self.options.enhanced,
);
compilation.add_entry(
Box::new(dep),
EntryOptions {
name: Some(self.options.name.clone()),
runtime: self.options.runtime.clone(),
filename: self.options.filename.clone(),
library: Some(self.options.library.clone()),
..Default::default()
},
).await?;
Ok(())
}
ContainerEntryDependency 不是普通 entry,它会走 ContainerEntryModuleFactory:
#[plugin_hook(CompilerCompilation for ContainerPlugin)]
async fn compilation(&self, compilation, params) -> Result<()> {
compilation.set_dependency_factory(DependencyType::ContainerEntry, Arc::new(ContainerEntryModuleFactory));
compilation.set_dependency_factory(DependencyType::ContainerExposed, params.normal_module_factory.clone());
Ok(())
}
ContainerEntryModule:暴露的容器
crates/rspack_plugin_mf/src/container/container_entry_module.rs 的 ContainerEntryModule 是「容器入口」。它的 build 为每个 expose 创建异步 block:
async fn build(mut self: Box<Self>, _build_context, _: Option<&Compilation>) -> Result<BuildResult> {
let mut blocks = vec![];
let mut dependencies: Vec<BoxDependency> = vec![];
// Container 逻辑
for (name, options) in &self.exposes {
let mut block = AsyncDependenciesBlock::new(
self.identifier,
None,
Some(name),
options.import.iter().map(|request| {
Box::new(ContainerExposedDependency::new(name.clone(), request.clone())) as Box<dyn Dependency>
}).collect(),
None,
);
block.set_group_options(GroupOptions::ChunkGroup(
ChunkGroupOptions::default().name_optional(options.name.clone()),
));
blocks.push(Box::new(block));
}
dependencies.push(Box::new(StaticExportsDependency::new(
StaticExportsSpec::Array(vec!["get".into(), "init".into()]),
false,
)));
// ...
}
每个 expose 对应一个 AsyncDependenciesBlock(异步块),因为远程加载是异步的。容器导出两个方法:get 和 init。
code_generation:生成 get/init
ContainerEntryModule 的 code_generation 生成容器的运行时代码。核心是 moduleMap:
let module_map = ExposeModuleMap::new(compilation, self, runtime_template);
let module_map_str = module_map.render(runtime_template);
ExposeModuleMap 把每个 expose 名映射到「加载该模块的 factory」:
impl ExposeModuleMap {
pub fn new(compilation, container_entry_module, runtime_template) -> Self {
let mut module_map = vec![];
for block_id in container_entry_module.get_blocks() {
// ...
let str = if modules_iter.clone().any(|(_, module, _, _)| module.is_none()) {
runtime_template.throw_missing_module_error_block(...)
} else {
let block_promise = runtime_template.block_promise(Some(block_id), compilation, "");
let modules = modules_iter.map(|(_, _, request, dependency_id)| {
runtime_template.module_raw(compilation, dependency_id, request, false)
}).collect::<Vec<_>>().join(", ");
let module_raw = runtime_template.returning_function(
&runtime_template.returning_function(&modules, ""), "");
format!("return {block_promise}.then({module_raw});")
};
module_map.push((name.to_string(), str));
}
Self(module_map)
}
}
每个 expose 的 value 是 return blockPromise.then(module => () => module)——先异步加载 block,再返回一个返回模块的函数。
生成的 get/init 代码
code_generation 生成的代码(非 enhanced 模式):
let source = format!(
r#"
var moduleMap = {module_map_str};
var get = function(module, getScope) {{
{current_remote_get_scope} = getScope;
getScope = (
{has_own_property}(moduleMap, module)
? moduleMap[module]()
: Promise.resolve().then({get_scope_reject})
);
{current_remote_get_scope} = undefined;
return getScope;
}}
var init = function(shareScope, initScope) {{
if (!{share_scope_map}) return;
var name = {share_scope};
var oldScope = {share_scope_map}[name];
if(oldScope && oldScope !== shareScope) throw new Error("Container initialization failed as it has already been initialized with a different share scope");
{share_scope_map}[name] = shareScope;
return {initialize_sharing}(name, initScope);
}}
{define_property_getters}({exports}, {{
get: {export_get},
init: {export_init}
}});"#,
// ...
);
get 从 moduleMap 取模块(异步),init 初始化 share scope(共享依赖)。define_property_getters 让 get/init 成为只读 getter。
源码入口:ContainerReferencePlugin 消费远程
container_reference_plugin.rs 的 ContainerReferencePlugin 负责「我要消费哪个远程」。它在 factorize Hook 里拦截 remote/xxx 请求,返回 RemoteModule。
RemoteModule:远程模块
crates/rspack_plugin_mf/src/container/remote_module.rs 的 RemoteModule 是「远程模块」的表示:
#[cacheable]
#[derive(Debug)]
pub struct RemoteModule {
// ...
request: String,
external_requests: Vec<String>,
pub internal_request: String,
pub share_scope: String,
pub remote_key: String,
// ...
}
它的 build 把远程模块转成「从远程容器加载」的依赖:
async fn build(mut self: Box<Self>, build_context: BuildContext, _: Option<&Compilation>) -> Result<BuildResult> {
let mut dependencies: Vec<BoxDependency> = Vec::new();
if self.external_requests.len() == 1 {
let dep = RemoteToExternalDependency::new(self.external_requests[0].clone());
// ...
dependencies.push(Box::new(dep));
} else {
let dep = FallbackDependency::new(self.external_requests.clone());
// ...
dependencies.push(Box::new(dep));
}
// ...
}
单个远程请求转 RemoteToExternalDependency,多个(带 fallback)转 FallbackDependency。
code_generation:从远程容器取
RemoteModule 的 code_generation 很轻:
async fn code_generation(&self, code_generation_context) -> Result<CodeGenerationResult> {
let mut codegen = CodeGenerationResult::default();
let module_graph = code_generation_context.compilation.get_module_graph();
let module = module_graph.get_module_by_dependency_id(&self.dependencies[0]);
let id = module.and_then(|m| ChunkGraph::get_module_id(...));
codegen.add(SourceType::Remote, RawStringSource::from_static("").boxed());
codegen.data.insert(CodeGenerationDataShareInit {
items: vec![ShareInitData {
share_scope: self.share_scope.clone(),
init_stage: 20,
init: DataInitInfo::ExternalModuleId(id.cloned()),
}],
});
Ok(codegen)
}
RemoteModule 自己不生成 JS,而是通过 CodeGenerationDataShareInit 声明「需要初始化 share scope,并引用某个外部模块的 id」。真正的加载逻辑在 remote_runtime_module 和 remotesLoading.ejs 里。
source_types:Remote 与 ShareInit
RemoteModule 的 source types 是特殊的:
fn source_types(&self, _module_graph: &ModuleGraph) -> &[SourceType] {
&[SourceType::Remote, SourceType::ShareInit]
}
Remote 类型对应远程加载代码,ShareInit 对应 share scope 初始化。这又是 source type 决定 code generation 的体现。
源码入口:ConsumeSharedModule 共享依赖
crates/rspack_plugin_mf/src/sharing/consume_shared_module.rs 的 ConsumeSharedModule 表示「消费共享模块」(如 shared 的 react)。
它的 code_generation 决定调用哪个 loader 函数:
let mut function = String::from("loaders.load");
let mut args = vec![
json_stringify(&self.options.share_scope),
json_stringify(&self.options.share_key),
];
if let Some(version) = &self.options.required_version {
if self.options.strict_version {
function += "Strict";
}
if self.options.singleton {
function += "Singleton";
}
let version = json_stringify(&version.to_string());
args.push(format!("loaders.parseRange({version})"));
function += "VersionCheck";
} else if self.options.singleton {
function += "Singleton";
}
根据 strict_version、singleton、required_version 组合出不同的 loader 函数名:load、loadStrict、loadSingleton、loadVersionCheck、loadStrictSingletonVersionCheck 等。
ConsumeSharedPlugin:factorize 拦截
consume_shared_plugin.rs 的 factorize Hook 拦截 shared 请求:
#[plugin_hook(NormalModuleFactoryFactorize for ConsumeSharedPlugin)]
async fn factorize(&self, data: &mut ModuleFactoryCreateData) -> Result<Option<BoxModule>> {
// 跳过 fallback 相关依赖
if matches!(dep.dependency_type(), DependencyType::ConsumeSharedFallback | ...) {
return Ok(None);
}
let request = &data.request;
let consumes = self.get_matched_consumes();
if let Some(matched) = consumes.unresolved.get(request) {
let module = self.create_consume_shared_module(&data.context, request, matched.clone(), ...).await;
return Ok(Some(module.boxed()));
}
// 前缀匹配
for (prefix, options) in &consumes.prefixed {
if request.starts_with(prefix) {
// ...
}
}
Ok(None)
}
这和 externals 的 factorize 拦截是同一个模式:在模块创建前拦截,返回替代模块。
resolve_matched_configs:匹配 shared 配置
shared 配置的 request 分三类:
for (request, config) in configs {
if RELATIVE_REQUEST.is_match(request) {
// ./xxx 相对路径 -> resolver 解析
} else if ABSOLUTE_REQUEST.is_match(request) {
// /xxx 绝对路径
} else if request.ends_with('/') {
// 前缀匹配
prefixed.insert(request.to_owned(), config.clone());
} else {
// 裸包名,运行时解析
unresolved.insert(request.to_owned(), config.clone());
}
}
unresolved 的是包名(如 react),它们在运行时才真正解析版本,所以走 factorize 拦截;resolved 的是已解析路径,走 create_module 拦截。
Rust 角度:模块类型是扩展点
Module Federation 展示了「模块类型」作为扩展点的威力:
ContainerEntryModule -> SourceType::JavaScript + Expose
RemoteModule -> SourceType::Remote + ShareInit
ConsumeSharedModule -> SourceType::ConsumeShared
每一种「特殊加载方式」都对应一种 Module 实现,通过 source_types 声明自己需要什么 code generation,通过 set_dependency_factory 注册依赖到模块的转换。整个模块图体系不需要为 MF 做特殊分支。
这一篇应该带走什么
- ContainerPlugin 暴露本地,ContainerEntryModule 生成 get/init 接口;
- ContainerReferencePlugin 消费远程,RemoteModule 表示远程模块;
- remote 的加载逻辑不在模块里,而在 runtime module + remotesLoading.ejs;
- ConsumeSharedModule 根据 strict/singleton/version 组合 loader 函数名;
- MF 通过「新模块类型 + dependency factory」扩展,不侵入模块图核心。
写在最后
MF 是「模块类型扩展」的极致。接下来回到 Rust 侧的「宏」——整个插件体系大量使用 #[plugin]、#[plugin_hook]、#[cacheable] 宏,但它们的实现细节一直没展开。下一篇进入 rspack_hook 与 rspack_macros,看 Hook 系统如何用宏生成异步插桩代码。
Auto-fetched and translated by AI Atlas. All rights belong to the original author.