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