use std::borrow::Cow;
use async_trait::async_trait;
use rspack_cacheable::{cacheable, cacheable_dyn};
use rspack_collections::{Identifiable, Identifier};
use rspack_core::{
AsyncDependenciesBlock, AsyncDependenciesBlockIdentifier, BoxDependency, BoxModule, BuildContext,
BuildInfo, BuildMeta, BuildMetaExportsType, BuildResult, ChunkGroupOptions, CodeGenerationResult,
CodeGenerationRuntimeRequirementsWrite, Compilation, Context, DependenciesBlock, Dependency,
DependencyId, DependencyType, ExportsArgument, FactoryMeta, GroupOptions, LibIdentOptions,
Module, ModuleCodeGenerationContext, ModuleCodeTemplate, ModuleDependency, ModuleGraph,
ModuleIdentifier, ModuleType, RuntimeGlobals, RuntimeGlobalsRenderMode, RuntimeSpec, SourceType,
StaticExportsDependency, StaticExportsSpec, impl_module_meta_info, impl_source_map_config,
module_update_hash,
rspack_sources::{BoxSource, RawStringSource, SourceExt},
runtime_mode::RuntimeMode,
};
use rspack_error::{Result, impl_empty_diagnosable_trait};
use rspack_hash::{RspackHashDigest, RspackHasher};
use rspack_util::{json_stringify_str, source_map::SourceMapKind};
use rustc_hash::FxHashSet;
use super::{
container_exposed_dependency::ContainerExposedDependency, container_plugin::ExposeOptions,
};
use crate::{
ShareScope,
utils::{json_stringify, module_identifier_namespace, module_require_scope_name},
};
#[impl_source_map_config]
#[cacheable]
#[derive(Debug)]
pub struct ContainerEntryModule {
blocks: Vec<AsyncDependenciesBlockIdentifier>,
dependencies: Vec<DependencyId>,
identifier: ModuleIdentifier,
lib_ident: String,
exposes: Vec<(String, ExposeOptions)>,
share_scope: ShareScope,
factory_meta: Option<FactoryMeta>,
build_info: BuildInfo,
build_meta: BuildMeta,
enhanced: bool,
request: Option<String>,
version: Option<String>,
dependency_type: DependencyType,
name: String,
}
impl ContainerEntryModule {
pub fn new(
name: String,
exposes: Vec<(String, ExposeOptions)>,
share_scope: ShareScope,
enhanced: bool,
runtime_mode: RuntimeMode,
) -> Self {
let namespace = module_identifier_namespace(runtime_mode);
let lib_ident = format!("{namespace}/container/entry/{name}");
Self {
blocks: Vec::new(),
dependencies: Vec::new(),
identifier: ModuleIdentifier::from(format!(
"container entry ({}) {}",
share_scope.key(),
json_stringify(&exposes),
)),
lib_ident,
exposes,
share_scope,
factory_meta: None,
build_info: BuildInfo {
strict: true,
top_level_declarations: Some(FxHashSet::default()),
..Default::default()
},
build_meta: BuildMeta::default().with_exports_type(BuildMetaExportsType::Namespace),
enhanced,
request: None,
version: None,
dependency_type: DependencyType::ContainerEntry,
source_map_kind: SourceMapKind::empty(),
name,
}
}
pub fn new_share_container_entry(
name: String,
request: String,
version: String,
runtime_mode: RuntimeMode,
) -> Self {
let namespace = module_identifier_namespace(runtime_mode);
let lib_ident = format!("{namespace}/share/container/{name}");
Self {
blocks: Vec::new(),
dependencies: Vec::new(),
identifier: ModuleIdentifier::from(format!("share container entry {}@{}", &name, &version,)),
lib_ident,
exposes: vec![],
share_scope: ShareScope::Multiple(vec![]),
factory_meta: None,
build_info: BuildInfo {
strict: true,
top_level_declarations: Some(FxHashSet::default()),
..Default::default()
},
build_meta: BuildMeta::default().with_exports_type(BuildMetaExportsType::Namespace),
enhanced: false,
request: Some(request),
version: Some(version),
dependency_type: DependencyType::ShareContainerEntry,
source_map_kind: SourceMapKind::empty(),
name,
}
}
pub fn exposes(&self) -> &[(String, ExposeOptions)] {
&self.exposes
}
pub fn name(&self) -> &str {
&self.name
}
}
impl Identifiable for ContainerEntryModule {
fn identifier(&self) -> Identifier {
self.identifier
}
}
impl DependenciesBlock for ContainerEntryModule {
fn add_block_id(&mut self, block: AsyncDependenciesBlockIdentifier) {
self.blocks.push(block)
}
fn get_blocks(&self) -> &[AsyncDependenciesBlockIdentifier] {
&self.blocks
}
fn add_dependency_id(&mut self, dependency: DependencyId) {
self.dependencies.push(dependency)
}
fn remove_dependency_id(&mut self, dependency: DependencyId) {
self.dependencies.retain(|d| d != &dependency)
}
fn get_dependencies(&self) -> &[DependencyId] {
&self.dependencies
}
}
#[cacheable_dyn]
#[async_trait]
impl Module for ContainerEntryModule {
impl_module_meta_info!();
fn size(&self, _source_type: Option<&SourceType>, _compilation: Option<&Compilation>) -> f64 {
42.0
}
fn module_type(&self) -> &ModuleType {
if self.dependency_type == DependencyType::ShareContainerEntry {
&ModuleType::ShareContainerShared
} else {
&ModuleType::JsDynamic
}
}
fn source_types(&self, _module_graph: &ModuleGraph) -> &[SourceType] {
&[SourceType::JavaScript, SourceType::Expose]
}
fn source(&self) -> Option<&BoxSource> {
None
}
fn readable_identifier(&self, _context: &Context) -> Cow<'_, str> {
if self.dependency_type == DependencyType::ShareContainerEntry {
"share container entry".into()
} else {
"container entry".into()
}
}
fn lib_ident(&self, _options: LibIdentOptions) -> Option<Cow<'_, str>> {
Some(self.lib_ident.as_str().into())
}
async fn build(
mut self: Box<Self>,
_build_context: BuildContext,
_: Option<&Compilation>,
) -> Result<BuildResult> {
let mut blocks = vec![];
let mut dependencies: Vec<BoxDependency> = vec![];
if self.dependency_type == DependencyType::ShareContainerEntry {
dependencies.push(Box::new(StaticExportsDependency::new(
StaticExportsSpec::Array(vec!["get".into(), "init".into()]),
false,
)));
if let Some(request) = &self.request {
let dep = ContainerExposedDependency::new_shared_fallback(request.clone());
dependencies.push(Box::new(dep));
}
} else {
for (name, options) in &self.exposes {
let mut block = AsyncDependenciesBlock::new(
self.identifier,
None,
Some(name),
options
.import
.iter()
.map(|request| {
Box::new(ContainerExposedDependency::new(
name.clone(),
request.clone(),
)) as Box<dyn Dependency>
})
.collect(),
None,
);
block.set_group_options(GroupOptions::ChunkGroup(
ChunkGroupOptions::default().name_optional(options.name.clone()),
));
blocks.push(Box::new(block));
}
dependencies.push(Box::new(StaticExportsDependency::new(
StaticExportsSpec::Array(vec!["get".into(), "init".into()]),
false,
)));
}
Ok(BuildResult {
module: BoxModule::new(self),
dependencies,
blocks,
optimization_bailouts: vec![],
})
}
async fn code_generation(
&self,
code_generation_context: &mut ModuleCodeGenerationContext,
) -> Result<CodeGenerationResult> {
let ModuleCodeGenerationContext {
compilation,
runtime_template,
..
} = code_generation_context;
let mut code_generation_result = CodeGenerationResult::default();
let require_name = module_require_scope_name(compilation, runtime_template);
let runtime_argument = require_name.clone();
if self.dependency_type == DependencyType::ShareContainerEntry {
let module_graph = compilation.get_module_graph();
let mut factory = String::new();
for dependency_id in self.get_dependencies() {
let dependency = module_graph.dependency_by_id(dependency_id);
if let Some(dependency) = dependency
.as_any()
.downcast_ref::<ContainerExposedDependency>()
&& *dependency.dependency_type() == DependencyType::ShareContainerFallback
{
let request: &str = dependency.user_request();
let module_expr = runtime_template.module_raw(compilation, dependency_id, request, false);
factory = runtime_template.returning_function(&module_expr, "");
}
}
let federation_global = format!("{require_name}.federation");
let install_initial_consumes_call = format!(
r#"localBundlerRuntime.installInitialConsumes({{
installedModules: localInstalledModules,
initialConsumes: {require_name}.consumesLoadingData.initialConsumes,
moduleToHandlerMapping: {require_name}.federation.consumesLoadingModuleToHandlerMapping || {{}},
webpackRequire: {runtime_argument},
asyncLoad: true
}})"#,
);
let install_initial_consumes_fn =
runtime_template.returning_function(&install_initial_consumes_call, "");
let init_body = format!(
r#"
var installedModules = {{}};
{federation_global}.instance = mfInstance;
{federation_global}.bundlerRuntime = bundlerRuntime;
// Save parameters to local variables to avoid closure issues
var localBundlerRuntime = bundlerRuntime;
var localInstalledModules = installedModules;
if(!{require_name}.consumesLoadingData){{return; }}
{federation_global}.installInitialConsumes = {install_initial_consumes_fn};
return {federation_global}.installInitialConsumes();
"#,
);
let init_share_container_fn =
runtime_template.basic_function("mfInstance, bundlerRuntime", &init_body);
let source = format!(
r#"
{require_name}.federation = {{ instance: undefined,bundlerRuntime: undefined }}
var factory = ()=>{factory};
var initShareContainer = {init_share_container_fn};
{runtime}({exports}, {{
get: function() {{ return factory;}},
init: function() {{ return initShareContainer;}}
}});
"#,
require_name = require_name,
runtime = runtime_template.render_runtime_globals(&RuntimeGlobals::DEFINE_PROPERTY_GETTERS),
exports = runtime_template.render_exports_argument(ExportsArgument::Exports),
factory = factory,
init_share_container_fn = init_share_container_fn
);
code_generation_result =
code_generation_result.with_javascript(RawStringSource::from(source).boxed());
code_generation_result.add(SourceType::Expose, RawStringSource::from_static("").boxed());
return Ok(code_generation_result);
}
runtime_template
.runtime_requirements_mut()
.insert(RuntimeGlobals::CURRENT_REMOTE_GET_SCOPE);
let module_map = ExposeModuleMap::new(compilation, self, runtime_template);
let mut module_map_runtime_requirements = *runtime_template.runtime_requirements();
module_map_runtime_requirements.remove(RuntimeGlobals::CURRENT_REMOTE_GET_SCOPE);
let module_map_str = module_map.render(runtime_template);
let source = if self.enhanced {
let define_property_getters =
runtime_template.render_runtime_globals(&RuntimeGlobals::DEFINE_PROPERTY_GETTERS);
let get_container = format!("{require_name}.getContainer");
let init_container = format!("{require_name}.initContainer");
format!(
r#"
{}({}, {{
get: {},
init: {}
}});"#,
define_property_getters,
runtime_template.render_exports_argument(ExportsArgument::Exports),
runtime_template.returning_function(&get_container, ""),
runtime_template.returning_function(&init_container, ""),
)
} else {
let current_remote_get_scope =
runtime_template.render_runtime_globals(&RuntimeGlobals::CURRENT_REMOTE_GET_SCOPE);
let current_remote_get_scope_setter =
(runtime_template.render_mode() == RuntimeGlobalsRenderMode::RspackExport).then(|| {
RuntimeGlobals::CURRENT_REMOTE_GET_SCOPE
.to_rspack_export_setter_name()
.expect("current remote get scope should have an export setter")
});
let render_current_remote_get_scope_assignment = |value: &str| {
if let Some(setter) = ¤t_remote_get_scope_setter {
format!("{setter}({value});")
} else {
format!("{current_remote_get_scope} = {value};")
}
};
format!(
r#"
var moduleMap = {module_map_str};
var get = function(module, getScope) {{
{set_current_remote_get_scope}
getScope = (
{has_own_property}(moduleMap, module)
? moduleMap[module]()
: Promise.resolve().then({get_scope_reject})
);
{clear_current_remote_get_scope}
return getScope;
}}
var init = function(shareScope, initScope) {{
if (!{share_scope_map}) return;
var name = {share_scope};
var oldScope = {share_scope_map}[name];
if(oldScope && oldScope !== shareScope) throw new Error("Container initialization failed as it has already been initialized with a different share scope");
{share_scope_map}[name] = shareScope;
return {initialize_sharing}(name, initScope);
}}
{define_property_getters}({exports}, {{
get: {export_get},
init: {export_init}
}});"#,
exports = runtime_template.render_exports_argument(ExportsArgument::Exports),
set_current_remote_get_scope = render_current_remote_get_scope_assignment("getScope"),
clear_current_remote_get_scope = render_current_remote_get_scope_assignment("undefined"),
has_own_property =
runtime_template.render_runtime_globals(&RuntimeGlobals::HAS_OWN_PROPERTY),
share_scope_map = runtime_template.render_runtime_globals(&RuntimeGlobals::SHARE_SCOPE_MAP),
share_scope = json_stringify_str(match &self.share_scope {
ShareScope::Single(s) => s.as_str(),
ShareScope::Multiple(_) => {
panic!("ContainerEntryModule: enhanced=false only supports string share scope")
}
}),
initialize_sharing =
runtime_template.render_runtime_globals(&RuntimeGlobals::INITIALIZE_SHARING),
define_property_getters =
runtime_template.render_runtime_globals(&RuntimeGlobals::DEFINE_PROPERTY_GETTERS),
get_scope_reject = runtime_template.basic_function(
"",
r#"throw new Error('Module "' + module + '" does not exist in container.');"#
),
export_get = runtime_template.returning_function("get", ""),
export_init = runtime_template.returning_function("init", ""),
)
};
code_generation_result =
code_generation_result.with_javascript(RawStringSource::from(source).boxed());
code_generation_result.add(SourceType::Expose, RawStringSource::from_static("").boxed());
if !self.enhanced {
code_generation_result
.data
.insert(CodeGenerationRuntimeRequirementsWrite {
runtime_requirements: RuntimeGlobals::CURRENT_REMOTE_GET_SCOPE,
});
}
if self.enhanced {
code_generation_result
.data
.insert(CodeGenerationDataExpose {
module_map,
module_map_runtime_requirements,
share_scope: self.share_scope.clone(),
});
}
Ok(code_generation_result)
}
async fn get_runtime_hash(
&self,
compilation: &Compilation,
runtime: Option<&RuntimeSpec>,
) -> Result<RspackHashDigest> {
let mut hasher = RspackHasher::from(&compilation.options.output);
module_update_hash(self, &mut hasher, compilation, runtime);
Ok(hasher.digest(&compilation.options.output.hash_digest))
}
}
impl_empty_diagnosable_trait!(ContainerEntryModule);
#[derive(Debug, Clone)]
pub struct ExposeModuleMap(Vec<(String, String)>);
impl ExposeModuleMap {
pub fn new(
compilation: &Compilation,
container_entry_module: &ContainerEntryModule,
runtime_template: &mut ModuleCodeTemplate,
) -> Self {
let mut module_map = vec![];
let module_graph = compilation.get_module_graph();
for block_id in container_entry_module.get_blocks() {
let block = module_graph
.block_by_id(block_id)
.expect("should have block");
let modules_iter = block.get_dependencies().iter().map(|dependency_id| {
let dep = module_graph.dependency_by_id(dependency_id);
let dep = dep
.downcast_ref::<ContainerExposedDependency>()
.expect("dependencies of ContainerEntryModule should be ContainerExposedDependency");
let name = dep.exposed_name.as_str();
let module = module_graph.get_module_by_dependency_id(dependency_id);
let user_request = dep.user_request();
(name, module, user_request, dependency_id)
});
let name = modules_iter.clone().next().expect("should have item").0;
let str = if modules_iter
.clone()
.any(|(_, module, _, _)| module.is_none())
{
runtime_template.throw_missing_module_error_block(
&modules_iter
.map(|(_, _, request, _)| request)
.collect::<Vec<&str>>()
.join(", "),
)
} else {
let block_promise = runtime_template.block_promise(Some(block_id), compilation, "");
let modules = modules_iter
.map(|(_, _, request, dependency_id)| {
runtime_template.module_raw(compilation, dependency_id, request, false)
})
.collect::<Vec<_>>()
.join(", ");
let module_raw = runtime_template
.returning_function(&runtime_template.returning_function(&modules, ""), "");
format!("return {block_promise}.then({module_raw});")
};
module_map.push((name.to_string(), str));
}
Self(module_map)
}
pub fn render(&self, runtime_template: &mut ModuleCodeTemplate) -> String {
let module_map = self
.0
.iter()
.map(|(name, factory)| {
format!(
"{}: {},",
json_stringify_str(name),
runtime_template.basic_function("", factory)
)
})
.collect::<Vec<_>>()
.join("\n");
format!(
r#"{{
{module_map}
}}"#
)
}
}
#[derive(Debug, Clone)]
pub struct CodeGenerationDataExpose {
pub module_map: ExposeModuleMap,
pub module_map_runtime_requirements: RuntimeGlobals,
pub share_scope: ShareScope,
}