turbopack_ecmascript/side_effect_optimization/facade/
module.rs

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