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::{OutputAssetsReference, OutputAssetsWithReferenced},
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 OutputAssetsReference for CssModuleChunkItem {
213    #[turbo_tasks::function]
214    async fn references(&self) -> Result<Vc<OutputAssetsWithReferenced>> {
215        let mut references = Vec::new();
216        if let ParseCssResult::Ok { url_references, .. } = &*self.module.parse_css().await? {
217            for (_, reference) in url_references.await? {
218                if let ReferencedAsset::Some(asset) = *reference
219                    .get_referenced_asset(*self.chunking_context)
220                    .await?
221                {
222                    references.push(asset);
223                }
224            }
225        }
226        Ok(OutputAssetsWithReferenced::from_assets(Vc::cell(
227            references,
228        )))
229    }
230}
231
232#[turbo_tasks::value_impl]
233impl ChunkItem for CssModuleChunkItem {
234    #[turbo_tasks::function]
235    fn asset_ident(&self) -> Vc<AssetIdent> {
236        self.module.ident()
237    }
238
239    #[turbo_tasks::function]
240    fn chunking_context(&self) -> Vc<Box<dyn ChunkingContext>> {
241        *self.chunking_context
242    }
243
244    #[turbo_tasks::function]
245    fn ty(&self) -> Vc<Box<dyn ChunkType>> {
246        Vc::upcast(Vc::<CssChunkType>::default())
247    }
248
249    #[turbo_tasks::function]
250    fn module(&self) -> Vc<Box<dyn Module>> {
251        Vc::upcast(*self.module)
252    }
253}
254
255#[turbo_tasks::value_impl]
256impl CssChunkItem for CssModuleChunkItem {
257    #[turbo_tasks::function]
258    async fn content(&self) -> Result<Vc<CssChunkItemContent>> {
259        let references = &*self.module.references().await?;
260        let mut imports = vec![];
261        let chunking_context = self.chunking_context;
262
263        for reference in references.iter() {
264            if let Some(import_ref) =
265                ResolvedVc::try_downcast_type::<ImportAssetReference>(*reference)
266            {
267                for &module in import_ref
268                    .resolve_reference()
269                    .resolve()
270                    .await?
271                    .primary_modules()
272                    .await?
273                    .iter()
274                {
275                    if let Some(placeable) =
276                        ResolvedVc::try_downcast::<Box<dyn CssChunkPlaceable>>(module)
277                    {
278                        let item = placeable.as_chunk_item(*self.module_graph, *chunking_context);
279                        if let Some(css_item) =
280                            Vc::try_resolve_downcast::<Box<dyn CssChunkItem>>(item).await?
281                        {
282                            imports.push(CssImport::Internal(
283                                import_ref,
284                                css_item.to_resolved().await?,
285                            ));
286                        }
287                    }
288                }
289            } else if let Some(compose_ref) =
290                ResolvedVc::try_downcast_type::<CssModuleComposeReference>(*reference)
291            {
292                for &module in compose_ref
293                    .resolve_reference()
294                    .resolve()
295                    .await?
296                    .primary_modules()
297                    .await?
298                    .iter()
299                {
300                    if let Some(placeable) =
301                        ResolvedVc::try_downcast::<Box<dyn CssChunkPlaceable>>(module)
302                    {
303                        let item = placeable.as_chunk_item(*self.module_graph, *chunking_context);
304                        if let Some(css_item) =
305                            Vc::try_resolve_downcast::<Box<dyn CssChunkItem>>(item).await?
306                        {
307                            imports.push(CssImport::Composes(css_item.to_resolved().await?));
308                        }
309                    }
310                }
311            }
312        }
313
314        let mut code_gens = Vec::new();
315        for r in references.iter() {
316            if let Some(code_gen) = ResolvedVc::try_sidecast::<Box<dyn CodeGenerateable>>(*r) {
317                code_gens.push(code_gen.code_generation(*chunking_context));
318            }
319        }
320        // need to keep that around to allow references into that
321        let code_gens = code_gens.into_iter().try_join().await?;
322        let code_gens = code_gens.iter().map(|cg| &**cg).collect::<Vec<_>>();
323        // TODO use interval tree with references into "code_gens"
324        for code_gen in code_gens {
325            for import in &code_gen.imports {
326                imports.push(import.clone());
327            }
328        }
329
330        let result = self
331            .module
332            .finalize_css(*chunking_context, *chunking_context.minify_type().await?)
333            .await?;
334
335        if let FinalCssResult::Ok {
336            output_code,
337            source_map,
338        } = &*result
339        {
340            Ok(CssChunkItemContent {
341                inner_code: output_code.to_owned().into(),
342                imports,
343                import_context: self.module.await?.import_context,
344                source_map: source_map.owned().await?,
345            }
346            .cell())
347        } else {
348            Ok(CssChunkItemContent {
349                inner_code: format!(
350                    "/* unparsable {} */",
351                    self.module.ident().to_string().await?
352                )
353                .into(),
354                imports: vec![],
355                import_context: None,
356                source_map: None,
357            }
358            .cell())
359        }
360    }
361}