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