Skip to content

Commit e957fcd

Browse files
authored
feat: add module federation plugin (#1764)
* feat: support module federation * feat: mf exposes to remote entries * chore: code styles * feat: mf container entry impl * fix: mf container entry * fix: mf runtime initOptions * feat: add containter references * feat: impl mf remote * feat: improve mf exposes * fix: mf exposes runtime factory * fix: mf plugin execution order * chore: update mf demo * feat: generate mf manifest in rust * fix: remote stats.json * refactor: code styles * chore: add some FIXME * refactor: mf plugin mods files * refactor: mf plugin mods files * chore: remove dead code * --wip-- [skip ci] * fix: remote stats.json * fix: typos * chore: simpify mf runtime codes fmt * refactor: mf containter plugin * feat: mf shared workaround * feat: mf shared workaround * fix: runtime template and remove some useless codes * fix: mf dev server * fix: mf shared config * feat: supports chunk group exclude * feat: mf patch code splitting * feat: mf shared manifest * feat: add config hash for mf shared module * chore: update mako typings * chore: code styles * chore: fix typo * chore: code styles * perf: improve performance * chore: code styles * chore: rename types * feat: add options to disable mf manifest * feat: entry config should be defined as BTreeMap * fix: mf shared consume and supports eager config * fix: mf shared eager * fix: not generate chunk for mf remote module * fix: typos * feat: add entry filename supports * chore: remove meaning less changes * fix: entry filename and mf config * release: @umijs/[email protected] * fix: ignore shared dep when it is been external * Revert "release: @umijs/[email protected]" This reverts commit d3105d9. * release: @umijs/[email protected] * fix: skip serialize mf manifest remoteEntry if none * fix: mf manifest remoteEntry address * Revert "release: @umijs/[email protected]" This reverts commit 6179982. * fix: typo * fix: mako mf manifest publicPath * fix: mf manifest panic * fix: mf typings * test: add e2e test for mf * fix: typo * chore: update README * fix: update mf bingding typings * fix: typings * fix: should not generate mf remotes runtime when remotes is empty * chore: remote wrong comment * feat: add chunk matcher for mf
1 parent 481e74a commit e957fcd

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

88 files changed

+2350
-87
lines changed

README.md

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<div align="center">
2-
2+
33
<img src="https://img.alicdn.com/imgextra/i2/O1CN01kdmA7X1FVqCPcRi3L_!!6000000000493-2-tps-584-584.png" alt="Mako logo" width="160" height="160" />
44

55
# Mako 🦈
@@ -10,7 +10,7 @@
1010
[![](https://badgen.net/npm/license/umi)](https://www.npmjs.com/package/@umijs/mako)
1111
[![codecov](https://codecov.io/gh/umijs/mako/graph/badge.svg?token=ptCnNedFGf)](https://codecov.io/gh/umijs/mako)
1212

13-
Mako `['mɑːkoʊ]` is an **extremely fast**, **production-grade** web bundler based on **Rust**.
13+
Mako `['mɑːkoʊ]` is an **extremely fast**, **production-grade** web bundler based on **Rust**.
1414

1515
✨ See more at [makojs.dev](https://makojs.dev).
1616
</div>
@@ -56,6 +56,7 @@ This project is inspired by:
5656
- [oxc-resolver](https://github.com/oxc-project/oxc-resolver) by [@Boshen](https://github.com/Boshen) which powered the resolver of Mako.
5757
- [Oxc](https://github.com/oxc-project/oxc/) by [@Boshen](https://github.com/Boshen) from which we learned a lot about how to develop efficiently in Rust.
5858
- [biome](https://github.com/biomejs/biome) by [@ematipico](https://github.com/ematipico) from which we learned a lot about how to develop efficiently in Rust.
59+
- [module-federation](https://github.com/module-federation/core) by [@ScriptedAlchemy](https://github.com/ScriptedAlchemy),which inspired a lot and powered the module federation feature of Mako.
5960

6061
## LICENSE
6162

crates/binding/src/js_plugin.rs

+1
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,7 @@ impl Plugin for JsPlugin {
218218
&self,
219219
content: &mut Content,
220220
path: &str,
221+
_is_entry: bool,
221222
context: &Arc<Context>,
222223
) -> Result<Option<Content>> {
223224
if let Some(hook) = &self.hooks.transform_include {

crates/binding/src/lib.rs

+25
Original file line numberDiff line numberDiff line change
@@ -157,9 +157,34 @@ pub struct BuildParams {
157157
rscClient?: false | {
158158
"logServerComponent": "error" | "ignore";
159159
};
160+
moduleFederation?: {
161+
name: string;
162+
filename?: string;
163+
exposes?: Record<string, string>;
164+
shared: Record<string,
165+
{
166+
singleton?: bool;
167+
strictVersion?: bool;
168+
requiredVersion?: string;
169+
eager?: bool;
170+
shareScope?: string;
171+
}
172+
>;
173+
remotes?: Record<string, string>;
174+
runtimePlugins?: string[];
175+
shareScope?: string;
176+
shareStrategy?: "version-first" | "loaded-first";
177+
implementation: string;
178+
};
160179
experimental?: {
161180
webpackSyntaxValidate?: string[];
181+
requireContext?: bool;
182+
ignoreNonLiteralRequire?: bool;
183+
magicComment?: bool;
184+
detectCircularDependence?: { ignore?: string[] };
162185
rustPlugins?: Array<[string, any]>;
186+
centralEnsure?: bool,
187+
importsChecker?: bool,
163188
};
164189
watch?: {
165190
ignoredPaths?: string[];

crates/mako/src/build.rs

+55-2
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,9 @@ use crate::ast::file::{Content, File, JsContent};
1616
use crate::ast::utils::get_module_system;
1717
use crate::compiler::{Compiler, Context};
1818
use crate::generate::chunk_pot::util::hash_hashmap;
19-
use crate::module::{Module, ModuleAst, ModuleId, ModuleInfo};
19+
use crate::module::{FedereationModuleType, Module, ModuleAst, ModuleId, ModuleInfo, ModuleSystem};
2020
use crate::plugin::NextBuildParam;
21-
use crate::resolve::ResolverResource;
21+
use crate::resolve::{ConsumeSharedInfo, RemoteInfo, ResolverResource};
2222
use crate::utils::thread_pool;
2323

2424
#[derive(Debug, Error)]
@@ -46,6 +46,15 @@ impl Compiler {
4646
rs.send(result).unwrap();
4747
});
4848
};
49+
50+
let build_consume_share_with_pool = |consume_share_info: ConsumeSharedInfo| {
51+
let rs = rs.clone();
52+
let context = self.context.clone();
53+
thread_pool::spawn(move || {
54+
let result = Self::build_consume_shared_module(consume_share_info, context.clone());
55+
rs.send(result).unwrap();
56+
});
57+
};
4958
let mut count = 0;
5059
for file in files {
5160
count += 1;
@@ -127,13 +136,22 @@ impl Compiler {
127136
ResolverResource::Ignored(_) => {
128137
Self::create_ignored_module(&path, self.context.clone())
129138
}
139+
ResolverResource::Remote(remote_into) => {
140+
Self::create_remote_module(remote_into)
141+
}
142+
ResolverResource::Shared(consume_share_info) => {
143+
count += 1;
144+
build_consume_share_with_pool(consume_share_info.clone());
145+
Self::create_empty_module(&dep_module_id)
146+
}
130147
};
131148

132149
// 拿到依赖之后需要直接添加 module 到 module_graph 里,不能等依赖 build 完再添加
133150
// 是因为由于是异步处理各个模块,后者会导致大量重复任务的 build_module 任务(3 倍左右)
134151
module_ids.insert(module.id.clone());
135152
module_graph.add_module(module);
136153
}
154+
137155
module_graph.add_dependency(&module_id, &dep_module_id, dep.dependency);
138156
}
139157
if count == 0 {
@@ -267,6 +285,12 @@ __mako_require__.loadScript('{}', (e) => e.type === 'load' ? resolve() : reject(
267285
result
268286
}
269287
}
288+
pub fn build_consume_shared_module(
289+
consume_share_info: ConsumeSharedInfo,
290+
_context: Arc<Context>,
291+
) -> Result<Module> {
292+
Ok(Self::create_consume_share_module(consume_share_info))
293+
}
270294

271295
pub fn build_module(
272296
file: &File,
@@ -279,6 +303,7 @@ __mako_require__.loadScript('{}', (e) => e.type === 'load' ? resolve() : reject(
279303
let content = context.plugin_driver.load_transform(
280304
&mut content,
281305
&file.path.to_string_lossy(),
306+
file.is_entry,
282307
&context,
283308
)?;
284309
file.set_content(content);
@@ -329,4 +354,32 @@ __mako_require__.loadScript('{}', (e) => e.type === 'load' ? resolve() : reject(
329354
let module = Module::new(module_id, is_entry, Some(info));
330355
Ok(module)
331356
}
357+
358+
pub(crate) fn create_remote_module(remote_info: RemoteInfo) -> Module {
359+
Module {
360+
is_entry: false,
361+
id: remote_info.module_id.as_str().into(),
362+
info: Some(ModuleInfo {
363+
resolved_resource: Some(ResolverResource::Remote(remote_info.clone())),
364+
federation: Some(FedereationModuleType::Remote),
365+
..Default::default()
366+
}),
367+
side_effects: true,
368+
}
369+
}
370+
371+
pub(crate) fn create_consume_share_module(consume_share_info: ConsumeSharedInfo) -> Module {
372+
Module {
373+
is_entry: false,
374+
id: consume_share_info.module_id.as_str().into(),
375+
info: Some(ModuleInfo {
376+
deps: consume_share_info.deps.clone(),
377+
resolved_resource: Some(ResolverResource::Shared(consume_share_info.clone())),
378+
federation: Some(FedereationModuleType::ConsumeShare),
379+
module_system: ModuleSystem::Custom,
380+
..Default::default()
381+
}),
382+
side_effects: true,
383+
}
384+
}
332385
}

crates/mako/src/build/analyze_deps.rs

+6-2
Original file line numberDiff line numberDiff line change
@@ -59,10 +59,14 @@ impl AnalyzeDeps {
5959
);
6060
match result {
6161
Ok(resolver_resource) => {
62-
resolved_deps.push(ResolvedDep {
62+
let resolved_dep = ResolvedDep {
6363
resolver_resource,
6464
dependency: dep,
65-
});
65+
};
66+
context
67+
.plugin_driver
68+
.after_resolve(&resolved_dep, &context)?;
69+
resolved_deps.push(resolved_dep);
6670
}
6771
Err(_err) => {
6872
missing_deps.insert(dep.source.clone(), dep);

crates/mako/src/compiler.rs

+7-2
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ use crate::generate::optimize_chunk::OptimizeChunksInfo;
2323
use crate::module_graph::ModuleGraph;
2424
use crate::plugin::{Plugin, PluginDriver, PluginGenerateEndParams};
2525
use crate::plugins;
26+
use crate::plugins::module_federation::ModuleFederationPlugin;
2627
use crate::resolve::{get_resolvers, Resolvers};
2728
use crate::share::helpers::SWC_HELPERS;
2829
use crate::stats::StatsInfo;
@@ -314,7 +315,6 @@ impl Compiler {
314315
Arc::new(plugins::bundless_compiler::BundlessCompilerPlugin {}),
315316
);
316317
}
317-
318318
if std::env::var("DEBUG_GRAPH").is_ok_and(|v| v == "true") {
319319
plugins.push(Arc::new(plugins::graphviz::Graphviz {}));
320320
}
@@ -327,6 +327,10 @@ impl Compiler {
327327
plugins.push(Arc::new(plugins::central_ensure::CentralChunkEnsure {}));
328328
}
329329

330+
if let Some(mf_cfg) = config.module_federation.as_ref() {
331+
plugins.push(Arc::new(ModuleFederationPlugin::new(mf_cfg.clone())));
332+
}
333+
330334
if let Some(minifish_config) = &config._minifish {
331335
let inject = if let Some(inject) = &minifish_config.inject {
332336
let mut map = HashMap::new();
@@ -423,7 +427,7 @@ impl Compiler {
423427
.entry
424428
.values()
425429
.map(|entry| {
426-
let mut entry = entry.to_string_lossy().to_string();
430+
let mut entry = entry.import.to_string_lossy().to_string();
427431
let is_browser = matches!(
428432
self.context.config.platform,
429433
crate::config::Platform::Browser
@@ -492,6 +496,7 @@ impl Compiler {
492496
self.context
493497
.plugin_driver
494498
.generate_end(&params, &self.context)?;
499+
495500
self.context.plugin_driver.write_bundle(&self.context)?;
496501
Ok(())
497502
}

crates/mako/src/config.rs

+22-10
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ mod code_splitting;
33
mod dev_server;
44
mod devtool;
55
mod duplicate_package_checker;
6+
pub mod entry;
67
mod experimental;
78
mod external;
89
mod generic_usize;
@@ -12,6 +13,7 @@ mod macros;
1213
mod manifest;
1314
mod minifish;
1415
mod mode;
16+
pub mod module_federation;
1517
mod module_id_strategy;
1618
mod optimization;
1719
mod output;
@@ -28,9 +30,9 @@ mod tree_shaking;
2830
mod umd;
2931
mod watch;
3032

31-
use std::collections::{BTreeMap, HashMap};
33+
use std::collections::HashMap;
3234
use std::fmt;
33-
use std::path::{Path, PathBuf};
35+
use std::path::Path;
3436

3537
pub use analyze::AnalyzeConfig;
3638
use anyhow::{anyhow, Result};
@@ -42,6 +44,7 @@ pub use devtool::{deserialize_devtool, DevtoolConfig};
4244
pub use duplicate_package_checker::{
4345
deserialize_check_duplicate_package, DuplicatePackageCheckerConfig,
4446
};
47+
use entry::{Entry, EntryItem};
4548
use experimental::ExperimentalConfig;
4649
pub use external::{
4750
ExternalAdvanced, ExternalAdvancedSubpath, ExternalAdvancedSubpathConverter,
@@ -54,6 +57,7 @@ pub use manifest::{deserialize_manifest, ManifestConfig};
5457
use miette::{miette, ByteOffset, Diagnostic, NamedSource, SourceOffset, SourceSpan};
5558
pub use minifish::{deserialize_minifish, MinifishConfig};
5659
pub use mode::Mode;
60+
use module_federation::ModuleFederationConfig;
5761
pub use module_id_strategy::ModuleIdStrategy;
5862
pub use optimization::{deserialize_optimization, OptimizationConfig};
5963
use output::get_default_chunk_loading_global;
@@ -135,7 +139,7 @@ pub enum CopyConfig {
135139
#[derive(Deserialize, Serialize, Debug)]
136140
#[serde(rename_all = "camelCase")]
137141
pub struct Config {
138-
pub entry: BTreeMap<String, PathBuf>,
142+
pub entry: Entry,
139143
pub output: OutputConfig,
140144
pub resolve: ResolveConfig,
141145
#[serde(deserialize_with = "deserialize_manifest", default)]
@@ -228,6 +232,7 @@ pub struct Config {
228232
default
229233
)]
230234
pub check_duplicate_package: Option<DuplicatePackageCheckerConfig>,
235+
pub module_federation: Option<ModuleFederationConfig>,
231236
// 是否开启 case sensitive 检查,只有mac平台才需要开启
232237
#[serde(rename = "caseSensitiveCheck")]
233238
pub case_sensitive_check: bool,
@@ -369,7 +374,13 @@ impl Config {
369374
for ext in JS_EXTENSIONS {
370375
let file_path = root.join(file_path).with_extension(ext);
371376
if file_path.exists() {
372-
config.entry.insert("index".to_string(), file_path);
377+
config.entry.insert(
378+
"index".to_string(),
379+
EntryItem {
380+
filename: None,
381+
import: file_path,
382+
},
383+
);
373384
break 'outer;
374385
}
375386
}
@@ -382,28 +393,29 @@ impl Config {
382393
// normalize entry
383394
config.entry.iter_mut().try_for_each(|(k, v)| {
384395
#[allow(clippy::needless_borrows_for_generic_args)]
385-
if let Ok(entry_path) = root.join(&v).canonicalize()
396+
if let Ok(entry_path) = root.join(&v.import).canonicalize()
386397
&& entry_path.is_file()
387398
{
388-
*v = entry_path;
399+
v.import = entry_path;
389400
} else {
390401
for ext in JS_EXTENSIONS {
391402
#[allow(clippy::needless_borrows_for_generic_args)]
392-
if let Ok(entry_path) = root.join(&v).with_extension(ext).canonicalize()
403+
if let Ok(entry_path) =
404+
root.join(&v.import).with_extension(ext).canonicalize()
393405
&& entry_path.is_file()
394406
{
395-
*v = entry_path;
407+
v.import = entry_path;
396408
return Ok(());
397409
}
398410

399411
if let Ok(entry_path) = root
400-
.join(&v)
412+
.join(&v.import)
401413
.join("index")
402414
.with_extension(ext)
403415
.canonicalize()
404416
&& entry_path.is_file()
405417
{
406-
*v = entry_path;
418+
v.import = entry_path;
407419
return Ok(());
408420
}
409421
}

crates/mako/src/config/entry.rs

+37
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
use std::collections::BTreeMap;
2+
use std::path::PathBuf;
3+
4+
use serde::{Deserialize, Serialize};
5+
use serde_json::Value;
6+
7+
#[derive(Serialize, Debug)]
8+
pub struct EntryItem {
9+
#[serde(default)]
10+
pub filename: Option<String>,
11+
pub import: PathBuf,
12+
}
13+
14+
pub type Entry = BTreeMap<String, EntryItem>;
15+
16+
impl<'de> Deserialize<'de> for EntryItem {
17+
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
18+
where
19+
D: serde::Deserializer<'de>,
20+
{
21+
let value: serde_json::Value = serde_json::Value::deserialize(deserializer)?;
22+
match &value {
23+
Value::String(s) => Ok(EntryItem {
24+
filename: None,
25+
import: s.into(),
26+
}),
27+
Value::Object(_) => {
28+
Ok(serde_json::from_value::<EntryItem>(value).map_err(serde::de::Error::custom)?)
29+
}
30+
_ => Err(serde::de::Error::custom(format!(
31+
"invalid `{}` value: {}",
32+
stringify!(deserialize_umd).replace("deserialize_", ""),
33+
value
34+
))),
35+
}
36+
}
37+
}

0 commit comments

Comments
 (0)