diff --git a/README.md b/README.md
index 73dd97edc..0fcdc32db 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,5 @@
-
+
# Mako 🦈
@@ -10,7 +10,7 @@
[](https://www.npmjs.com/package/@umijs/mako)
[](https://codecov.io/gh/umijs/mako)
-Mako `['mɑːkoʊ]` is an **extremely fast**, **production-grade** web bundler based on **Rust**.
+Mako `['mɑːkoʊ]` is an **extremely fast**, **production-grade** web bundler based on **Rust**.
✨ See more at [makojs.dev](https://makojs.dev).
@@ -56,6 +56,7 @@ This project is inspired by:
- [oxc-resolver](https://github.com/oxc-project/oxc-resolver) by [@Boshen](https://github.com/Boshen) which powered the resolver of Mako.
- [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.
- [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.
+- [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.
## LICENSE
diff --git a/crates/binding/src/js_plugin.rs b/crates/binding/src/js_plugin.rs
index 221658554..9c3895a74 100644
--- a/crates/binding/src/js_plugin.rs
+++ b/crates/binding/src/js_plugin.rs
@@ -218,6 +218,7 @@ impl Plugin for JsPlugin {
&self,
content: &mut Content,
path: &str,
+ _is_entry: bool,
context: &Arc,
) -> Result> {
if let Some(hook) = &self.hooks.transform_include {
diff --git a/crates/binding/src/lib.rs b/crates/binding/src/lib.rs
index ec5f5ae6e..8eadaf29a 100644
--- a/crates/binding/src/lib.rs
+++ b/crates/binding/src/lib.rs
@@ -157,9 +157,34 @@ pub struct BuildParams {
rscClient?: false | {
"logServerComponent": "error" | "ignore";
};
+ moduleFederation?: {
+ name: string;
+ filename?: string;
+ exposes?: Record;
+ shared: Record;
+ remotes?: Record;
+ runtimePlugins?: string[];
+ shareScope?: string;
+ shareStrategy?: "version-first" | "loaded-first";
+ implementation: string;
+ };
experimental?: {
webpackSyntaxValidate?: string[];
+ requireContext?: bool;
+ ignoreNonLiteralRequire?: bool;
+ magicComment?: bool;
+ detectCircularDependence?: { ignore?: string[] };
rustPlugins?: Array<[string, any]>;
+ centralEnsure?: bool,
+ importsChecker?: bool,
};
watch?: {
ignoredPaths?: string[];
diff --git a/crates/mako/src/build.rs b/crates/mako/src/build.rs
index 7c66dd0e7..d061d9aab 100644
--- a/crates/mako/src/build.rs
+++ b/crates/mako/src/build.rs
@@ -16,9 +16,9 @@ use crate::ast::file::{Content, File, JsContent};
use crate::ast::utils::get_module_system;
use crate::compiler::{Compiler, Context};
use crate::generate::chunk_pot::util::hash_hashmap;
-use crate::module::{Module, ModuleAst, ModuleId, ModuleInfo};
+use crate::module::{FedereationModuleType, Module, ModuleAst, ModuleId, ModuleInfo, ModuleSystem};
use crate::plugin::NextBuildParam;
-use crate::resolve::ResolverResource;
+use crate::resolve::{ConsumeSharedInfo, RemoteInfo, ResolverResource};
use crate::utils::thread_pool;
#[derive(Debug, Error)]
@@ -46,6 +46,15 @@ impl Compiler {
rs.send(result).unwrap();
});
};
+
+ let build_consume_share_with_pool = |consume_share_info: ConsumeSharedInfo| {
+ let rs = rs.clone();
+ let context = self.context.clone();
+ thread_pool::spawn(move || {
+ let result = Self::build_consume_shared_module(consume_share_info, context.clone());
+ rs.send(result).unwrap();
+ });
+ };
let mut count = 0;
for file in files {
count += 1;
@@ -127,6 +136,14 @@ impl Compiler {
ResolverResource::Ignored(_) => {
Self::create_ignored_module(&path, self.context.clone())
}
+ ResolverResource::Remote(remote_into) => {
+ Self::create_remote_module(remote_into)
+ }
+ ResolverResource::Shared(consume_share_info) => {
+ count += 1;
+ build_consume_share_with_pool(consume_share_info.clone());
+ Self::create_empty_module(&dep_module_id)
+ }
};
// 拿到依赖之后需要直接添加 module 到 module_graph 里,不能等依赖 build 完再添加
@@ -134,6 +151,7 @@ impl Compiler {
module_ids.insert(module.id.clone());
module_graph.add_module(module);
}
+
module_graph.add_dependency(&module_id, &dep_module_id, dep.dependency);
}
if count == 0 {
@@ -267,6 +285,12 @@ __mako_require__.loadScript('{}', (e) => e.type === 'load' ? resolve() : reject(
result
}
}
+ pub fn build_consume_shared_module(
+ consume_share_info: ConsumeSharedInfo,
+ _context: Arc,
+ ) -> Result {
+ Ok(Self::create_consume_share_module(consume_share_info))
+ }
pub fn build_module(
file: &File,
@@ -279,6 +303,7 @@ __mako_require__.loadScript('{}', (e) => e.type === 'load' ? resolve() : reject(
let content = context.plugin_driver.load_transform(
&mut content,
&file.path.to_string_lossy(),
+ file.is_entry,
&context,
)?;
file.set_content(content);
@@ -329,4 +354,32 @@ __mako_require__.loadScript('{}', (e) => e.type === 'load' ? resolve() : reject(
let module = Module::new(module_id, is_entry, Some(info));
Ok(module)
}
+
+ pub(crate) fn create_remote_module(remote_info: RemoteInfo) -> Module {
+ Module {
+ is_entry: false,
+ id: remote_info.module_id.as_str().into(),
+ info: Some(ModuleInfo {
+ resolved_resource: Some(ResolverResource::Remote(remote_info.clone())),
+ federation: Some(FedereationModuleType::Remote),
+ ..Default::default()
+ }),
+ side_effects: true,
+ }
+ }
+
+ pub(crate) fn create_consume_share_module(consume_share_info: ConsumeSharedInfo) -> Module {
+ Module {
+ is_entry: false,
+ id: consume_share_info.module_id.as_str().into(),
+ info: Some(ModuleInfo {
+ deps: consume_share_info.deps.clone(),
+ resolved_resource: Some(ResolverResource::Shared(consume_share_info.clone())),
+ federation: Some(FedereationModuleType::ConsumeShare),
+ module_system: ModuleSystem::Custom,
+ ..Default::default()
+ }),
+ side_effects: true,
+ }
+ }
}
diff --git a/crates/mako/src/build/analyze_deps.rs b/crates/mako/src/build/analyze_deps.rs
index 1bb374aa5..2f7b5eb11 100644
--- a/crates/mako/src/build/analyze_deps.rs
+++ b/crates/mako/src/build/analyze_deps.rs
@@ -59,10 +59,14 @@ impl AnalyzeDeps {
);
match result {
Ok(resolver_resource) => {
- resolved_deps.push(ResolvedDep {
+ let resolved_dep = ResolvedDep {
resolver_resource,
dependency: dep,
- });
+ };
+ context
+ .plugin_driver
+ .after_resolve(&resolved_dep, &context)?;
+ resolved_deps.push(resolved_dep);
}
Err(_err) => {
missing_deps.insert(dep.source.clone(), dep);
diff --git a/crates/mako/src/compiler.rs b/crates/mako/src/compiler.rs
index 960222c8e..187d9f3da 100644
--- a/crates/mako/src/compiler.rs
+++ b/crates/mako/src/compiler.rs
@@ -23,6 +23,7 @@ use crate::generate::optimize_chunk::OptimizeChunksInfo;
use crate::module_graph::ModuleGraph;
use crate::plugin::{Plugin, PluginDriver, PluginGenerateEndParams};
use crate::plugins;
+use crate::plugins::module_federation::ModuleFederationPlugin;
use crate::resolve::{get_resolvers, Resolvers};
use crate::share::helpers::SWC_HELPERS;
use crate::stats::StatsInfo;
@@ -314,7 +315,6 @@ impl Compiler {
Arc::new(plugins::bundless_compiler::BundlessCompilerPlugin {}),
);
}
-
if std::env::var("DEBUG_GRAPH").is_ok_and(|v| v == "true") {
plugins.push(Arc::new(plugins::graphviz::Graphviz {}));
}
@@ -327,6 +327,10 @@ impl Compiler {
plugins.push(Arc::new(plugins::central_ensure::CentralChunkEnsure {}));
}
+ if let Some(mf_cfg) = config.module_federation.as_ref() {
+ plugins.push(Arc::new(ModuleFederationPlugin::new(mf_cfg.clone())));
+ }
+
if let Some(minifish_config) = &config._minifish {
let inject = if let Some(inject) = &minifish_config.inject {
let mut map = HashMap::new();
@@ -423,7 +427,7 @@ impl Compiler {
.entry
.values()
.map(|entry| {
- let mut entry = entry.to_string_lossy().to_string();
+ let mut entry = entry.import.to_string_lossy().to_string();
let is_browser = matches!(
self.context.config.platform,
crate::config::Platform::Browser
@@ -492,6 +496,7 @@ impl Compiler {
self.context
.plugin_driver
.generate_end(¶ms, &self.context)?;
+
self.context.plugin_driver.write_bundle(&self.context)?;
Ok(())
}
diff --git a/crates/mako/src/config.rs b/crates/mako/src/config.rs
index 58f278eb5..822281c55 100644
--- a/crates/mako/src/config.rs
+++ b/crates/mako/src/config.rs
@@ -3,6 +3,7 @@ mod code_splitting;
mod dev_server;
mod devtool;
mod duplicate_package_checker;
+pub mod entry;
mod experimental;
mod external;
mod generic_usize;
@@ -12,6 +13,7 @@ mod macros;
mod manifest;
mod minifish;
mod mode;
+pub mod module_federation;
mod module_id_strategy;
mod optimization;
mod output;
@@ -28,9 +30,9 @@ mod tree_shaking;
mod umd;
mod watch;
-use std::collections::{BTreeMap, HashMap};
+use std::collections::HashMap;
use std::fmt;
-use std::path::{Path, PathBuf};
+use std::path::Path;
pub use analyze::AnalyzeConfig;
use anyhow::{anyhow, Result};
@@ -42,6 +44,7 @@ pub use devtool::{deserialize_devtool, DevtoolConfig};
pub use duplicate_package_checker::{
deserialize_check_duplicate_package, DuplicatePackageCheckerConfig,
};
+use entry::{Entry, EntryItem};
use experimental::ExperimentalConfig;
pub use external::{
ExternalAdvanced, ExternalAdvancedSubpath, ExternalAdvancedSubpathConverter,
@@ -54,6 +57,7 @@ pub use manifest::{deserialize_manifest, ManifestConfig};
use miette::{miette, ByteOffset, Diagnostic, NamedSource, SourceOffset, SourceSpan};
pub use minifish::{deserialize_minifish, MinifishConfig};
pub use mode::Mode;
+use module_federation::ModuleFederationConfig;
pub use module_id_strategy::ModuleIdStrategy;
pub use optimization::{deserialize_optimization, OptimizationConfig};
use output::get_default_chunk_loading_global;
@@ -135,7 +139,7 @@ pub enum CopyConfig {
#[derive(Deserialize, Serialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct Config {
- pub entry: BTreeMap,
+ pub entry: Entry,
pub output: OutputConfig,
pub resolve: ResolveConfig,
#[serde(deserialize_with = "deserialize_manifest", default)]
@@ -228,6 +232,7 @@ pub struct Config {
default
)]
pub check_duplicate_package: Option,
+ pub module_federation: Option,
// 是否开启 case sensitive 检查,只有mac平台才需要开启
#[serde(rename = "caseSensitiveCheck")]
pub case_sensitive_check: bool,
@@ -369,7 +374,13 @@ impl Config {
for ext in JS_EXTENSIONS {
let file_path = root.join(file_path).with_extension(ext);
if file_path.exists() {
- config.entry.insert("index".to_string(), file_path);
+ config.entry.insert(
+ "index".to_string(),
+ EntryItem {
+ filename: None,
+ import: file_path,
+ },
+ );
break 'outer;
}
}
@@ -382,28 +393,29 @@ impl Config {
// normalize entry
config.entry.iter_mut().try_for_each(|(k, v)| {
#[allow(clippy::needless_borrows_for_generic_args)]
- if let Ok(entry_path) = root.join(&v).canonicalize()
+ if let Ok(entry_path) = root.join(&v.import).canonicalize()
&& entry_path.is_file()
{
- *v = entry_path;
+ v.import = entry_path;
} else {
for ext in JS_EXTENSIONS {
#[allow(clippy::needless_borrows_for_generic_args)]
- if let Ok(entry_path) = root.join(&v).with_extension(ext).canonicalize()
+ if let Ok(entry_path) =
+ root.join(&v.import).with_extension(ext).canonicalize()
&& entry_path.is_file()
{
- *v = entry_path;
+ v.import = entry_path;
return Ok(());
}
if let Ok(entry_path) = root
- .join(&v)
+ .join(&v.import)
.join("index")
.with_extension(ext)
.canonicalize()
&& entry_path.is_file()
{
- *v = entry_path;
+ v.import = entry_path;
return Ok(());
}
}
diff --git a/crates/mako/src/config/entry.rs b/crates/mako/src/config/entry.rs
new file mode 100644
index 000000000..87ce2811d
--- /dev/null
+++ b/crates/mako/src/config/entry.rs
@@ -0,0 +1,37 @@
+use std::collections::BTreeMap;
+use std::path::PathBuf;
+
+use serde::{Deserialize, Serialize};
+use serde_json::Value;
+
+#[derive(Serialize, Debug)]
+pub struct EntryItem {
+ #[serde(default)]
+ pub filename: Option,
+ pub import: PathBuf,
+}
+
+pub type Entry = BTreeMap;
+
+impl<'de> Deserialize<'de> for EntryItem {
+ fn deserialize(deserializer: D) -> Result
+ where
+ D: serde::Deserializer<'de>,
+ {
+ let value: serde_json::Value = serde_json::Value::deserialize(deserializer)?;
+ match &value {
+ Value::String(s) => Ok(EntryItem {
+ filename: None,
+ import: s.into(),
+ }),
+ Value::Object(_) => {
+ Ok(serde_json::from_value::(value).map_err(serde::de::Error::custom)?)
+ }
+ _ => Err(serde::de::Error::custom(format!(
+ "invalid `{}` value: {}",
+ stringify!(deserialize_umd).replace("deserialize_", ""),
+ value
+ ))),
+ }
+ }
+}
diff --git a/crates/mako/src/config/module_federation.rs b/crates/mako/src/config/module_federation.rs
new file mode 100644
index 000000000..314c3bb0a
--- /dev/null
+++ b/crates/mako/src/config/module_federation.rs
@@ -0,0 +1,62 @@
+use std::collections::HashMap;
+
+use serde::{Deserialize, Serialize};
+
+#[derive(Debug, Clone, Serialize, Deserialize)]
+#[serde(rename_all = "camelCase")]
+pub struct ModuleFederationConfig {
+ pub name: String,
+ pub filename: Option,
+ pub exposes: Option,
+ pub shared: Option,
+ pub remotes: Option,
+ #[serde(default)]
+ pub runtime_plugins: Vec,
+ pub implementation: String,
+ #[serde(default)]
+ pub share_strategy: ShareStrategy,
+ #[serde(default = "default_share_scope")]
+ pub share_scope: String,
+ #[serde(default)]
+ pub manifest: bool,
+}
+
+pub type ExposesConfig = HashMap;
+
+pub type SharedConfig = HashMap;
+
+#[derive(Debug, Clone, Serialize, Deserialize, Default)]
+#[serde(rename_all = "camelCase")]
+pub struct SharedItemConfig {
+ #[serde(default)]
+ /// not supported now
+ pub eager: bool,
+ #[serde(default)]
+ pub singleton: bool,
+ #[serde(default)]
+ pub required_version: Option,
+ #[serde(default)]
+ pub strict_version: bool,
+ #[serde(default = "default_share_scope")]
+ pub shared_scope: String,
+}
+
+#[derive(Debug, Clone, Serialize, Deserialize)]
+pub enum ShareStrategy {
+ #[serde(rename = "version-first")]
+ VersionFirst,
+ #[serde(rename = "loaded-first")]
+ LoadedFirst,
+}
+
+impl Default for ShareStrategy {
+ fn default() -> Self {
+ Self::LoadedFirst
+ }
+}
+
+pub type RemotesConfig = HashMap;
+
+fn default_share_scope() -> String {
+ "default".to_string()
+}
diff --git a/crates/mako/src/config/output.rs b/crates/mako/src/config/output.rs
index 3042ab885..7099a6667 100644
--- a/crates/mako/src/config/output.rs
+++ b/crates/mako/src/config/output.rs
@@ -7,7 +7,7 @@ use swc_core::ecma::ast::EsVersion;
use super::Umd;
use crate::create_deserialize_fn;
-use crate::utils::get_pkg_name;
+use crate::utils::get_app_info;
#[derive(Deserialize, Serialize, Debug)]
#[serde(rename_all = "camelCase")]
@@ -53,7 +53,7 @@ impl fmt::Display for CrossOriginLoading {
pub fn get_default_chunk_loading_global(umd: Option, root: &Path) -> String {
let unique_name = umd.map_or_else(
- || get_pkg_name(root).unwrap_or("global".to_string()),
+ || get_app_info(root).0.unwrap_or("global".to_string()),
|umd| umd.name.clone(),
);
diff --git a/crates/mako/src/config/umd.rs b/crates/mako/src/config/umd.rs
index 5c64dfa44..6ddbba0f4 100644
--- a/crates/mako/src/config/umd.rs
+++ b/crates/mako/src/config/umd.rs
@@ -12,13 +12,12 @@ where
D: serde::Deserializer<'de>,
{
let value: serde_json::Value = serde_json::Value::deserialize(deserializer)?;
- match value {
- serde_json::Value::Object(obj) => Ok(Some(
- serde_json::from_value::(serde_json::Value::Object(obj))
- .map_err(serde::de::Error::custom)?,
+ match &value {
+ serde_json::Value::Object(_) => Ok(Some(
+ serde_json::from_value::(value).map_err(serde::de::Error::custom)?,
)),
serde_json::Value::String(name) => Ok(Some(Umd {
- name,
+ name: name.clone(),
..Default::default()
})),
serde_json::Value::Bool(false) => Ok(None),
diff --git a/crates/mako/src/dev.rs b/crates/mako/src/dev.rs
index 0dae20352..e88db069a 100644
--- a/crates/mako/src/dev.rs
+++ b/crates/mako/src/dev.rs
@@ -85,7 +85,6 @@ impl DevServer {
let server = Server::bind(&addr).serve(make_svc);
// TODO: print when mako is run standalone
if std::env::var("MAKO_CLI").is_ok() {
- println!();
if config_port != port {
println!(
"{}",
@@ -214,7 +213,14 @@ impl DevServer {
.body(hyper::Body::empty())
.unwrap();
let res = staticfile.serve(req).await;
- res.map_err(anyhow::Error::from)
+ res.map_or_else(
+ |e| Err(anyhow::Error::from(e)),
+ |mut res| {
+ res.headers_mut()
+ .insert(ACCESS_CONTROL_ALLOW_ORIGIN, "*".parse().unwrap());
+ Ok(res)
+ },
+ )
}
}
}
diff --git a/crates/mako/src/dev/update.rs b/crates/mako/src/dev/update.rs
index cc041f742..0ecc1f7d2 100644
--- a/crates/mako/src/dev/update.rs
+++ b/crates/mako/src/dev/update.rs
@@ -283,7 +283,7 @@ impl Compiler {
debug!("build by modify: {:?} start", entry);
// first build
let is_entry = {
- let mut entries = self.context.config.entry.values();
+ let mut entries = self.context.config.entry.values().map(|e| &e.import);
entries.any(|e| e.eq(entry))
};
diff --git a/crates/mako/src/generate/chunk.rs b/crates/mako/src/generate/chunk.rs
index 6bd52993d..e1817e8fb 100644
--- a/crates/mako/src/generate/chunk.rs
+++ b/crates/mako/src/generate/chunk.rs
@@ -137,6 +137,10 @@ impl Chunk {
self.modules.contains(module_id)
}
+ pub fn root_module(&self) -> Option<&ModuleId> {
+ self.modules.iter().last()
+ }
+
pub fn hash(&self, mg: &ModuleGraph) -> u64 {
let mut sorted_module_ids = self.modules.iter().cloned().collect::>();
sorted_module_ids.sort_by_key(|m| m.id.clone());
diff --git a/crates/mako/src/generate/chunk_pot/ast_impl.rs b/crates/mako/src/generate/chunk_pot/ast_impl.rs
index 793d041ad..7d775cdb0 100644
--- a/crates/mako/src/generate/chunk_pot/ast_impl.rs
+++ b/crates/mako/src/generate/chunk_pot/ast_impl.rs
@@ -218,6 +218,12 @@ pub(crate) fn render_entry_js_chunk(
.into_bytes()
};
+ let entry_info = if let ChunkType::Entry(_, name, _) = &chunk.chunk_type {
+ context.config.entry.get(name)
+ } else {
+ None
+ };
+
Ok(ChunkFile {
raw_hash: hmr_hash,
content,
@@ -227,7 +233,12 @@ pub(crate) fn render_entry_js_chunk(
chunk_id: pot.chunk_id.clone(),
file_type: ChunkFileType::JS,
chunk_name: pot.chunk_name.clone(),
- file_name_template: context.config.output.filename.clone(),
+ file_name_template: entry_info.and_then(|e| {
+ e.filename
+ .as_ref()
+ .xor(context.config.output.filename.as_ref())
+ .cloned()
+ }),
})
}
diff --git a/crates/mako/src/generate/chunk_pot/str_impl.rs b/crates/mako/src/generate/chunk_pot/str_impl.rs
index beebc5a2f..430ae3218 100644
--- a/crates/mako/src/generate/chunk_pot/str_impl.rs
+++ b/crates/mako/src/generate/chunk_pot/str_impl.rs
@@ -95,6 +95,12 @@ pub(super) fn render_entry_js_chunk(
let mut source_map_buf: Vec = vec![];
sourcemap::SourceMap::from(chunk_raw_sourcemap).to_writer(&mut source_map_buf)?;
+ let entry_info = if let ChunkType::Entry(_, name, _) = &chunk.chunk_type {
+ context.config.entry.get(name)
+ } else {
+ None
+ };
+
Ok(ChunkFile {
raw_hash: hmr_hash,
content,
@@ -103,8 +109,13 @@ pub(super) fn render_entry_js_chunk(
file_name: pot.js_name.clone(),
chunk_id: pot.chunk_id.clone(),
file_type: ChunkFileType::JS,
- file_name_template: None,
chunk_name: pot.chunk_name.clone(),
+ file_name_template: entry_info.and_then(|e| {
+ e.filename
+ .as_ref()
+ .xor(context.config.output.filename.as_ref())
+ .cloned()
+ }),
})
}
diff --git a/crates/mako/src/generate/chunk_pot/util.rs b/crates/mako/src/generate/chunk_pot/util.rs
index 28420a066..4d027c9a8 100644
--- a/crates/mako/src/generate/chunk_pot/util.rs
+++ b/crates/mako/src/generate/chunk_pot/util.rs
@@ -25,7 +25,7 @@ use crate::config::Mode;
use crate::generate::chunk_pot::ChunkPot;
use crate::generate::runtime::AppRuntimeTemplate;
use crate::module::{relative_to_root, Module, ModuleAst};
-use crate::utils::get_pkg_name;
+use crate::utils::get_app_info;
pub(crate) fn render_module_js(
ast: &SwcModule,
@@ -105,6 +105,15 @@ pub(crate) fn runtime_code(context: &Arc) -> Result {
let chunk_graph = context.chunk_graph.read().unwrap();
let has_dynamic_chunks = chunk_graph.get_all_chunks().len() > 1;
let has_hmr = context.args.watch;
+ let chunk_matcher = context.config.module_federation.as_ref().and_then(|mf| {
+ mf.remotes.as_ref().and_then(|remotes| {
+ if remotes.is_empty() {
+ None
+ } else {
+ Some(r#"/^mako\/container\/remote\//"#.to_string())
+ }
+ })
+ });
let app_runtime = AppRuntimeTemplate {
has_dynamic_chunks,
has_hmr,
@@ -120,13 +129,14 @@ pub(crate) fn runtime_code(context: &Arc) -> Result {
.cross_origin_loading
.clone()
.map(|s| s.to_string()),
- pkg_name: get_pkg_name(&context.root),
+ pkg_name: get_app_info(&context.root).0,
concatenate_enabled: context
.config
.optimization
.as_ref()
.map_or(false, |o| o.concatenate_modules.unwrap_or(false)),
global_module_registry: context.config.output.global_module_registry,
+ chunk_matcher,
};
let app_runtime = app_runtime.render_once()?;
let app_runtime = app_runtime.replace(
diff --git a/crates/mako/src/generate/generate_chunks.rs b/crates/mako/src/generate/generate_chunks.rs
index 0e9646753..90499e1fd 100644
--- a/crates/mako/src/generate/generate_chunks.rs
+++ b/crates/mako/src/generate/generate_chunks.rs
@@ -83,9 +83,19 @@ type ChunksHashReplacer = HashMap;
impl Compiler {
pub fn generate_chunk_files(&self, hmr_hash: u64) -> Result> {
- crate::mako_profile_function!();
+ let module_graph = self.context.module_graph.read().unwrap();
let chunk_graph = self.context.chunk_graph.read().unwrap();
- let chunks = chunk_graph.get_chunks();
+
+ let chunks: Vec<&Chunk> = chunk_graph
+ .get_chunks()
+ .into_iter()
+ .filter(|c| {
+ !module_graph
+ .get_module(c.root_module().unwrap())
+ .unwrap()
+ .is_remote()
+ })
+ .collect();
let (entry_chunks, normal_chunks): (Vec<&Chunk>, Vec<&Chunk>) = chunks
.into_iter()
@@ -173,6 +183,15 @@ impl Compiler {
let descendant_chunk = chunk_graph.chunk(descendant_chunk_id).unwrap();
// TODO: maybe we can split chunks to chunk pots before generate, because normal chunks will be
// split here and fn generate_normal_chunk_files twice
+ //
+ if module_graph
+ .get_module(descendant_chunk.root_module().unwrap())
+ .unwrap()
+ .is_remote()
+ {
+ return (acc_js, acc_css);
+ }
+
let chunk_pot =
ChunkPot::from(descendant_chunk, &module_graph, &context);
diff --git a/crates/mako/src/generate/group_chunk.rs b/crates/mako/src/generate/group_chunk.rs
index c08a40fe3..3e84f525a 100644
--- a/crates/mako/src/generate/group_chunk.rs
+++ b/crates/mako/src/generate/group_chunk.rs
@@ -31,7 +31,7 @@ impl Compiler {
for (key, value) in &self.context.config.entry {
// hmr entry id has query '?hmr'
- if parse_path(&value.to_string_lossy()).unwrap().0
+ if parse_path(&value.import.to_string_lossy()).unwrap().0
== parse_path(&entry.id).unwrap().0
{
entry_chunk_name = key;
diff --git a/crates/mako/src/generate/optimize_chunk.rs b/crates/mako/src/generate/optimize_chunk.rs
index f37897402..4eb5e1701 100644
--- a/crates/mako/src/generate/optimize_chunk.rs
+++ b/crates/mako/src/generate/optimize_chunk.rs
@@ -26,7 +26,11 @@ impl Compiler {
pub fn optimize_chunk(&self) {
crate::mako_profile_function!();
debug!("optimize chunk");
- if let Some(optimize_options) = self.get_optimize_chunk_options() {
+ if let Some(mut optimize_options) = self.get_optimize_chunk_options() {
+ self.context
+ .plugin_driver
+ .after_optimize_chunk_options(&mut optimize_options)
+ .unwrap();
debug!("optimize options: {:?}", optimize_options);
// stage: prepare
let mut optimize_chunks_infos = optimize_options
@@ -123,7 +127,7 @@ impl Compiler {
let async_chunk_root_modules = chunks
.iter()
.filter_map(|chunk| match chunk.chunk_type {
- ChunkType::Async => chunk.modules.iter().last(),
+ ChunkType::Async => chunk.root_module(),
_ => None,
})
.collect::>();
diff --git a/crates/mako/src/generate/runtime.rs b/crates/mako/src/generate/runtime.rs
index 81edd7ca3..84879c2f7 100644
--- a/crates/mako/src/generate/runtime.rs
+++ b/crates/mako/src/generate/runtime.rs
@@ -14,4 +14,5 @@ pub struct AppRuntimeTemplate {
pub concatenate_enabled: bool,
pub cross_origin_loading: Option,
pub global_module_registry: bool,
+ pub chunk_matcher: Option,
}
diff --git a/crates/mako/src/generate/transform.rs b/crates/mako/src/generate/transform.rs
index 8cc2dfeb2..4e927091e 100644
--- a/crates/mako/src/generate/transform.rs
+++ b/crates/mako/src/generate/transform.rs
@@ -100,6 +100,7 @@ pub fn transform_modules_in_thread(
chunk_id: None,
to_replace_source: chunk_name,
resolved_module_id: id.clone(),
+ _is_federation_expose: false,
}
}
ResolveType::DynamicImport(import_options) => {
@@ -112,12 +113,14 @@ pub fn transform_modules_in_thread(
chunk_id,
to_replace_source: id.generate(&context),
resolved_module_id: id.clone(),
+ _is_federation_expose: import_options._is_federation_expose,
}
}
_ => ResolvedReplaceInfo {
chunk_id: None,
to_replace_source: id.generate(&context),
resolved_module_id: id.clone(),
+ _is_federation_expose: false,
},
};
@@ -189,6 +192,7 @@ fn insert_swc_helper_replace(
chunk_id: None,
to_replace_source: m_id.generate(context),
resolved_module_id: m_id,
+ _is_federation_expose: false,
},
);
});
diff --git a/crates/mako/src/lib.rs b/crates/mako/src/lib.rs
index 3bebd29c7..4cf918a36 100644
--- a/crates/mako/src/lib.rs
+++ b/crates/mako/src/lib.rs
@@ -1,6 +1,7 @@
#![feature(box_patterns)]
#![feature(hasher_prefixfree_extras)]
#![feature(let_chains)]
+#![feature(is_none_or)]
pub mod ast;
mod build;
diff --git a/crates/mako/src/main.rs b/crates/mako/src/main.rs
index cfc740591..ae366423d 100644
--- a/crates/mako/src/main.rs
+++ b/crates/mako/src/main.rs
@@ -1,5 +1,6 @@
#![feature(box_patterns)]
#![feature(let_chains)]
+#![feature(is_none_or)]
extern crate swc_malloc;
diff --git a/crates/mako/src/module.rs b/crates/mako/src/module.rs
index 353774559..a2a85b976 100644
--- a/crates/mako/src/module.rs
+++ b/crates/mako/src/module.rs
@@ -137,6 +137,7 @@ impl From<&NamedExport> for NamedExportType {
pub struct ImportOptions {
pub chunk_name: Option,
pub ignore: bool,
+ pub _is_federation_expose: bool,
}
impl ImportOptions {
@@ -200,6 +201,7 @@ pub struct ModuleInfo {
/// The transformed source map chain of this module
pub source_map_chain: Vec>,
pub module_system: ModuleSystem,
+ pub federation: Option,
}
impl Default for ModuleInfo {
@@ -217,11 +219,12 @@ impl Default for ModuleInfo {
resolved_resource: None,
source_map_chain: vec![],
is_ignored: false,
+ federation: None,
}
}
}
-fn md5_hash(source_str: &str, lens: usize) -> String {
+pub fn md5_hash(source_str: &str, lens: usize) -> String {
format!("{:x}", md5::compute(source_str))
.chars()
.take(lens)
@@ -390,6 +393,12 @@ pub enum ModuleType {
PlaceHolder,
}
+#[derive(Clone, Debug)]
+pub enum FedereationModuleType {
+ Remote,
+ ConsumeShare,
+}
+
#[derive(Clone)]
pub struct Module {
pub id: ModuleId,
@@ -426,6 +435,26 @@ impl Module {
.map_or(false, |info| info.external.is_some())
}
+ pub fn is_remote(&self) -> bool {
+ if let Some(info) = self.info.as_ref()
+ && let Some(FedereationModuleType::Remote) = info.federation
+ {
+ true
+ } else {
+ false
+ }
+ }
+
+ pub fn is_consume_share(&self) -> bool {
+ if let Some(info) = self.info.as_ref()
+ && let Some(FedereationModuleType::ConsumeShare) = info.federation
+ {
+ true
+ } else {
+ false
+ }
+ }
+
pub fn is_placeholder(&self) -> bool {
self.get_module_type() == ModuleType::PlaceHolder
}
diff --git a/crates/mako/src/plugin.rs b/crates/mako/src/plugin.rs
index 68add9b7d..7ae3aabdb 100644
--- a/crates/mako/src/plugin.rs
+++ b/crates/mako/src/plugin.rs
@@ -9,8 +9,9 @@ use swc_core::common::Mark;
use swc_core::ecma::ast::Module;
use crate::ast::file::{Content, File};
+use crate::build::analyze_deps::ResolvedDep;
use crate::compiler::{Args, Compiler, Context};
-use crate::config::Config;
+use crate::config::{CodeSplittingAdvancedOptions, Config};
use crate::generate::chunk_graph::ChunkGraph;
use crate::generate::generate_chunks::ChunkFile;
use crate::module::{Dependency, ModuleAst, ModuleId};
@@ -24,8 +25,9 @@ pub struct PluginLoadParam<'a> {
}
#[derive(Debug)]
-pub struct PluginResolveIdParams {
+pub struct PluginResolveIdParams<'a> {
pub is_entry: bool,
+ pub dep: &'a Dependency,
}
pub struct PluginParseParam<'a> {
@@ -66,6 +68,7 @@ pub trait Plugin: Any + Send + Sync {
&self,
_content: &mut Content,
_path: &str,
+ _is_entry: bool,
_context: &Arc,
) -> Result> {
Ok(None)
@@ -115,6 +118,10 @@ pub trait Plugin: Any + Send + Sync {
Ok(())
}
+ fn after_resolve(&self, _resolved_dep: &ResolvedDep, _context: &Arc) -> Result<()> {
+ Ok(())
+ }
+
fn after_build(&self, _context: &Arc, _compiler: &Compiler) -> Result<()> {
Ok(())
}
@@ -175,6 +182,13 @@ pub trait Plugin: Any + Send + Sync {
Ok(())
}
+ fn after_optimize_chunk_options(
+ &self,
+ _optimize_chunk_options: &mut CodeSplittingAdvancedOptions,
+ ) -> Result<()> {
+ Ok(())
+ }
+
fn optimize_chunk(
&self,
_chunk_graph: &mut ChunkGraph,
@@ -293,6 +307,13 @@ impl PluginDriver {
Ok(())
}
+ pub fn after_resolve(&self, resolved_dep: &ResolvedDep, context: &Arc) -> Result<()> {
+ for plugin in &self.plugins {
+ plugin.after_resolve(resolved_dep, context)?;
+ }
+ Ok(())
+ }
+
pub fn resolve_id(
&self,
source: &str,
@@ -410,6 +431,17 @@ impl PluginDriver {
Ok(())
}
+ pub fn after_optimize_chunk_options(
+ &self,
+ optimize_chunk_options: &mut CodeSplittingAdvancedOptions,
+ ) -> Result<()> {
+ for p in &self.plugins {
+ p.after_optimize_chunk_options(optimize_chunk_options)?;
+ }
+
+ Ok(())
+ }
+
pub fn optimize_chunk(
&self,
chunk_graph: &mut ChunkGraph,
@@ -440,10 +472,11 @@ impl PluginDriver {
&self,
content: &mut Content,
path: &str,
+ _is_entry: bool,
context: &Arc,
) -> Result {
for plugin in &self.plugins {
- if let Some(transformed) = plugin.load_transform(content, path, context)? {
+ if let Some(transformed) = plugin.load_transform(content, path, _is_entry, context)? {
*content = transformed;
}
}
diff --git a/crates/mako/src/plugins.rs b/crates/mako/src/plugins.rs
index 36de47d69..3dc037846 100644
--- a/crates/mako/src/plugins.rs
+++ b/crates/mako/src/plugins.rs
@@ -15,6 +15,7 @@ pub mod imports_checker;
pub mod invalid_webpack_syntax;
pub mod manifest;
pub mod minifish;
+pub mod module_federation;
pub mod progress;
pub mod require_context;
pub mod runtime;
diff --git a/crates/mako/src/plugins/bundless_compiler.rs b/crates/mako/src/plugins/bundless_compiler.rs
index 0ec58ebe9..0a69247d5 100644
--- a/crates/mako/src/plugins/bundless_compiler.rs
+++ b/crates/mako/src/plugins/bundless_compiler.rs
@@ -86,6 +86,7 @@ impl BundlessCompiler {
chunk_id: None,
to_replace_source: replacement,
resolved_module_id: id.clone(),
+ _is_federation_expose: false,
},
))
})
diff --git a/crates/mako/src/plugins/hmr_runtime/hmr_runtime.js b/crates/mako/src/plugins/hmr_runtime/hmr_runtime.js
index 0227be6a5..ce4afc957 100644
--- a/crates/mako/src/plugins/hmr_runtime/hmr_runtime.js
+++ b/crates/mako/src/plugins/hmr_runtime/hmr_runtime.js
@@ -30,7 +30,26 @@
}
return require(request);
};
- Object.assign(fn, require);
+
+ var createPropertyDescriptor = function (name) {
+ return {
+ configurable: true,
+ enumerable: true,
+ get: function () {
+ return require[name];
+ },
+ set: function (value) {
+ require[name] = value;
+ },
+ };
+ };
+
+ for (var name in require) {
+ if (Object.prototype.hasOwnProperty.call(require, name)) {
+ Object.defineProperty(fn, name, createPropertyDescriptor(name));
+ }
+ }
+
return fn;
};
const applyHotUpdate = (_chunkId, update) => {
diff --git a/crates/mako/src/plugins/module_federation.rs b/crates/mako/src/plugins/module_federation.rs
new file mode 100644
index 000000000..7fb941833
--- /dev/null
+++ b/crates/mako/src/plugins/module_federation.rs
@@ -0,0 +1,131 @@
+use std::collections::HashMap;
+use std::path::Path;
+use std::sync::{Arc, RwLock};
+
+use anyhow::Result;
+use constants::{FEDERATION_REMOTE_MODULE_PREFIX, FEDERATION_REMOTE_REFERENCE_PREFIX};
+use provide_shared::SharedDependency;
+
+use crate::ast::file::Content;
+use crate::build::analyze_deps::ResolvedDep;
+use crate::compiler::{Args, Context};
+use crate::config::module_federation::ModuleFederationConfig;
+use crate::config::{CodeSplittingAdvancedOptions, Config};
+use crate::generate::chunk_graph::ChunkGraph;
+use crate::module_graph::ModuleGraph;
+use crate::plugin::{Plugin, PluginGenerateEndParams, PluginResolveIdParams};
+use crate::resolve::ResolverResource;
+
+mod constants;
+mod consume_shared;
+mod container;
+mod container_reference;
+mod manifest;
+mod provide_for_consume;
+mod provide_shared;
+mod util;
+
+pub struct ModuleFederationPlugin {
+ pub config: ModuleFederationConfig,
+ shared_dependency_map: RwLock>,
+}
+
+impl ModuleFederationPlugin {
+ pub fn new(config: ModuleFederationConfig) -> Self {
+ Self {
+ config,
+ shared_dependency_map: RwLock::new(HashMap::new()),
+ }
+ }
+}
+
+impl Plugin for ModuleFederationPlugin {
+ fn name(&self) -> &str {
+ "module_federation"
+ }
+
+ fn modify_config(&self, config: &mut Config, root: &Path, _args: &Args) -> Result<()> {
+ self.add_container_entry(config, root);
+ Ok(())
+ }
+
+ fn load_transform(
+ &self,
+ content: &mut Content,
+ _path: &str,
+ is_entry: bool,
+ context: &Arc,
+ ) -> Result> {
+ if !is_entry {
+ Ok(None)
+ } else {
+ // add container entry runtime dependency
+ match content {
+ Content::Js(js_content) => {
+ let entry_runtime_dep_path = self.prepare_container_entry_dep(&context.root);
+ js_content.content.insert_str(
+ 0,
+ format!(r#"import "{}";"#, entry_runtime_dep_path).as_str(),
+ );
+ Ok(Some(content.clone()))
+ }
+ _ => Ok(None),
+ }
+ }
+ }
+
+ fn runtime_plugins(&self, context: &Arc) -> Result> {
+ Ok(vec![
+ self.init_federation_runtime_options(),
+ self.init_federation_runtime_remotes(context),
+ self.init_federation_runtime_consume(context),
+ self.init_federation_runtime_sharing(context),
+ self.export_federation_container(),
+ ])
+ }
+
+ fn resolve_id(
+ &self,
+ source: &str,
+ importer: &str,
+ params: &PluginResolveIdParams,
+ context: &Arc,
+ ) -> Result> {
+ let remote_module = self.resolve_remote(source);
+ if let Ok(Some(_)) = remote_module.as_ref() {
+ remote_module
+ } else {
+ self.resolve_to_consume_share(source, importer, params, context)
+ }
+ }
+
+ fn generate_end(&self, params: &PluginGenerateEndParams, context: &Arc) -> Result<()> {
+ if self.config.manifest {
+ self.generate_federation_manifest(context, params)?;
+ }
+ Ok(())
+ }
+
+ fn after_resolve(&self, resolved_dep: &ResolvedDep, _context: &Arc) -> Result<()> {
+ self.collect_provide_shared(resolved_dep);
+ Ok(())
+ }
+
+ fn optimize_chunk(
+ &self,
+ chunk_graph: &mut ChunkGraph,
+ module_graph: &mut ModuleGraph,
+ _context: &Arc,
+ ) -> Result<()> {
+ self.connect_provide_shared_to_container(chunk_graph, module_graph);
+ Ok(())
+ }
+
+ fn after_optimize_chunk_options(
+ &self,
+ optimize_chunk_options: &mut CodeSplittingAdvancedOptions,
+ ) -> Result<()> {
+ self.patch_code_splitting(optimize_chunk_options);
+ Ok(())
+ }
+}
diff --git a/crates/mako/src/plugins/module_federation/constants.rs b/crates/mako/src/plugins/module_federation/constants.rs
new file mode 100644
index 000000000..ebe18c9b4
--- /dev/null
+++ b/crates/mako/src/plugins/module_federation/constants.rs
@@ -0,0 +1,9 @@
+pub(crate) const FEDERATION_GLOBAL: &str = "__mako_require__.federation";
+
+pub(crate) const FEDERATION_REMOTE_MODULE_PREFIX: &str = "mako/container/remote/";
+
+pub(crate) const FEDERATION_REMOTE_REFERENCE_PREFIX: &str = "mako/container/reference/";
+
+pub(crate) const FEDERATION_SHARED_REFERENCE_PREFIX: &str = "mako/sharing/consume/";
+
+pub(crate) const FEDERATION_EXPOSE_CHUNK_PREFIX: &str = "__mf_expose_";
diff --git a/crates/mako/src/plugins/module_federation/consume_shared.rs b/crates/mako/src/plugins/module_federation/consume_shared.rs
new file mode 100644
index 000000000..41156e37e
--- /dev/null
+++ b/crates/mako/src/plugins/module_federation/consume_shared.rs
@@ -0,0 +1,231 @@
+use std::collections::{HashMap, HashSet};
+use std::sync::Arc;
+
+use pathdiff::diff_paths;
+
+use super::constants::FEDERATION_SHARED_REFERENCE_PREFIX;
+use super::ModuleFederationPlugin;
+use crate::build::analyze_deps::{AnalyzeDepsResult, ResolvedDep};
+use crate::compiler::Context;
+use crate::generate::chunk::ChunkType;
+use crate::module::{md5_hash, Dependency, ResolveType};
+use crate::plugin::PluginResolveIdParams;
+use crate::resolve::{do_resolve, ConsumeSharedInfo, ResolverResource, ResolverType};
+
+impl ModuleFederationPlugin {
+ pub(super) fn init_federation_runtime_consume(&self, context: &Context) -> String {
+ let module_graph = context.module_graph.read().unwrap();
+ let chunk_graph = context.chunk_graph.read().unwrap();
+ let share_dependencies = self.shared_dependency_map.read().unwrap();
+
+ let mut initial_consumes = Vec::::new();
+
+ let consume_modules_chunk_map: HashMap> = chunk_graph
+ .get_all_chunks()
+ .into_iter()
+ .filter_map(|c| {
+ let modules = c
+ .modules
+ .iter()
+ .filter_map(|m| {
+ if let Some(module) = module_graph.get_module(m)
+ && module.is_consume_share()
+ {
+ if let ChunkType::Entry(_, _, _) = c.chunk_type {
+ initial_consumes.push(m.id.clone());
+ }
+
+ Some(m.id.clone())
+ } else {
+ None
+ }
+ })
+ .collect::>();
+ if modules.is_empty() {
+ None
+ } else {
+ Some((c.id.id.clone(), modules))
+ }
+ })
+ .collect();
+
+ let consume_shared_module_ids =
+ consume_modules_chunk_map
+ .iter()
+ .fold(HashSet::<&String>::new(), |mut acc, cur| {
+ acc.extend(cur.1.iter());
+ acc
+ });
+
+ let consume_shared_modules = consume_shared_module_ids
+ .iter()
+ .map(|id| module_graph.get_module(&id.as_str().into()).unwrap())
+ .collect::>();
+
+ let module_to_handler_mapping_code = consume_shared_modules
+ .iter()
+ .map(|s| {
+ let resolved_resource = s.info.as_ref().unwrap().resolved_resource.as_ref().unwrap();
+ let module_full_path = match resolved_resource {
+ ResolverResource::Shared(info) =>
+ info.deps.resolved_deps[0].resolver_resource.get_resolved_path(),
+ _ =>
+ panic!("{} is not a shared module", resolved_resource.get_resolved_path())
+ };
+ let module_relative_path =
+ diff_paths(&module_full_path, &context.root)
+ .unwrap()
+ .to_string_lossy()
+ .to_string();
+
+ let module_in_chunk = chunk_graph.get_chunk_for_module(&module_full_path.as_str().into()).unwrap();
+
+ let getter = match &module_in_chunk.chunk_type {
+ ChunkType::Entry(_, _, _) | ChunkType::Worker(_) => {
+ format!(r#"() => (() => requireModule("{module_relative_path}"))"#
+ )
+ },
+ ChunkType::Async
+ | ChunkType::Sync
+ => {
+ let dependency_chunks = chunk_graph.sync_dependencies_chunk(&module_in_chunk.id);
+ format!(
+ r#"() => (Promise.all([{}]).then(() => requireModule("{module_relative_path}")))"#,
+ [
+ dependency_chunks,
+ vec![module_in_chunk.id.clone()]
+ ]
+ .concat().iter()
+ .map(|e| format!(r#"requireModule.ensure("{}")"#, e.id))
+ .collect::>().join(",")
+ )
+ },
+ ChunkType::Runtime => panic!("mf shared dependency should not be bundled to runtime chunk")
+ };
+
+ let share_dependency = share_dependencies.get(&s.id.id).unwrap();
+ format!(
+ r#""{shared_consume_id}": {{
+ getter: {getter},
+ shareInfo: {{ shareConfig: {share_config} }},
+ shareKey: "{share_key}"
+ }}"#,
+ shared_consume_id = s.id.id,
+ share_config = serde_json::to_string(&share_dependency.shared_config).unwrap(),
+ share_key = share_dependency.share_key
+
+ )
+ })
+ .collect::>()
+ .join(",");
+
+ let initial_consumes_code = serde_json::to_string(&initial_consumes).unwrap();
+ let install_initial_consumes_code = if initial_consumes.is_empty() {
+ ""
+ } else {
+ r#"
+ requireModule.federation.installInitialConsumes = () => (requireModule.federation.bundlerRuntime.installInitialConsumes({
+ initialConsumes: initialConsumes,
+ installedModules: installedModules,
+ moduleToHandlerMapping: moduleToHandlerMapping,
+ webpackRequire: requireModule
+ }))"#
+ };
+ let chunk_mapping_code = serde_json::to_string(&consume_modules_chunk_map).unwrap();
+ format!(
+ r#"
+/* mako/runtime/federation consumes */
+!(() => {{
+ var installedModules = {{}};
+ var moduleToHandlerMapping = {{{module_to_handler_mapping_code}}};
+ var initialConsumes = {initial_consumes_code};
+ {install_initial_consumes_code}
+ var chunkMapping = {chunk_mapping_code};
+ requireModule.chunkEnsures.consumes = (chunkId, promises) => {{
+ requireModule.federation.bundlerRuntime.consumes({{
+ chunkMapping: chunkMapping,
+ installedModules: installedModules,
+ chunkId: chunkId,
+ moduleToHandlerMapping: moduleToHandlerMapping,
+ promises: promises,
+ webpackRequire: requireModule
+ }});
+ }}
+}})();"#
+ )
+ }
+
+ pub(super) fn resolve_to_consume_share(
+ &self,
+ source: &str,
+ importer: &str,
+ params: &PluginResolveIdParams,
+ context: &Arc,
+ ) -> Result, anyhow::Error> {
+ if let Some(shared) = self.config.shared.as_ref()
+ && let Some(shared_info) = shared.get(source)
+ {
+ let resolver = if params.dep.resolve_type == ResolveType::Require {
+ context.resolvers.get(&ResolverType::Cjs)
+ } else if params.dep.resolve_type == ResolveType::Css {
+ context.resolvers.get(&ResolverType::Css)
+ } else {
+ context.resolvers.get(&ResolverType::Esm)
+ }
+ .unwrap();
+ let resolver_resource =
+ do_resolve(importer, source, resolver, Some(&context.config.externals))?;
+ if let ResolverResource::Resolved(_) = resolver_resource {
+ let config_joined_str = format!(
+ "{}|{}|{}|{}|{}|{}|{}",
+ shared_info.shared_scope,
+ source,
+ shared_info
+ .required_version
+ .as_ref()
+ .map_or("", |v| v.as_str()),
+ shared_info.strict_version,
+ resolver_resource.get_resolved_path(),
+ shared_info.singleton,
+ shared_info.eager
+ );
+ let hash = md5_hash(&config_joined_str, 4);
+ return Ok(Some(ResolverResource::Shared(ConsumeSharedInfo {
+ name: source.to_string(),
+ version: resolver_resource.get_pkg_info().unwrap().version.unwrap(),
+ share_scope: shared_info.shared_scope.clone(),
+ eager: shared_info.eager,
+ singletion: shared_info.singleton,
+ required_version: shared_info.required_version.clone(),
+ strict_version: shared_info.strict_version,
+ module_id: format!(
+ "{}{}/{}/{}?{}",
+ FEDERATION_SHARED_REFERENCE_PREFIX,
+ shared_info.shared_scope,
+ source,
+ source,
+ hash
+ ),
+ deps: AnalyzeDepsResult {
+ resolved_deps: vec![ResolvedDep {
+ resolver_resource,
+ dependency: Dependency {
+ source: params.dep.source.clone(),
+ resolve_as: None,
+ resolve_type: if shared_info.eager {
+ ResolveType::Require
+ } else {
+ ResolveType::DynamicImport(Default::default())
+ },
+ order: params.dep.order,
+ span: params.dep.span,
+ },
+ }],
+ ..Default::default()
+ },
+ })));
+ }
+ }
+ Ok(None)
+ }
+}
diff --git a/crates/mako/src/plugins/module_federation/container.rs b/crates/mako/src/plugins/module_federation/container.rs
new file mode 100644
index 000000000..442f362b2
--- /dev/null
+++ b/crates/mako/src/plugins/module_federation/container.rs
@@ -0,0 +1,275 @@
+use std::collections::btree_map;
+use std::fs;
+use std::path::Path;
+
+use serde::Serialize;
+use tracing::warn;
+
+use super::constants::{FEDERATION_EXPOSE_CHUNK_PREFIX, FEDERATION_GLOBAL};
+use super::util::parse_remote;
+use super::ModuleFederationPlugin;
+use crate::config::entry::EntryItem;
+use crate::config::{AllowChunks, Config};
+use crate::module::md5_hash;
+use crate::visitors::mako_require::MAKO_REQUIRE;
+
+impl ModuleFederationPlugin {
+ pub(super) fn add_container_entry(&self, config: &mut Config, root: &Path) {
+ // add container entry
+ if let Some(exposes) = self.config.exposes.as_ref() {
+ let container_entry_name = &self.config.name;
+ if !exposes.is_empty() {
+ match config.entry.entry(container_entry_name.clone()) {
+ btree_map::Entry::Occupied(_) => {
+ warn!(
+ "mf exposed name {} is conflicting with entry config.",
+ container_entry_name
+ );
+ }
+ btree_map::Entry::Vacant(vacant_entry) => {
+ // TODO: refactor with virtual entry
+ let container_entry_code = self.get_container_entry_code(root);
+ let container_entry_path = root.join(format!(
+ "node_modules/.federation/.entry.container.{}.js",
+ container_entry_name
+ ));
+ let container_entry_parent_path = container_entry_path.parent().unwrap();
+ if !fs::exists(container_entry_parent_path).unwrap() {
+ fs::create_dir_all(container_entry_parent_path).unwrap();
+ }
+ fs::write(&container_entry_path, container_entry_code).unwrap();
+
+ vacant_entry.insert(EntryItem {
+ filename: self.config.filename.clone(),
+ import: container_entry_path,
+ });
+ }
+ }
+ }
+ }
+ }
+
+ pub(super) fn get_container_entry_code(&self, root: &Path) -> String {
+ let exposes_modules_code = self
+ .config
+ .exposes
+ .as_ref()
+ .unwrap()
+ .iter()
+ .map(|(name, module)| {
+ format!(
+ r#""{name}": () => import(
+ /* makoChunkName: "{FEDERATION_EXPOSE_CHUNK_PREFIX}{striped_name}" */
+ /* federationExpose: true */
+ "{module}"
+),"#,
+ module = root.join(module).canonicalize().unwrap().to_string_lossy(),
+ striped_name = name.replace("./", "")
+ )
+ })
+ .collect::>()
+ .join("\n");
+
+ format!(
+ r#"var moduleMap = {{
+ {exposes_modules_code}
+}};
+
+var get = (module, getScope) => {{
+ {MAKO_REQUIRE}.R = getScope;
+ getScope = (
+ Object.prototype.hasOwnProperty.call(moduleMap, module)
+ ? moduleMap[module]()
+ : Promise.resolve().then(() => {{
+ throw new Error('Module "' + module + '" does not exist in container.');
+ }})
+ );
+ {MAKO_REQUIRE}.R = undefined;
+ return getScope;
+}};
+
+var init = (shareScope, initScope, remoteEntryInitOptions) => {{
+ return {FEDERATION_GLOBAL}.bundlerRuntime.initContainerEntry({{
+ webpackRequire: {MAKO_REQUIRE},
+ shareScope: shareScope,
+ initScope: initScope,
+ remoteEntryInitOptions: remoteEntryInitOptions,
+ shareScopeKey: "{share_scope}"
+ }})
+}};
+
+export {{ get, init }};
+"#,
+ share_scope = self.config.share_scope
+ )
+ }
+
+ pub(super) fn prepare_container_entry_dep(&self, root: &Path) -> String {
+ let container_content = self.get_federation_init_code();
+
+ let content_hash = md5_hash(&container_content, 32);
+
+ let dep_path = root.join(format!(
+ "node_modules/.federation/.entry.{}.js",
+ content_hash
+ ));
+ let dep_parent_path = dep_path.parent().unwrap();
+ if !fs::exists(dep_parent_path).unwrap() {
+ fs::create_dir_all(dep_parent_path).unwrap();
+ }
+ if !fs::exists(&dep_path).unwrap() {
+ fs::write(&dep_path, container_content).unwrap();
+ }
+
+ dep_path.to_string_lossy().to_string()
+ }
+
+ pub(super) fn get_federation_init_code(&self) -> String {
+ let (plugins_imports, plugins_instantiations) = self.get_mf_runtime_plugins_content();
+
+ format!(
+ r#"import federation from "{federation_impl}";
+{plugins_imports}
+
+if(!{FEDERATION_GLOBAL}.runtime) {{
+ var preFederation = {FEDERATION_GLOBAL};
+ {FEDERATION_GLOBAL} = {{}};
+ for(var key in federation) {{
+ {FEDERATION_GLOBAL}[key] = federation[key];
+ }}
+ for(var key in preFederation) {{
+ {FEDERATION_GLOBAL}[key] = preFederation[key];
+ }}
+}}
+
+if(!{FEDERATION_GLOBAL}.instance) {{
+ {plugins_instantiations}
+ {FEDERATION_GLOBAL}.instance = {FEDERATION_GLOBAL}.runtime.init({FEDERATION_GLOBAL}.initOptions);
+ if({FEDERATION_GLOBAL}.attachShareScopeMap) {{
+ {FEDERATION_GLOBAL}.attachShareScopeMap({MAKO_REQUIRE});
+ }}
+ if({FEDERATION_GLOBAL}.installInitialConsumes) {{
+ {FEDERATION_GLOBAL}.installInitialConsumes();
+ }}
+}}
+"#,
+ federation_impl = self.config.implementation,
+ )
+ }
+
+ pub(super) fn init_federation_runtime_options(&self) -> String {
+ let runtime_remotes = self.config.remotes.as_ref().map_or(Vec::new(), |remotes| {
+ remotes
+ .iter()
+ .map(|(alias, remote)| {
+ let (name, entry) = parse_remote(remote).unwrap();
+ RuntimeRemoteItem {
+ name,
+ alias: alias.clone(),
+ entry,
+ share_scope: self.config.share_scope.clone(),
+ }
+ })
+ .collect()
+ });
+ let init_options: RuntimeInitOptions = RuntimeInitOptions {
+ name: self.config.name.clone(),
+ remotes: runtime_remotes,
+ share_strategy: serde_json::to_value(&self.config.share_strategy)
+ .unwrap()
+ .as_str()
+ .unwrap()
+ .to_string(),
+ };
+ let init_options_code = serde_json::to_string(&init_options).unwrap();
+
+ let federation_runtime_code = format!(
+ r#"
+/* mako/runtime/federation runtime */
+!(function() {{
+ if(!requireModule.federation) {{
+ requireModule.federation = {{
+ initOptions: {init_options_code},
+ chunkMatcher: () => true,
+ rootOutputDir: "",
+ initialConsumes: undefined,
+ bundlerRuntimeOptions: {{}}
+ }};
+ }}
+}})();"#
+ );
+ federation_runtime_code
+ }
+
+ pub(super) fn get_mf_runtime_plugins_content(&self) -> (String, String) {
+ let (imported_plugin_names, import_plugin_instantiations) =
+ self.config.runtime_plugins.iter().enumerate().fold(
+ (Vec::new(), Vec::new()),
+ |(mut names, mut stmts), (index, plugin)| {
+ names.push(format!("plugin_{index}"));
+ stmts.push(format!(r#"import plugin_{index} from "{plugin}";"#));
+ (names, stmts)
+ },
+ );
+
+ let plugins_imports = import_plugin_instantiations.join("\n");
+
+ let plugins_to_add = imported_plugin_names
+ .iter()
+ .map(|item| format!(r#"{item} ? (item.default || item)() : false"#))
+ .collect::>()
+ .join(",");
+
+ let plugins_instantiations = if imported_plugin_names.is_empty() {
+ "".to_string()
+ } else {
+ format!(
+ r#"var pluginsToAdd = [{plugins_to_add}].filter(Boolean);
+ {FEDERATION_GLOBAL}.initOptions.plugins = {FEDERATION_GLOBAL}.initOptions.plugins ?
+ {FEDERATION_GLOBAL}.initOptions.plugins.concat(pluginsToAdd) : pluginsToAdd;
+"#,
+ )
+ };
+
+ (plugins_imports, plugins_instantiations)
+ }
+
+ pub(super) fn export_federation_container(&self) -> String {
+ if let Some(exposes) = self.config.exposes.as_ref() {
+ if !exposes.is_empty() {
+ format!(
+ r#"global["{}"] = requireModule(entryModuleId);"#,
+ self.config.name
+ )
+ } else {
+ "".to_string()
+ }
+ } else {
+ "".to_string()
+ }
+ }
+ pub(crate) fn patch_code_splitting(
+ &self,
+ optimize_chunk_options: &mut crate::config::CodeSplittingAdvancedOptions,
+ ) {
+ optimize_chunk_options.groups.iter_mut().for_each(|group| {
+ group.allow_chunks = AllowChunks::Async;
+ });
+ }
+}
+
+#[derive(Serialize)]
+#[serde(rename_all = "camelCase")]
+struct RuntimeRemoteItem {
+ name: String,
+ alias: String,
+ entry: String,
+ share_scope: String,
+}
+
+#[derive(Serialize)]
+struct RuntimeInitOptions {
+ name: String,
+ remotes: Vec,
+ share_strategy: String,
+}
diff --git a/crates/mako/src/plugins/module_federation/container_reference.rs b/crates/mako/src/plugins/module_federation/container_reference.rs
new file mode 100644
index 000000000..841da7cd8
--- /dev/null
+++ b/crates/mako/src/plugins/module_federation/container_reference.rs
@@ -0,0 +1,127 @@
+use std::collections::HashMap;
+use std::result::Result;
+use std::sync::Arc;
+
+use serde::Serialize;
+
+use super::{
+ ModuleFederationPlugin, FEDERATION_REMOTE_MODULE_PREFIX, FEDERATION_REMOTE_REFERENCE_PREFIX,
+};
+use crate::compiler::Context;
+use crate::resolve::{RemoteInfo, ResolverResource};
+
+impl ModuleFederationPlugin {
+ pub(super) fn init_federation_runtime_remotes(&self, context: &Arc) -> String {
+ if self.config.remotes.as_ref().is_none_or(|r| r.is_empty()) {
+ return "".to_string();
+ }
+
+ let module_graph = context.module_graph.read().unwrap();
+ let chunk_graph = context.chunk_graph.read().unwrap();
+ let all_chunks = chunk_graph.get_all_chunks();
+
+ let mut chunk_mapping: HashMap<&str, Vec<&str>> = HashMap::new();
+ let mut id_to_external_and_name_mapping: HashMap<&str, Vec<&str>> = HashMap::new();
+ let mut id_to_remote_map: HashMap<&str, Vec> = HashMap::new();
+ all_chunks.iter().for_each(|c| {
+ c.modules.iter().for_each(|m| {
+ if let Some(m) = module_graph.get_module(m) {
+ if m.is_remote() {
+ {
+ chunk_mapping
+ .entry(c.id.id.as_str())
+ .or_default()
+ .push(m.id.id.as_str());
+ }
+
+ {
+ let remote_module = m
+ .info
+ .as_ref()
+ .unwrap()
+ .resolved_resource
+ .as_ref()
+ .unwrap()
+ .get_remote_info()
+ .unwrap();
+ let remote_info = id_to_external_and_name_mapping
+ .entry(m.id.id.as_str())
+ .or_default();
+
+ remote_info.push(&remote_module.share_scope);
+ remote_info.push(&remote_module.sub_path);
+ remote_info.push(&remote_module.external_reference_id);
+
+ let external_info =
+ id_to_remote_map.entry(m.id.id.as_str()).or_default();
+
+ external_info.push(RemoteExternal {
+ name: remote_module.name.clone(),
+ external_type: remote_module.external_type.clone(),
+ external_module_id: remote_module.external_reference_id.clone(),
+ });
+ }
+ }
+ }
+ });
+ });
+
+ let chunk_mapping = serde_json::to_string(&chunk_mapping).unwrap();
+ let id_to_external_and_name_mapping =
+ serde_json::to_string(&id_to_external_and_name_mapping).unwrap();
+ let id_to_remote_map = serde_json::to_string(&id_to_remote_map).unwrap();
+
+ format!(
+ r#"
+/* mako/runtime/federation remotes consume */
+!(function() {{
+ var chunkMapping = {chunk_mapping};
+ var idToExternalAndNameMapping = {id_to_external_and_name_mapping};
+ var idToRemoteMap = {id_to_remote_map};
+ requireModule.federation.bundlerRuntimeOptions.remotes = {{idToRemoteMap, chunkMapping, idToExternalAndNameMapping, webpackRequire: requireModule}};
+ requireModule.chunkEnsures.remotes = (chunkId, promises) => {{
+ requireModule.federation.bundlerRuntime.remotes({{ idToRemoteMap,chunkMapping, idToExternalAndNameMapping, chunkId, promises, webpackRequire: requireModule}});
+ }}
+}}
+)()"#,
+ )
+ }
+
+ pub(super) fn resolve_remote(
+ &self,
+ source: &str,
+ ) -> Result, anyhow::Error> {
+ let source_parts = source
+ .split_once("/")
+ .map_or((source.to_string(), ".".to_string()), |(part_0, part_1)| {
+ (part_0.to_string(), part_1.to_string())
+ });
+ Ok(self.config.remotes.as_ref().map_or_else(
+ || None,
+ |remotes| {
+ remotes.get(&source_parts.0).map(|_remote| {
+ ResolverResource::Remote(RemoteInfo {
+ module_id: format!("{}{}", FEDERATION_REMOTE_MODULE_PREFIX, source),
+ external_reference_id: format!(
+ "{}{}",
+ FEDERATION_REMOTE_REFERENCE_PREFIX, source_parts.0
+ ),
+ // FIXME: hard code now
+ external_type: "script".to_string(),
+ sub_path: format!("./{}", source_parts.1),
+ name: source_parts.0.to_string(),
+ share_scope: self.config.share_scope.clone(),
+ })
+ })
+ },
+ ))
+ }
+}
+
+#[derive(Serialize)]
+#[serde(rename_all = "camelCase")]
+struct RemoteExternal {
+ external_type: String,
+ name: String,
+ external_module_id: String,
+}
diff --git a/crates/mako/src/plugins/module_federation/manifest.rs b/crates/mako/src/plugins/module_federation/manifest.rs
new file mode 100644
index 000000000..7c23ff9ef
--- /dev/null
+++ b/crates/mako/src/plugins/module_federation/manifest.rs
@@ -0,0 +1,308 @@
+use std::fs;
+use std::sync::Arc;
+
+use serde::Serialize;
+
+use super::constants::FEDERATION_EXPOSE_CHUNK_PREFIX;
+use super::util::{parse_remote, serialize_none_to_false};
+use super::ModuleFederationPlugin;
+use crate::compiler::Context;
+use crate::generate::chunk_graph::ChunkGraph;
+use crate::module::ModuleId;
+use crate::plugin::PluginGenerateEndParams;
+use crate::stats::StatsJsonMap;
+use crate::utils::get_app_info;
+
+impl ModuleFederationPlugin {
+ pub(super) fn generate_federation_manifest(
+ &self,
+ context: &Arc,
+ params: &PluginGenerateEndParams,
+ ) -> Result<(), anyhow::Error> {
+ let app_info = get_app_info(&context.root);
+ let manifest = Manifest {
+ id: self.config.name.clone(),
+ name: self.config.name.clone(),
+ exposes: self.config.exposes.as_ref().map_or(Vec::new(), |exposes| {
+ exposes
+ .iter()
+ .map(|(path, module)| {
+ let name = path.replace("./", "");
+ let remote_module_id: ModuleId = context
+ .root
+ .join(module)
+ .canonicalize()
+ .unwrap()
+ .to_string_lossy()
+ .to_string()
+ .into();
+ // FIXME: this may be slow
+ let chunk_graph = context.chunk_graph.read().unwrap();
+ let sync_chunks = chunk_graph
+ .graph
+ .node_weights()
+ .filter_map(|c| {
+ if c.id.id.starts_with(FEDERATION_EXPOSE_CHUNK_PREFIX)
+ && c.has_module(&remote_module_id)
+ {
+ Some(c.id.clone())
+ } else {
+ None
+ }
+ })
+ .collect::>();
+
+ let assets = extract_chunk_assets(sync_chunks, &chunk_graph, params);
+ ManifestExpose {
+ id: format!("{}:{}", self.config.name, name),
+ name,
+ path: path.clone(),
+ assets,
+ }
+ })
+ .collect()
+ }),
+ shared: {
+ let chunk_graph = context.chunk_graph.read().unwrap();
+ let provide_shared_map = self.shared_dependency_map.read().unwrap();
+ provide_shared_map
+ .iter()
+ .filter_map(|(_, config)| {
+ let module_id: ModuleId = config.file_path.clone().into();
+ let chunk_id = chunk_graph
+ .get_chunk_for_module(&module_id)
+ .as_ref()?
+ .id
+ .clone();
+ let assets = extract_chunk_assets(vec![chunk_id], &chunk_graph, params);
+ Some(ManifestShared {
+ id: format!("{}:{}", self.config.name, config.share_key),
+ name: config.share_key.clone(),
+ require_version: config.shared_config.required_version.clone(),
+ version: config.version.clone(),
+ singleton: config.shared_config.singleton,
+ assets,
+ })
+ })
+ .collect()
+ },
+ remotes: {
+ let module_graph = context.module_graph.read().unwrap();
+ params
+ .stats
+ .chunk_modules
+ .iter()
+ .filter_map(|cm| {
+ if let Some(module) = module_graph.get_module(&cm.id.clone().into())
+ && module.is_remote()
+ {
+ let data = cm.id.split('/').collect::>();
+ Some(ManifestRemote {
+ entry: parse_remote(
+ self.config.remotes.as_ref().unwrap().get(data[3]).unwrap(),
+ )
+ .unwrap()
+ .1,
+ module_name: data[4].to_string(),
+ alias: data[3].to_string(),
+ federation_container_name: data[3].to_string(),
+ })
+ } else {
+ None
+ }
+ })
+ .collect()
+ },
+ meta_data: {
+ let chunk_graph = context.chunk_graph.read().unwrap();
+ let mf_container_entry_root_module: Option = context
+ .config
+ .entry
+ .get(&self.config.name)
+ .map(|e| e.import.to_string_lossy().to_string().into());
+ let mf_container_entry_chunk = mf_container_entry_root_module
+ .map(|m| chunk_graph.get_chunk_for_module(&m).unwrap());
+
+ ManifestMetaData {
+ name: self.config.name.clone(),
+ build_info: ManifestMetaBuildInfo {
+ build_name: app_info.0.unwrap_or("default".to_string()),
+ build_version: app_info.1.unwrap_or("".to_string()),
+ },
+ global_name: self.config.name.clone(),
+ public_path: context.config.public_path.clone(),
+ // FIXME: hardcode now
+ r#type: "global".to_string(),
+ remote_entry: mf_container_entry_chunk.map(|c| ManifestMetaRemoteEntry {
+ name: extract_assets(&[c.id.clone()], ¶ms.stats).0[0].clone(),
+ path: "".to_string(),
+ r#type: "global".to_string(),
+ }),
+ ..Default::default()
+ }
+ },
+ };
+ fs::write(
+ context.root.join("./dist/mf-manifest.json"),
+ serde_json::to_string_pretty(&manifest)?,
+ )
+ .unwrap();
+ Ok(())
+ }
+}
+
+fn extract_chunk_assets(
+ sync_chunks: Vec,
+ chunk_graph: &ChunkGraph,
+ params: &PluginGenerateEndParams,
+) -> ManifestAssets {
+ let sync_chunk_dependencies = sync_chunks.iter().fold(Vec::new(), |mut acc, cur| {
+ let sync_deps = chunk_graph.sync_dependencies_chunk(cur);
+ acc.splice(..0, sync_deps);
+ acc
+ });
+
+ let all_sync_chunks = [sync_chunk_dependencies, sync_chunks].concat();
+ let all_async_chunks: Vec = all_sync_chunks.iter().fold(vec![], |mut acc, cur| {
+ acc.extend(chunk_graph.installable_descendants_chunk(cur));
+ acc
+ });
+
+ let (sync_js_files, sync_css_files) = extract_assets(&all_sync_chunks, ¶ms.stats);
+
+ let (async_js_files, async_css_files) = extract_assets(&all_async_chunks, ¶ms.stats);
+
+ let async_js_files = async_js_files
+ .into_iter()
+ .filter(|f| !sync_js_files.contains(f))
+ .collect();
+
+ let async_css_files = async_css_files
+ .into_iter()
+ .filter(|f| !sync_js_files.contains(f))
+ .collect();
+
+ ManifestAssets {
+ js: ManifestAssetsItem {
+ sync: sync_js_files,
+ r#async: async_js_files,
+ },
+ css: ManifestAssetsItem {
+ sync: sync_css_files,
+ r#async: async_css_files,
+ },
+ }
+}
+
+fn extract_assets(
+ all_exposes_sync_chunks: &[ModuleId],
+ stats: &StatsJsonMap,
+) -> (Vec, Vec) {
+ all_exposes_sync_chunks.iter().fold(
+ (Vec::::new(), Vec::::new()),
+ |mut acc, cur| {
+ if let Some(c) = stats.chunks.iter().find(|c| c.id == cur.id) {
+ c.files.iter().for_each(|f| {
+ if f.ends_with(".js") {
+ acc.0.push(f.clone());
+ }
+ if f.ends_with(".css") {
+ acc.1.push(f.clone());
+ }
+ });
+ }
+ acc
+ },
+ )
+}
+
+#[derive(Serialize, Default)]
+pub struct ManifestAssetsItem {
+ pub sync: Vec,
+ pub r#async: Vec,
+}
+
+#[derive(Serialize, Default)]
+pub struct ManifestAssets {
+ pub js: ManifestAssetsItem,
+ pub css: ManifestAssetsItem,
+}
+
+#[derive(Serialize, Default)]
+#[serde(rename_all = "camelCase")]
+pub struct ManifestExpose {
+ pub id: String,
+ pub name: String,
+ pub assets: ManifestAssets,
+ pub path: String,
+}
+
+#[derive(Serialize, Default)]
+#[serde(rename_all = "camelCase")]
+pub struct ManifestShared {
+ id: String,
+ name: String,
+ assets: ManifestAssets,
+ version: String,
+ #[serde(serialize_with = "serialize_none_to_false")]
+ require_version: Option,
+ singleton: bool,
+}
+
+#[derive(Serialize, Default)]
+#[serde(rename_all = "camelCase")]
+pub struct ManifestRemote {
+ pub entry: String,
+ pub alias: String,
+ pub module_name: String,
+ pub federation_container_name: String,
+}
+
+#[derive(Serialize, Default)]
+#[serde(rename_all = "camelCase")]
+pub struct ManifestMetaTypes {
+ path: String,
+ name: String,
+ zip: String,
+ api: String,
+}
+
+#[derive(Serialize, Default)]
+#[serde(rename_all = "camelCase")]
+pub struct ManifestMetaData {
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub remote_entry: Option,
+ pub global_name: String,
+ pub public_path: String,
+ pub r#type: String,
+ pub build_info: ManifestMetaBuildInfo,
+ pub name: String,
+ pub types: ManifestMetaTypes,
+ pub plugin_version: String,
+}
+
+#[derive(Serialize, Default)]
+#[serde(rename_all = "camelCase")]
+pub struct ManifestMetaBuildInfo {
+ pub build_version: String,
+ pub build_name: String,
+}
+
+#[derive(Serialize, Default)]
+#[serde(rename_all = "camelCase")]
+pub struct ManifestMetaRemoteEntry {
+ pub name: String,
+ pub path: String,
+ pub r#type: String,
+}
+
+#[derive(Serialize, Default)]
+#[serde(rename_all = "camelCase")]
+pub struct Manifest {
+ pub id: String,
+ pub name: String,
+ pub meta_data: ManifestMetaData,
+ pub shared: Vec,
+ pub remotes: Vec,
+ pub exposes: Vec,
+}
diff --git a/crates/mako/src/plugins/module_federation/provide_for_consume.rs b/crates/mako/src/plugins/module_federation/provide_for_consume.rs
new file mode 100644
index 000000000..2bb114517
--- /dev/null
+++ b/crates/mako/src/plugins/module_federation/provide_for_consume.rs
@@ -0,0 +1,43 @@
+use super::ModuleFederationPlugin;
+use crate::generate::chunk::ChunkType;
+use crate::generate::chunk_graph::ChunkGraph;
+use crate::module_graph::ModuleGraph;
+
+impl ModuleFederationPlugin {
+ pub(super) fn connect_provide_shared_to_container(
+ &self,
+ chunk_graph: &mut ChunkGraph,
+ _module_graph: &mut ModuleGraph,
+ ) {
+ let entry_chunks = chunk_graph
+ .get_chunks()
+ .into_iter()
+ .filter_map(|c| {
+ if matches!(c.chunk_type, ChunkType::Entry(_, _, false)) {
+ Some(c.id.clone())
+ } else {
+ None
+ }
+ })
+ .collect::>();
+
+ let provide_shared_map = self.shared_dependency_map.read().unwrap();
+
+ let provide_shared_in_chunks = provide_shared_map
+ .iter()
+ .map(|m| {
+ chunk_graph
+ .get_chunk_for_module(&m.0.as_str().into())
+ .unwrap()
+ .id
+ .clone()
+ })
+ .collect::>();
+
+ entry_chunks.iter().for_each(|ec| {
+ provide_shared_in_chunks.iter().for_each(|c| {
+ chunk_graph.add_edge(ec, c);
+ });
+ });
+ }
+}
diff --git a/crates/mako/src/plugins/module_federation/provide_shared.rs b/crates/mako/src/plugins/module_federation/provide_shared.rs
new file mode 100644
index 000000000..a6f0e59cf
--- /dev/null
+++ b/crates/mako/src/plugins/module_federation/provide_shared.rs
@@ -0,0 +1,148 @@
+use pathdiff::diff_paths;
+use serde::Serialize;
+
+use super::util::serialize_none_to_false;
+use super::ModuleFederationPlugin;
+use crate::build::analyze_deps::ResolvedDep;
+use crate::compiler::Context;
+use crate::generate::chunk::ChunkType;
+use crate::module::ModuleId;
+
+impl ModuleFederationPlugin {
+ pub(super) fn init_federation_runtime_sharing(&self, context: &Context) -> String {
+ let provide_shared_map = self.shared_dependency_map.read().unwrap();
+ let chunk_graph = context.chunk_graph.read().unwrap();
+
+ if provide_shared_map.is_empty() {
+ return "".to_string();
+ }
+
+ let provide_shared_map_code = format!(
+ r#"{{{}}}"#,
+ provide_shared_map
+ .iter()
+ .filter_map(|(_, share_item)| {
+ let module_id: ModuleId = share_item.file_path.as_str().into();
+ let module_in_chunk = chunk_graph.get_chunk_for_module(&module_id)?;
+
+ let share_item_code = format!(r#""{share_key}": [{infos}]"#,
+ share_key = share_item.share_key,
+ infos = {
+ let getter = {
+ let module_relative_path =
+ diff_paths(&share_item.file_path, &context.root)
+ .unwrap()
+ .to_string_lossy()
+ .to_string();
+
+ match &module_in_chunk.chunk_type {
+ ChunkType::Entry(_, _, _) | ChunkType::Worker(_) => {
+ format!(
+ r#"() => (() => requireModule("{module_relative_path}"))"#
+ )
+ },
+ ChunkType::Async
+ | ChunkType::Sync
+ => {
+ let dependency_chunks = chunk_graph.sync_dependencies_chunk(&module_in_chunk.id);
+ format!(
+ r#"() => (Promise.all([{}]).then(() => (() => requireModule("{module_relative_path}"))))"#,
+ [dependency_chunks, vec![module_in_chunk.id.clone()]]
+ .concat().iter().map(|e| format!(r#"requireModule.ensure("{}")"#, e.id))
+ .collect::>().join(",")
+ )
+ },
+ // FIXME:
+ _ => panic!("mf shared dependency should not bundled to worker chunk, entries' shared chunk or runtime chunk")
+ }
+ };
+ format!(
+ r#"{{ version: "{version}", get: {getter}, scope: {scope}, shareConfig: {share_config} }}"#,
+ version = share_item.version,
+ scope = serde_json::to_string(&share_item.scope).unwrap(),
+ share_config = serde_json::to_string(&share_item.shared_config).unwrap()
+ )
+ },
+ );
+ Some(share_item_code)}
+ )
+ .collect::>()
+ .join(",")
+ );
+
+ if let Some(shared) = self.config.shared.as_ref()
+ && !shared.is_empty()
+ {
+ format!(
+ r#"
+/* mako/runtime/federation sharing */
+!(function() {{
+ requireModule.federation.initOptions.shared = {provide_shared_map_code};
+ requireModule.S = {{}};
+ var initPromises = {{}};
+ var initTokens = {{}};
+ requireModule.I = function(name, initScope) {{
+ return requireModule.federation.bundlerRuntime.I({{
+ shareScopeName: name,
+ webpackRequire: requireModule,
+ initPromises: initPromises,
+ initTokens: initTokens,
+ initScope: initScope
+ }});
+ }};
+}})();
+"#,
+ )
+ } else {
+ "".to_string()
+ }
+ }
+
+ pub(super) fn collect_provide_shared(&self, resolved_dep: &ResolvedDep) {
+ if let Some(shared) = self.config.shared.as_ref()
+ && let Some(pkg_info) = resolved_dep.resolver_resource.get_pkg_info()
+ && let Some(pkg_name) = pkg_info.name
+ && let Some(shared_info) = shared.get(&pkg_name)
+ && pkg_name == resolved_dep.dependency.source
+ {
+ let mut provide_shared_map = self.shared_dependency_map.write().unwrap();
+ provide_shared_map
+ .entry(resolved_dep.resolver_resource.get_resolved_path())
+ .or_insert(SharedDependency {
+ share_key: pkg_name.clone(),
+ version: pkg_info.version.clone().unwrap(),
+ scope: vec![shared_info.shared_scope.clone()],
+ file_path: pkg_info.file_path.clone(),
+ shared_config: SharedConfig {
+ eager: shared_info.eager,
+ strict_version: shared_info.strict_version,
+ singleton: shared_info.singleton,
+ required_version: shared_info.required_version.clone(),
+ // FIXME: hard code now
+ fixed_dependencies: false,
+ },
+ });
+ }
+ }
+}
+
+#[derive(Debug)]
+pub(super) struct SharedDependency {
+ pub(super) share_key: String,
+ pub(super) version: String,
+ pub(super) scope: Vec,
+ pub(super) shared_config: SharedConfig,
+ pub(super) file_path: String,
+}
+
+#[derive(Serialize, Debug)]
+#[serde(rename_all = "camelCase")]
+pub(super) struct SharedConfig {
+ #[serde(default)]
+ pub(super) fixed_dependencies: bool,
+ pub(super) eager: bool,
+ pub(super) strict_version: bool,
+ pub(super) singleton: bool,
+ #[serde(serialize_with = "serialize_none_to_false")]
+ pub(super) required_version: Option,
+}
diff --git a/crates/mako/src/plugins/module_federation/util.rs b/crates/mako/src/plugins/module_federation/util.rs
new file mode 100644
index 000000000..110296416
--- /dev/null
+++ b/crates/mako/src/plugins/module_federation/util.rs
@@ -0,0 +1,23 @@
+use anyhow::{anyhow, Result};
+use serde::{Serialize, Serializer};
+
+pub(super) fn parse_remote(remote: &str) -> Result<(String, String)> {
+ let (left, right) = remote
+ .split_once('@')
+ .ok_or(anyhow!("invalid remote {}", remote))?;
+ if left.is_empty() || right.is_empty() {
+ Err(anyhow!("invalid remote {}", remote))
+ } else {
+ Ok((left.to_string(), right.to_string()))
+ }
+}
+
+pub(super) fn serialize_none_to_false(
+ t: &Option,
+ s: S,
+) -> Result {
+ match t {
+ Some(t) => t.serialize(s),
+ None => s.serialize_bool(false),
+ }
+}
diff --git a/crates/mako/src/plugins/ssu.rs b/crates/mako/src/plugins/ssu.rs
index 83a683c13..52bd99f35 100644
--- a/crates/mako/src/plugins/ssu.rs
+++ b/crates/mako/src/plugins/ssu.rs
@@ -166,7 +166,7 @@ impl Plugin for SUPlus {
fn modify_config(&self, config: &mut Config, _root: &Path, _args: &Args) -> Result<()> {
for p in config.entry.values_mut() {
- *p = PathBuf::from(format!("{SSU_ENTRY_PREFIX}{}", p.to_string_lossy()));
+ p.import = PathBuf::from(format!("{SSU_ENTRY_PREFIX}{}", p.import.to_string_lossy()));
}
config.code_splitting = Some(CodeSplitting {
@@ -258,8 +258,8 @@ let patch = require._su_patch();
console.log(patch);
try{{
{}
-}}catch(e){{
-//ignore the error
+}}catch(e){{
+//ignore the error
}}
module.export = Promise.all(
patch.map((d)=>__mako_require__.ensure(d))
diff --git a/crates/mako/src/plugins/tree_shaking/module.rs b/crates/mako/src/plugins/tree_shaking/module.rs
index 4c449b9fb..b401f2012 100644
--- a/crates/mako/src/plugins/tree_shaking/module.rs
+++ b/crates/mako/src/plugins/tree_shaking/module.rs
@@ -297,7 +297,7 @@ impl TreeShakeModule {
crate::module::ModuleAst::None => StatementGraph::empty(),
};
- let used_exports = if module.is_entry {
+ let used_exports = if module.is_entry || module.is_consume_share() {
UsedExports::All
} else {
UsedExports::Partial(Default::default())
diff --git a/crates/mako/src/plugins/tree_shaking/shake.rs b/crates/mako/src/plugins/tree_shaking/shake.rs
index 57d23a545..52c4a0ba1 100644
--- a/crates/mako/src/plugins/tree_shaking/shake.rs
+++ b/crates/mako/src/plugins/tree_shaking/shake.rs
@@ -38,7 +38,9 @@ pub fn optimize_modules(module_graph: &mut ModuleGraph, context: &Arc)
let module_type = module.get_module_type();
// skip non script modules and external modules
- if module_type != ModuleType::Script || module.is_external() {
+ if (module_type != ModuleType::Script && !module.is_consume_share())
+ || module.is_external()
+ {
if module_type != ModuleType::Script && !module.is_external() {
// mark all non script modules' script dependencies as side_effects
for dep_id in module_graph.dependence_module_ids(module_id) {
diff --git a/crates/mako/src/resolve.rs b/crates/mako/src/resolve.rs
index 71ece577c..32b0d15d3 100644
--- a/crates/mako/src/resolve.rs
+++ b/crates/mako/src/resolve.rs
@@ -13,7 +13,9 @@ use tracing::debug;
mod resolution;
mod resource;
pub use resolution::Resolution;
-pub use resource::{ExternalResource, ResolvedResource, ResolverResource};
+pub use resource::{
+ ConsumeSharedInfo, ExternalResource, RemoteInfo, ResolvedResource, ResolverResource,
+};
use crate::ast::file::parse_path;
use crate::compiler::Context;
@@ -49,9 +51,6 @@ pub fn resolve(
resolvers: &Resolvers,
context: &Arc,
) -> Result {
- crate::mako_profile_function!();
- crate::mako_profile_scope!("resolve", &dep.source);
-
// plugin first
if let Some(resolved) = context.plugin_driver.resolve_id(
&dep.source,
@@ -59,7 +58,10 @@ pub fn resolve(
// it's a compatibility feature for unplugin hooks
// is_entry is always false for dependencies
// since entry file does not need be be resolved
- &PluginResolveIdParams { is_entry: false },
+ &PluginResolveIdParams {
+ is_entry: false,
+ dep,
+ },
context,
)? {
return Ok(resolved);
@@ -231,7 +233,7 @@ fn get_external_target_from_global_obj(global_obj_name: &str, external: &str) ->
format!("{}{}", global_obj_name, external)
}
-fn do_resolve(
+pub fn do_resolve(
path: &str,
source: &str,
resolver: &Resolver,
diff --git a/crates/mako/src/resolve/resource.rs b/crates/mako/src/resolve/resource.rs
index 6e9219481..648c47628 100644
--- a/crates/mako/src/resolve/resource.rs
+++ b/crates/mako/src/resolve/resource.rs
@@ -1,7 +1,31 @@
use std::path::PathBuf;
+use crate::build::analyze_deps::AnalyzeDepsResult;
use crate::resolve::Resolution;
+#[derive(Debug, Clone)]
+pub struct RemoteInfo {
+ pub module_id: String,
+ pub external_reference_id: String,
+ pub external_type: String,
+ pub sub_path: String,
+ pub name: String,
+ pub share_scope: String,
+}
+
+#[derive(Debug, Clone)]
+pub struct ConsumeSharedInfo {
+ pub module_id: String,
+ pub name: String,
+ pub share_scope: String,
+ pub version: String,
+ pub eager: bool,
+ pub required_version: Option,
+ pub strict_version: bool,
+ pub singletion: bool,
+ pub deps: AnalyzeDepsResult,
+}
+
#[derive(Debug, Clone)]
pub struct ExternalResource {
pub source: String,
@@ -18,6 +42,8 @@ pub enum ResolverResource {
Resolved(ResolvedResource),
Ignored(PathBuf),
Virtual(PathBuf),
+ Remote(RemoteInfo),
+ Shared(ConsumeSharedInfo),
}
impl ResolverResource {
@@ -29,22 +55,56 @@ impl ResolverResource {
}
ResolverResource::Ignored(path) => path.to_string_lossy().to_string(),
ResolverResource::Virtual(path) => path.to_string_lossy().to_string(),
+ ResolverResource::Remote(info) => info.module_id.to_string(),
+ ResolverResource::Shared(info) => info.module_id.clone(),
}
}
pub fn get_external(&self) -> Option {
match self {
ResolverResource::External(ExternalResource { external, .. }) => Some(external.clone()),
- ResolverResource::Resolved(_) => None,
- ResolverResource::Ignored(_) => None,
- ResolverResource::Virtual(_) => None,
+ _ => None,
}
}
pub fn get_script(&self) -> Option {
match self {
ResolverResource::External(ExternalResource { script, .. }) => script.clone(),
- ResolverResource::Resolved(_) => None,
- ResolverResource::Ignored(_) => None,
- ResolverResource::Virtual(_) => None,
+ _ => None,
+ }
+ }
+
+ pub fn get_remote_info(&self) -> Option<&RemoteInfo> {
+ match &self {
+ ResolverResource::Remote(remote_info) => Some(remote_info),
+ _ => None,
+ }
+ }
+
+ pub fn get_pkg_info(&self) -> Option {
+ match self {
+ ResolverResource::Resolved(ResolvedResource(resolution)) => Some(PkgInfo {
+ file_path: resolution.full_path().to_string_lossy().to_string(),
+ name: resolution.package_json().and_then(|p| {
+ p.raw_json()
+ .get("name")
+ .and_then(|v| v.as_str().map(|v| v.to_string()))
+ }),
+ version: resolution.package_json().and_then(|p| {
+ p.raw_json()
+ .get("version")
+ .and_then(|v| v.as_str().map(|v| v.to_string()))
+ }),
+ }),
+ ResolverResource::Shared(info) => {
+ info.deps.resolved_deps[0].resolver_resource.get_pkg_info()
+ }
+ _ => None,
}
}
}
+
+#[derive(Debug)]
+pub struct PkgInfo {
+ pub file_path: String,
+ pub name: Option,
+ pub version: Option,
+}
diff --git a/crates/mako/src/stats.rs b/crates/mako/src/stats.rs
index a9fe89683..71cf8b2da 100644
--- a/crates/mako/src/stats.rs
+++ b/crates/mako/src/stats.rs
@@ -124,11 +124,9 @@ impl Compiler {
ChunkType::Sync => chunk_graph
.dependents_chunk(&chunk.id)
.iter()
- .filter_map(|chunk_id| {
- chunk_graph.chunk(chunk_id).unwrap().modules.iter().last()
- })
+ .filter_map(|chunk_id| chunk_graph.chunk(chunk_id).unwrap().root_module())
.collect::>(),
- _ => vec![chunk.modules.iter().last().unwrap()],
+ _ => vec![chunk.root_module().unwrap()],
};
let mut origins_set = IndexMap::new();
for origin_chunk_module in origin_chunk_modules {
@@ -506,9 +504,9 @@ pub struct StatsJsonMap {
root_path: String,
output_path: String,
assets: Vec,
- chunk_modules: Vec,
+ pub chunk_modules: Vec,
modules: HashMap,
- chunks: Vec,
+ pub chunks: Vec,
entrypoints: HashMap,
rsc_client_components: Vec,
#[serde(rename = "rscCSSModules")]
diff --git a/crates/mako/src/utils.rs b/crates/mako/src/utils.rs
index a469dd3a8..0dc917b6b 100644
--- a/crates/mako/src/utils.rs
+++ b/crates/mako/src/utils.rs
@@ -53,18 +53,23 @@ pub fn process_req_url(public_path: &str, req_url: &str) -> Result {
Ok(req_url.to_string())
}
-pub(crate) fn get_pkg_name(root: &Path) -> Option {
+pub(crate) fn get_app_info(root: &Path) -> (Option, Option) {
let pkg_json_path = root.join("package.json");
if pkg_json_path.exists() {
let pkg_json = std::fs::read_to_string(pkg_json_path).unwrap();
let pkg_json: serde_json::Value = serde_json::from_str(&pkg_json).unwrap();
- pkg_json
- .get("name")
- .map(|name| name.as_str().unwrap().to_string())
+ (
+ pkg_json
+ .get("name")
+ .map(|name| name.as_str().unwrap().to_string()),
+ pkg_json
+ .get("version")
+ .map(|name| name.as_str().unwrap().to_string()),
+ )
} else {
- None
+ (None, None)
}
}
diff --git a/crates/mako/src/visitors/dep_analyzer.rs b/crates/mako/src/visitors/dep_analyzer.rs
index ca3f89295..c6c7efc2c 100644
--- a/crates/mako/src/visitors/dep_analyzer.rs
+++ b/crates/mako/src/visitors/dep_analyzer.rs
@@ -66,7 +66,19 @@ impl DepAnalyzer {
})
});
- ImportOptions { chunk_name, ignore }
+ let _is_federation_expose = comments_texts.iter().any(|t| {
+ get_magic_federation_expose_regex()
+ .captures(t.trim())
+ .map_or(false, |cap| {
+ cap.get(2).map_or(false, |m| m.as_str() == "true")
+ })
+ });
+
+ ImportOptions {
+ chunk_name,
+ ignore,
+ _is_federation_expose,
+ }
}
}
@@ -241,6 +253,10 @@ fn get_magic_comment_ignore_regex() -> Regex {
create_cached_regex(r#"(makoIgnore|webpackIgnore):\s*(true|false)"#)
}
+fn get_magic_federation_expose_regex() -> Regex {
+ create_cached_regex(r#"(federationExpose):\s*(true|false)"#)
+}
+
#[cfg(test)]
mod tests {
use swc_core::common::GLOBALS;
diff --git a/crates/mako/src/visitors/dep_replacer.rs b/crates/mako/src/visitors/dep_replacer.rs
index 47167f6e7..272783551 100644
--- a/crates/mako/src/visitors/dep_replacer.rs
+++ b/crates/mako/src/visitors/dep_replacer.rs
@@ -28,6 +28,7 @@ pub struct ResolvedReplaceInfo {
pub chunk_id: Option,
pub to_replace_source: String,
pub resolved_module_id: ModuleId,
+ pub _is_federation_expose: bool,
}
#[derive(Debug, Clone)]
@@ -351,6 +352,7 @@ try {
chunk_id: None,
to_replace_source: module_id.into(),
resolved_module_id: "".into(),
+ _is_federation_expose: false,
}
}
}
diff --git a/crates/mako/src/visitors/dynamic_import.rs b/crates/mako/src/visitors/dynamic_import.rs
index b8cd66ac9..aa3200607 100644
--- a/crates/mako/src/visitors/dynamic_import.rs
+++ b/crates/mako/src/visitors/dynamic_import.rs
@@ -135,8 +135,18 @@ impl<'a> VisitMut for DynamicImport<'a> {
vec![self.interop.clone().as_arg(), lazy_require_call.as_arg()],
);
+ let dr_call_arg = if resolved_info._is_federation_expose {
+ dr_call
+ .as_call(call_expr.span, Vec::new())
+ .into_lazy_fn(Vec::new())
+ .into_lazy_fn(Vec::new())
+ .as_arg()
+ } else {
+ dr_call.as_arg()
+ };
+
member_expr!(@EXT, DUMMY_SP, load_promise.into(), then)
- .as_call(call_expr.span, vec![dr_call.as_arg()])
+ .as_call(call_expr.span, vec![dr_call_arg])
};
}
}
@@ -250,12 +260,14 @@ Promise.all([
"@swc/helpers/_/_interop_require_wildcard".to_string() => ResolvedReplaceInfo {
chunk_id: None,
to_replace_source: "hashed_helper".to_string(),
- resolved_module_id:"dummy".into()
+ resolved_module_id:"dummy".into(),
+ _is_federation_expose: false,
},
"foo".to_string() => ResolvedReplaceInfo {
chunk_id: Some("foo".into()),
to_replace_source: "foo".into(),
- resolved_module_id: "foo".into()
+ resolved_module_id: "foo".into(),
+ _is_federation_expose: false,
}
},
missing: HashMap::new(),
diff --git a/crates/mako/src/visitors/mako_require.rs b/crates/mako/src/visitors/mako_require.rs
index 4929c9cde..701be9d39 100644
--- a/crates/mako/src/visitors/mako_require.rs
+++ b/crates/mako/src/visitors/mako_require.rs
@@ -9,7 +9,7 @@ use crate::ast::utils::is_ident_undefined;
use crate::compiler::Context;
use crate::config::Platform;
-const MAKO_REQUIRE: &str = "__mako_require__";
+pub const MAKO_REQUIRE: &str = "__mako_require__";
pub struct MakoRequire {
pub unresolved_mark: Mark,
diff --git a/crates/mako/templates/app_runtime.stpl b/crates/mako/templates/app_runtime.stpl
index 6824b75f1..69734d85c 100644
--- a/crates/mako/templates/app_runtime.stpl
+++ b/crates/mako/templates/app_runtime.stpl
@@ -64,11 +64,21 @@ function createRuntime(makoModules, entryModuleId, global) {
get: all[name],
});
};
+
+ // hasOwnProperty shorthand
+ requireModule.o = function (obj, prop) { return (Object.prototype.hasOwnProperty.call(obj, prop));};
+
+ // required modules
+ requireModule.m = makoModules;
+
+ // modules registry
+ requireModule.c = modulesRegistry;
+
<% if concatenate_enabled { %>
// Export Star util for concatenated modules
requireModule.es = function(to, from) {
Object.keys(from).forEach(function(k) {
- if (k !== "default" && !Object.prototype.hasOwnProperty.call(to, k)) {
+ if (k !== "default" && !requireModule.o(to, k)) {
Object.defineProperty(to, k, {
enumerable: true,
get: from[k]
@@ -125,6 +135,11 @@ function createRuntime(makoModules, entryModuleId, global) {
var data = installedChunks[chunkId];
if (data === 0) return;
+ <% if chunk_matcher.is_some() { %>
+ // skip federation remote chunk
+ if (<%= chunk_matcher.unwrap() %>.test(chunkId)) return
+ <% } %>
+
if (data) {
// 0 1 2
// [resolve, reject, promise]
diff --git a/e2e/fixtures/module-federation.consumer/.gitignore b/e2e/fixtures/module-federation.consumer/.gitignore
new file mode 100644
index 000000000..de4d1f007
--- /dev/null
+++ b/e2e/fixtures/module-federation.consumer/.gitignore
@@ -0,0 +1,2 @@
+dist
+node_modules
diff --git a/e2e/fixtures/module-federation.consumer/expect.js b/e2e/fixtures/module-federation.consumer/expect.js
new file mode 100644
index 000000000..3cf2fe3b1
--- /dev/null
+++ b/e2e/fixtures/module-federation.consumer/expect.js
@@ -0,0 +1,22 @@
+const assert = require("assert");
+const { parseBuildResult } = require("../../../scripts/test-utils");
+const { files } = parseBuildResult(__dirname);
+
+const manifest = JSON.parse(files["mf-manifest.json"]);
+
+assert(
+ manifest.remotes[0].alias === 'producer'
+ && manifest.remotes[0].federationContainerName === 'producer'
+ && manifest.remotes[0].moduleName === 'App',
+ "should include mf remotes info"
+)
+
+assert(
+ manifest.shared.map(s => s.name).sort().join(",") === "react,react-dom",
+ "should include mf shared dependencies"
+)
+
+assert(
+ manifest.shared.every(s => s.assets.js.sync.length !== 0),
+ "should include mf shared assets"
+)
diff --git a/e2e/fixtures/module-federation.consumer/mako.config.json b/e2e/fixtures/module-federation.consumer/mako.config.json
new file mode 100644
index 000000000..dcfe4ae67
--- /dev/null
+++ b/e2e/fixtures/module-federation.consumer/mako.config.json
@@ -0,0 +1,15 @@
+{
+ "entry": {
+ "app1": "./src/index.ts"
+ },
+ "minify": false,
+ "moduleFederation": {
+ "name": "consumer",
+ "remotes": {
+ "producer": "producer@http://localhost:3000/remoteEntry.js"
+ },
+ "shared": { "react": { "eager": true }, "react-dom": { "eager": true } },
+ "manifest": true,
+ "implementation": "../../../../../packages/mako/node_modules/@module-federation/webpack-bundler-runtime"
+ }
+}
diff --git a/e2e/fixtures/module-federation.consumer/package.json b/e2e/fixtures/module-federation.consumer/package.json
new file mode 100644
index 000000000..723a9fb2d
--- /dev/null
+++ b/e2e/fixtures/module-federation.consumer/package.json
@@ -0,0 +1,10 @@
+{
+ "name": "mf-consumer",
+ "version": "0.0.1",
+ "dependencies": {
+ "react": "18.2.0",
+ "react-dom": "18.2.0",
+ "@types/react": "18.2.0",
+ "@types/react-dom": "18.2.0"
+ }
+}
diff --git a/e2e/fixtures/module-federation.consumer/public/index.html b/e2e/fixtures/module-federation.consumer/public/index.html
new file mode 100644
index 000000000..7e0bd8d29
--- /dev/null
+++ b/e2e/fixtures/module-federation.consumer/public/index.html
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/e2e/fixtures/module-federation.consumer/src/App.tsx b/e2e/fixtures/module-federation.consumer/src/App.tsx
new file mode 100644
index 000000000..0d9833d2e
--- /dev/null
+++ b/e2e/fixtures/module-federation.consumer/src/App.tsx
@@ -0,0 +1,26 @@
+import React from 'react';
+
+// @ts-ignore
+const RemoteComp = React.lazy(() => import('producer/App'));
+
+const App = () => {
+ return (
+
+
+
Consumer App
+
+
+
+
+
+ );
+};
+
+export default App;
diff --git a/e2e/fixtures/module-federation.consumer/src/bootstrap.tsx b/e2e/fixtures/module-federation.consumer/src/bootstrap.tsx
new file mode 100644
index 000000000..b597a4423
--- /dev/null
+++ b/e2e/fixtures/module-federation.consumer/src/bootstrap.tsx
@@ -0,0 +1,5 @@
+import React from 'react';
+import ReactDOM from 'react-dom';
+import App from './App';
+
+ReactDOM.render( , document.getElementById('root'));
diff --git a/e2e/fixtures/module-federation.consumer/src/index.ts b/e2e/fixtures/module-federation.consumer/src/index.ts
new file mode 100644
index 000000000..e59d6a0ad
--- /dev/null
+++ b/e2e/fixtures/module-federation.consumer/src/index.ts
@@ -0,0 +1 @@
+import './bootstrap';
diff --git a/e2e/fixtures/module-federation.producer/.gitignore b/e2e/fixtures/module-federation.producer/.gitignore
new file mode 100644
index 000000000..de4d1f007
--- /dev/null
+++ b/e2e/fixtures/module-federation.producer/.gitignore
@@ -0,0 +1,2 @@
+dist
+node_modules
diff --git a/e2e/fixtures/module-federation.producer/expect.js b/e2e/fixtures/module-federation.producer/expect.js
new file mode 100644
index 000000000..ab5ff0183
--- /dev/null
+++ b/e2e/fixtures/module-federation.producer/expect.js
@@ -0,0 +1,30 @@
+const assert = require("assert");
+const { parseBuildResult } = require("../../../scripts/test-utils");
+const { files } = parseBuildResult(__dirname);
+
+const manifest = JSON.parse(files["mf-manifest.json"]);
+
+assert(
+ manifest.metaData.remoteEntry.name === 'remoteEntry.js',
+ "should generate mf contanier entry"
+)
+
+assert(
+ manifest.exposes[0].name === 'App',
+ "should include mf exposes"
+)
+
+assert(
+ manifest.exposes[0].assets.js.sync.length !== 0,
+ "should include mf exposes assets"
+)
+
+assert(
+ manifest.shared.map(s => s.name).sort().join(",") === "react,react-dom",
+ "should include mf shared dependencies"
+)
+
+assert(
+ manifest.shared.every(s => s.assets.js.sync.length !== 0),
+ "should include mf shared assets"
+)
diff --git a/e2e/fixtures/module-federation.producer/mako.config.json b/e2e/fixtures/module-federation.producer/mako.config.json
new file mode 100644
index 000000000..e966ee81d
--- /dev/null
+++ b/e2e/fixtures/module-federation.producer/mako.config.json
@@ -0,0 +1,16 @@
+{
+ "entry": {
+ "app2": "./src/index.ts"
+ },
+ "publicPath": "auto",
+ "moduleFederation": {
+ "name": "producer",
+ "filename": "remoteEntry.js",
+ "exposes": {
+ "./App": "./src/App.tsx"
+ },
+ "shared": { "react": {}, "react-dom": {} },
+ "manifest": true,
+ "implementation": "../../../../../packages/mako/node_modules/@module-federation/webpack-bundler-runtime"
+ }
+}
diff --git a/e2e/fixtures/module-federation.producer/package.json b/e2e/fixtures/module-federation.producer/package.json
new file mode 100644
index 000000000..c803f552e
--- /dev/null
+++ b/e2e/fixtures/module-federation.producer/package.json
@@ -0,0 +1,10 @@
+{
+ "name": "mf-producer",
+ "version": "0.0.1",
+ "dependencies": {
+ "react": "18.2.0",
+ "react-dom": "18.2.0",
+ "@types/react": "18.2.0",
+ "@types/react-dom": "18.2.0"
+ }
+}
diff --git a/e2e/fixtures/module-federation.producer/public/index.html b/e2e/fixtures/module-federation.producer/public/index.html
new file mode 100644
index 000000000..5de48ef2f
--- /dev/null
+++ b/e2e/fixtures/module-federation.producer/public/index.html
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/e2e/fixtures/module-federation.producer/src/App.tsx b/e2e/fixtures/module-federation.producer/src/App.tsx
new file mode 100644
index 000000000..2a1875c7f
--- /dev/null
+++ b/e2e/fixtures/module-federation.producer/src/App.tsx
@@ -0,0 +1,18 @@
+import React from 'react';
+
+const App = () => {
+ return (
+
+
Widget App2
+
+ );
+};
+
+export default App;
diff --git a/e2e/fixtures/module-federation.producer/src/bootstrap.tsx b/e2e/fixtures/module-federation.producer/src/bootstrap.tsx
new file mode 100644
index 000000000..ae31e4134
--- /dev/null
+++ b/e2e/fixtures/module-federation.producer/src/bootstrap.tsx
@@ -0,0 +1,6 @@
+import React from 'react';
+import ReactDOM from 'react-dom';
+
+import App from './App';
+
+ReactDOM.render( , document.getElementById('root'));
diff --git a/e2e/fixtures/module-federation.producer/src/index.ts b/e2e/fixtures/module-federation.producer/src/index.ts
new file mode 100644
index 000000000..b93c7a026
--- /dev/null
+++ b/e2e/fixtures/module-federation.producer/src/index.ts
@@ -0,0 +1 @@
+import('./bootstrap');
diff --git a/examples/module-federation/host/.gitignore b/examples/module-federation/host/.gitignore
new file mode 100644
index 000000000..de4d1f007
--- /dev/null
+++ b/examples/module-federation/host/.gitignore
@@ -0,0 +1,2 @@
+dist
+node_modules
diff --git a/examples/module-federation/host/mako.config.json b/examples/module-federation/host/mako.config.json
new file mode 100644
index 000000000..bb73b47fb
--- /dev/null
+++ b/examples/module-federation/host/mako.config.json
@@ -0,0 +1,33 @@
+{
+ "entry": {
+ "app1": "./src/index.ts"
+ },
+ "minify": false,
+ "moduleFederation": {
+ "name": "mfHost",
+ "remotes": {
+ "widget": "mfWidget@http://localhost:3000/remoteEntry.js"
+ },
+ "shared": { "react": { "eager": true }, "react-dom": { "eager": true } },
+ "implementation": "../../../../../packages/mako/node_modules/@module-federation/webpack-bundler-runtime"
+ },
+ "experimental": {
+ "centralEnsure": false
+ },
+ "define": {
+ "process.env.SOCKET_SERVER": "\"http://localhost:3001\""
+ },
+ "codeSplitting": {
+ "strategy": "advanced",
+ "options": {
+ "groups": [
+ {
+ "name": "vendor",
+ "allowChunks": "all",
+ "minSize": 1,
+ "test": "node_modules"
+ }
+ ]
+ }
+ }
+}
diff --git a/examples/module-federation/host/package.json b/examples/module-federation/host/package.json
new file mode 100644
index 000000000..1f3c721bc
--- /dev/null
+++ b/examples/module-federation/host/package.json
@@ -0,0 +1,10 @@
+{
+ "name": "mf-host",
+ "version": "0.0.1",
+ "dependencies": {
+ "react": "18.2.0",
+ "react-dom": "18.2.0",
+ "@types/react": "18.2.0",
+ "@types/react-dom": "18.2.0"
+ }
+}
diff --git a/examples/module-federation/host/public/index.html b/examples/module-federation/host/public/index.html
new file mode 100644
index 000000000..7e0bd8d29
--- /dev/null
+++ b/examples/module-federation/host/public/index.html
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/examples/module-federation/host/src/App.tsx b/examples/module-federation/host/src/App.tsx
new file mode 100644
index 000000000..1cc8f71ef
--- /dev/null
+++ b/examples/module-federation/host/src/App.tsx
@@ -0,0 +1,31 @@
+import React from 'react';
+// import Widget1 from 'widget/App1';
+// import Widget2 from 'widget/App2';
+
+const Widget1 = React.lazy(() => import('widget/App1'));
+const Widget2 = React.lazy(() => import('widget/App2'));
+
+const App = () => {
+ return (
+
+
+
Host App
+
+
+
+
+
+
+
+
+ );
+};
+
+export default App;
diff --git a/examples/module-federation/host/src/bootstrap.tsx b/examples/module-federation/host/src/bootstrap.tsx
new file mode 100644
index 000000000..b597a4423
--- /dev/null
+++ b/examples/module-federation/host/src/bootstrap.tsx
@@ -0,0 +1,5 @@
+import React from 'react';
+import ReactDOM from 'react-dom';
+import App from './App';
+
+ReactDOM.render( , document.getElementById('root'));
diff --git a/examples/module-federation/host/src/index.ts b/examples/module-federation/host/src/index.ts
new file mode 100644
index 000000000..e59d6a0ad
--- /dev/null
+++ b/examples/module-federation/host/src/index.ts
@@ -0,0 +1 @@
+import './bootstrap';
diff --git a/examples/module-federation/widget/.gitignore b/examples/module-federation/widget/.gitignore
new file mode 100644
index 000000000..de4d1f007
--- /dev/null
+++ b/examples/module-federation/widget/.gitignore
@@ -0,0 +1,2 @@
+dist
+node_modules
diff --git a/examples/module-federation/widget/mako.config.json b/examples/module-federation/widget/mako.config.json
new file mode 100644
index 000000000..bf67e0f18
--- /dev/null
+++ b/examples/module-federation/widget/mako.config.json
@@ -0,0 +1,36 @@
+{
+ "entry": {
+ "app2": "./src/index.ts"
+ },
+ "minify": false,
+ "publicPath": "auto",
+ "moduleFederation": {
+ "name": "mfWidget",
+ "filename": "remoteEntry.js",
+ "exposes": {
+ "./App1": "./src/App1.tsx",
+ "./App2": "./src/App2.tsx"
+ },
+ "shared": { "react": {}, "react-dom": {} },
+ "runtimePlugins": [],
+ "implementation": "../../../../../packages/mako/node_modules/@module-federation/webpack-bundler-runtime"
+ },
+ "experimental": {
+ "centralEnsure": false
+ },
+ "define": {
+ "process.env.SOCKET_SERVER": "\"http://localhost:3000\""
+ },
+ "codeSplitting": {
+ "strategy": "advanced",
+ "options": {
+ "groups": [
+ {
+ "name": "vendor",
+ "allowChunks": "async",
+ "test": "node_modules"
+ }
+ ]
+ }
+ }
+}
diff --git a/examples/module-federation/widget/package.json b/examples/module-federation/widget/package.json
new file mode 100644
index 000000000..2c7c3154d
--- /dev/null
+++ b/examples/module-federation/widget/package.json
@@ -0,0 +1,10 @@
+{
+ "name": "mf-widget",
+ "version": "0.0.1",
+ "dependencies": {
+ "react": "18.2.0",
+ "react-dom": "18.2.0",
+ "@types/react": "18.2.0",
+ "@types/react-dom": "18.2.0"
+ }
+}
diff --git a/examples/module-federation/widget/public/index.html b/examples/module-federation/widget/public/index.html
new file mode 100644
index 000000000..5de48ef2f
--- /dev/null
+++ b/examples/module-federation/widget/public/index.html
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/examples/module-federation/widget/src/App1.tsx b/examples/module-federation/widget/src/App1.tsx
new file mode 100644
index 000000000..66fb59110
--- /dev/null
+++ b/examples/module-federation/widget/src/App1.tsx
@@ -0,0 +1,25 @@
+import React, { Suspense } from 'react';
+
+const Lazy = React.lazy(() => import('./lazy'));
+
+const App = () => {
+ return (
+
+
Widget App1
+
+
+
+
+
+
+ );
+};
+
+export default App;
diff --git a/examples/module-federation/widget/src/App2.tsx b/examples/module-federation/widget/src/App2.tsx
new file mode 100644
index 000000000..2a1875c7f
--- /dev/null
+++ b/examples/module-federation/widget/src/App2.tsx
@@ -0,0 +1,18 @@
+import React from 'react';
+
+const App = () => {
+ return (
+
+
Widget App2
+
+ );
+};
+
+export default App;
diff --git a/examples/module-federation/widget/src/bootstrap.tsx b/examples/module-federation/widget/src/bootstrap.tsx
new file mode 100644
index 000000000..39c8c02eb
--- /dev/null
+++ b/examples/module-federation/widget/src/bootstrap.tsx
@@ -0,0 +1,6 @@
+import React from 'react';
+import ReactDOM from 'react-dom';
+
+import App from './App1';
+
+ReactDOM.render( , document.getElementById('root'));
diff --git a/examples/module-federation/widget/src/index.ts b/examples/module-federation/widget/src/index.ts
new file mode 100644
index 000000000..b93c7a026
--- /dev/null
+++ b/examples/module-federation/widget/src/index.ts
@@ -0,0 +1 @@
+import('./bootstrap');
diff --git a/examples/module-federation/widget/src/lazy.less b/examples/module-federation/widget/src/lazy.less
new file mode 100644
index 000000000..7e30e7dbb
--- /dev/null
+++ b/examples/module-federation/widget/src/lazy.less
@@ -0,0 +1,3 @@
+.lazy {
+ color: green;
+}
diff --git a/examples/module-federation/widget/src/lazy.tsx b/examples/module-federation/widget/src/lazy.tsx
new file mode 100644
index 000000000..e0edcac47
--- /dev/null
+++ b/examples/module-federation/widget/src/lazy.tsx
@@ -0,0 +1,4 @@
+import React from 'react';
+import './lazy.less';
+
+export default () => Lazy
;
diff --git a/packages/mako/binding.d.ts b/packages/mako/binding.d.ts
index 7dd433296..755640c86 100644
--- a/packages/mako/binding.d.ts
+++ b/packages/mako/binding.d.ts
@@ -258,9 +258,34 @@ export interface BuildParams {
| {
logServerComponent: 'error' | 'ignore';
};
+ moduleFederation?: {
+ name: string;
+ // filename?: string;
+ exposes?: Record;
+ shared: Record<
+ string,
+ {
+ singleton?: bool;
+ strictVersion?: bool;
+ requiredVersion?: string;
+ /* eager?: bool; */ /* shareScope?: string; */
+ }
+ >;
+ remotes?: Record;
+ runtimePlugins?: string[];
+ shareScope?: string;
+ shareStrategy?: 'version-first' | 'loaded-first';
+ implementation: string;
+ };
experimental?: {
webpackSyntaxValidate?: string[];
+ requireContext?: bool;
+ ignoreNonLiteralRequire?: bool;
+ magicComment?: bool;
+ detectCircularDependence?: { ignore?: string[] };
rustPlugins?: Array<[string, any]>;
+ centralEnsure?: bool;
+ importsChecker?: bool;
};
watch?: {
ignoredPaths?: string[];
diff --git a/packages/mako/package.json b/packages/mako/package.json
index aaf57b95d..01d4fa07d 100644
--- a/packages/mako/package.json
+++ b/packages/mako/package.json
@@ -36,7 +36,8 @@
"react-refresh": "^0.14.0",
"resolve": "^1.22.8",
"semver": "^7.6.2",
- "yargs-parser": "^21.1.1"
+ "yargs-parser": "^21.1.1",
+ "@module-federation/webpack-bundler-runtime": "^0.8.0"
},
"devDependencies": {
"@napi-rs/cli": "^2.18.0",
diff --git a/packages/mako/src/index.ts b/packages/mako/src/index.ts
index f70502f81..feb410a62 100644
--- a/packages/mako/src/index.ts
+++ b/packages/mako/src/index.ts
@@ -59,7 +59,7 @@ export async function build(params: BuildParams) {
await rustPluginResolver(rustPlugins);
}
- let makoConfig: any = {};
+ let makoConfig: binding.BuildParams['config'] = {};
let makoConfigPath = path.join(params.root, 'mako.config.json');
if (fs.existsSync(makoConfigPath)) {
try {
@@ -129,9 +129,9 @@ export async function build(params: BuildParams) {
},
});
- if (makoConfig?.sass || params.config?.sass) {
+ if ((makoConfig as any)?.sass || params.config?.sass) {
const sassOpts = {
- ...(makoConfig?.sass || {}),
+ ...((makoConfig as any)?.sass || {}),
...(params.config?.sass || {}),
};
let sass = sassLoader(null, sassOpts);
@@ -151,6 +151,31 @@ export async function build(params: BuildParams) {
});
}
+ if (makoConfig?.moduleFederation || params.config?.moduleFederation) {
+ // @ts-ignore
+ const moduleFederation = {
+ ...(makoConfig.moduleFederation || {}),
+ ...(params.config.moduleFederation || {}),
+ };
+ if (!moduleFederation.implementation) {
+ // @ts-ignore
+ moduleFederation.implementation = require.resolve(
+ '@module-federation/webpack-bundler-runtime',
+ );
+ }
+
+ if (moduleFederation?.shared) {
+ if (Array.isArray(moduleFederation.shared)) {
+ moduleFederation.shared = moduleFederation.shared.reduce(
+ (acc, cur) => ({ ...acc, [cur]: {} }),
+ {},
+ );
+ }
+ }
+ // @ts-ignore
+ params.config.moduleFederation = moduleFederation;
+ }
+
// support dump mako config
if (process.env.DUMP_MAKO_CONFIG) {
const configFile = path.join(params.root, 'mako.config.json');
@@ -187,7 +212,7 @@ export async function build(params: BuildParams) {
return plugin;
}
});
- makoConfig.plugins?.forEach((plugin: any) => {
+ (makoConfig as any).plugins?.forEach((plugin: any) => {
if (typeof plugin === 'string') {
let fn = require(
resolve.sync(plugin, {
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 5d45e57e8..ffc945928 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -191,6 +191,36 @@ importers:
specifier: 18.2.0
version: 18.2.0(react@18.2.0)
+ e2e/fixtures/module-federation.consumer:
+ dependencies:
+ '@types/react':
+ specifier: 18.2.0
+ version: 18.2.0
+ '@types/react-dom':
+ specifier: 18.2.0
+ version: 18.2.0
+ react:
+ specifier: 18.2.0
+ version: 18.2.0
+ react-dom:
+ specifier: 18.2.0
+ version: 18.2.0(react@18.2.0)
+
+ e2e/fixtures/module-federation.producer:
+ dependencies:
+ '@types/react':
+ specifier: 18.2.0
+ version: 18.2.0
+ '@types/react-dom':
+ specifier: 18.2.0
+ version: 18.2.0
+ react:
+ specifier: 18.2.0
+ version: 18.2.0
+ react-dom:
+ specifier: 18.2.0
+ version: 18.2.0(react@18.2.0)
+
examples/config-externals: {}
examples/dead-simple: {}
@@ -509,6 +539,9 @@ importers:
packages/mako:
dependencies:
+ '@module-federation/webpack-bundler-runtime':
+ specifier: ^0.8.0
+ version: 0.8.0
'@swc/helpers':
specifier: 0.5.1
version: 0.5.1
@@ -652,7 +685,7 @@ packages:
'@emotion/hash': 0.8.0
'@emotion/unitless': 0.7.5
classnames: 2.3.2
- csstype: 3.1.2
+ csstype: 3.1.3
rc-util: 5.37.0(react-dom@18.2.0)(react@18.2.0)
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
@@ -5006,6 +5039,30 @@ packages:
resolution: {integrity: sha512-9b8mPpKrfeGRuhFH5iO1iwCLeIIsV6+H1sRfxbkoGXIyQE2BTsPd9zqSqQJ+pv5sJ/hT5M1zvOFL02MnEezFug==}
dev: true
+ /@module-federation/error-codes@0.8.0:
+ resolution: {integrity: sha512-xU0eUA0xTUx93Li/eYCZ+kuGP9qt+8fEaOu+i6U9jJP1RYONvPifibfLNo4SQszQTzqfGViyrx1O3uiA7XUYQQ==}
+ dev: false
+
+ /@module-federation/runtime@0.8.0:
+ resolution: {integrity: sha512-UfDsJYAFOyJoErpmjf1sy8d2WXHGitFsSQrIiDzNpDHv4SzHgjuhWQeAuJKlQq2zdE/F4IPhkHgTatQigRKZCA==}
+ dependencies:
+ '@module-federation/error-codes': 0.8.0
+ '@module-federation/sdk': 0.8.0
+ dev: false
+
+ /@module-federation/sdk@0.8.0:
+ resolution: {integrity: sha512-V2cNGO//sWCyHTaQ0iTcoslolqVgdBIBOkZVLyk9AkZ4B3CO49pe/TmIIaVs9jVg3GO+ZmmazBFKRkqdn2PdRg==}
+ dependencies:
+ isomorphic-rslog: 0.0.6
+ dev: false
+
+ /@module-federation/webpack-bundler-runtime@0.8.0:
+ resolution: {integrity: sha512-NwkQbgUgh0ubwmPR/36YNuOaITkWPTYnJyYqi9vgxHBDp4tURnRI2b1ocG2Gw8c9sEW88ImWYMeF1qT58hQ32w==}
+ dependencies:
+ '@module-federation/runtime': 0.8.0
+ '@module-federation/sdk': 0.8.0
+ dev: false
+
/@monaco-editor/loader@1.3.3(monaco-editor@0.38.0):
resolution: {integrity: sha512-6KKF4CTzcJiS8BJwtxtfyYt9shBiEv32ateQ9T4UVogwn4HM/uPo9iJd2Dmbkpz8CM6Y0PDUpjnZzCwC+eYo2Q==}
peerDependencies:
@@ -5862,7 +5919,6 @@ packages:
/@types/prop-types@15.7.5:
resolution: {integrity: sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==}
- dev: true
/@types/ps-tree@1.1.2:
resolution: {integrity: sha512-ZREFYlpUmPQJ0esjxoG1fMvB2HNaD3z+mjqdSosZvd3RalncI9NEur73P8ZJz4YQdL64CmV1w0RuqoRUlhQRBw==}
@@ -5876,6 +5932,12 @@ packages:
resolution: {integrity: sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==}
dev: true
+ /@types/react-dom@18.2.0:
+ resolution: {integrity: sha512-8yQrvS6sMpSwIovhPOwfyNf2Wz6v/B62LFSVYQ85+Rq3tLsBIG7rP5geMxaijTUxSkrO6RzN/IRuIAADYQsleA==}
+ dependencies:
+ '@types/react': 18.2.7
+ dev: false
+
/@types/react-dom@18.2.4:
resolution: {integrity: sha512-G2mHoTMTL4yoydITgOGwWdWMVd8sNgyEP85xVmMKAPUBwQWm9wBPQUmvbeF4V3WBY1P7mmL4BkjQ0SqUpf1snw==}
dependencies:
@@ -5906,20 +5968,26 @@ packages:
'@types/react': 18.2.7
dev: true
+ /@types/react@18.2.0:
+ resolution: {integrity: sha512-0FLj93y5USLHdnhIhABk83rm8XEGA7kH3cr+YUlvxoUGp1xNt/DINUMvqPxLyOQMzLmZe8i4RTHbvb8MC7NmrA==}
+ dependencies:
+ '@types/prop-types': 15.7.5
+ '@types/scheduler': 0.16.3
+ csstype: 3.1.3
+ dev: false
+
/@types/react@18.2.7:
resolution: {integrity: sha512-ojrXpSH2XFCmHm7Jy3q44nXDyN54+EYKP2lBhJ2bqfyPj6cIUW/FZW/Csdia34NQgq7KYcAlHi5184m4X88+yw==}
dependencies:
'@types/prop-types': 15.7.5
'@types/scheduler': 0.16.3
csstype: 3.1.3
- dev: true
/@types/resolve@1.20.6:
resolution: {integrity: sha512-A4STmOXPhMUtHH+S6ymgE2GiBSMqf4oTvcQZMcHzokuTLVYzXTB8ttjcgxOVaAp2lGwEdzZ0J+cRbbeevQj1UQ==}
/@types/scheduler@0.16.3:
resolution: {integrity: sha512-5cJ8CB4yAx7BH1oMvdU0Jh9lrEXyPkar6F9G/ERswkCuvP4KQZfZkSjcMbAICCpQTN4OuZn8tz0HiKv9TGZgrQ==}
- dev: true
/@types/semver@7.5.8:
resolution: {integrity: sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==}
@@ -9052,7 +9120,6 @@ packages:
/csstype@3.1.3:
resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==}
- dev: true
/current-script-polyfill@1.0.0:
resolution: {integrity: sha512-qv8s+G47V6Hq+g2kRE5th+ASzzrL7b6l+tap1DHKK25ZQJv3yIFhH96XaQ7NGL+zRW3t/RDbweJf/dJDe5Z5KA==}
@@ -11803,6 +11870,11 @@ packages:
whatwg-fetch: 3.6.2
dev: true
+ /isomorphic-rslog@0.0.6:
+ resolution: {integrity: sha512-HM0q6XqQ93psDlqvuViNs/Ea3hAyGDkIdVAHlrEocjjAwGrs1fZ+EdQjS9eUPacnYB7Y8SoDdSY3H8p3ce205A==}
+ engines: {node: '>=14.17.6'}
+ dev: false
+
/isomorphic-unfetch@4.0.2:
resolution: {integrity: sha512-1Yd+CF/7al18/N2BDbsLBcp6RO3tucSW+jcLq24dqdX5MNbCNTw1z4BsGsp4zNmjr/Izm2cs/cEqZPp4kvWSCA==}
dependencies:
diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml
index 58b2f7b75..b5d16bf03 100644
--- a/pnpm-workspace.yaml
+++ b/pnpm-workspace.yaml
@@ -3,7 +3,7 @@ packages:
- "client"
- "crates/binding"
- "packages/*"
- - "crates/mako/test/compile/auto-code-splitting"
+ - "e2e/fixtures/module-federation.*"
- "e2e/fixtures.umi/react-16"
- "e2e/fixtures.umi/config.less.plugins"
- "e2e/fixtures.umi/stable-hash"