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