1use anyhow::Result;
2use turbo_rcstr::rcstr;
3use turbo_tasks::{IntoTraitRef, ResolvedVc, TryJoinIterExt, ValueToString, Vc};
4use turbo_tasks_fs::{FileContent, FileSystemPath};
5use turbopack_core::{
6 asset::{Asset, AssetContent},
7 chunk::{ChunkItem, ChunkType, ChunkableModule, ChunkingContext, MinifyType},
8 context::AssetContext,
9 environment::Environment,
10 ident::AssetIdent,
11 module::{Module, ModuleSideEffects, StyleModule, StyleType},
12 module_graph::ModuleGraph,
13 output::{OutputAssetsReference, OutputAssetsWithReferenced},
14 reference::{ModuleReference, ModuleReferences},
15 reference_type::ImportContext,
16 resolve::origin::ResolveOrigin,
17 source::{OptionSource, Source},
18 source_map::GenerateSourceMap,
19};
20
21use crate::{
22 CssModuleAssetType,
23 chunk::{CssChunkItem, CssChunkItemContent, CssChunkPlaceable, CssChunkType, CssImport},
24 code_gen::CodeGenerateable,
25 process::{
26 CssWithPlaceholderResult, FinalCssResult, ParseCss, ParseCssResult, ProcessCss,
27 finalize_css, parse_css, process_css_with_placeholder,
28 },
29 references::{
30 compose::CssModuleComposeReference, import::ImportAssetReference, url::ReferencedAsset,
31 },
32};
33
34#[turbo_tasks::value]
35#[derive(Clone)]
36pub struct CssModuleAsset {
38 source: ResolvedVc<Box<dyn Source>>,
39 asset_context: ResolvedVc<Box<dyn AssetContext>>,
40 import_context: Option<ResolvedVc<ImportContext>>,
41 ty: CssModuleAssetType,
42 environment: Option<ResolvedVc<Environment>>,
43}
44
45#[turbo_tasks::value_impl]
46impl CssModuleAsset {
47 #[turbo_tasks::function]
49 pub fn new(
50 source: ResolvedVc<Box<dyn Source>>,
51 asset_context: ResolvedVc<Box<dyn AssetContext>>,
52 ty: CssModuleAssetType,
53 import_context: Option<ResolvedVc<ImportContext>>,
54 environment: Option<ResolvedVc<Environment>>,
55 ) -> Vc<Self> {
56 Self::cell(CssModuleAsset {
57 source,
58 asset_context,
59 import_context,
60 ty,
61 environment,
62 })
63 }
64
65 #[turbo_tasks::function]
67 pub fn source_ident(&self) -> Vc<AssetIdent> {
68 self.source.ident()
69 }
70}
71
72#[turbo_tasks::value_impl]
73impl ParseCss for CssModuleAsset {
74 #[turbo_tasks::function]
75 async fn parse_css(self: Vc<Self>) -> Result<Vc<ParseCssResult>> {
76 let this = self.await?;
77
78 Ok(parse_css(
79 *this.source,
80 Vc::upcast(self),
81 this.import_context.map(|v| *v),
82 this.ty,
83 this.environment.as_deref().copied(),
84 ))
85 }
86}
87
88#[turbo_tasks::value_impl]
89impl ProcessCss for CssModuleAsset {
90 #[turbo_tasks::function]
91 async fn get_css_with_placeholder(self: Vc<Self>) -> Result<Vc<CssWithPlaceholderResult>> {
92 let this = self.await?;
93 let parse_result = self.parse_css();
94
95 Ok(process_css_with_placeholder(
96 parse_result,
97 this.environment.as_deref().copied(),
98 ))
99 }
100
101 #[turbo_tasks::function]
102 async fn finalize_css(
103 self: Vc<Self>,
104 chunking_context: Vc<Box<dyn ChunkingContext>>,
105 minify_type: MinifyType,
106 ) -> Result<Vc<FinalCssResult>> {
107 let process_result = self.get_css_with_placeholder();
108
109 let this = self.await?;
110 let origin_source_map =
111 match ResolvedVc::try_sidecast::<Box<dyn GenerateSourceMap>>(this.source) {
112 Some(gsm) => gsm.generate_source_map(),
113 None => FileContent::NotFound.cell(),
114 };
115 Ok(finalize_css(
116 process_result,
117 chunking_context,
118 minify_type,
119 origin_source_map,
120 this.environment.as_deref().copied(),
121 ))
122 }
123}
124
125#[turbo_tasks::value_impl]
126impl Module for CssModuleAsset {
127 #[turbo_tasks::function]
128 async fn ident(&self) -> Result<Vc<AssetIdent>> {
129 let mut ident = self
130 .source
131 .ident()
132 .with_modifier(rcstr!("css"))
133 .with_layer(self.asset_context.into_trait_ref().await?.layer());
134 if let Some(import_context) = self.import_context {
135 ident = ident.with_modifier(import_context.modifier().owned().await?)
136 }
137 Ok(ident)
138 }
139
140 #[turbo_tasks::function]
141 fn source(&self) -> Vc<OptionSource> {
142 Vc::cell(Some(self.source))
143 }
144
145 #[turbo_tasks::function]
146 async fn references(self: Vc<Self>) -> Result<Vc<ModuleReferences>> {
147 let result = self.parse_css().await?;
148 match &*result {
151 ParseCssResult::Ok { references, .. } => Ok(**references),
152 ParseCssResult::Unparsable => Ok(ModuleReferences::empty()),
153 ParseCssResult::NotFound => Ok(ModuleReferences::empty()),
154 }
155 }
156 #[turbo_tasks::function]
157 fn side_effects(self: Vc<Self>) -> Vc<ModuleSideEffects> {
158 ModuleSideEffects::SideEffectful.cell()
160 }
161}
162
163#[turbo_tasks::value_impl]
164impl StyleModule for CssModuleAsset {
165 #[turbo_tasks::function]
166 fn style_type(&self) -> Vc<StyleType> {
167 match self.ty {
168 CssModuleAssetType::Default => StyleType::GlobalStyle.cell(),
169 CssModuleAssetType::Module => StyleType::IsolatedStyle.cell(),
170 }
171 }
172}
173
174#[turbo_tasks::value_impl]
175impl Asset for CssModuleAsset {
176 #[turbo_tasks::function]
177 fn content(&self) -> Vc<AssetContent> {
178 self.source.content()
179 }
180}
181
182#[turbo_tasks::value_impl]
183impl ChunkableModule for CssModuleAsset {
184 #[turbo_tasks::function]
185 fn as_chunk_item(
186 self: ResolvedVc<Self>,
187 module_graph: ResolvedVc<ModuleGraph>,
188 chunking_context: ResolvedVc<Box<dyn ChunkingContext>>,
189 ) -> Vc<Box<dyn turbopack_core::chunk::ChunkItem>> {
190 Vc::upcast(CssModuleChunkItem::cell(CssModuleChunkItem {
191 module: self,
192 module_graph,
193 chunking_context,
194 }))
195 }
196}
197
198#[turbo_tasks::value_impl]
199impl CssChunkPlaceable for CssModuleAsset {}
200
201#[turbo_tasks::value_impl]
202impl ResolveOrigin for CssModuleAsset {
203 #[turbo_tasks::function]
204 fn origin_path(&self) -> Vc<FileSystemPath> {
205 self.source.ident().path()
206 }
207
208 #[turbo_tasks::function]
209 fn asset_context(&self) -> Vc<Box<dyn AssetContext>> {
210 *self.asset_context
211 }
212}
213
214#[turbo_tasks::value]
215struct CssModuleChunkItem {
216 module: ResolvedVc<CssModuleAsset>,
217 module_graph: ResolvedVc<ModuleGraph>,
218 chunking_context: ResolvedVc<Box<dyn ChunkingContext>>,
219}
220
221#[turbo_tasks::value_impl]
222impl OutputAssetsReference for CssModuleChunkItem {
223 #[turbo_tasks::function]
224 async fn references(&self) -> Result<Vc<OutputAssetsWithReferenced>> {
225 let mut references = Vec::new();
226 if let ParseCssResult::Ok { url_references, .. } = &*self.module.parse_css().await? {
227 for (_, reference) in url_references.await? {
228 if let ReferencedAsset::Some(asset) = *reference
229 .get_referenced_asset(*self.chunking_context)
230 .await?
231 {
232 references.push(asset);
233 }
234 }
235 }
236 Ok(OutputAssetsWithReferenced::from_assets(Vc::cell(
237 references,
238 )))
239 }
240}
241
242#[turbo_tasks::value_impl]
243impl ChunkItem for CssModuleChunkItem {
244 #[turbo_tasks::function]
245 fn asset_ident(&self) -> Vc<AssetIdent> {
246 self.module.ident()
247 }
248
249 #[turbo_tasks::function]
250 fn chunking_context(&self) -> Vc<Box<dyn ChunkingContext>> {
251 *self.chunking_context
252 }
253
254 #[turbo_tasks::function]
255 fn ty(&self) -> Vc<Box<dyn ChunkType>> {
256 Vc::upcast(Vc::<CssChunkType>::default())
257 }
258
259 #[turbo_tasks::function]
260 fn module(&self) -> Vc<Box<dyn Module>> {
261 Vc::upcast(*self.module)
262 }
263}
264
265#[turbo_tasks::value_impl]
266impl CssChunkItem for CssModuleChunkItem {
267 #[turbo_tasks::function]
268 async fn content(&self) -> Result<Vc<CssChunkItemContent>> {
269 let references = &*self.module.references().await?;
270 let mut imports = vec![];
271 let chunking_context = self.chunking_context;
272
273 for reference in references.iter() {
274 if let Some(import_ref) =
275 ResolvedVc::try_downcast_type::<ImportAssetReference>(*reference)
276 {
277 for &module in import_ref
278 .resolve_reference()
279 .resolve()
280 .await?
281 .primary_modules()
282 .await?
283 .iter()
284 {
285 if let Some(placeable) =
286 ResolvedVc::try_downcast::<Box<dyn CssChunkPlaceable>>(module)
287 {
288 let item = placeable.as_chunk_item(*self.module_graph, *chunking_context);
289 if let Some(css_item) =
290 Vc::try_resolve_downcast::<Box<dyn CssChunkItem>>(item).await?
291 {
292 imports.push(CssImport::Internal(
293 import_ref,
294 css_item.to_resolved().await?,
295 ));
296 }
297 }
298 }
299 } else if let Some(compose_ref) =
300 ResolvedVc::try_downcast_type::<CssModuleComposeReference>(*reference)
301 {
302 for &module in compose_ref
303 .resolve_reference()
304 .resolve()
305 .await?
306 .primary_modules()
307 .await?
308 .iter()
309 {
310 if let Some(placeable) =
311 ResolvedVc::try_downcast::<Box<dyn CssChunkPlaceable>>(module)
312 {
313 let item = placeable.as_chunk_item(*self.module_graph, *chunking_context);
314 if let Some(css_item) =
315 Vc::try_resolve_downcast::<Box<dyn CssChunkItem>>(item).await?
316 {
317 imports.push(CssImport::Composes(css_item.to_resolved().await?));
318 }
319 }
320 }
321 }
322 }
323
324 let mut code_gens = Vec::new();
325 for r in references.iter() {
326 if let Some(code_gen) = ResolvedVc::try_sidecast::<Box<dyn CodeGenerateable>>(*r) {
327 code_gens.push(code_gen.code_generation(*chunking_context));
328 }
329 }
330 let code_gens = code_gens.into_iter().try_join().await?;
332 let code_gens = code_gens.iter().map(|cg| &**cg).collect::<Vec<_>>();
333 for code_gen in code_gens {
335 for import in &code_gen.imports {
336 imports.push(import.clone());
337 }
338 }
339
340 let result = self
341 .module
342 .finalize_css(*chunking_context, *chunking_context.minify_type().await?)
343 .await?;
344
345 if let FinalCssResult::Ok {
346 output_code,
347 source_map,
348 } = &*result
349 {
350 Ok(CssChunkItemContent {
351 inner_code: output_code.to_owned().into(),
352 imports,
353 import_context: self.module.await?.import_context,
354 source_map: *source_map,
355 }
356 .cell())
357 } else {
358 Ok(CssChunkItemContent {
359 inner_code: format!(
360 "/* unparsable {} */",
361 self.module.ident().to_string().await?
362 )
363 .into(),
364 imports: vec![],
365 import_context: None,
366 source_map: FileContent::NotFound.resolved_cell(),
367 }
368 .cell())
369 }
370 }
371}