Skip to main content

turbopack_ecmascript/tree_shake/part/
module.rs

1use 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 crate::{
14    AnalyzeEcmascriptModuleResult, EcmascriptAnalyzable, EcmascriptAnalyzableExt,
15    EcmascriptModuleAsset, EcmascriptModuleAssetType, EcmascriptModuleContent,
16    EcmascriptModuleContentOptions, EcmascriptParsable,
17    chunk::{
18        EcmascriptChunkItemContent, EcmascriptChunkPlaceable, EcmascriptExports,
19        ecmascript_chunk_item,
20    },
21    parse::ParseResult,
22    references::{
23        FollowExportsResult, analyze_ecmascript_module, esm::FoundExportType, follow_reexports,
24    },
25    rename::module::EcmascriptModuleRenameModule,
26    tree_shake::{
27        Key, SplitResult, get_part_id, part_of_module, side_effects::module::SideEffectsModule,
28        split_module,
29    },
30};
31
32/// A reference to part of an ES module.
33///
34/// This type is used for an advanced tree shkaing.
35#[turbo_tasks::value]
36pub struct EcmascriptModulePartAsset {
37    pub full_module: ResolvedVc<EcmascriptModuleAsset>,
38    pub part: ModulePart,
39}
40
41#[turbo_tasks::value_impl]
42impl EcmascriptParsable for EcmascriptModulePartAsset {
43    #[turbo_tasks::function]
44    fn failsafe_parse(&self) -> Result<Vc<ParseResult>> {
45        let split_data = split_module(*self.full_module);
46        assert_ne!(self.part, ModulePart::Facade);
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 source_module = Vc::upcast(module);
179                let FollowExportsWithSideEffectsResult {
180                    side_effects,
181                    result,
182                } = &*follow_reexports_with_side_effects(source_module, export.clone()).await?;
183                let FollowExportsResult {
184                    module: final_module,
185                    export_name: new_export,
186                    ..
187                } = &*result.await?;
188                let final_module = if let Some(new_export) = new_export {
189                    if *new_export == export {
190                        *final_module
191                    } else {
192                        ResolvedVc::upcast(
193                            EcmascriptModuleRenameModule::new(
194                                **final_module,
195                                ModulePart::renamed_export(new_export.clone(), export.clone()),
196                            )
197                            .to_resolved()
198                            .await?,
199                        )
200                    }
201                } else {
202                    ResolvedVc::upcast(
203                        EcmascriptModuleRenameModule::new(
204                            **final_module,
205                            ModulePart::renamed_namespace(export.clone()),
206                        )
207                        .to_resolved()
208                        .await?,
209                    )
210                };
211                if side_effects.is_empty() {
212                    return Ok(*final_module);
213                }
214                let side_effects_module = SideEffectsModule::new(
215                    module,
216                    ModulePart::Export(export),
217                    *final_module,
218                    side_effects.iter().map(|v| **v).collect(),
219                );
220                return Ok(Vc::upcast(side_effects_module));
221            }
222            _ => (),
223        }
224
225        Ok(Vc::upcast(
226            EcmascriptModulePartAsset::new_with_resolved_part(module, part.clone()),
227        ))
228    }
229
230    #[turbo_tasks::function]
231    pub async fn is_async_module(self: Vc<Self>) -> Result<Vc<bool>> {
232        let this = self.await?;
233        let result = analyze(*this.full_module, this.part.clone());
234
235        if let Some(async_module) = *result.await?.async_module.await? {
236            Ok(async_module.is_self_async(self.references()))
237        } else {
238            Ok(Vc::cell(false))
239        }
240    }
241}
242
243#[turbo_tasks::value]
244struct FollowExportsWithSideEffectsResult {
245    side_effects: Vec<ResolvedVc<Box<dyn EcmascriptChunkPlaceable>>>,
246    result: ResolvedVc<FollowExportsResult>,
247}
248
249#[turbo_tasks::function]
250async fn follow_reexports_with_side_effects(
251    module: ResolvedVc<Box<dyn EcmascriptChunkPlaceable>>,
252    export_name: RcStr,
253) -> Result<Vc<FollowExportsWithSideEffectsResult>> {
254    let mut side_effects = vec![];
255
256    let mut current_module = module;
257    let mut current_export_name = export_name;
258    let result = loop {
259        if *current_module.side_effects().await? != ModuleSideEffects::SideEffectFree {
260            side_effects.push(only_effects(*current_module).to_resolved().await?);
261        }
262
263        // We ignore the side effect of the entry module here, because we need to proceed.
264        let result = follow_reexports(*current_module, current_export_name.clone(), true)
265            .to_resolved()
266            .await?;
267
268        let FollowExportsResult {
269            module,
270            export_name,
271            ty,
272        } = &*result.await?;
273
274        match ty {
275            FoundExportType::SideEffects => {
276                current_module = *module;
277                current_export_name = export_name.clone().unwrap_or(current_export_name);
278            }
279            _ => break result,
280        }
281    };
282
283    Ok(FollowExportsWithSideEffectsResult {
284        side_effects,
285        result,
286    }
287    .cell())
288}
289
290#[turbo_tasks::value_impl]
291impl Module for EcmascriptModulePartAsset {
292    #[turbo_tasks::function]
293    fn ident(&self) -> Vc<AssetIdent> {
294        self.full_module.ident().with_part(self.part.clone())
295    }
296
297    #[turbo_tasks::function]
298    fn source(&self) -> Vc<turbopack_core::source::OptionSource> {
299        Vc::cell(None)
300    }
301
302    #[turbo_tasks::function]
303    fn is_self_async(self: Vc<Self>) -> Vc<bool> {
304        self.is_async_module()
305    }
306
307    #[turbo_tasks::function]
308    async fn references(&self) -> Result<Vc<ModuleReferences>> {
309        let part_dep = |part: ModulePart| -> Vc<Box<dyn ModuleReference>> {
310            let export = match &part {
311                ModulePart::Export(export) => ExportUsage::named(export.clone()),
312                ModulePart::Evaluation => ExportUsage::evaluation(),
313                _ => ExportUsage::all(),
314            };
315
316            Vc::upcast(SingleChunkableModuleReference::new(
317                Vc::upcast(EcmascriptModulePartAsset::new_with_resolved_part(
318                    *self.full_module,
319                    part,
320                )),
321                rcstr!("part reference"),
322                export,
323            ))
324        };
325
326        if let ModulePart::Facade = self.part {
327            // Facade depends on evaluation and re-exports
328            let mut references = vec![];
329            references.push(part_dep(ModulePart::evaluation()).to_resolved().await?);
330            references.push(part_dep(ModulePart::exports()).to_resolved().await?);
331            return Ok(Vc::cell(references));
332        }
333
334        let analyze = analyze(*self.full_module, self.part.clone());
335
336        Ok(analyze.references())
337    }
338
339    #[turbo_tasks::function]
340    async fn side_effects(&self) -> Vc<ModuleSideEffects> {
341        match self.part {
342            ModulePart::Exports | ModulePart::Export(..) => {
343                ModuleSideEffects::SideEffectFree.cell()
344            }
345            _ => self.full_module.side_effects(),
346        }
347    }
348}
349
350#[turbo_tasks::value_impl]
351impl EcmascriptChunkPlaceable for EcmascriptModulePartAsset {
352    #[turbo_tasks::function]
353    async fn get_exports(self: Vc<Self>) -> Result<Vc<EcmascriptExports>> {
354        Ok(*self.analyze().await?.exports)
355    }
356
357    #[turbo_tasks::function]
358    async fn chunk_item_content(
359        self: Vc<Self>,
360        chunking_context: Vc<Box<dyn ChunkingContext>>,
361        _module_graph: Vc<ModuleGraph>,
362        async_module_info: Option<Vc<AsyncModuleInfo>>,
363        _estimated: bool,
364    ) -> Result<Vc<EcmascriptChunkItemContent>> {
365        let analyze = self.analyze().await?;
366        let async_module_options = analyze.async_module.module_options(async_module_info);
367
368        let content = self.module_content(chunking_context, async_module_info);
369
370        Ok(EcmascriptChunkItemContent::new(
371            content,
372            chunking_context,
373            async_module_options,
374        ))
375    }
376}
377
378#[turbo_tasks::value_impl]
379impl ChunkableModule for EcmascriptModulePartAsset {
380    #[turbo_tasks::function]
381    fn as_chunk_item(
382        self: ResolvedVc<Self>,
383        module_graph: ResolvedVc<ModuleGraph>,
384        chunking_context: ResolvedVc<Box<dyn ChunkingContext>>,
385    ) -> Vc<Box<dyn turbopack_core::chunk::ChunkItem>> {
386        ecmascript_chunk_item(ResolvedVc::upcast(self), module_graph, chunking_context)
387    }
388}
389
390#[turbo_tasks::value_impl]
391impl EcmascriptModulePartAsset {
392    #[turbo_tasks::function]
393    pub(super) fn analyze(&self) -> Vc<AnalyzeEcmascriptModuleResult> {
394        analyze(*self.full_module, self.part.clone())
395    }
396}
397
398#[turbo_tasks::function]
399fn analyze(
400    module: Vc<EcmascriptModuleAsset>,
401    part: ModulePart,
402) -> Vc<AnalyzeEcmascriptModuleResult> {
403    analyze_ecmascript_module(module, Some(part))
404}
405
406#[turbo_tasks::value_impl]
407impl EvaluatableAsset for EcmascriptModulePartAsset {}
408
409#[turbo_tasks::function]
410async fn only_effects(
411    module: ResolvedVc<Box<dyn EcmascriptChunkPlaceable>>,
412) -> Result<Vc<Box<dyn EcmascriptChunkPlaceable>>> {
413    if let Some(module) = ResolvedVc::try_downcast_type::<EcmascriptModuleAsset>(module) {
414        let module =
415            EcmascriptModulePartAsset::new_with_resolved_part(*module, ModulePart::evaluation());
416        return Ok(Vc::upcast(module));
417    }
418
419    Ok(*module)
420}