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    chunk::{ChunkItem, ChunkType, ChunkableModule, ChunkingContext, MinifyType},
7    context::AssetContext,
8    environment::Environment,
9    ident::AssetIdent,
10    module::{Module, ModuleSideEffects, StyleModule, StyleType},
11    module_graph::ModuleGraph,
12    output::{OutputAssetsReference, OutputAssetsWithReferenced},
13    reference::{ModuleReference, ModuleReferences},
14    reference_type::ImportContext,
15    resolve::origin::ResolveOrigin,
16    source::{OptionSource, Source},
17    source_map::GenerateSourceMap,
18};
19
20use crate::{
21    CssModuleAssetType,
22    chunk::{CssChunkItem, CssChunkItemContent, CssChunkPlaceable, CssChunkType, CssImport},
23    code_gen::CodeGenerateable,
24    process::{
25        CssWithPlaceholderResult, FinalCssResult, ParseCss, ParseCssResult, ProcessCss,
26        finalize_css, parse_css, process_css_with_placeholder,
27    },
28    references::{
29        compose::CssModuleComposeReference, import::ImportAssetReference, url::ReferencedAsset,
30    },
31};
32
33#[turbo_tasks::value]
34#[derive(Clone)]
35/// A global CSS asset. Notably not a `.module.css` module, which is [`ModuleCssAsset`] instead.
36pub struct CssModuleAsset {
37    source: ResolvedVc<Box<dyn Source>>,
38    asset_context: ResolvedVc<Box<dyn AssetContext>>,
39    import_context: Option<ResolvedVc<ImportContext>>,
40    ty: CssModuleAssetType,
41    environment: Option<ResolvedVc<Environment>>,
42}
43
44#[turbo_tasks::value_impl]
45impl CssModuleAsset {
46    /// Creates a new CSS asset.
47    #[turbo_tasks::function]
48    pub fn new(
49        source: ResolvedVc<Box<dyn Source>>,
50        asset_context: ResolvedVc<Box<dyn AssetContext>>,
51        ty: CssModuleAssetType,
52        import_context: Option<ResolvedVc<ImportContext>>,
53        environment: Option<ResolvedVc<Environment>>,
54    ) -> Vc<Self> {
55        Self::cell(CssModuleAsset {
56            source,
57            asset_context,
58            import_context,
59            ty,
60            environment,
61        })
62    }
63
64    /// Returns the asset ident of the source without the "css" modifier
65    #[turbo_tasks::function]
66    pub fn source_ident(&self) -> Vc<AssetIdent> {
67        self.source.ident()
68    }
69}
70
71#[turbo_tasks::value_impl]
72impl ParseCss for CssModuleAsset {
73    #[turbo_tasks::function]
74    async fn parse_css(self: Vc<Self>) -> Result<Vc<ParseCssResult>> {
75        let this = self.await?;
76
77        Ok(parse_css(
78            *this.source,
79            Vc::upcast(self),
80            this.import_context.map(|v| *v),
81            this.ty,
82            this.environment.as_deref().copied(),
83        ))
84    }
85}
86
87#[turbo_tasks::value_impl]
88impl ProcessCss for CssModuleAsset {
89    #[turbo_tasks::function]
90    async fn get_css_with_placeholder(self: Vc<Self>) -> Result<Vc<CssWithPlaceholderResult>> {
91        let this = self.await?;
92        let parse_result = self.parse_css();
93
94        Ok(process_css_with_placeholder(
95            parse_result,
96            this.environment.as_deref().copied(),
97        ))
98    }
99
100    #[turbo_tasks::function]
101    async fn finalize_css(
102        self: Vc<Self>,
103        chunking_context: Vc<Box<dyn ChunkingContext>>,
104        minify_type: MinifyType,
105    ) -> Result<Vc<FinalCssResult>> {
106        let process_result = self.get_css_with_placeholder();
107
108        let this = self.await?;
109        let origin_source_map =
110            match ResolvedVc::try_sidecast::<Box<dyn GenerateSourceMap>>(this.source) {
111                Some(gsm) => gsm.generate_source_map(),
112                None => FileContent::NotFound.cell(),
113            };
114        Ok(finalize_css(
115            process_result,
116            chunking_context,
117            minify_type,
118            origin_source_map,
119            this.environment.as_deref().copied(),
120        ))
121    }
122}
123
124#[turbo_tasks::value_impl]
125impl Module for CssModuleAsset {
126    #[turbo_tasks::function]
127    async fn ident(&self) -> Result<Vc<AssetIdent>> {
128        let mut ident = self
129            .source
130            .ident()
131            .with_modifier(rcstr!("css"))
132            .with_layer(self.asset_context.into_trait_ref().await?.layer());
133        if let Some(import_context) = self.import_context {
134            ident = ident.with_modifier(import_context.modifier().owned().await?)
135        }
136        Ok(ident)
137    }
138
139    #[turbo_tasks::function]
140    fn source(&self) -> Vc<OptionSource> {
141        Vc::cell(Some(self.source))
142    }
143
144    #[turbo_tasks::function]
145    async fn references(self: Vc<Self>) -> Result<Vc<ModuleReferences>> {
146        let result = self.parse_css().await?;
147        // TODO: include CSS source map
148
149        match &*result {
150            ParseCssResult::Ok { references, .. } => Ok(**references),
151            ParseCssResult::Unparsable => Ok(ModuleReferences::empty()),
152            ParseCssResult::NotFound => Ok(ModuleReferences::empty()),
153        }
154    }
155    #[turbo_tasks::function]
156    fn side_effects(self: Vc<Self>) -> Vc<ModuleSideEffects> {
157        // global css is always a side effect
158        ModuleSideEffects::SideEffectful.cell()
159    }
160}
161
162#[turbo_tasks::value_impl]
163impl StyleModule for CssModuleAsset {
164    #[turbo_tasks::function]
165    fn style_type(&self) -> Vc<StyleType> {
166        match self.ty {
167            CssModuleAssetType::Default => StyleType::GlobalStyle.cell(),
168            CssModuleAssetType::Module => StyleType::IsolatedStyle.cell(),
169        }
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 OutputAssetsReference for CssModuleChunkItem {
214    #[turbo_tasks::function]
215    async fn references(&self) -> Result<Vc<OutputAssetsWithReferenced>> {
216        let mut references = Vec::new();
217        if let ParseCssResult::Ok { url_references, .. } = &*self.module.parse_css().await? {
218            for (_, reference) in url_references.await? {
219                if let ReferencedAsset::Some(asset) = *reference
220                    .get_referenced_asset(*self.chunking_context)
221                    .await?
222                {
223                    references.push(asset);
224                }
225            }
226        }
227        Ok(OutputAssetsWithReferenced::from_assets(Vc::cell(
228            references,
229        )))
230    }
231}
232
233#[turbo_tasks::value_impl]
234impl ChunkItem for CssModuleChunkItem {
235    #[turbo_tasks::function]
236    fn asset_ident(&self) -> Vc<AssetIdent> {
237        self.module.ident()
238    }
239
240    #[turbo_tasks::function]
241    fn chunking_context(&self) -> Vc<Box<dyn ChunkingContext>> {
242        *self.chunking_context
243    }
244
245    #[turbo_tasks::function]
246    fn ty(&self) -> Vc<Box<dyn ChunkType>> {
247        Vc::upcast(Vc::<CssChunkType>::default())
248    }
249
250    #[turbo_tasks::function]
251    fn module(&self) -> Vc<Box<dyn Module>> {
252        Vc::upcast(*self.module)
253    }
254}
255
256#[turbo_tasks::value_impl]
257impl CssChunkItem for CssModuleChunkItem {
258    #[turbo_tasks::function]
259    async fn content(&self) -> Result<Vc<CssChunkItemContent>> {
260        let references = &*self.module.references().await?;
261        let mut imports = vec![];
262        let chunking_context = self.chunking_context;
263
264        for reference in references.iter() {
265            if let Some(import_ref) =
266                ResolvedVc::try_downcast_type::<ImportAssetReference>(*reference)
267            {
268                for &module in import_ref
269                    .resolve_reference()
270                    .resolve()
271                    .await?
272                    .primary_modules()
273                    .await?
274                    .iter()
275                {
276                    if let Some(placeable) =
277                        ResolvedVc::try_downcast::<Box<dyn CssChunkPlaceable>>(module)
278                    {
279                        let item = placeable.as_chunk_item(*self.module_graph, *chunking_context);
280                        if let Some(css_item) =
281                            Vc::try_resolve_downcast::<Box<dyn CssChunkItem>>(item).await?
282                        {
283                            imports.push(CssImport::Internal(
284                                import_ref,
285                                css_item.to_resolved().await?,
286                            ));
287                        }
288                    }
289                }
290            } else if let Some(compose_ref) =
291                ResolvedVc::try_downcast_type::<CssModuleComposeReference>(*reference)
292            {
293                for &module in compose_ref
294                    .resolve_reference()
295                    .resolve()
296                    .await?
297                    .primary_modules()
298                    .await?
299                    .iter()
300                {
301                    if let Some(placeable) =
302                        ResolvedVc::try_downcast::<Box<dyn CssChunkPlaceable>>(module)
303                    {
304                        let item = placeable.as_chunk_item(*self.module_graph, *chunking_context);
305                        if let Some(css_item) =
306                            Vc::try_resolve_downcast::<Box<dyn CssChunkItem>>(item).await?
307                        {
308                            imports.push(CssImport::Composes(css_item.to_resolved().await?));
309                        }
310                    }
311                }
312            }
313        }
314
315        let mut code_gens = Vec::new();
316        for r in references.iter() {
317            if let Some(code_gen) = ResolvedVc::try_sidecast::<Box<dyn CodeGenerateable>>(*r) {
318                code_gens.push(code_gen.code_generation(*chunking_context));
319            }
320        }
321        // need to keep that around to allow references into that
322        let code_gens = code_gens.into_iter().try_join().await?;
323        let code_gens = code_gens.iter().map(|cg| &**cg).collect::<Vec<_>>();
324        // TODO use interval tree with references into "code_gens"
325        for code_gen in code_gens {
326            for import in &code_gen.imports {
327                imports.push(import.clone());
328            }
329        }
330
331        let result = self
332            .module
333            .finalize_css(*chunking_context, *chunking_context.minify_type().await?)
334            .await?;
335
336        if let FinalCssResult::Ok {
337            output_code,
338            source_map,
339        } = &*result
340        {
341            Ok(CssChunkItemContent {
342                inner_code: output_code.to_owned().into(),
343                imports,
344                import_context: self.module.await?.import_context,
345                source_map: *source_map,
346            }
347            .cell())
348        } else {
349            Ok(CssChunkItemContent {
350                inner_code: format!(
351                    "/* unparsable {} */",
352                    self.module.ident().to_string().await?
353                )
354                .into(),
355                imports: vec![],
356                import_context: None,
357                source_map: FileContent::NotFound.resolved_cell(),
358            }
359            .cell())
360        }
361    }
362}