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::{AsyncModuleInfo, ChunkableModule, ChunkingContext, EvaluatableAsset},
9    ident::AssetIdent,
10    module::Module,
11    module_graph::ModuleGraph,
12    reference::ModuleReferences,
13    resolve::ModulePart,
14};
15
16use super::chunk_item::EcmascriptModuleFacadeChunkItem;
17use crate::{
18    AnalyzeEcmascriptModuleResult, EcmascriptAnalyzable, EcmascriptModuleContent,
19    EcmascriptModuleContentOptions, SpecifiedModuleType,
20    chunk::{EcmascriptChunkPlaceable, EcmascriptExports},
21    code_gen::CodeGens,
22    parse::ParseResult,
23    references::{
24        async_module::{AsyncModule, OptionAsyncModule},
25        esm::{EsmExport, EsmExports, base::EsmAssetReferences},
26    },
27    side_effect_optimization::reference::EcmascriptModulePartReference,
28};
29
30/// A module derived from an original ecmascript module that only contains all
31/// the reexports from that module and also reexports the locals from
32/// [EcmascriptModuleLocalsModule]. It allows to follow
33#[turbo_tasks::value]
34pub struct EcmascriptModuleFacadeModule {
35    pub module: ResolvedVc<Box<dyn EcmascriptChunkPlaceable>>,
36    pub ty: ModulePart,
37}
38
39#[turbo_tasks::value_impl]
40impl EcmascriptModuleFacadeModule {
41    #[turbo_tasks::function]
42    pub fn new(module: ResolvedVc<Box<dyn EcmascriptChunkPlaceable>>, ty: ModulePart) -> Vc<Self> {
43        EcmascriptModuleFacadeModule { module, ty }.cell()
44    }
45
46    #[turbo_tasks::function]
47    pub async fn async_module(&self) -> Result<Vc<AsyncModule>> {
48        let (import_externals, has_top_level_await) =
49            if let Some(async_module) = *self.module.get_async_module().await? {
50                (
51                    async_module.await?.import_externals,
52                    async_module.await?.has_top_level_await,
53                )
54            } else {
55                (false, false)
56            };
57        Ok(AsyncModule {
58            has_top_level_await,
59            import_externals,
60        }
61        .cell())
62    }
63}
64
65impl EcmascriptModuleFacadeModule {
66    pub async fn specific_references(
67        &self,
68    ) -> Result<(
69        ResolvedVc<EsmAssetReferences>,
70        Vec<ResolvedVc<EcmascriptModulePartReference>>,
71    )> {
72        Ok(match &self.ty {
73            ModulePart::Evaluation => {
74                let Some(module) =
75                    ResolvedVc::try_sidecast::<Box<dyn EcmascriptAnalyzable>>(self.module)
76                else {
77                    bail!(
78                        "Expected EcmascriptModuleAsset for a EcmascriptModuleFacadeModule with \
79                         ModulePart::Evaluation"
80                    );
81                };
82                let result = module.analyze().await?;
83                (
84                    result.esm_evaluation_references,
85                    vec![
86                        EcmascriptModulePartReference::new_part(*self.module, ModulePart::locals())
87                            .to_resolved()
88                            .await?,
89                    ],
90                )
91            }
92            ModulePart::Exports => {
93                let Some(module) =
94                    ResolvedVc::try_sidecast::<Box<dyn EcmascriptAnalyzable>>(self.module)
95                else {
96                    bail!(
97                        "Expected EcmascriptModuleAsset for a EcmascriptModuleFacadeModule with \
98                         ModulePart::Exports"
99                    );
100                };
101                let result = module.analyze().await?;
102                (
103                    result.esm_reexport_references,
104                    vec![
105                        EcmascriptModulePartReference::new_part(*self.module, ModulePart::locals())
106                            .to_resolved()
107                            .await?,
108                    ],
109                )
110            }
111            ModulePart::Facade => (
112                EsmAssetReferences::empty().to_resolved().await?,
113                vec![
114                    EcmascriptModulePartReference::new_part(*self.module, ModulePart::evaluation())
115                        .to_resolved()
116                        .await?,
117                    EcmascriptModulePartReference::new_part(*self.module, ModulePart::exports())
118                        .to_resolved()
119                        .await?,
120                ],
121            ),
122            ModulePart::RenamedNamespace { .. } => (
123                EsmAssetReferences::empty().to_resolved().await?,
124                vec![
125                    EcmascriptModulePartReference::new(*self.module)
126                        .to_resolved()
127                        .await?,
128                ],
129            ),
130            ModulePart::RenamedExport { .. } => (
131                EsmAssetReferences::empty().to_resolved().await?,
132                vec![
133                    EcmascriptModulePartReference::new(*self.module)
134                        .to_resolved()
135                        .await?,
136                ],
137            ),
138            _ => {
139                bail!("Unexpected ModulePart for EcmascriptModuleFacadeModule");
140            }
141        })
142    }
143}
144
145#[turbo_tasks::value_impl]
146impl Module for EcmascriptModuleFacadeModule {
147    #[turbo_tasks::function]
148    async fn ident(&self) -> Result<Vc<AssetIdent>> {
149        let inner = self.module.ident();
150
151        Ok(inner.with_part(self.ty.clone()))
152    }
153
154    #[turbo_tasks::function]
155    async fn references(self: Vc<Self>) -> Result<Vc<ModuleReferences>> {
156        let (esm_references, part_references) = self.await?.specific_references().await?;
157        let references = esm_references
158            .await?
159            .iter()
160            .map(|r| ResolvedVc::upcast(*r))
161            .chain(part_references.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: Vc<Self>,
208        module_graph: ResolvedVc<ModuleGraph>,
209        chunking_context: ResolvedVc<Box<dyn ChunkingContext>>,
210        async_module_info: Option<ResolvedVc<AsyncModuleInfo>>,
211    ) -> Result<Vc<EcmascriptModuleContentOptions>> {
212        let (esm_references, part_references) = self.await?.specific_references().await?;
213
214        Ok(EcmascriptModuleContentOptions {
215            parsed: ParseResult::empty().to_resolved().await?,
216            ident: self.ident().to_resolved().await?,
217            specified_module_type: SpecifiedModuleType::EcmaScript,
218            module_graph,
219            chunking_context,
220            references: self.references().to_resolved().await?,
221            esm_references,
222            part_references,
223            code_generation: CodeGens::empty().to_resolved().await?,
224            async_module: ResolvedVc::cell(Some(self.async_module().to_resolved().await?)),
225            generate_source_map: false,
226            original_source_map: None,
227            exports: self.get_exports().to_resolved().await?,
228            async_module_info,
229        }
230        .cell())
231    }
232}
233
234#[turbo_tasks::value_impl]
235impl EcmascriptChunkPlaceable for EcmascriptModuleFacadeModule {
236    #[turbo_tasks::function]
237    async fn get_exports(&self) -> Result<Vc<EcmascriptExports>> {
238        let mut exports = BTreeMap::new();
239        let mut star_exports = Vec::new();
240
241        match &self.ty {
242            ModulePart::Exports => {
243                let EcmascriptExports::EsmExports(esm_exports) = *self.module.get_exports().await?
244                else {
245                    bail!(
246                        "EcmascriptModuleFacadeModule must only be used on modules with EsmExports"
247                    );
248                };
249                let esm_exports = esm_exports.await?;
250                for (name, export) in &esm_exports.exports {
251                    let name = name.clone();
252                    match export {
253                        EsmExport::LocalBinding(_, mutable) => {
254                            exports.insert(
255                                name.clone(),
256                                EsmExport::ImportedBinding(
257                                    ResolvedVc::upcast(
258                                        EcmascriptModulePartReference::new_part(
259                                            *self.module,
260                                            ModulePart::locals(),
261                                        )
262                                        .to_resolved()
263                                        .await?,
264                                    ),
265                                    name,
266                                    *mutable,
267                                ),
268                            );
269                        }
270                        EsmExport::ImportedNamespace(reference) => {
271                            exports.insert(name, EsmExport::ImportedNamespace(*reference));
272                        }
273                        EsmExport::ImportedBinding(reference, imported_name, mutable) => {
274                            exports.insert(
275                                name,
276                                EsmExport::ImportedBinding(
277                                    *reference,
278                                    imported_name.clone(),
279                                    *mutable,
280                                ),
281                            );
282                        }
283                        EsmExport::Error => {
284                            exports.insert(name, EsmExport::Error);
285                        }
286                    }
287                }
288                star_exports.extend(esm_exports.star_exports.iter().copied());
289            }
290            ModulePart::Facade => {
291                // Reexport everything from the reexports module
292                // (including default export if any)
293                let EcmascriptExports::EsmExports(esm_exports) = *self.module.get_exports().await?
294                else {
295                    bail!(
296                        "EcmascriptModuleFacadeModule must only be used on modules with EsmExports"
297                    );
298                };
299                let esm_exports = esm_exports.await?;
300                if esm_exports.exports.keys().any(|name| name == "default") {
301                    exports.insert(
302                        "default".into(),
303                        EsmExport::ImportedBinding(
304                            ResolvedVc::upcast(
305                                EcmascriptModulePartReference::new_part(
306                                    *self.module,
307                                    ModulePart::exports(),
308                                )
309                                .to_resolved()
310                                .await?,
311                            ),
312                            "default".into(),
313                            false,
314                        ),
315                    );
316                }
317                star_exports.push(ResolvedVc::upcast(
318                    EcmascriptModulePartReference::new_part(*self.module, ModulePart::exports())
319                        .to_resolved()
320                        .await?,
321                ));
322            }
323            ModulePart::RenamedExport {
324                original_export,
325                export,
326            } => {
327                exports.insert(
328                    export.clone(),
329                    EsmExport::ImportedBinding(
330                        ResolvedVc::upcast(
331                            EcmascriptModulePartReference::new(*self.module)
332                                .to_resolved()
333                                .await?,
334                        ),
335                        original_export.clone(),
336                        false,
337                    ),
338                );
339            }
340            ModulePart::RenamedNamespace { export } => {
341                exports.insert(
342                    export.clone(),
343                    EsmExport::ImportedNamespace(ResolvedVc::upcast(
344                        EcmascriptModulePartReference::new(*self.module)
345                            .to_resolved()
346                            .await?,
347                    )),
348                );
349            }
350            ModulePart::Evaluation => {
351                // no exports
352            }
353            _ => bail!("Unexpected ModulePart for EcmascriptModuleFacadeModule"),
354        }
355
356        let exports = EsmExports {
357            exports,
358            star_exports,
359        }
360        .resolved_cell();
361        Ok(EcmascriptExports::EsmExports(exports).cell())
362    }
363
364    #[turbo_tasks::function]
365    async fn is_marked_as_side_effect_free(
366        &self,
367        side_effect_free_packages: Vc<Glob>,
368    ) -> Result<Vc<bool>> {
369        Ok(match self.ty {
370            ModulePart::Evaluation | ModulePart::Facade => self
371                .module
372                .is_marked_as_side_effect_free(side_effect_free_packages),
373            ModulePart::Exports
374            | ModulePart::RenamedExport { .. }
375            | ModulePart::RenamedNamespace { .. } => Vc::cell(true),
376            _ => bail!("Unexpected ModulePart for EcmascriptModuleFacadeModule"),
377        })
378    }
379
380    #[turbo_tasks::function]
381    async fn get_async_module(self: Vc<Self>) -> Result<Vc<OptionAsyncModule>> {
382        Ok(Vc::cell(Some(self.async_module().to_resolved().await?)))
383    }
384}
385
386#[turbo_tasks::value_impl]
387impl ChunkableModule for EcmascriptModuleFacadeModule {
388    #[turbo_tasks::function]
389    async fn as_chunk_item(
390        self: ResolvedVc<Self>,
391        module_graph: ResolvedVc<ModuleGraph>,
392        chunking_context: ResolvedVc<Box<dyn ChunkingContext>>,
393    ) -> Result<Vc<Box<dyn turbopack_core::chunk::ChunkItem>>> {
394        Ok(Vc::upcast(
395            EcmascriptModuleFacadeChunkItem {
396                module: self,
397                module_graph,
398                chunking_context,
399            }
400            .cell(),
401        ))
402    }
403}
404
405#[turbo_tasks::value_impl]
406impl EvaluatableAsset for EcmascriptModuleFacadeModule {}