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