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