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 crate::{
18    AnalyzeEcmascriptModuleResult, EcmascriptAnalyzable, EcmascriptAnalyzableExt,
19    EcmascriptModuleContent, EcmascriptModuleContentOptions, EcmascriptOptions,
20    MergedEcmascriptModule, SpecifiedModuleType,
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        Ok((
86            vec![
87                // TODO skip if side effect free and no local exports
88                EcmascriptModulePartReference::new_part(
89                    *self.module,
90                    ModulePart::locals(),
91                    ExportUsage::all(),
92                )
93                .to_resolved()
94                .await?,
95            ],
96            result.esm_reexport_references,
97        ))
98    }
99}
100
101#[turbo_tasks::value_impl]
102impl Module for EcmascriptModuleFacadeModule {
103    #[turbo_tasks::function]
104    fn ident(&self) -> Vc<AssetIdent> {
105        self.module.ident().with_part(ModulePart::Facade)
106    }
107
108    #[turbo_tasks::function]
109    fn source(&self) -> Vc<turbopack_core::source::OptionSource> {
110        Vc::cell(None)
111    }
112
113    #[turbo_tasks::function]
114    async fn references(&self) -> Result<Vc<ModuleReferences>> {
115        let (part_references, esm_references) = self.specific_references().await?;
116        let references = part_references
117            .iter()
118            .map(|r| ResolvedVc::upcast(*r))
119            .chain(esm_references.await?.iter().map(|r| ResolvedVc::upcast(*r)))
120            .collect();
121        Ok(Vc::cell(references))
122    }
123
124    #[turbo_tasks::function]
125    async fn is_self_async(self: Vc<Self>) -> Result<Vc<bool>> {
126        let async_module = self.async_module();
127        let references = self.references();
128        let is_self_async = async_module
129            .resolve()
130            .await?
131            .is_self_async(references.resolve().await?)
132            .resolve()
133            .await?;
134        Ok(is_self_async)
135    }
136
137    #[turbo_tasks::function]
138    fn side_effects(&self) -> Vc<ModuleSideEffects> {
139        ModuleSideEffects::ModuleEvaluationIsSideEffectFree.cell()
140    }
141}
142
143#[turbo_tasks::value_impl]
144impl EcmascriptAnalyzable for EcmascriptModuleFacadeModule {
145    #[turbo_tasks::function]
146    fn analyze(&self) -> Result<Vc<AnalyzeEcmascriptModuleResult>> {
147        bail!("EcmascriptModuleFacadeModule::analyze shouldn't be called");
148    }
149
150    #[turbo_tasks::function]
151    fn module_content_without_analysis(
152        &self,
153        _generate_source_map: bool,
154    ) -> Result<Vc<EcmascriptModuleContent>> {
155        bail!("EcmascriptModuleFacadeModule::module_content_without_analysis shouldn't be called");
156    }
157
158    #[turbo_tasks::function]
159    async fn module_content_options(
160        self: ResolvedVc<Self>,
161        chunking_context: ResolvedVc<Box<dyn ChunkingContext>>,
162        async_module_info: Option<ResolvedVc<AsyncModuleInfo>>,
163    ) -> Result<Vc<EcmascriptModuleContentOptions>> {
164        let (part_references, esm_references) = self.await?.specific_references().await?;
165
166        Ok(EcmascriptModuleContentOptions {
167            parsed: None,
168            module: ResolvedVc::upcast(self),
169            specified_module_type: SpecifiedModuleType::EcmaScript,
170            chunking_context,
171            references: self.references().to_resolved().await?,
172            part_references,
173            esm_references,
174            code_generation: CodeGens::empty().to_resolved().await?,
175            async_module: ResolvedVc::cell(Some(self.async_module().to_resolved().await?)),
176            // The facade module cannot generate source maps, because the inserted references
177            // contain spans from the original module, but the facade module itself doesn't have the
178            // original module's swc_common::SourceMap in `parsed`.
179            generate_source_map: false,
180            original_source_map: None,
181            exports: self.get_exports().to_resolved().await?,
182            async_module_info,
183        }
184        .cell())
185    }
186}
187
188#[turbo_tasks::value_impl]
189impl EcmascriptChunkPlaceable for EcmascriptModuleFacadeModule {
190    #[turbo_tasks::function]
191    async fn get_exports(&self) -> Result<Vc<EcmascriptExports>> {
192        let mut exports = BTreeMap::new();
193        let mut star_exports = Vec::new();
194
195        let EcmascriptExports::EsmExports(esm_exports) = &*self.module.get_exports().await? else {
196            bail!("EcmascriptModuleFacadeModule must only be used on modules with EsmExports");
197        };
198        let esm_exports = esm_exports.await?;
199        for (name, export) in &esm_exports.exports {
200            let name = name.clone();
201            match export {
202                EsmExport::LocalBinding(_, liveness) => {
203                    exports.insert(
204                        name.clone(),
205                        EsmExport::ImportedBinding(
206                            ResolvedVc::upcast(
207                                EcmascriptModulePartReference::new_part(
208                                    *self.module,
209                                    ModulePart::locals(),
210                                    ExportUsage::named(name.clone()),
211                                )
212                                .to_resolved()
213                                .await?,
214                            ),
215                            name,
216                            *liveness == Liveness::Mutable,
217                        ),
218                    );
219                }
220                EsmExport::ImportedNamespace(reference) => {
221                    exports.insert(name, EsmExport::ImportedNamespace(*reference));
222                }
223                EsmExport::ImportedBinding(reference, imported_name, mutable) => {
224                    exports.insert(
225                        name,
226                        EsmExport::ImportedBinding(*reference, imported_name.clone(), *mutable),
227                    );
228                }
229                EsmExport::Error => {
230                    exports.insert(name, EsmExport::Error);
231                }
232            }
233        }
234        star_exports.extend(esm_exports.star_exports.iter().copied());
235
236        let exports = EsmExports {
237            exports,
238            star_exports,
239        }
240        .resolved_cell();
241        Ok(EcmascriptExports::EsmExports(exports).cell())
242    }
243
244    #[turbo_tasks::function]
245    async fn get_async_module(self: Vc<Self>) -> Result<Vc<OptionAsyncModule>> {
246        Ok(Vc::cell(Some(self.async_module().to_resolved().await?)))
247    }
248
249    #[turbo_tasks::function]
250    async fn chunk_item_content(
251        self: Vc<Self>,
252        chunking_context: Vc<Box<dyn ChunkingContext>>,
253        _module_graph: Vc<ModuleGraph>,
254        async_module_info: Option<Vc<AsyncModuleInfo>>,
255        _estimated: bool,
256    ) -> Result<Vc<EcmascriptChunkItemContent>> {
257        let content = self.module_content(chunking_context, async_module_info);
258
259        let async_module_options = self.get_async_module().module_options(async_module_info);
260
261        Ok(EcmascriptChunkItemContent::new(
262            content,
263            chunking_context,
264            async_module_options,
265        ))
266    }
267}
268
269#[turbo_tasks::value_impl]
270impl ChunkableModule for EcmascriptModuleFacadeModule {
271    #[turbo_tasks::function]
272    fn as_chunk_item(
273        self: ResolvedVc<Self>,
274        module_graph: ResolvedVc<ModuleGraph>,
275        chunking_context: ResolvedVc<Box<dyn ChunkingContext>>,
276    ) -> Vc<Box<dyn turbopack_core::chunk::ChunkItem>> {
277        ecmascript_chunk_item(ResolvedVc::upcast(self), module_graph, chunking_context)
278    }
279}
280
281#[turbo_tasks::value_impl]
282impl EvaluatableAsset for EcmascriptModuleFacadeModule {}
283
284#[turbo_tasks::value_impl]
285impl MergeableModule for EcmascriptModuleFacadeModule {
286    #[turbo_tasks::function]
287    async fn merge(
288        self: Vc<Self>,
289        modules: Vc<MergeableModulesExposed>,
290        entry_points: Vc<MergeableModules>,
291    ) -> Result<Vc<Box<dyn ChunkableModule>>> {
292        Ok(Vc::upcast(
293            *MergedEcmascriptModule::new(
294                modules,
295                entry_points,
296                EcmascriptOptions::default().resolved_cell(),
297            )
298            .await?,
299        ))
300    }
301}