Skip to main content

turbopack_ecmascript/side_effect_optimization/facade/
module.rs

1use anyhow::{Result, bail};
2use turbo_frozenmap::FrozenMap;
3use turbo_tasks::{ResolvedVc, Vc};
4use turbopack_core::{
5    chunk::{
6        AsyncModuleInfo, ChunkableModule, ChunkingContext, EvaluatableAsset, MergeableModule,
7        MergeableModules, MergeableModulesExposed,
8    },
9    ident::AssetIdent,
10    module::{Module, ModuleSideEffects},
11    module_graph::ModuleGraph,
12    reference::ModuleReferences,
13    resolve::{ExportUsage, ModulePart},
14};
15
16use crate::{
17    AnalyzeEcmascriptModuleResult, EcmascriptAnalyzable, EcmascriptAnalyzableExt,
18    EcmascriptModuleContent, EcmascriptModuleContentOptions, EcmascriptOptions, EnvVarInfo,
19    MergedEcmascriptModule, SpecifiedModuleType,
20    chunk::{
21        EcmascriptChunkItemContent, EcmascriptChunkPlaceable, EcmascriptExports,
22        ecmascript_chunk_item,
23    },
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 contains all
34/// the reexports from that module and also reexports the locals from
35/// [`EcmascriptModuleLocalsModule`].
36///
37/// [`EcmascriptModuleLocalsModule`]: crate::side_effect_optimization::locals::module::EcmascriptModuleLocalsModule
38#[turbo_tasks::value]
39pub struct EcmascriptModuleFacadeModule {
40    module: ResolvedVc<Box<dyn EcmascriptChunkPlaceable>>,
41}
42
43#[turbo_tasks::value_impl]
44impl EcmascriptModuleFacadeModule {
45    #[turbo_tasks::function]
46    pub fn new(module: ResolvedVc<Box<dyn EcmascriptChunkPlaceable>>) -> Vc<Self> {
47        EcmascriptModuleFacadeModule { module }.cell()
48    }
49
50    #[turbo_tasks::function]
51    pub async fn async_module(&self) -> Result<Vc<AsyncModule>> {
52        let (import_externals, has_top_level_await) =
53            if let Some(async_module) = *self.module.get_async_module().await? {
54                (
55                    async_module.await?.import_externals,
56                    async_module.await?.has_top_level_await,
57                )
58            } else {
59                (false, false)
60            };
61        Ok(AsyncModule {
62            has_top_level_await,
63            import_externals,
64        }
65        .cell())
66    }
67}
68
69impl EcmascriptModuleFacadeModule {
70    pub async fn specific_references(
71        &self,
72    ) -> Result<(
73        Vec<ResolvedVc<EcmascriptModulePartReference>>,
74        ResolvedVc<EsmAssetReferences>,
75    )> {
76        let Some(module) = ResolvedVc::try_sidecast::<Box<dyn EcmascriptAnalyzable>>(self.module)
77        else {
78            bail!(
79                "Expected EcmascriptModuleAsset for a EcmascriptModuleFacadeModule with \
80                 ModulePart::Facade"
81            );
82        };
83        let result = module.analyze().await?;
84        Ok((
85            vec![
86                // TODO skip if side effect free and no local exports
87                EcmascriptModulePartReference::new_part(
88                    *self.module,
89                    ModulePart::locals(),
90                    ExportUsage::all(),
91                )
92                .to_resolved()
93                .await?,
94            ],
95            result.esm_reexport_references,
96        ))
97    }
98}
99
100#[turbo_tasks::value_impl]
101impl Module for EcmascriptModuleFacadeModule {
102    #[turbo_tasks::function]
103    async fn ident(&self) -> Result<Vc<AssetIdent>> {
104        Ok(self
105            .module
106            .ident()
107            .owned()
108            .await?
109            .with_part(ModulePart::Facade)
110            .into_vc())
111    }
112
113    #[turbo_tasks::function]
114    fn source(&self) -> Vc<turbopack_core::source::OptionSource> {
115        Vc::cell(None)
116    }
117
118    #[turbo_tasks::function]
119    async fn references(&self) -> Result<Vc<ModuleReferences>> {
120        let (part_references, esm_references) = self.specific_references().await?;
121        let references = part_references
122            .iter()
123            .map(|r| ResolvedVc::upcast(*r))
124            .chain(esm_references.await?.iter().map(|r| ResolvedVc::upcast(*r)))
125            .collect();
126        Ok(Vc::cell(references))
127    }
128
129    #[turbo_tasks::function]
130    async fn is_self_async(self: Vc<Self>) -> Result<Vc<bool>> {
131        let async_module = self.async_module();
132        let references = self.references();
133        let is_self_async = async_module
134            .to_resolved()
135            .await?
136            .is_self_async(*references.to_resolved().await?)
137            .to_resolved()
138            .await?;
139        Ok(*is_self_async)
140    }
141
142    #[turbo_tasks::function]
143    fn side_effects(&self) -> Vc<ModuleSideEffects> {
144        ModuleSideEffects::ModuleEvaluationIsSideEffectFree.cell()
145    }
146}
147
148#[turbo_tasks::value_impl]
149impl EcmascriptAnalyzable for EcmascriptModuleFacadeModule {
150    #[turbo_tasks::function]
151    fn analyze(&self) -> Result<Vc<AnalyzeEcmascriptModuleResult>> {
152        bail!("EcmascriptModuleFacadeModule::analyze shouldn't be called");
153    }
154
155    #[turbo_tasks::function]
156    fn env_var_info(self: Vc<Self>) -> Vc<EnvVarInfo> {
157        EnvVarInfo::empty()
158    }
159
160    #[turbo_tasks::function]
161    fn module_content_without_analysis(
162        &self,
163        _generate_source_map: bool,
164    ) -> Result<Vc<EcmascriptModuleContent>> {
165        bail!("EcmascriptModuleFacadeModule::module_content_without_analysis shouldn't be called");
166    }
167
168    #[turbo_tasks::function]
169    async fn module_content_options(
170        self: ResolvedVc<Self>,
171        chunking_context: ResolvedVc<Box<dyn ChunkingContext>>,
172        async_module_info: Option<ResolvedVc<AsyncModuleInfo>>,
173    ) -> Result<Vc<EcmascriptModuleContentOptions>> {
174        let (part_references, esm_references) = self.await?.specific_references().await?;
175
176        Ok(EcmascriptModuleContentOptions {
177            parsed: None,
178            module: ResolvedVc::upcast(self),
179            specified_module_type: SpecifiedModuleType::EcmaScript,
180            chunking_context,
181            references: self.references().to_resolved().await?,
182            part_references,
183            esm_references,
184            code_generation: CodeGens::empty().to_resolved().await?,
185            async_module: ResolvedVc::cell(Some(self.async_module().to_resolved().await?)),
186            // The facade module cannot generate source maps, because the inserted references
187            // contain spans from the original module, but the facade module itself doesn't have the
188            // original module's swc_common::SourceMap in `parsed`.
189            generate_source_map: false,
190            original_source_map: None,
191            exports: self.get_exports().to_resolved().await?,
192            async_module_info,
193        }
194        .cell())
195    }
196}
197
198#[turbo_tasks::value_impl]
199impl EcmascriptChunkPlaceable for EcmascriptModuleFacadeModule {
200    #[turbo_tasks::function]
201    async fn get_exports(&self) -> Result<Vc<EcmascriptExports>> {
202        let EcmascriptExports::EsmExports(esm_exports) = &*self.module.get_exports().await? else {
203            bail!("EcmascriptModuleFacadeModule must only be used on modules with EsmExports");
204        };
205        let esm_exports = esm_exports.await?;
206        let mut exports = Vec::with_capacity(esm_exports.exports.len());
207        for (name, export) in &esm_exports.exports {
208            let name = name.clone();
209            match export {
210                EsmExport::LocalBinding(_, liveness) => {
211                    exports.push((
212                        name.clone(),
213                        EsmExport::ImportedBinding(
214                            ResolvedVc::upcast(
215                                EcmascriptModulePartReference::new_part(
216                                    *self.module,
217                                    ModulePart::locals(),
218                                    ExportUsage::named(name.clone()),
219                                )
220                                .to_resolved()
221                                .await?,
222                            ),
223                            name,
224                            *liveness == Liveness::Mutable,
225                        ),
226                    ));
227                }
228                EsmExport::ImportedNamespace(reference) => {
229                    exports.push((name, EsmExport::ImportedNamespace(*reference)));
230                }
231                EsmExport::ImportedBinding(reference, imported_name, mutable) => {
232                    exports.push((
233                        name,
234                        EsmExport::ImportedBinding(*reference, imported_name.clone(), *mutable),
235                    ));
236                }
237                EsmExport::Error => {
238                    exports.push((name, EsmExport::Error));
239                }
240            }
241        }
242
243        let exports = EsmExports {
244            exports: FrozenMap::from_unique_sorted_box(exports.into_boxed_slice()),
245            star_exports: esm_exports.star_exports.clone(),
246            mangle_export_names: esm_exports.mangle_export_names,
247        }
248        .resolved_cell();
249        Ok(EcmascriptExports::EsmExports(exports).cell())
250    }
251
252    #[turbo_tasks::function]
253    async fn get_async_module(self: Vc<Self>) -> Result<Vc<OptionAsyncModule>> {
254        Ok(Vc::cell(Some(self.async_module().to_resolved().await?)))
255    }
256
257    #[turbo_tasks::function]
258    async fn chunk_item_content(
259        self: Vc<Self>,
260        chunking_context: Vc<Box<dyn ChunkingContext>>,
261        _module_graph: Vc<ModuleGraph>,
262        async_module_info: Option<Vc<AsyncModuleInfo>>,
263        _estimated: bool,
264    ) -> Result<Vc<EcmascriptChunkItemContent>> {
265        let content = self.module_content(chunking_context, async_module_info);
266
267        let async_module_options = self.get_async_module().module_options(async_module_info);
268
269        Ok(EcmascriptChunkItemContent::new(
270            content,
271            chunking_context,
272            async_module_options,
273        ))
274    }
275}
276
277#[turbo_tasks::value_impl]
278impl ChunkableModule for EcmascriptModuleFacadeModule {
279    #[turbo_tasks::function]
280    fn as_chunk_item(
281        self: ResolvedVc<Self>,
282        module_graph: ResolvedVc<ModuleGraph>,
283        chunking_context: ResolvedVc<Box<dyn ChunkingContext>>,
284    ) -> Vc<Box<dyn turbopack_core::chunk::ChunkItem>> {
285        ecmascript_chunk_item(ResolvedVc::upcast(self), module_graph, chunking_context)
286    }
287}
288
289#[turbo_tasks::value_impl]
290impl EvaluatableAsset for EcmascriptModuleFacadeModule {}
291
292#[turbo_tasks::value_impl]
293impl MergeableModule for EcmascriptModuleFacadeModule {
294    #[turbo_tasks::function]
295    async fn merge(
296        self: Vc<Self>,
297        modules: Vc<MergeableModulesExposed>,
298        entry_points: Vc<MergeableModules>,
299    ) -> Result<Vc<Box<dyn ChunkableModule>>> {
300        Ok(Vc::upcast(
301            *MergedEcmascriptModule::new(
302                modules,
303                entry_points,
304                EcmascriptOptions::default().resolved_cell(),
305            )
306            .await?,
307        ))
308    }
309}