use rspack_cacheable::{cacheable, cacheable_dyn};
use rspack_collections::{Identifiable, Identifier};
use rspack_core::{
BoxModule, BuildContext, BuildInfo, BuildMeta, CodeGenerationResultBuilder, Compilation,
CompilerOptions, DependenciesBlock, DependenciesBlockData, FactoryMetaStore, FreezeLock, Module,
ModuleCodeGenerationContext, ModuleExt, ModuleFactory, ModuleFactoryCreateData,
ModuleFactoryResult, ModuleGraph, ModuleLayer, RuntimeSpec, SourceType, impl_module_meta_info,
impl_source_map_config, module_update_hash, rspack_sources::BoxSource,
};
use rspack_error::{Result, impl_empty_diagnosable_trait};
use rspack_hash::{RspackHash, RspackHashDigest, RspackHasher};
use rspack_util::{identifier::split_at_query_mark, itoa};
use crate::{
css_dependency::CssDependency,
plugin::{MODULE_TYPE, SOURCE_TYPE},
};
#[impl_source_map_config]
#[cacheable]
#[derive(Debug)]
pub(crate) struct CssModule {
pub(crate) identifier: String,
pub(crate) content: String,
pub(crate) _context: String,
pub(crate) module_layer: Option<ModuleLayer>,
pub(crate) media: Option<String>,
pub(crate) supports: Option<String>,
pub(crate) source_map: Option<String>,
pub(crate) css_layer: Option<String>,
pub(crate) identifier_index: u32,
factory_meta: FactoryMetaStore,
build_info: FreezeLock<BuildInfo>,
build_meta: FreezeLock<BuildMeta>,
dependencies_block: DependenciesBlockData,
identifier__: Identifier,
}
impl CssModule {
pub fn new(dep: &CssDependency) -> Self {
let mut identifier_index_buffer = itoa::Buffer::new();
let identifier_index_str = identifier_index_buffer.format(dep.identifier_index);
let identifier__ = format!(
"css|{}|{}|{}|{}|{}}}",
dep.identifier,
identifier_index_str,
dep.css_layer.as_deref().unwrap_or_default(),
dep.supports.as_deref().unwrap_or_default(),
dep.media.as_deref().unwrap_or_default(),
)
.into();
Self {
identifier: dep.identifier.clone(),
content: dep.content.clone(),
module_layer: dep.module_layer.clone(),
css_layer: dep.css_layer.clone(),
_context: dep.context.clone(),
media: dep.media.clone(),
supports: dep.supports.clone(),
source_map: dep.source_map.clone(),
identifier_index: dep.identifier_index,
dependencies_block: Default::default(),
factory_meta: Default::default(),
build_info: BuildInfo {
cacheable: dep.cacheable,
strict: true,
dependencies: dep.dependencies.clone(),
..Default::default()
}
.into(),
build_meta: Default::default(),
source_map_kind: rspack_util::source_map::SourceMapKind::empty(),
identifier__,
}
}
fn compute_hash(&self, options: &CompilerOptions) -> RspackHashDigest {
let mut hasher = RspackHasher::from(&options.output);
self.content.hash(&mut hasher);
if let Some(layer) = &self.css_layer {
layer.hash(&mut hasher);
}
self.supports.hash(&mut hasher);
self.media.hash(&mut hasher);
self.source_map.hash(&mut hasher);
hasher.digest(&options.output.hash_digest)
}
}
#[cacheable_dyn]
#[async_trait::async_trait]
impl Module for CssModule {
impl_module_meta_info!();
fn readable_identifier(&self, context: &rspack_core::Context) -> std::borrow::Cow<'_, str> {
let index_suffix = if self.identifier_index > 0 {
let mut index_buffer = itoa::Buffer::new();
let index_str = index_buffer.format(self.identifier_index);
format!("({index_str})")
} else {
String::new()
};
std::borrow::Cow::Owned(format!(
"css {}{}{}{}{}",
context.shorten(&self.identifier),
index_suffix,
if let Some(layer) = &self.css_layer {
format!(" (layer {layer})")
} else {
String::new()
},
if let Some(supports) = &self.supports
&& !supports.is_empty()
{
format!(" (supports {supports})")
} else {
String::new()
},
if let Some(media) = &self.media
&& !media.is_empty()
{
format!(" (media {media})")
} else {
String::new()
}
))
}
fn name_for_condition(&self) -> Option<Box<str>> {
self
.identifier
.split('!')
.next_back()
.map(|resource| split_at_query_mark(resource).0.into())
}
fn size(&self, _source_type: Option<&SourceType>, _compilation: Option<&Compilation>) -> f64 {
self.content.len() as f64
}
fn source(&self) -> Option<&BoxSource> {
None
}
fn module_type(&self) -> &rspack_core::ModuleType {
&MODULE_TYPE
}
fn source_types(&self, _module_graph: &ModuleGraph) -> &[SourceType] {
&*SOURCE_TYPE
}
fn need_id(&self) -> bool {
false
}
async fn build(
mut self: Box<Self>,
build_context: BuildContext,
_compilation: Option<&Compilation>,
) -> Result<BoxModule> {
self.build_info.get_mut().hash = Some(self.compute_hash(&build_context.compiler_options));
Ok(BoxModule::new(self))
}
async fn code_generation(
&self,
_code_generation_context: &mut ModuleCodeGenerationContext,
) -> Result<CodeGenerationResultBuilder> {
Ok(CodeGenerationResultBuilder::default())
}
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);
self.build_info.read().hash.hash(&mut hasher);
Ok(hasher.digest(&compilation.options.output.hash_digest))
}
fn get_layer(&self) -> Option<&ModuleLayer> {
self.module_layer.as_ref()
}
}
impl Identifiable for CssModule {
fn identifier(&self) -> rspack_collections::Identifier {
self.identifier__
}
}
impl DependenciesBlock for CssModule {
fn dependencies_block(&self) -> &DependenciesBlockData {
&self.dependencies_block
}
fn dependencies_block_mut(&mut self) -> &mut DependenciesBlockData {
&mut self.dependencies_block
}
}
#[derive(Debug)]
pub(crate) struct CssModuleFactory;
#[async_trait::async_trait]
impl ModuleFactory for CssModuleFactory {
async fn create(&self, data: &mut ModuleFactoryCreateData) -> Result<ModuleFactoryResult> {
let css_dep = data.dependencies[0]
.downcast_ref::<CssDependency>()
.expect("unreachable");
Ok(ModuleFactoryResult::new_with_module(
CssModule::new(css_dep).boxed(),
))
}
}
impl_empty_diagnosable_trait!(CssModule);
impl_empty_diagnosable_trait!(CssModuleFactory);