Skip to main content

turbopack_ecmascript/references/esm/
module_item.rs

1use std::mem::replace;
2
3use anyhow::Result;
4use bincode::{Decode, Encode};
5use swc_core::{
6    common::DUMMY_SP,
7    ecma::ast::{
8        ClassDecl, Decl, DefaultDecl, ExportDecl, ExportDefaultDecl, ExportDefaultExpr, FnDecl,
9        Ident, ModuleDecl, ModuleItem, Stmt, VarDecl, VarDeclarator,
10    },
11    quote,
12};
13use turbo_tasks::{NonLocalValue, Vc, debug::ValueDebugFormat, trace::TraceRawVcs};
14use turbopack_core::chunk::ChunkingContext;
15
16use crate::{
17    code_gen::{CodeGen, CodeGeneration},
18    create_visitor,
19    magic_identifier::MAGIC_IDENTIFIER_DEFAULT_EXPORT_ATOM,
20    references::AstPath,
21};
22
23/// Makes code changes to remove export/import declarations and places the
24/// expr/decl in a normal statement. Unnamed expr/decl will be named with the
25/// magic identifier "export default"
26#[derive(
27    PartialEq, Eq, TraceRawVcs, ValueDebugFormat, NonLocalValue, Debug, Hash, Encode, Decode,
28)]
29pub struct EsmModuleItem {
30    pub path: AstPath,
31    pub supports_block_scoping: bool,
32}
33
34impl EsmModuleItem {
35    pub fn new(path: AstPath, supports_block_scoping: bool) -> Self {
36        EsmModuleItem {
37            path,
38            supports_block_scoping,
39        }
40    }
41
42    pub async fn code_generation(
43        &self,
44        _chunking_context: Vc<Box<dyn ChunkingContext>>,
45    ) -> Result<CodeGeneration> {
46        let mut visitors = Vec::new();
47        let supports_block_scoping = self.supports_block_scoping;
48
49        visitors.push(create_visitor!(
50            self.path,
51            visit_mut_module_item,
52            |module_item: &mut ModuleItem| {
53                let item = replace(module_item, ModuleItem::Stmt(quote!(";" as Stmt)));
54                if let ModuleItem::ModuleDecl(module_decl) = item {
55                    match module_decl {
56                        ModuleDecl::ExportDefaultExpr(ExportDefaultExpr { box expr, .. }) => {
57                            let decl = Decl::Var(Box::new(VarDecl {
58                                span: DUMMY_SP,
59                                ctxt: Default::default(),
60                                kind: if supports_block_scoping {
61                                    swc_core::ecma::ast::VarDeclKind::Const
62                                } else {
63                                    // This is not entirely correct: this hides TDZ errors with
64                                    // circular imports, but there is no way to model this runtime
65                                    // behavior well for older browsers.
66                                    swc_core::ecma::ast::VarDeclKind::Var
67                                },
68                                declare: false,
69                                decls: vec![VarDeclarator {
70                                    span: DUMMY_SP,
71                                    name: Ident::new(
72                                        MAGIC_IDENTIFIER_DEFAULT_EXPORT_ATOM.clone(),
73                                        DUMMY_SP,
74                                        Default::default(),
75                                    )
76                                    .into(),
77                                    init: Some(Box::new(expr)),
78                                    definite: false,
79                                }],
80                            }));
81                            *module_item = ModuleItem::Stmt(Stmt::Decl(decl));
82                        }
83                        ModuleDecl::ExportDefaultDecl(ExportDefaultDecl { decl, span }) => {
84                            match decl {
85                                DefaultDecl::Class(class) => {
86                                    *module_item =
87                                        ModuleItem::Stmt(Stmt::Decl(Decl::Class(ClassDecl {
88                                            ident: class.ident.unwrap_or_else(|| {
89                                                Ident::new(
90                                                    MAGIC_IDENTIFIER_DEFAULT_EXPORT_ATOM.clone(),
91                                                    DUMMY_SP,
92                                                    Default::default(),
93                                                )
94                                            }),
95                                            declare: false,
96                                            class: class.class,
97                                        })))
98                                }
99                                DefaultDecl::Fn(fn_expr) => {
100                                    *module_item = ModuleItem::Stmt(Stmt::Decl(Decl::Fn(FnDecl {
101                                        ident: fn_expr.ident.unwrap_or_else(|| {
102                                            Ident::new(
103                                                MAGIC_IDENTIFIER_DEFAULT_EXPORT_ATOM.clone(),
104                                                DUMMY_SP,
105                                                Default::default(),
106                                            )
107                                        }),
108                                        declare: false,
109                                        function: fn_expr.function,
110                                    })))
111                                }
112                                DefaultDecl::TsInterfaceDecl(_) => {
113                                    // not matching, might happen due to eventual consistency
114                                    *module_item =
115                                        ModuleItem::ModuleDecl(ModuleDecl::ExportDefaultDecl(
116                                            ExportDefaultDecl { decl, span },
117                                        ));
118                                }
119                            }
120                        }
121                        ModuleDecl::ExportDecl(ExportDecl { decl, .. }) => {
122                            *module_item = ModuleItem::Stmt(Stmt::Decl(decl));
123                        }
124                        ModuleDecl::ExportNamed(_) => {
125                            // already removed
126                        }
127                        ModuleDecl::ExportAll(_) => {
128                            // already removed
129                        }
130                        ModuleDecl::Import(_) => {
131                            // already removed
132                        }
133                        _ => {
134                            // not matching, might happen due to eventual consistency
135                            *module_item = ModuleItem::ModuleDecl(module_decl);
136                        }
137                    }
138                } else {
139                    // not matching, might happen due to eventual consistency
140                    *module_item = item;
141                }
142            }
143        ));
144
145        Ok(CodeGeneration::visitors(visitors))
146    }
147}
148
149impl From<EsmModuleItem> for CodeGen {
150    fn from(val: EsmModuleItem) -> Self {
151        CodeGen::EsmModuleItem(val)
152    }
153}