Skip to main content

turbopack_ecmascript/module_fragments/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, EnvVarInfo,
17    chunk::{
18        EcmascriptChunkItemContent, EcmascriptChunkPlaceable, EcmascriptExports,
19        ecmascript_chunk_item,
20    },
21    module_fragments::{
22        Key, SplitResult, get_part_id, part_of_module, side_effects::module::SideEffectsModule,
23        split_module,
24    },
25    parse::ParseResult,
26    references::{
27        FollowExportsResult, analyze_ecmascript_module, esm::FoundExportType,
28        exports::compute_ecmascript_module_exports, follow_reexports,
29    },
30    rename::module::EcmascriptModuleRenameModule,
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    async fn env_var_info(self: Vc<Self>) -> Result<Vc<EnvVarInfo>> {
61        Ok(*self.analyze().await?.env_var_info)
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: self.get_exports().to_resolved().await?,
106            export_registration_mode: None,
107            async_module_info,
108        }
109        .cell())
110    }
111}
112
113#[turbo_tasks::value_impl]
114impl EcmascriptModulePartAsset {
115    /// Create a new instance of [Vc<EcmascriptModulePartAsset>], which consists
116    /// of a pointer to the full module and the [ModulePart] pointing the part
117    /// of the module.
118    #[turbo_tasks::function]
119    fn new_raw(module: ResolvedVc<EcmascriptModuleAsset>, part: ModulePart) -> Vc<Self> {
120        Self {
121            full_module: module,
122            part,
123        }
124        .cell()
125    }
126
127    #[turbo_tasks::function]
128    pub async fn new_with_resolved_part(
129        module: ResolvedVc<EcmascriptModuleAsset>,
130        part: ModulePart,
131    ) -> Result<Vc<Self>> {
132        if matches!(
133            part,
134            ModulePart::Internal(..) | ModulePart::Facade | ModulePart::Exports
135        ) {
136            return Ok(Self::new_raw(*module, part));
137        }
138
139        // This is a workaround to avoid creating duplicate assets for internal parts.
140        let split_result = split_module(*module).await?;
141        let part_id = get_part_id(&split_result, &part).await?;
142
143        Ok(Self::new_raw(*module, ModulePart::internal(part_id)))
144    }
145
146    #[turbo_tasks::function]
147    pub async fn select_part(
148        module: Vc<EcmascriptModuleAsset>,
149        part: ModulePart,
150    ) -> Result<Vc<Box<dyn EcmascriptChunkPlaceable>>> {
151        let SplitResult::Ok { entrypoints, .. } = &*split_module(module).await? else {
152            return Ok(Vc::upcast(module));
153        };
154
155        match part {
156            ModulePart::Evaluation => {
157                // We resolve the module evaluation here to prevent duplicate assets.
158                let idx = *entrypoints.get(&Key::ModuleEvaluation).unwrap();
159                return Ok(Vc::upcast(
160                    EcmascriptModulePartAsset::new_with_resolved_part(
161                        module,
162                        ModulePart::internal(idx),
163                    ),
164                ));
165            }
166
167            part @ (ModulePart::Export(_) | ModulePart::PartialExport { .. }) => {
168                let (export, namespace_member) = match part {
169                    ModulePart::Export(export) => (export, None),
170                    ModulePart::PartialExport { export, member } => (export, Some(member)),
171                    _ => unreachable!(),
172                };
173                if entrypoints.contains_key(&Key::Export(export.clone())) {
174                    return Ok(Vc::upcast(
175                        EcmascriptModulePartAsset::new_with_resolved_part(
176                            module,
177                            ModulePart::Export(export),
178                        ),
179                    ));
180                }
181                let source_module = Vc::upcast(module);
182                let FollowExportsWithSideEffectsResult {
183                    side_effects,
184                    result,
185                } = &*follow_reexports_with_side_effects(source_module, export.clone()).await?;
186                let FollowExportsResult {
187                    module: final_module,
188                    export_name: new_export,
189                    ..
190                } = &*result.await?;
191                let final_module = if let Some(new_export) = new_export {
192                    if *new_export == export {
193                        *final_module
194                    } else {
195                        ResolvedVc::upcast(
196                            EcmascriptModuleRenameModule::new(
197                                **final_module,
198                                ModulePart::renamed_export(new_export.clone(), export.clone()),
199                            )
200                            .to_resolved()
201                            .await?,
202                        )
203                    }
204                } else {
205                    ResolvedVc::upcast(
206                        EcmascriptModuleRenameModule::new(
207                            **final_module,
208                            match namespace_member {
209                                Some(member) => {
210                                    ModulePart::renamed_partial_namespace(export.clone(), member)
211                                }
212                                None => ModulePart::renamed_namespace(export.clone()),
213                            },
214                        )
215                        .to_resolved()
216                        .await?,
217                    )
218                };
219                if side_effects.is_empty() {
220                    return Ok(*final_module);
221                }
222                let side_effects_module = SideEffectsModule::new(
223                    module,
224                    ModulePart::Export(export),
225                    *final_module,
226                    side_effects.iter().map(|v| **v).collect(),
227                );
228                return Ok(Vc::upcast(side_effects_module));
229            }
230            _ => (),
231        }
232
233        Ok(Vc::upcast(
234            EcmascriptModulePartAsset::new_with_resolved_part(module, part.clone()),
235        ))
236    }
237
238    #[turbo_tasks::function]
239    pub async fn is_async_module(self: Vc<Self>) -> Result<Vc<bool>> {
240        let this = self.await?;
241        let result = analyze_ecmascript_module(*this.full_module, Some(this.part.clone()));
242
243        if let Some(async_module) = *result.await?.async_module.await? {
244            Ok(async_module.is_self_async(self.references()))
245        } else {
246            Ok(Vc::cell(false))
247        }
248    }
249}
250
251#[turbo_tasks::value]
252struct FollowExportsWithSideEffectsResult {
253    side_effects: Vec<ResolvedVc<Box<dyn EcmascriptChunkPlaceable>>>,
254    result: ResolvedVc<FollowExportsResult>,
255}
256
257#[turbo_tasks::function]
258async fn follow_reexports_with_side_effects(
259    module: ResolvedVc<Box<dyn EcmascriptChunkPlaceable>>,
260    export_name: RcStr,
261) -> Result<Vc<FollowExportsWithSideEffectsResult>> {
262    let mut side_effects = vec![];
263
264    let mut current_module = module;
265    let mut current_export_name = export_name;
266    let result = loop {
267        if *current_module.side_effects().await? != ModuleSideEffects::SideEffectFree {
268            side_effects.push(only_effects(*current_module).to_resolved().await?);
269        }
270
271        // We ignore the side effect of the entry module here, because we need to proceed.
272        let result = follow_reexports(*current_module, current_export_name.clone(), true)
273            .to_resolved()
274            .await?;
275
276        let FollowExportsResult {
277            module,
278            export_name,
279            ty,
280        } = &*result.await?;
281
282        match ty {
283            FoundExportType::SideEffects => {
284                current_module = *module;
285                current_export_name = export_name.clone().unwrap_or(current_export_name);
286            }
287            _ => break result,
288        }
289    };
290
291    Ok(FollowExportsWithSideEffectsResult {
292        side_effects,
293        result,
294    }
295    .cell())
296}
297
298#[turbo_tasks::value_impl]
299impl Module for EcmascriptModulePartAsset {
300    #[turbo_tasks::function]
301    async fn ident(&self) -> Result<Vc<AssetIdent>> {
302        Ok(self
303            .full_module
304            .ident()
305            .owned()
306            .await?
307            .with_part(self.part.clone())
308            .into_vc())
309    }
310
311    #[turbo_tasks::function]
312    fn source(&self) -> Vc<turbopack_core::source::OptionSource> {
313        Vc::cell(None)
314    }
315
316    #[turbo_tasks::function]
317    fn is_self_async(self: Vc<Self>) -> Vc<bool> {
318        self.is_async_module()
319    }
320
321    #[turbo_tasks::function]
322    async fn references(&self) -> Result<Vc<ModuleReferences>> {
323        let part_dep = |part: ModulePart| -> Vc<Box<dyn ModuleReference>> {
324            let export = match &part {
325                ModulePart::Export(export) | ModulePart::PartialExport { export, .. } => {
326                    ExportUsage::named(export.clone())
327                }
328                ModulePart::Evaluation => ExportUsage::evaluation(),
329                _ => ExportUsage::all(),
330            };
331
332            Vc::upcast(SingleChunkableModuleReference::new(
333                Vc::upcast(EcmascriptModulePartAsset::new_with_resolved_part(
334                    *self.full_module,
335                    part,
336                )),
337                rcstr!("part reference"),
338                export,
339            ))
340        };
341
342        if let ModulePart::Facade = self.part {
343            // Facade depends on evaluation and re-exports
344            let mut references = vec![];
345            references.push(part_dep(ModulePart::evaluation()).to_resolved().await?);
346            references.push(part_dep(ModulePart::exports()).to_resolved().await?);
347            return Ok(Vc::cell(references));
348        }
349
350        let analyze = analyze_ecmascript_module(*self.full_module, Some(self.part.clone()));
351
352        Ok(analyze.references())
353    }
354
355    #[turbo_tasks::function]
356    async fn side_effects(&self) -> Vc<ModuleSideEffects> {
357        match self.part {
358            ModulePart::Exports | ModulePart::Export(_) | ModulePart::PartialExport { .. } => {
359                ModuleSideEffects::SideEffectFree.cell()
360            }
361            _ => self.full_module.side_effects(),
362        }
363    }
364}
365
366#[turbo_tasks::value_impl]
367impl EcmascriptChunkPlaceable for EcmascriptModulePartAsset {
368    #[turbo_tasks::function]
369    async fn get_exports(&self) -> Result<Vc<EcmascriptExports>> {
370        Ok(
371            *compute_ecmascript_module_exports(*self.full_module, Some(self.part.clone()))
372                .await?
373                .exports,
374        )
375    }
376
377    #[turbo_tasks::function]
378    async fn chunk_item_content(
379        self: Vc<Self>,
380        chunking_context: Vc<Box<dyn ChunkingContext>>,
381        _module_graph: Vc<ModuleGraph>,
382        async_module_info: Option<Vc<AsyncModuleInfo>>,
383        _estimated: bool,
384    ) -> Result<Vc<EcmascriptChunkItemContent>> {
385        let analyze = self.analyze().await?;
386        let async_module_options = analyze.async_module.module_options(async_module_info);
387
388        let content = self.module_content(chunking_context, async_module_info);
389
390        Ok(EcmascriptChunkItemContent::new(
391            content,
392            chunking_context,
393            async_module_options,
394        ))
395    }
396}
397
398#[turbo_tasks::value_impl]
399impl ChunkableModule for EcmascriptModulePartAsset {
400    #[turbo_tasks::function]
401    fn as_chunk_item(
402        self: ResolvedVc<Self>,
403        module_graph: ResolvedVc<ModuleGraph>,
404        chunking_context: ResolvedVc<Box<dyn ChunkingContext>>,
405    ) -> Vc<Box<dyn turbopack_core::chunk::ChunkItem>> {
406        ecmascript_chunk_item(ResolvedVc::upcast(self), module_graph, chunking_context)
407    }
408}
409
410#[turbo_tasks::value_impl]
411impl EcmascriptModulePartAsset {
412    #[turbo_tasks::function]
413    pub(super) fn analyze(&self) -> Vc<AnalyzeEcmascriptModuleResult> {
414        analyze_ecmascript_module(*self.full_module, Some(self.part.clone()))
415    }
416}
417
418#[turbo_tasks::value_impl]
419impl EvaluatableAsset for EcmascriptModulePartAsset {}
420
421#[turbo_tasks::function]
422async fn only_effects(
423    module: ResolvedVc<Box<dyn EcmascriptChunkPlaceable>>,
424) -> Result<Vc<Box<dyn EcmascriptChunkPlaceable>>> {
425    if let Some(module) = ResolvedVc::try_downcast_type::<EcmascriptModuleAsset>(module) {
426        let module =
427            EcmascriptModulePartAsset::new_with_resolved_part(*module, ModulePart::evaluation());
428        return Ok(Vc::upcast(module));
429    }
430
431    Ok(*module)
432}