Skip to main content

turbopack_css/
asset.rs

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