Skip to main content

rspack_plugin_css/dependency/
url.rs

1use cow_utils::CowUtils;
2use rspack_cacheable::{cacheable, cacheable_dyn};
3use rspack_core::{
4  AsContextDependency, CodeGenerationDataFilename, CodeGenerationDataUrl, Compilation, Dependency,
5  DependencyCategory, DependencyCodeGeneration, DependencyId, DependencyRange, DependencyTemplate,
6  DependencyTemplateType, DependencyType, FactorizeInfo, ModuleDependency, ModuleIdentifier,
7  TemplateContext, TemplateReplaceSource,
8};
9
10use crate::utils::{AUTO_PUBLIC_PATH_PLACEHOLDER, css_escape_string};
11
12#[cacheable]
13#[derive(Debug, Clone)]
14pub struct CssUrlDependency {
15  id: DependencyId,
16  request: String,
17  range: DependencyRange,
18  replace_function: bool,
19  factorize_info: FactorizeInfo,
20}
21
22impl CssUrlDependency {
23  pub fn new(request: String, range: DependencyRange, replace_function: bool) -> Self {
24    Self {
25      request,
26      range,
27      id: DependencyId::new(),
28      replace_function,
29      factorize_info: Default::default(),
30    }
31  }
32
33  fn get_target_url(
34    &self,
35    identifier: &ModuleIdentifier,
36    compilation: &Compilation,
37  ) -> Option<String> {
38    // url points to asset modules, and asset modules should have same codegen results for all runtimes
39    let code_gen_result = compilation.code_generation_results.get_one(identifier);
40
41    if let Some(url) = code_gen_result.data.get::<CodeGenerationDataUrl>() {
42      Some(url.inner().to_string())
43    } else if let Some(data) = code_gen_result.data.get::<CodeGenerationDataFilename>() {
44      let filename = data.filename();
45      let public_path = data.public_path().cow_replace(
46        "__RSPACK_PLUGIN_ASSET_AUTO_PUBLIC_PATH__",
47        AUTO_PUBLIC_PATH_PLACEHOLDER,
48      );
49      Some(format!("{public_path}{filename}"))
50    } else {
51      None
52    }
53  }
54}
55
56#[cacheable_dyn]
57impl Dependency for CssUrlDependency {
58  fn id(&self) -> &DependencyId {
59    &self.id
60  }
61
62  fn category(&self) -> &DependencyCategory {
63    &DependencyCategory::Url
64  }
65
66  fn dependency_type(&self) -> &DependencyType {
67    &DependencyType::CssUrl
68  }
69
70  fn range(&self) -> Option<DependencyRange> {
71    Some(self.range)
72  }
73
74  fn could_affect_referencing_module(&self) -> rspack_core::AffectType {
75    rspack_core::AffectType::True
76  }
77}
78
79#[cacheable_dyn]
80impl ModuleDependency for CssUrlDependency {
81  fn request(&self) -> &str {
82    &self.request
83  }
84
85  fn user_request(&self) -> &str {
86    &self.request
87  }
88
89  fn factorize_info(&self) -> &FactorizeInfo {
90    &self.factorize_info
91  }
92
93  fn factorize_info_mut(&mut self) -> &mut FactorizeInfo {
94    &mut self.factorize_info
95  }
96}
97
98#[cacheable_dyn]
99impl DependencyCodeGeneration for CssUrlDependency {
100  fn dependency_template(&self) -> Option<DependencyTemplateType> {
101    Some(CssUrlDependencyTemplate::template_type())
102  }
103}
104
105impl AsContextDependency for CssUrlDependency {}
106
107#[cacheable]
108#[derive(Debug, Clone, Default)]
109pub struct CssUrlDependencyTemplate;
110
111impl CssUrlDependencyTemplate {
112  pub fn template_type() -> DependencyTemplateType {
113    DependencyTemplateType::Dependency(DependencyType::CssUrl)
114  }
115}
116
117impl DependencyTemplate for CssUrlDependencyTemplate {
118  fn render(
119    &self,
120    dep: &dyn DependencyCodeGeneration,
121    source: &mut TemplateReplaceSource,
122    code_generatable_context: &mut TemplateContext,
123  ) {
124    let dep = dep
125      .as_any()
126      .downcast_ref::<CssUrlDependency>()
127      .expect("CssUrlDependencyTemplate should be used for CssUrlDependency");
128
129    let TemplateContext { compilation, .. } = code_generatable_context;
130    if let Some(mgm) = compilation
131      .get_module_graph()
132      .module_graph_module_by_dependency_id(dep.id())
133      && let Some(target_url) = dep.get_target_url(&mgm.module_identifier, compilation)
134    {
135      let target_url = css_escape_string(&target_url);
136      let content = if dep.replace_function {
137        format!("url({target_url})")
138      } else {
139        target_url
140      };
141      source.replace(dep.range.start, dep.range.end, &content, None);
142    }
143  }
144}