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