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