turbopack_ecmascript/side_effect_optimization/facade/
module.rs

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