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#[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 origin_path: FileSystemPath,
49}
50
51#[turbo_tasks::value_impl]
52impl CssModule {
53 #[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 #[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 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 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 let code_gens = code_gens.into_iter().try_join().await?;
332 let code_gens = code_gens.iter().map(|cg| &**cg).collect::<Vec<_>>();
333 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}