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