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