turbopack_css/
asset.rs

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