turbopack_ecmascript/tree_shake/
asset.rs

1use 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/// A reference to part of an ES module.
32///
33/// This type is used for an advanced tree shkaing.
34#[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    /// Create a new instance of [Vc<EcmascriptModulePartAsset>], which consists
116    /// of a pointer to the full module and the [ModulePart] pointing the part
117    /// of the module.
118    #[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        // This is a workaround to avoid creating duplicate assets for internal parts.
140        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                // We resolve the module evaluation here to prevent duplicate assets.
158                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        // We ignore the side effect of the entry module here, because we need to proceed.
262        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            // Facade depends on evaluation and re-exports
326            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}