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