turbopack_ecmascript/tree_shake/
asset.rs1use anyhow::Result;
2use turbo_rcstr::{RcStr, rcstr};
3use turbo_tasks::{ResolvedVc, Vc};
4use turbo_tasks_fs::glob::Glob;
5use turbopack_core::{
6 asset::{Asset, AssetContent},
7 chunk::{AsyncModuleInfo, ChunkableModule, ChunkingContext, EvaluatableAsset},
8 context::AssetContext,
9 ident::AssetIdent,
10 module::Module,
11 module_graph::ModuleGraph,
12 reference::{ModuleReference, ModuleReferences, SingleChunkableModuleReference},
13 resolve::{ExportUsage, ModulePart, origin::ResolveOrigin},
14};
15
16use super::{
17 SplitResult, chunk_item::EcmascriptModulePartChunkItem, get_part_id, part_of_module,
18 split_module,
19};
20use crate::{
21 AnalyzeEcmascriptModuleResult, EcmascriptAnalyzable, EcmascriptModuleAsset,
22 EcmascriptModuleAssetType, EcmascriptModuleContent, EcmascriptModuleContentOptions,
23 EcmascriptParsable,
24 chunk::{EcmascriptChunkPlaceable, EcmascriptExports},
25 parse::ParseResult,
26 references::{
27 FollowExportsResult, analyze_ecmascript_module, esm::FoundExportType, follow_reexports,
28 },
29 side_effect_optimization::facade::module::EcmascriptModuleFacadeModule,
30 tree_shake::{Key, side_effect_module::SideEffectsModule},
31};
32
33#[turbo_tasks::value]
37pub struct EcmascriptModulePartAsset {
38 pub full_module: ResolvedVc<EcmascriptModuleAsset>,
39 pub part: ModulePart,
40}
41
42#[turbo_tasks::value_impl]
43impl EcmascriptParsable for EcmascriptModulePartAsset {
44 #[turbo_tasks::function]
45 fn failsafe_parse(&self) -> Result<Vc<ParseResult>> {
46 let split_data = split_module(*self.full_module);
47 Ok(part_of_module(split_data, self.part.clone()))
48 }
49 #[turbo_tasks::function]
50 fn parse_original(&self) -> Vc<ParseResult> {
51 self.full_module.parse_original()
52 }
53
54 #[turbo_tasks::function]
55 fn ty(&self) -> Vc<EcmascriptModuleAssetType> {
56 self.full_module.ty()
57 }
58}
59
60#[turbo_tasks::value_impl]
61impl EcmascriptAnalyzable for EcmascriptModulePartAsset {
62 #[turbo_tasks::function]
63 fn analyze(&self) -> Vc<AnalyzeEcmascriptModuleResult> {
64 analyze_ecmascript_module(*self.full_module, Some(self.part.clone()))
65 }
66
67 #[turbo_tasks::function]
68 fn module_content_without_analysis(
69 &self,
70 generate_source_map: bool,
71 ) -> Vc<EcmascriptModuleContent> {
72 self.full_module
73 .module_content_without_analysis(generate_source_map)
74 }
75
76 #[turbo_tasks::function]
77 async fn module_content_options(
78 self: ResolvedVc<Self>,
79 chunking_context: ResolvedVc<Box<dyn ChunkingContext>>,
80 async_module_info: Option<ResolvedVc<AsyncModuleInfo>>,
81 ) -> Result<Vc<EcmascriptModuleContentOptions>> {
82 let module = self.await?;
83
84 let split_data = split_module(*module.full_module);
85 let parsed = part_of_module(split_data, module.part.clone())
86 .to_resolved()
87 .await?;
88
89 let analyze = self.analyze();
90 let analyze_ref = analyze.await?;
91
92 let module_type_result = module.full_module.determine_module_type().await?;
93 let generate_source_map = *chunking_context
94 .reference_module_source_maps(Vc::upcast(*self))
95 .await?;
96 Ok(EcmascriptModuleContentOptions {
97 parsed: Some(parsed),
98 module: ResolvedVc::upcast(self),
99 specified_module_type: module_type_result.module_type,
100 chunking_context,
101 references: analyze.references().to_resolved().await?,
102 esm_references: analyze_ref.esm_references,
103 part_references: vec![],
104 code_generation: analyze_ref.code_generation,
105 async_module: analyze_ref.async_module,
106 generate_source_map,
107 original_source_map: analyze_ref.source_map,
108 exports: analyze_ref.exports,
109 async_module_info,
110 }
111 .cell())
112 }
113}
114
115#[turbo_tasks::value_impl]
116impl EcmascriptModulePartAsset {
117 #[turbo_tasks::function]
121 fn new_raw(module: ResolvedVc<EcmascriptModuleAsset>, part: ModulePart) -> Vc<Self> {
122 Self {
123 full_module: module,
124 part,
125 }
126 .cell()
127 }
128
129 #[turbo_tasks::function]
130 pub async fn new_with_resolved_part(
131 module: ResolvedVc<EcmascriptModuleAsset>,
132 part: ModulePart,
133 ) -> Result<Vc<Self>> {
134 if matches!(
135 part,
136 ModulePart::Internal(..) | ModulePart::Facade | ModulePart::Exports
137 ) {
138 return Ok(Self::new_raw(*module, part));
139 }
140
141 let split_result = split_module(*module).await?;
143 let part_id = get_part_id(&split_result, &part).await?;
144
145 Ok(Self::new_raw(*module, ModulePart::internal(part_id)))
146 }
147
148 #[turbo_tasks::function]
149 pub async fn select_part(
150 module: Vc<EcmascriptModuleAsset>,
151 part: ModulePart,
152 ) -> Result<Vc<Box<dyn EcmascriptChunkPlaceable>>> {
153 let SplitResult::Ok { entrypoints, .. } = &*split_module(module).await? else {
154 return Ok(Vc::upcast(module));
155 };
156
157 match part {
158 ModulePart::Evaluation => {
159 let idx = *entrypoints.get(&Key::ModuleEvaluation).unwrap();
161 return Ok(Vc::upcast(
162 EcmascriptModulePartAsset::new_with_resolved_part(
163 module,
164 ModulePart::internal(idx),
165 ),
166 ));
167 }
168
169 ModulePart::Export(export) => {
170 if entrypoints.contains_key(&Key::Export(export.clone())) {
171 return Ok(Vc::upcast(
172 EcmascriptModulePartAsset::new_with_resolved_part(
173 module,
174 ModulePart::Export(export),
175 ),
176 ));
177 }
178 let side_effect_free_packages = module.asset_context().side_effect_free_packages();
179 let source_module = Vc::upcast(module);
180 let FollowExportsWithSideEffectsResult {
181 side_effects,
182 result,
183 } = &*follow_reexports_with_side_effects(
184 source_module,
185 export.clone(),
186 side_effect_free_packages,
187 )
188 .await?;
189 let FollowExportsResult {
190 module: final_module,
191 export_name: new_export,
192 ..
193 } = &*result.await?;
194 let final_module = if let Some(new_export) = new_export {
195 if *new_export == export {
196 *final_module
197 } else {
198 ResolvedVc::upcast(
199 EcmascriptModuleFacadeModule::new(
200 **final_module,
201 ModulePart::renamed_export(new_export.clone(), export.clone()),
202 )
203 .to_resolved()
204 .await?,
205 )
206 }
207 } else {
208 ResolvedVc::upcast(
209 EcmascriptModuleFacadeModule::new(
210 **final_module,
211 ModulePart::renamed_namespace(export.clone()),
212 )
213 .to_resolved()
214 .await?,
215 )
216 };
217 if side_effects.is_empty() {
218 return Ok(*final_module);
219 }
220 let side_effects_module = SideEffectsModule::new(
221 module,
222 ModulePart::Export(export),
223 *final_module,
224 side_effects.iter().map(|v| **v).collect(),
225 );
226 return Ok(Vc::upcast(side_effects_module));
227 }
228 _ => (),
229 }
230
231 Ok(Vc::upcast(
232 EcmascriptModulePartAsset::new_with_resolved_part(module, part.clone()),
233 ))
234 }
235
236 #[turbo_tasks::function]
237 pub async fn is_async_module(self: Vc<Self>) -> Result<Vc<bool>> {
238 let this = self.await?;
239 let result = analyze(*this.full_module, this.part.clone());
240
241 if let Some(async_module) = *result.await?.async_module.await? {
242 Ok(async_module.is_self_async(self.references()))
243 } else {
244 Ok(Vc::cell(false))
245 }
246 }
247}
248
249#[turbo_tasks::value]
250struct FollowExportsWithSideEffectsResult {
251 side_effects: Vec<ResolvedVc<Box<dyn EcmascriptChunkPlaceable>>>,
252 result: ResolvedVc<FollowExportsResult>,
253}
254
255#[turbo_tasks::function]
256async fn follow_reexports_with_side_effects(
257 module: ResolvedVc<Box<dyn EcmascriptChunkPlaceable>>,
258 export_name: RcStr,
259 side_effect_free_packages: Vc<Glob>,
260) -> Result<Vc<FollowExportsWithSideEffectsResult>> {
261 let mut side_effects = vec![];
262
263 let mut current_module = module;
264 let mut current_export_name = export_name;
265 let result = loop {
266 let is_side_effect_free = *current_module
267 .is_marked_as_side_effect_free(side_effect_free_packages)
268 .await?;
269
270 if !is_side_effect_free {
271 side_effects.push(only_effects(*current_module).to_resolved().await?);
272 }
273
274 let result = follow_reexports(
276 *current_module,
277 current_export_name.clone(),
278 side_effect_free_packages,
279 true,
280 )
281 .to_resolved()
282 .await?;
283
284 let FollowExportsResult {
285 module,
286 export_name,
287 ty,
288 } = &*result.await?;
289
290 match ty {
291 FoundExportType::SideEffects => {
292 current_module = *module;
293 current_export_name = export_name.clone().unwrap_or(current_export_name);
294 }
295 _ => break result,
296 }
297 };
298
299 Ok(FollowExportsWithSideEffectsResult {
300 side_effects,
301 result,
302 }
303 .cell())
304}
305
306#[turbo_tasks::value_impl]
307impl Module for EcmascriptModulePartAsset {
308 #[turbo_tasks::function]
309 fn ident(&self) -> Vc<AssetIdent> {
310 self.full_module.ident().with_part(self.part.clone())
311 }
312
313 #[turbo_tasks::function]
314 fn is_self_async(self: Vc<Self>) -> Vc<bool> {
315 self.is_async_module()
316 }
317
318 #[turbo_tasks::function]
319 async fn references(&self) -> Result<Vc<ModuleReferences>> {
320 let part_dep = |part: ModulePart| -> Vc<Box<dyn ModuleReference>> {
321 let export = match &part {
322 ModulePart::Export(export) => ExportUsage::named(export.clone()),
323 ModulePart::Evaluation => ExportUsage::evaluation(),
324 _ => ExportUsage::all(),
325 };
326
327 Vc::upcast(SingleChunkableModuleReference::new(
328 Vc::upcast(EcmascriptModulePartAsset::new_with_resolved_part(
329 *self.full_module,
330 part,
331 )),
332 rcstr!("part reference"),
333 export,
334 ))
335 };
336
337 if let ModulePart::Facade = self.part {
338 let mut references = vec![];
340 references.push(part_dep(ModulePart::evaluation()).to_resolved().await?);
341 references.push(part_dep(ModulePart::exports()).to_resolved().await?);
342 return Ok(Vc::cell(references));
343 }
344
345 let analyze = analyze(*self.full_module, self.part.clone());
346
347 Ok(analyze.references())
348 }
349}
350
351#[turbo_tasks::value_impl]
352impl Asset for EcmascriptModulePartAsset {
353 #[turbo_tasks::function]
354 fn content(&self) -> Vc<AssetContent> {
355 self.full_module.content()
356 }
357}
358
359#[turbo_tasks::value_impl]
360impl EcmascriptChunkPlaceable for EcmascriptModulePartAsset {
361 #[turbo_tasks::function]
362 async fn get_exports(self: Vc<Self>) -> Result<Vc<EcmascriptExports>> {
363 Ok(*self.analyze().await?.exports)
364 }
365
366 #[turbo_tasks::function]
367 async fn is_marked_as_side_effect_free(
368 self: Vc<Self>,
369 side_effect_free_packages: Vc<Glob>,
370 ) -> Result<Vc<bool>> {
371 let this = self.await?;
372
373 match this.part {
374 ModulePart::Exports | ModulePart::Export(..) => Ok(Vc::cell(true)),
375 _ => Ok(this
376 .full_module
377 .is_marked_as_side_effect_free(side_effect_free_packages)),
378 }
379 }
380}
381
382#[turbo_tasks::value_impl]
383impl ChunkableModule for EcmascriptModulePartAsset {
384 #[turbo_tasks::function]
385 fn as_chunk_item(
386 self: ResolvedVc<Self>,
387 _module_graph: ResolvedVc<ModuleGraph>,
388 chunking_context: ResolvedVc<Box<dyn ChunkingContext>>,
389 ) -> Vc<Box<dyn turbopack_core::chunk::ChunkItem>> {
390 Vc::upcast(
391 EcmascriptModulePartChunkItem {
392 module: self,
393 chunking_context,
394 }
395 .cell(),
396 )
397 }
398}
399
400#[turbo_tasks::value_impl]
401impl EcmascriptModulePartAsset {
402 #[turbo_tasks::function]
403 pub(super) fn analyze(&self) -> Vc<AnalyzeEcmascriptModuleResult> {
404 analyze(*self.full_module, self.part.clone())
405 }
406}
407
408#[turbo_tasks::function]
409fn analyze(
410 module: Vc<EcmascriptModuleAsset>,
411 part: ModulePart,
412) -> Vc<AnalyzeEcmascriptModuleResult> {
413 analyze_ecmascript_module(module, Some(part))
414}
415
416#[turbo_tasks::value_impl]
417impl EvaluatableAsset for EcmascriptModulePartAsset {}
418
419#[turbo_tasks::function]
420async fn only_effects(
421 module: ResolvedVc<Box<dyn EcmascriptChunkPlaceable>>,
422) -> Result<Vc<Box<dyn EcmascriptChunkPlaceable>>> {
423 if let Some(module) = ResolvedVc::try_downcast_type::<EcmascriptModuleAsset>(module) {
424 let module =
425 EcmascriptModulePartAsset::new_with_resolved_part(*module, ModulePart::evaluation());
426 return Ok(Vc::upcast(module));
427 }
428
429 Ok(*module)
430}