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            .with_modifier(rcstr!("css"))
141            .with_layer(self.asset_context.into_trait_ref().await?.layer());
142        if let Some(import_context) = self.import_context {
143            ident = ident.with_modifier(import_context.modifier().owned().await?)
144        }
145        Ok(ident)
146    }
147
148    #[turbo_tasks::function]
149    fn source(&self) -> Vc<OptionSource> {
150        Vc::cell(Some(self.source))
151    }
152
153    #[turbo_tasks::function]
154    async fn references(self: Vc<Self>) -> Result<Vc<ModuleReferences>> {
155        let result = self.parse_css().await?;
156        // TODO: include CSS source map
157
158        match &*result {
159            ParseCssResult::Ok { references, .. } => Ok(**references),
160            ParseCssResult::Unparsable => Ok(ModuleReferences::empty()),
161            ParseCssResult::NotFound => Ok(ModuleReferences::empty()),
162        }
163    }
164    #[turbo_tasks::function]
165    fn side_effects(self: Vc<Self>) -> Vc<ModuleSideEffects> {
166        // global css is always a side effect
167        ModuleSideEffects::SideEffectful.cell()
168    }
169}
170
171#[turbo_tasks::value_impl]
172impl StyleModule for CssModule {
173    #[turbo_tasks::function]
174    fn style_type(&self) -> Vc<StyleType> {
175        match self.ty {
176            CssModuleType::Default => StyleType::GlobalStyle.cell(),
177            CssModuleType::Module => StyleType::IsolatedStyle.cell(),
178        }
179    }
180}
181
182#[turbo_tasks::value_impl]
183impl ChunkableModule for CssModule {
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 CssModule {}
200
201#[turbo_tasks::value_impl]
202impl ResolveOrigin for CssModule {
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<CssModule>,
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) = 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                    .resolve()
302                    .await?
303                    .primary_modules()
304                    .await?
305                    .iter()
306                {
307                    if let Some(placeable) =
308                        ResolvedVc::try_downcast::<Box<dyn CssChunkPlaceable>>(module)
309                    {
310                        let item = placeable.as_chunk_item(*self.module_graph, *chunking_context);
311                        if let Some(css_item) = ResolvedVc::try_downcast::<Box<dyn CssChunkItem>>(
312                            item.to_resolved().await?,
313                        ) {
314                            imports.push(CssImport::Composes(css_item));
315                        }
316                    }
317                }
318            }
319        }
320
321        let mut code_gens = Vec::new();
322        for r in references.iter() {
323            if let Some(code_gen) = ResolvedVc::try_sidecast::<Box<dyn CodeGenerateable>>(*r) {
324                code_gens.push(code_gen.code_generation(*chunking_context));
325            }
326        }
327        // need to keep that around to allow references into that
328        let code_gens = code_gens.into_iter().try_join().await?;
329        let code_gens = code_gens.iter().map(|cg| &**cg).collect::<Vec<_>>();
330        // TODO use interval tree with references into "code_gens"
331        for code_gen in code_gens {
332            for import in &code_gen.imports {
333                imports.push(import.clone());
334            }
335        }
336
337        let result = self
338            .module
339            .finalize_css(*chunking_context, *chunking_context.minify_type().await?)
340            .await?;
341
342        if let FinalCssResult::Ok {
343            output_code,
344            source_map,
345        } = &*result
346        {
347            Ok(CssChunkItemContent {
348                inner_code: output_code.to_owned().into(),
349                imports,
350                import_context: self.module.await?.import_context,
351                source_map: *source_map,
352            }
353            .cell())
354        } else {
355            Ok(CssChunkItemContent {
356                inner_code: turbofmt!("/* unparsable {} */", self.module.ident())
357                    .await?
358                    .to_string()
359                    .into(),
360                imports: vec![],
361                import_context: None,
362                source_map: FileContent::NotFound.resolved_cell(),
363            }
364            .cell())
365        }
366    }
367}