turbopack_css/
asset.rs

1use anyhow::Result;
2use turbo_rcstr::rcstr;
3use turbo_tasks::{IntoTraitRef, ResolvedVc, TryJoinIterExt, ValueToString, Vc};
4use turbo_tasks_fs::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, StyleModule, StyleType},
12    module_graph::ModuleGraph,
13    output::OutputAssets,
14    reference::{ModuleReference, ModuleReferences},
15    reference_type::ImportContext,
16    resolve::origin::ResolveOrigin,
17    source::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)]
36/// A global CSS asset. Notably not a `.module.css` module, which is [`ModuleCssAsset`] instead.
37pub 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    /// Creates a new CSS asset.
48    #[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    /// Returns the asset ident of the source without the "css" modifier
66    #[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 => Vc::cell(None),
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    async fn references(self: Vc<Self>) -> Result<Vc<ModuleReferences>> {
142        let result = self.parse_css().await?;
143        // TODO: include CSS source map
144
145        match &*result {
146            ParseCssResult::Ok { references, .. } => Ok(**references),
147            ParseCssResult::Unparsable => Ok(ModuleReferences::empty()),
148            ParseCssResult::NotFound => Ok(ModuleReferences::empty()),
149        }
150    }
151}
152
153#[turbo_tasks::value_impl]
154impl StyleModule for CssModuleAsset {
155    #[turbo_tasks::function]
156    fn style_type(&self) -> Vc<StyleType> {
157        match self.ty {
158            CssModuleAssetType::Default => StyleType::GlobalStyle.cell(),
159            CssModuleAssetType::Module => StyleType::IsolatedStyle.cell(),
160        }
161    }
162}
163
164#[turbo_tasks::value_impl]
165impl Asset for CssModuleAsset {
166    #[turbo_tasks::function]
167    fn content(&self) -> Vc<AssetContent> {
168        self.source.content()
169    }
170}
171
172#[turbo_tasks::value_impl]
173impl ChunkableModule for CssModuleAsset {
174    #[turbo_tasks::function]
175    fn as_chunk_item(
176        self: ResolvedVc<Self>,
177        module_graph: ResolvedVc<ModuleGraph>,
178        chunking_context: ResolvedVc<Box<dyn ChunkingContext>>,
179    ) -> Vc<Box<dyn turbopack_core::chunk::ChunkItem>> {
180        Vc::upcast(CssModuleChunkItem::cell(CssModuleChunkItem {
181            module: self,
182            module_graph,
183            chunking_context,
184        }))
185    }
186}
187
188#[turbo_tasks::value_impl]
189impl CssChunkPlaceable for CssModuleAsset {}
190
191#[turbo_tasks::value_impl]
192impl ResolveOrigin for CssModuleAsset {
193    #[turbo_tasks::function]
194    fn origin_path(&self) -> Vc<FileSystemPath> {
195        self.source.ident().path()
196    }
197
198    #[turbo_tasks::function]
199    fn asset_context(&self) -> Vc<Box<dyn AssetContext>> {
200        *self.asset_context
201    }
202}
203
204#[turbo_tasks::value]
205struct CssModuleChunkItem {
206    module: ResolvedVc<CssModuleAsset>,
207    module_graph: ResolvedVc<ModuleGraph>,
208    chunking_context: ResolvedVc<Box<dyn ChunkingContext>>,
209}
210
211#[turbo_tasks::value_impl]
212impl ChunkItem for CssModuleChunkItem {
213    #[turbo_tasks::function]
214    fn asset_ident(&self) -> Vc<AssetIdent> {
215        self.module.ident()
216    }
217
218    #[turbo_tasks::function]
219    fn chunking_context(&self) -> Vc<Box<dyn ChunkingContext>> {
220        *self.chunking_context
221    }
222
223    #[turbo_tasks::function]
224    fn ty(&self) -> Vc<Box<dyn ChunkType>> {
225        Vc::upcast(Vc::<CssChunkType>::default())
226    }
227
228    #[turbo_tasks::function]
229    fn module(&self) -> Vc<Box<dyn Module>> {
230        Vc::upcast(*self.module)
231    }
232
233    #[turbo_tasks::function]
234    async fn references(&self) -> Result<Vc<OutputAssets>> {
235        let mut references = Vec::new();
236        if let ParseCssResult::Ok { url_references, .. } = &*self.module.parse_css().await? {
237            for (_, reference) in url_references.await? {
238                if let ReferencedAsset::Some(asset) = *reference
239                    .get_referenced_asset(*self.chunking_context)
240                    .await?
241                {
242                    references.push(asset);
243                }
244            }
245        }
246        Ok(Vc::cell(references))
247    }
248}
249
250#[turbo_tasks::value_impl]
251impl CssChunkItem for CssModuleChunkItem {
252    #[turbo_tasks::function]
253    async fn content(&self) -> Result<Vc<CssChunkItemContent>> {
254        let references = &*self.module.references().await?;
255        let mut imports = vec![];
256        let chunking_context = self.chunking_context;
257
258        for reference in references.iter() {
259            if let Some(import_ref) =
260                ResolvedVc::try_downcast_type::<ImportAssetReference>(*reference)
261            {
262                for &module in import_ref
263                    .resolve_reference()
264                    .resolve()
265                    .await?
266                    .primary_modules()
267                    .await?
268                    .iter()
269                {
270                    if let Some(placeable) =
271                        ResolvedVc::try_downcast::<Box<dyn CssChunkPlaceable>>(module)
272                    {
273                        let item = placeable.as_chunk_item(*self.module_graph, *chunking_context);
274                        if let Some(css_item) =
275                            Vc::try_resolve_downcast::<Box<dyn CssChunkItem>>(item).await?
276                        {
277                            imports.push(CssImport::Internal(
278                                import_ref,
279                                css_item.to_resolved().await?,
280                            ));
281                        }
282                    }
283                }
284            } else if let Some(compose_ref) =
285                ResolvedVc::try_downcast_type::<CssModuleComposeReference>(*reference)
286            {
287                for &module in compose_ref
288                    .resolve_reference()
289                    .resolve()
290                    .await?
291                    .primary_modules()
292                    .await?
293                    .iter()
294                {
295                    if let Some(placeable) =
296                        ResolvedVc::try_downcast::<Box<dyn CssChunkPlaceable>>(module)
297                    {
298                        let item = placeable.as_chunk_item(*self.module_graph, *chunking_context);
299                        if let Some(css_item) =
300                            Vc::try_resolve_downcast::<Box<dyn CssChunkItem>>(item).await?
301                        {
302                            imports.push(CssImport::Composes(css_item.to_resolved().await?));
303                        }
304                    }
305                }
306            }
307        }
308
309        let mut code_gens = Vec::new();
310        for r in references.iter() {
311            if let Some(code_gen) = ResolvedVc::try_sidecast::<Box<dyn CodeGenerateable>>(*r) {
312                code_gens.push(code_gen.code_generation(*chunking_context));
313            }
314        }
315        // need to keep that around to allow references into that
316        let code_gens = code_gens.into_iter().try_join().await?;
317        let code_gens = code_gens.iter().map(|cg| &**cg).collect::<Vec<_>>();
318        // TODO use interval tree with references into "code_gens"
319        for code_gen in code_gens {
320            for import in &code_gen.imports {
321                imports.push(import.clone());
322            }
323        }
324
325        let result = self
326            .module
327            .finalize_css(*chunking_context, *chunking_context.minify_type().await?)
328            .await?;
329
330        if let FinalCssResult::Ok {
331            output_code,
332            source_map,
333            ..
334        } = &*result
335        {
336            Ok(CssChunkItemContent {
337                inner_code: output_code.to_owned().into(),
338                imports,
339                import_context: self.module.await?.import_context,
340                source_map: source_map.owned().await?,
341            }
342            .into())
343        } else {
344            Ok(CssChunkItemContent {
345                inner_code: format!(
346                    "/* unparsable {} */",
347                    self.module.ident().to_string().await?
348                )
349                .into(),
350                imports: vec![],
351                import_context: None,
352                source_map: None,
353            }
354            .into())
355        }
356    }
357}