turbopack_css/
asset.rs

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