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