use std::{
any::Any,
borrow::Cow,
fmt::{Debug, Display, Formatter},
hash::Hash,
sync::Arc,
};
use async_trait::async_trait;
use json::JsonValue;
use rspack_cacheable::{
cacheable, cacheable_dyn,
with::{AsInner, AsInnerConverter, AsMap, AsOption, AsPreset, AsVec},
};
use rspack_collections::{Identifiable, Identifier, IdentifierMap, IdentifierSet};
use rspack_error::{Diagnosable, Result};
use rspack_fs::ReadableFileSystem;
use rspack_hash::RspackHashDigest;
use rspack_paths::ArcPathSet;
use rspack_sources::BoxSource;
use rspack_util::{
atom::Atom,
ext::{AsAny, DynHash},
fx_hash::FxIndexMap,
source_map::ModuleSourceMapConfig,
};
use rustc_hash::{FxHashMap as HashMap, FxHashSet as HashSet};
use serde::Serialize;
use swc_core::atoms::Wtf8Atom;
use crate::{
AsyncDependenciesBlock, BindingCell, BoxDependency, BoxDependencyTemplate, BoxModuleDependency,
ChunkGraph, ChunkUkey, CodeGenerationResult, CollectedTypeScriptInfo, Compilation,
CompilationAsset, CompilationId, CompilerId, CompilerOptions, ConcatenationScope,
ConnectionState, Context, ContextModule, DependenciesBlock, DependencyId, ExportProvided,
ExportsInfoArtifact, ExternalModule, GetTargetResult, ModuleCodeTemplate, ModuleGraph,
ModuleGraphCacheArtifact, ModuleLayer, ModuleType, NormalModule, OptimizationBailoutItem,
PrefetchExportsInfoMode, RawModule, Resolve, ResolverFactory, RuntimeSpec, SelfModule,
SharedPluginDriver, SideEffectsStateArtifact, SourceType,
concatenated_module::ConcatenatedModule, dependencies_block::dependencies_block_update_hash,
get_target, value_cache_versions::ValueCacheVersions,
};
pub struct BuildContext {
pub compiler_id: CompilerId,
pub compilation_id: CompilationId,
pub compiler_options: Arc<CompilerOptions>,
pub resolver_factory: Arc<ResolverFactory>,
pub runtime_template: ModuleCodeTemplate,
pub plugin_driver: SharedPluginDriver,
pub fs: Arc<dyn ReadableFileSystem>,
}
#[cacheable]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum RscModuleType {
ServerEntry,
Server,
Client,
}
#[cacheable]
#[derive(Debug, Clone)]
pub struct RscMeta {
pub module_type: RscModuleType,
#[cacheable(with=AsVec<AsPreset>)]
pub server_refs: Vec<Wtf8Atom>,
#[cacheable(with=AsVec<AsPreset>)]
pub client_refs: Vec<Wtf8Atom>,
pub is_cjs: bool,
#[cacheable(with=AsMap<AsPreset, AsPreset>)]
pub action_ids: FxIndexMap<Atom, Atom>,
}
#[cacheable]
#[derive(Debug, Clone)]
pub struct BuildInfo {
pub cacheable: bool,
pub hash: Option<RspackHashDigest>,
pub strict: bool,
pub module_argument: ModuleArgument,
pub exports_argument: ExportsArgument,
pub file_dependencies: ArcPathSet,
pub context_dependencies: ArcPathSet,
pub missing_dependencies: ArcPathSet,
pub build_dependencies: ArcPathSet,
pub value_dependencies: HashMap<String, String>,
#[cacheable(with=AsVec<AsPreset>)]
pub esm_named_exports: HashSet<Atom>,
pub all_star_exports: Vec<DependencyId>,
pub need_create_require: bool,
#[cacheable(with=AsOption<AsPreset>)]
pub json_data: Option<JsonValue>,
#[cacheable(with=AsOption<AsVec<AsPreset>>)]
pub side_effects_free: Option<HashSet<Atom>>,
#[cacheable(with=AsOption<AsVec<AsPreset>>)]
pub top_level_declarations: Option<HashSet<Atom>>,
pub module_concatenation_bailout: Option<String>,
pub assets: BindingCell<HashMap<String, CompilationAsset>>,
pub module: bool,
pub inline_exports: bool,
pub collected_typescript_info: Option<CollectedTypeScriptInfo>,
pub rsc: Option<RscMeta>,
#[cacheable(with=AsPreset)]
pub extras: serde_json::Map<String, serde_json::Value>,
#[cacheable(with=AsVec)]
pub deferred_pure_checks: HashSet<DeferredPureCheck>,
}
impl Default for BuildInfo {
fn default() -> Self {
Self {
cacheable: true,
hash: None,
strict: false,
module_argument: Default::default(),
exports_argument: Default::default(),
file_dependencies: ArcPathSet::default(),
context_dependencies: ArcPathSet::default(),
missing_dependencies: ArcPathSet::default(),
build_dependencies: ArcPathSet::default(),
value_dependencies: HashMap::default(),
esm_named_exports: HashSet::default(),
all_star_exports: Vec::default(),
need_create_require: false,
json_data: None,
side_effects_free: None,
top_level_declarations: None,
module_concatenation_bailout: None,
assets: Default::default(),
module: false,
inline_exports: false,
collected_typescript_info: None,
rsc: None,
extras: Default::default(),
deferred_pure_checks: HashSet::default(),
}
}
}
#[cacheable]
#[derive(Debug, Default, Clone, Copy, Hash, PartialEq, Eq, Serialize)]
#[serde(rename_all = "camelCase")]
pub enum BuildMetaExportsType {
#[default]
Unset,
Default,
Namespace,
Flagged,
Dynamic,
}
impl Display for BuildMetaExportsType {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
let d = match self {
BuildMetaExportsType::Unset => "unknown exports (runtime-defined)",
BuildMetaExportsType::Default => "default exports",
BuildMetaExportsType::Namespace => "namespace exports",
BuildMetaExportsType::Flagged => "flagged exports",
BuildMetaExportsType::Dynamic => "dynamic exports",
};
f.write_str(d)
}
}
#[derive(Debug, Clone, Copy, Hash)]
pub enum ExportsType {
DefaultOnly,
Namespace,
DefaultWithNamed,
Dynamic,
}
#[cacheable]
#[derive(Debug, Default, Clone, Copy, Hash, Serialize)]
#[serde(rename_all = "camelCase")]
pub enum BuildMetaDefaultObject {
#[default]
False,
Redirect,
RedirectWarn,
}
#[cacheable]
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct DeferredPureCheck {
#[cacheable(with=AsPreset)]
pub atom: Atom,
pub dep_id: DependencyId,
pub start: u32,
pub end: u32,
}
#[cacheable]
#[derive(Debug, Default, Clone, Copy, Hash, Serialize)]
#[serde(rename_all = "camelCase")]
pub enum ModuleArgument {
#[default]
Module,
RspackModule,
}
#[cacheable]
#[derive(Debug, Default, Clone, Copy, Hash, PartialEq, Eq, Serialize)]
#[serde(rename_all = "camelCase")]
pub enum ExportsArgument {
#[default]
Exports,
RspackExports,
}
#[cacheable]
#[derive(Debug, Default, Clone, Hash, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct BuildMeta {
pub strict_esm_module: bool,
pub has_top_level_await: bool,
pub esm: bool,
pub exports_type: BuildMetaExportsType,
pub default_object: BuildMetaDefaultObject,
#[serde(skip_serializing_if = "Option::is_none")]
pub side_effect_free: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub exports_final_name: Option<Vec<(String, String)>>,
}
#[derive(Debug)]
pub struct BuildResult {
pub module: BoxModule,
pub dependencies: Vec<BoxDependency>,
pub blocks: Vec<Box<AsyncDependenciesBlock>>,
pub optimization_bailouts: Vec<OptimizationBailoutItem>,
}
#[cacheable]
#[derive(Debug, Default, Clone)]
pub struct FactoryMeta {
pub side_effect_free: Option<bool>,
}
pub type ModuleIdentifier = Identifier;
pub type ResourceIdentifier = Identifier;
#[derive(Debug)]
pub struct ModuleCodeGenerationContext<'a> {
pub compilation: &'a Compilation,
pub runtime: Option<&'a RuntimeSpec>,
pub concatenation_scope: Option<ConcatenationScope>,
pub runtime_template: &'a mut ModuleCodeTemplate,
}
#[cacheable_dyn]
#[async_trait]
pub trait Module:
Debug
+ Send
+ Sync
+ Any
+ AsAny
+ Identifiable
+ DependenciesBlock
+ Diagnosable
+ ModuleSourceMapConfig
{
fn module_type(&self) -> &ModuleType;
fn source_types(&self, module_graph: &ModuleGraph) -> &[SourceType];
fn source(&self) -> Option<&BoxSource>;
fn readable_identifier(&self, _context: &Context) -> Cow<'_, str>;
fn size(&self, source_type: Option<&SourceType>, compilation: Option<&Compilation>) -> f64;
async fn build(
self: Box<Self>,
_build_context: BuildContext,
_compilation: Option<&Compilation>,
) -> Result<BuildResult>;
fn factory_meta(&self) -> Option<&FactoryMeta>;
fn set_factory_meta(&mut self, factory_meta: FactoryMeta);
fn build_info(&self) -> &BuildInfo;
fn build_info_mut(&mut self) -> &mut BuildInfo;
fn build_meta(&self) -> &BuildMeta;
fn build_meta_mut(&mut self) -> &mut BuildMeta;
fn get_exports_argument(&self) -> ExportsArgument {
self.build_info().exports_argument
}
fn get_module_argument(&self) -> ModuleArgument {
self.build_info().module_argument
}
fn get_exports_type(
&self,
module_graph: &ModuleGraph,
module_graph_cache: &ModuleGraphCacheArtifact,
exports_info_artifact: &ExportsInfoArtifact,
strict: bool,
) -> ExportsType {
module_graph_cache.cached_get_exports_type((self.identifier(), strict), || {
get_exports_type_impl(
self.identifier(),
self.build_meta(),
module_graph,
exports_info_artifact,
strict,
)
})
}
fn get_strict_esm_module(&self) -> bool {
self.build_meta().strict_esm_module
}
async fn code_generation(
&self,
_code_generation_context: &mut ModuleCodeGenerationContext,
) -> Result<CodeGenerationResult>;
fn name_for_condition(&self) -> Option<Box<str>> {
None
}
async fn get_runtime_hash(
&self,
compilation: &Compilation,
runtime: Option<&RuntimeSpec>,
) -> Result<RspackHashDigest>;
fn lib_ident(&self, _options: LibIdentOptions) -> Option<Cow<'_, str>> {
None
}
fn get_code_generation_dependencies(&self) -> Option<&[BoxModuleDependency]> {
None
}
fn get_presentational_dependencies(&self) -> Option<&[BoxDependencyTemplate]> {
None
}
fn get_concatenation_bailout_reason(
&self,
_mg: &ModuleGraph,
_cg: &ChunkGraph,
) -> Option<Cow<'static, str>> {
Some(
format!(
"Module Concatenation is not implemented for {}",
self.module_type()
)
.into(),
)
}
fn get_resolve_options(&self) -> Option<Arc<Resolve>> {
None
}
fn get_context(&self) -> Option<Box<Context>> {
None
}
fn get_layer(&self) -> Option<&ModuleLayer> {
None
}
fn chunk_condition(&self, _chunk_key: &ChunkUkey, _compilation: &Compilation) -> Option<bool> {
None
}
fn get_side_effects_connection_state(
&self,
_module_graph: &ModuleGraph,
_module_graph_cache: &ModuleGraphCacheArtifact,
_side_effects_state_artifact: &SideEffectsStateArtifact,
_module_chain: &mut IdentifierSet,
_connection_state_cache: &mut IdentifierMap<ConnectionState>,
) -> ConnectionState {
ConnectionState::Active(true)
}
fn need_build(&self, value_cache_version: &ValueCacheVersions) -> bool {
let build_info = self.build_info();
!build_info.cacheable
|| value_cache_version.has_diff(&build_info.value_dependencies)
|| self.diagnostics().iter().any(|item| item.is_error())
}
fn need_id(&self) -> bool {
true
}
}
fn get_exports_type_impl(
identifier: ModuleIdentifier,
build_meta: &BuildMeta,
mg: &ModuleGraph,
exports_info_artifact: &ExportsInfoArtifact,
strict: bool,
) -> ExportsType {
let export_type = &build_meta.exports_type;
let default_object = &build_meta.default_object;
match export_type {
BuildMetaExportsType::Flagged => {
if strict {
ExportsType::DefaultWithNamed
} else {
ExportsType::Namespace
}
}
BuildMetaExportsType::Namespace => ExportsType::Namespace,
BuildMetaExportsType::Default => match default_object {
BuildMetaDefaultObject::Redirect => ExportsType::DefaultWithNamed,
BuildMetaDefaultObject::RedirectWarn => {
if strict {
ExportsType::DefaultOnly
} else {
ExportsType::DefaultWithNamed
}
}
BuildMetaDefaultObject::False => ExportsType::DefaultOnly,
},
BuildMetaExportsType::Dynamic => {
if strict {
ExportsType::DefaultWithNamed
} else {
fn handle_default(default_object: &BuildMetaDefaultObject) -> ExportsType {
match default_object {
BuildMetaDefaultObject::Redirect => ExportsType::DefaultWithNamed,
BuildMetaDefaultObject::RedirectWarn => ExportsType::DefaultWithNamed,
_ => ExportsType::DefaultOnly,
}
}
let name = Atom::from("__esModule");
let exports_info = exports_info_artifact
.get_prefetched_exports_info_optional(&identifier, PrefetchExportsInfoMode::Default);
if let Some(export_info) = exports_info
.as_ref()
.map(|info| info.get_read_only_export_info(&name))
{
if matches!(export_info.provided(), Some(ExportProvided::NotProvided)) {
handle_default(default_object)
} else {
let Some(GetTargetResult::Target(target)) = get_target(
export_info,
mg,
exports_info_artifact,
&|_| true,
&mut Default::default(),
) else {
return ExportsType::Dynamic;
};
if target
.export
.and_then(|t| {
if t.len() == 1 {
t.first().cloned()
} else {
None
}
})
.is_some_and(|v| v == "__esModule")
{
let Some(target_exports_type) = mg
.module_by_identifier(&target.module)
.map(|m| m.build_meta().exports_type)
else {
return ExportsType::Dynamic;
};
match target_exports_type {
BuildMetaExportsType::Flagged | BuildMetaExportsType::Namespace => {
ExportsType::Namespace
}
BuildMetaExportsType::Default => handle_default(default_object),
_ => ExportsType::Dynamic,
}
} else {
ExportsType::Dynamic
}
}
} else {
ExportsType::DefaultWithNamed
}
}
}
BuildMetaExportsType::Unset => {
if strict {
ExportsType::DefaultWithNamed
} else {
ExportsType::Dynamic
}
}
}
}
pub fn module_update_hash(
module: &dyn Module,
hasher: &mut dyn std::hash::Hasher,
compilation: &Compilation,
runtime: Option<&RuntimeSpec>,
) {
let chunk_graph = &compilation.build_chunk_graph_artifact.chunk_graph;
chunk_graph
.get_module_graph_hash(module, compilation, runtime)
.dyn_hash(hasher);
if let Some(deps) = module.get_presentational_dependencies() {
for dep in deps {
dep.update_hash(hasher, compilation, runtime);
}
}
dependencies_block_update_hash(
module.get_dependencies(),
module.get_blocks(),
hasher,
compilation,
runtime,
);
}
pub trait ModuleExt {
fn boxed(self) -> BoxModule;
}
impl<T: Module> ModuleExt for T {
fn boxed(self) -> BoxModule {
BoxModule(Box::new(self))
}
}
#[cacheable(with=AsInner)]
#[repr(transparent)]
pub struct BoxModule(Box<dyn Module>);
impl BoxModule {
pub fn new(module: Box<dyn Module>) -> Self {
BoxModule(module)
}
pub async fn build(
self,
build_context: BuildContext,
compilation: Option<&Compilation>,
) -> Result<BuildResult> {
self.0.build(build_context, compilation).await
}
}
impl AsInnerConverter for BoxModule {
type Inner = Box<dyn Module>;
fn to_inner(&self) -> &Self::Inner {
&self.0
}
fn from_inner(data: Self::Inner) -> Self {
BoxModule(data)
}
}
impl std::ops::Deref for BoxModule {
type Target = Box<dyn Module>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl std::ops::DerefMut for BoxModule {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
impl From<Box<dyn Module>> for BoxModule {
fn from(inner: Box<dyn Module>) -> Self {
BoxModule(inner)
}
}
impl AsRef<dyn Module> for BoxModule {
fn as_ref(&self) -> &dyn Module {
self.0.as_ref()
}
}
impl AsMut<dyn Module> for BoxModule {
fn as_mut(&mut self) -> &mut dyn Module {
self.0.as_mut()
}
}
impl Debug for BoxModule {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
self.0.fmt(f)
}
}
impl Identifiable for BoxModule {
fn identifier(&self) -> Identifier {
self.0.as_ref().identifier()
}
}
impl dyn Module {
pub fn downcast_ref<T: Module + Any>(&self) -> Option<&T> {
self.as_any().downcast_ref::<T>()
}
pub fn downcast_mut<T: Module + Any>(&mut self) -> Option<&mut T> {
self.as_any_mut().downcast_mut::<T>()
}
}
#[macro_export]
macro_rules! impl_module_meta_info {
() => {
fn factory_meta(&self) -> Option<&$crate::FactoryMeta> {
self.factory_meta.as_ref()
}
fn set_factory_meta(&mut self, v: $crate::FactoryMeta) {
self.factory_meta = Some(v);
}
fn build_info(&self) -> &$crate::BuildInfo {
&self.build_info
}
fn build_info_mut(&mut self) -> &mut $crate::BuildInfo {
&mut self.build_info
}
fn build_meta(&self) -> &$crate::BuildMeta {
&self.build_meta
}
fn build_meta_mut(&mut self) -> &mut $crate::BuildMeta {
&mut self.build_meta
}
};
}
macro_rules! impl_module_downcast_helpers {
($ty:ty, $ident:ident) => {
impl dyn Module {
::paste::paste! {
pub fn [<as_ $ident>](&self) -> Option<&$ty> {
self.as_any().downcast_ref::<$ty>()
}
pub fn [<as_ $ident _mut>](&mut self) -> Option<&mut $ty> {
self.as_any_mut().downcast_mut::<$ty>()
}
pub fn [<try_as_ $ident>](&self) -> Result<&$ty> {
self.[<as_ $ident>]().ok_or_else(|| {
::rspack_error::error!(
"Failed to cast module to a {}",
stringify!($ty)
)
})
}
pub fn [<try_as_ $ident _mut>](&mut self) -> Result<&mut $ty> {
self.[<as_ $ident _mut>]().ok_or_else(|| {
::rspack_error::error!(
"Failed to cast module to a {}",
stringify!($ty)
)
})
}
}
}
};
}
impl_module_downcast_helpers!(NormalModule, normal_module);
impl_module_downcast_helpers!(RawModule, raw_module);
impl_module_downcast_helpers!(ContextModule, context_module);
impl_module_downcast_helpers!(ExternalModule, external_module);
impl_module_downcast_helpers!(SelfModule, self_module);
impl_module_downcast_helpers!(ConcatenatedModule, concatenated_module);
pub struct LibIdentOptions<'me> {
pub context: &'me str,
}
#[cfg(test)]
mod test {
use std::borrow::Cow;
use rspack_cacheable::cacheable;
use rspack_collections::{Identifiable, Identifier};
use rspack_error::{Result, impl_empty_diagnosable_trait};
use rspack_hash::RspackHashDigest;
use rspack_sources::BoxSource;
use rspack_util::source_map::{ModuleSourceMapConfig, SourceMapKind};
use super::{BoxModule, Module};
use crate::{
AsyncDependenciesBlockIdentifier, BuildContext, BuildResult, CodeGenerationResult, Compilation,
Context, DependenciesBlock, DependencyId, ModuleCodeGenerationContext, ModuleExt, ModuleGraph,
ModuleType, RuntimeSpec, SourceType,
};
#[cacheable]
#[derive(Debug)]
struct RawModule(String);
#[cacheable]
#[derive(Debug)]
struct ExternalModule(String);
macro_rules! impl_noop_trait_module_type {
($ident: ident) => {
impl Identifiable for $ident {
fn identifier(&self) -> Identifier {
self.0.clone().into()
}
}
impl_empty_diagnosable_trait!($ident);
impl DependenciesBlock for $ident {
fn add_block_id(&mut self, _: AsyncDependenciesBlockIdentifier) {
unreachable!()
}
fn get_blocks(&self) -> &[AsyncDependenciesBlockIdentifier] {
unreachable!()
}
fn add_dependency_id(&mut self, _: DependencyId) {
unreachable!()
}
fn remove_dependency_id(&mut self, _: DependencyId) {
unreachable!()
}
fn get_dependencies(&self) -> &[DependencyId] {
unreachable!()
}
}
#[::rspack_cacheable::cacheable_dyn]
#[::async_trait::async_trait]
impl Module for $ident {
fn module_type(&self) -> &ModuleType {
unreachable!()
}
fn source_types(&self, _module_graph: &ModuleGraph) -> &[SourceType] {
unreachable!()
}
fn source(&self) -> Option<&BoxSource> {
unreachable!()
}
fn size(
&self,
_source_type: Option<&SourceType>,
_compilation: Option<&Compilation>,
) -> f64 {
unreachable!()
}
fn readable_identifier(&self, _context: &Context) -> Cow<'_, str> {
self.0.clone().into()
}
async fn build(
self: Box<Self>,
_build_context: BuildContext,
_compilation: Option<&Compilation>,
) -> Result<BuildResult> {
unreachable!()
}
async fn get_runtime_hash(
&self,
_compilation: &Compilation,
_runtime: Option<&RuntimeSpec>,
) -> Result<RspackHashDigest> {
unreachable!()
}
async fn code_generation(
&self,
_code_generation_context: &mut ModuleCodeGenerationContext,
) -> Result<CodeGenerationResult> {
unreachable!()
}
fn factory_meta(&self) -> Option<&crate::FactoryMeta> {
unreachable!()
}
fn build_info(&self) -> &crate::BuildInfo {
unreachable!()
}
fn build_info_mut(&mut self) -> &mut crate::BuildInfo {
unreachable!()
}
fn build_meta(&self) -> &crate::BuildMeta {
unreachable!()
}
fn build_meta_mut(&mut self) -> &mut crate::BuildMeta {
unreachable!()
}
fn set_factory_meta(&mut self, _: crate::FactoryMeta) {
unreachable!()
}
}
impl ModuleSourceMapConfig for $ident {
fn get_source_map_kind(&self) -> &SourceMapKind {
unreachable!()
}
fn set_source_map_kind(&mut self, _source_map: SourceMapKind) {
unreachable!()
}
}
};
}
impl_noop_trait_module_type!(RawModule);
impl_noop_trait_module_type!(ExternalModule);
#[test]
fn should_downcast_successfully() {
let a: BoxModule = ExternalModule(String::from("a")).boxed();
let b: BoxModule = RawModule(String::from("a")).boxed();
assert!(a.downcast_ref::<ExternalModule>().is_some());
assert!(b.downcast_ref::<RawModule>().is_some());
let a = a.as_ref();
let b = b.as_ref();
assert!(a.downcast_ref::<ExternalModule>().is_some());
assert!(b.downcast_ref::<RawModule>().is_some());
}
}