turbopack_ecmascript/tree_shake/
asset.rs

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