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