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