Skip to main content

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