turbopack_ecmascript/side_effect_optimization/facade/
module.rs

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