Skip to main content

turbopack_ecmascript/side_effect_optimization/facade/
module.rs

1use std::collections::BTreeMap;
2
3use anyhow::{Result, bail};
4use turbo_tasks::{ResolvedVc, Vc};
5use turbopack_core::{
6    chunk::{
7        AsyncModuleInfo, ChunkableModule, ChunkingContext, EvaluatableAsset, MergeableModule,
8        MergeableModules, MergeableModulesExposed,
9    },
10    ident::AssetIdent,
11    module::{Module, ModuleSideEffects},
12    module_graph::ModuleGraph,
13    reference::ModuleReferences,
14    resolve::{ExportUsage, ModulePart},
15};
16
17use super::chunk_item::EcmascriptModuleFacadeChunkItem;
18use crate::{
19    AnalyzeEcmascriptModuleResult, EcmascriptAnalyzable, EcmascriptModuleContent,
20    EcmascriptModuleContentOptions, EcmascriptOptions, MergedEcmascriptModule, SpecifiedModuleType,
21    chunk::{EcmascriptChunkPlaceable, EcmascriptExports},
22    code_gen::CodeGens,
23    export::Liveness,
24    references::{
25        async_module::{AsyncModule, OptionAsyncModule},
26        esm::{EsmExport, EsmExports, base::EsmAssetReferences},
27    },
28    side_effect_optimization::reference::EcmascriptModulePartReference,
29};
30
31/// A module derived from an original ecmascript module that only contains all
32/// the reexports from that module and also reexports the locals from
33/// [EcmascriptModuleLocalsModule]. It allows to follow
34#[turbo_tasks::value]
35pub struct EcmascriptModuleFacadeModule {
36    module: ResolvedVc<Box<dyn EcmascriptChunkPlaceable>>,
37    /// The part of the module that this facade represents.
38    /// ModulePart::Facade | ModulePart::RenamedExport |
39    /// ModulePart::RenamedNamespace
40    part: ModulePart,
41}
42
43#[turbo_tasks::value_impl]
44impl EcmascriptModuleFacadeModule {
45    #[turbo_tasks::function]
46    pub fn new(
47        module: ResolvedVc<Box<dyn EcmascriptChunkPlaceable>>,
48        part: ModulePart,
49    ) -> Vc<Self> {
50        debug_assert!(
51            matches!(
52                part,
53                ModulePart::Facade
54                    | ModulePart::RenamedExport { .. }
55                    | ModulePart::RenamedNamespace { .. }
56            ),
57            "{part:?} is unexpected for EcmascriptModuleFacadeModule"
58        );
59        EcmascriptModuleFacadeModule { module, part }.cell()
60    }
61
62    #[turbo_tasks::function]
63    pub async fn async_module(&self) -> Result<Vc<AsyncModule>> {
64        let (import_externals, has_top_level_await) =
65            if let Some(async_module) = *self.module.get_async_module().await? {
66                (
67                    async_module.await?.import_externals,
68                    async_module.await?.has_top_level_await,
69                )
70            } else {
71                (false, false)
72            };
73        Ok(AsyncModule {
74            has_top_level_await,
75            import_externals,
76        }
77        .cell())
78    }
79}
80
81impl EcmascriptModuleFacadeModule {
82    pub async fn specific_references(
83        &self,
84    ) -> Result<(
85        Vec<ResolvedVc<EcmascriptModulePartReference>>,
86        ResolvedVc<EsmAssetReferences>,
87    )> {
88        Ok(match &self.part {
89            ModulePart::Facade => {
90                let Some(module) =
91                    ResolvedVc::try_sidecast::<Box<dyn EcmascriptAnalyzable>>(self.module)
92                else {
93                    bail!(
94                        "Expected EcmascriptModuleAsset for a EcmascriptModuleFacadeModule with \
95                         ModulePart::Facade"
96                    );
97                };
98                let result = module.analyze().await?;
99                (
100                    vec![
101                        // TODO skip if side effect free and no local exports
102                        EcmascriptModulePartReference::new_part(
103                            *self.module,
104                            ModulePart::locals(),
105                            ExportUsage::all(),
106                        )
107                        .to_resolved()
108                        .await?,
109                    ],
110                    result.esm_reexport_references,
111                )
112            }
113            ModulePart::RenamedNamespace { .. } => (
114                vec![
115                    EcmascriptModulePartReference::new_normal(
116                        *self.module,
117                        self.part.clone(),
118                        ExportUsage::all(),
119                    )
120                    .to_resolved()
121                    .await?,
122                ],
123                EsmAssetReferences::empty().to_resolved().await?,
124            ),
125            ModulePart::RenamedExport {
126                original_export, ..
127            } => (
128                vec![
129                    EcmascriptModulePartReference::new_normal(
130                        *self.module,
131                        self.part.clone(),
132                        ExportUsage::named(original_export.clone()),
133                    )
134                    .to_resolved()
135                    .await?,
136                ],
137                EsmAssetReferences::empty().to_resolved().await?,
138            ),
139            _ => {
140                bail!("Unexpected ModulePart for EcmascriptModuleFacadeModule");
141            }
142        })
143    }
144}
145
146#[turbo_tasks::value_impl]
147impl Module for EcmascriptModuleFacadeModule {
148    #[turbo_tasks::function]
149    fn ident(&self) -> Vc<AssetIdent> {
150        self.module.ident().with_part(self.part.clone())
151    }
152
153    #[turbo_tasks::function]
154    fn source(&self) -> Vc<turbopack_core::source::OptionSource> {
155        Vc::cell(None)
156    }
157
158    #[turbo_tasks::function]
159    async fn references(&self) -> Result<Vc<ModuleReferences>> {
160        let (part_references, esm_references) = self.specific_references().await?;
161        let references = part_references
162            .iter()
163            .map(|r| ResolvedVc::upcast(*r))
164            .chain(esm_references.await?.iter().map(|r| ResolvedVc::upcast(*r)))
165            .collect();
166        Ok(Vc::cell(references))
167    }
168
169    #[turbo_tasks::function]
170    async fn is_self_async(self: Vc<Self>) -> Result<Vc<bool>> {
171        let async_module = self.async_module();
172        let references = self.references();
173        let is_self_async = async_module
174            .resolve()
175            .await?
176            .is_self_async(references.resolve().await?)
177            .resolve()
178            .await?;
179        Ok(is_self_async)
180    }
181
182    #[turbo_tasks::function]
183    fn side_effects(&self) -> Result<Vc<ModuleSideEffects>> {
184        Ok(match self.part {
185            ModulePart::Facade => self.module.side_effects(),
186            ModulePart::RenamedExport { .. } | ModulePart::RenamedNamespace { .. } => {
187                ModuleSideEffects::SideEffectFree.cell()
188            }
189            _ => bail!("Unexpected ModulePart for EcmascriptModuleFacadeModule"),
190        })
191    }
192}
193
194#[turbo_tasks::value_impl]
195impl EcmascriptAnalyzable for EcmascriptModuleFacadeModule {
196    #[turbo_tasks::function]
197    fn analyze(&self) -> Result<Vc<AnalyzeEcmascriptModuleResult>> {
198        bail!("EcmascriptModuleFacadeModule::analyze shouldn't be called");
199    }
200
201    #[turbo_tasks::function]
202    fn module_content_without_analysis(
203        &self,
204        _generate_source_map: bool,
205    ) -> Result<Vc<EcmascriptModuleContent>> {
206        bail!("EcmascriptModuleFacadeModule::module_content_without_analysis shouldn't be called");
207    }
208
209    #[turbo_tasks::function]
210    async fn module_content_options(
211        self: ResolvedVc<Self>,
212        chunking_context: ResolvedVc<Box<dyn ChunkingContext>>,
213        async_module_info: Option<ResolvedVc<AsyncModuleInfo>>,
214    ) -> Result<Vc<EcmascriptModuleContentOptions>> {
215        let (part_references, esm_references) = self.await?.specific_references().await?;
216
217        Ok(EcmascriptModuleContentOptions {
218            parsed: None,
219            module: ResolvedVc::upcast(self),
220            specified_module_type: SpecifiedModuleType::EcmaScript,
221            chunking_context,
222            references: self.references().to_resolved().await?,
223            part_references,
224            esm_references,
225            code_generation: CodeGens::empty().to_resolved().await?,
226            async_module: ResolvedVc::cell(Some(self.async_module().to_resolved().await?)),
227            // The facade module cannot generate source maps, because the inserted references
228            // contain spans from the original module, but the facade module itself doesn't have the
229            // original module's swc_common::SourceMap in `parsed`.
230            generate_source_map: false,
231            original_source_map: None,
232            exports: self.get_exports().to_resolved().await?,
233            async_module_info,
234        }
235        .cell())
236    }
237}
238
239#[turbo_tasks::value_impl]
240impl EcmascriptChunkPlaceable for EcmascriptModuleFacadeModule {
241    #[turbo_tasks::function]
242    async fn get_exports(&self) -> Result<Vc<EcmascriptExports>> {
243        let mut exports = BTreeMap::new();
244        let mut star_exports = Vec::new();
245
246        match &self.part {
247            ModulePart::Facade => {
248                let EcmascriptExports::EsmExports(esm_exports) = *self.module.get_exports().await?
249                else {
250                    bail!(
251                        "EcmascriptModuleFacadeModule must only be used on modules with EsmExports"
252                    );
253                };
254                let esm_exports = esm_exports.await?;
255                for (name, export) in &esm_exports.exports {
256                    let name = name.clone();
257                    match export {
258                        EsmExport::LocalBinding(_, liveness) => {
259                            exports.insert(
260                                name.clone(),
261                                EsmExport::ImportedBinding(
262                                    ResolvedVc::upcast(
263                                        EcmascriptModulePartReference::new_part(
264                                            *self.module,
265                                            ModulePart::locals(),
266                                            ExportUsage::named(name.clone()),
267                                        )
268                                        .to_resolved()
269                                        .await?,
270                                    ),
271                                    name,
272                                    *liveness == Liveness::Mutable,
273                                ),
274                            );
275                        }
276                        EsmExport::ImportedNamespace(reference) => {
277                            exports.insert(name, EsmExport::ImportedNamespace(*reference));
278                        }
279                        EsmExport::ImportedBinding(reference, imported_name, mutable) => {
280                            exports.insert(
281                                name,
282                                EsmExport::ImportedBinding(
283                                    *reference,
284                                    imported_name.clone(),
285                                    *mutable,
286                                ),
287                            );
288                        }
289                        EsmExport::Error => {
290                            exports.insert(name, EsmExport::Error);
291                        }
292                    }
293                }
294                star_exports.extend(esm_exports.star_exports.iter().copied());
295            }
296            ModulePart::RenamedExport {
297                original_export,
298                export,
299            } => {
300                exports.insert(
301                    export.clone(),
302                    EsmExport::ImportedBinding(
303                        ResolvedVc::upcast(
304                            EcmascriptModulePartReference::new_normal(
305                                *self.module,
306                                self.part.clone(),
307                                ExportUsage::named(original_export.clone()),
308                            )
309                            .to_resolved()
310                            .await?,
311                        ),
312                        original_export.clone(),
313                        false,
314                    ),
315                );
316            }
317            ModulePart::RenamedNamespace { export } => {
318                exports.insert(
319                    export.clone(),
320                    EsmExport::ImportedNamespace(ResolvedVc::upcast(
321                        EcmascriptModulePartReference::new_normal(
322                            *self.module,
323                            self.part.clone(),
324                            ExportUsage::all(),
325                        )
326                        .to_resolved()
327                        .await?,
328                    )),
329                );
330            }
331            _ => bail!("Unexpected ModulePart for EcmascriptModuleFacadeModule"),
332        }
333
334        let exports = EsmExports {
335            exports,
336            star_exports,
337        }
338        .resolved_cell();
339        Ok(EcmascriptExports::EsmExports(exports).cell())
340    }
341
342    #[turbo_tasks::function]
343    async fn get_async_module(self: Vc<Self>) -> Result<Vc<OptionAsyncModule>> {
344        Ok(Vc::cell(Some(self.async_module().to_resolved().await?)))
345    }
346}
347
348#[turbo_tasks::value_impl]
349impl ChunkableModule for EcmascriptModuleFacadeModule {
350    #[turbo_tasks::function]
351    fn as_chunk_item(
352        self: ResolvedVc<Self>,
353        _module_graph: ResolvedVc<ModuleGraph>,
354        chunking_context: ResolvedVc<Box<dyn ChunkingContext>>,
355    ) -> Result<Vc<Box<dyn turbopack_core::chunk::ChunkItem>>> {
356        Ok(Vc::upcast(
357            EcmascriptModuleFacadeChunkItem {
358                module: self,
359                chunking_context,
360            }
361            .cell(),
362        ))
363    }
364}
365
366#[turbo_tasks::value_impl]
367impl EvaluatableAsset for EcmascriptModuleFacadeModule {}
368
369#[turbo_tasks::value_impl]
370impl MergeableModule for EcmascriptModuleFacadeModule {
371    #[turbo_tasks::function]
372    async fn merge(
373        self: Vc<Self>,
374        modules: Vc<MergeableModulesExposed>,
375        entry_points: Vc<MergeableModules>,
376    ) -> Result<Vc<Box<dyn ChunkableModule>>> {
377        Ok(Vc::upcast(
378            *MergedEcmascriptModule::new(
379                modules,
380                entry_points,
381                EcmascriptOptions::default().resolved_cell(),
382            )
383            .await?,
384        ))
385    }
386}