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,
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 module_content_without_analysis(
157        &self,
158        _generate_source_map: bool,
159    ) -> Result<Vc<EcmascriptModuleContent>> {
160        bail!("EcmascriptModuleFacadeModule::module_content_without_analysis shouldn't be called");
161    }
162
163    #[turbo_tasks::function]
164    async fn module_content_options(
165        self: ResolvedVc<Self>,
166        chunking_context: ResolvedVc<Box<dyn ChunkingContext>>,
167        async_module_info: Option<ResolvedVc<AsyncModuleInfo>>,
168    ) -> Result<Vc<EcmascriptModuleContentOptions>> {
169        let (part_references, esm_references) = self.await?.specific_references().await?;
170
171        Ok(EcmascriptModuleContentOptions {
172            parsed: None,
173            module: ResolvedVc::upcast(self),
174            specified_module_type: SpecifiedModuleType::EcmaScript,
175            chunking_context,
176            references: self.references().to_resolved().await?,
177            part_references,
178            esm_references,
179            code_generation: CodeGens::empty().to_resolved().await?,
180            async_module: ResolvedVc::cell(Some(self.async_module().to_resolved().await?)),
181            // The facade module cannot generate source maps, because the inserted references
182            // contain spans from the original module, but the facade module itself doesn't have the
183            // original module's swc_common::SourceMap in `parsed`.
184            generate_source_map: false,
185            original_source_map: None,
186            exports: self.get_exports().to_resolved().await?,
187            async_module_info,
188        }
189        .cell())
190    }
191}
192
193#[turbo_tasks::value_impl]
194impl EcmascriptChunkPlaceable for EcmascriptModuleFacadeModule {
195    #[turbo_tasks::function]
196    async fn get_exports(&self) -> Result<Vc<EcmascriptExports>> {
197        let EcmascriptExports::EsmExports(esm_exports) = &*self.module.get_exports().await? else {
198            bail!("EcmascriptModuleFacadeModule must only be used on modules with EsmExports");
199        };
200        let esm_exports = esm_exports.await?;
201        let mut exports = Vec::with_capacity(esm_exports.exports.len());
202        for (name, export) in &esm_exports.exports {
203            let name = name.clone();
204            match export {
205                EsmExport::LocalBinding(_, liveness) => {
206                    exports.push((
207                        name.clone(),
208                        EsmExport::ImportedBinding(
209                            ResolvedVc::upcast(
210                                EcmascriptModulePartReference::new_part(
211                                    *self.module,
212                                    ModulePart::locals(),
213                                    ExportUsage::named(name.clone()),
214                                )
215                                .to_resolved()
216                                .await?,
217                            ),
218                            name,
219                            *liveness == Liveness::Mutable,
220                        ),
221                    ));
222                }
223                EsmExport::ImportedNamespace(reference) => {
224                    exports.push((name, EsmExport::ImportedNamespace(*reference)));
225                }
226                EsmExport::ImportedBinding(reference, imported_name, mutable) => {
227                    exports.push((
228                        name,
229                        EsmExport::ImportedBinding(*reference, imported_name.clone(), *mutable),
230                    ));
231                }
232                EsmExport::Error => {
233                    exports.push((name, EsmExport::Error));
234                }
235            }
236        }
237
238        let exports = EsmExports {
239            exports: FrozenMap::from_unique_sorted_box(exports.into_boxed_slice()),
240            star_exports: esm_exports.star_exports.clone(),
241        }
242        .resolved_cell();
243        Ok(EcmascriptExports::EsmExports(exports).cell())
244    }
245
246    #[turbo_tasks::function]
247    async fn get_async_module(self: Vc<Self>) -> Result<Vc<OptionAsyncModule>> {
248        Ok(Vc::cell(Some(self.async_module().to_resolved().await?)))
249    }
250
251    #[turbo_tasks::function]
252    async fn chunk_item_content(
253        self: Vc<Self>,
254        chunking_context: Vc<Box<dyn ChunkingContext>>,
255        _module_graph: Vc<ModuleGraph>,
256        async_module_info: Option<Vc<AsyncModuleInfo>>,
257        _estimated: bool,
258    ) -> Result<Vc<EcmascriptChunkItemContent>> {
259        let content = self.module_content(chunking_context, async_module_info);
260
261        let async_module_options = self.get_async_module().module_options(async_module_info);
262
263        Ok(EcmascriptChunkItemContent::new(
264            content,
265            chunking_context,
266            async_module_options,
267        ))
268    }
269}
270
271#[turbo_tasks::value_impl]
272impl ChunkableModule for EcmascriptModuleFacadeModule {
273    #[turbo_tasks::function]
274    fn as_chunk_item(
275        self: ResolvedVc<Self>,
276        module_graph: ResolvedVc<ModuleGraph>,
277        chunking_context: ResolvedVc<Box<dyn ChunkingContext>>,
278    ) -> Vc<Box<dyn turbopack_core::chunk::ChunkItem>> {
279        ecmascript_chunk_item(ResolvedVc::upcast(self), module_graph, chunking_context)
280    }
281}
282
283#[turbo_tasks::value_impl]
284impl EvaluatableAsset for EcmascriptModuleFacadeModule {}
285
286#[turbo_tasks::value_impl]
287impl MergeableModule for EcmascriptModuleFacadeModule {
288    #[turbo_tasks::function]
289    async fn merge(
290        self: Vc<Self>,
291        modules: Vc<MergeableModulesExposed>,
292        entry_points: Vc<MergeableModules>,
293    ) -> Result<Vc<Box<dyn ChunkableModule>>> {
294        Ok(Vc::upcast(
295            *MergedEcmascriptModule::new(
296                modules,
297                entry_points,
298                EcmascriptOptions::default().resolved_cell(),
299            )
300            .await?,
301        ))
302    }
303}