swc_bundler/bundler/chunk/
cjs.rs

1use std::{collections::HashMap, sync::atomic::Ordering};
2
3use anyhow::Error;
4use rustc_hash::FxHashMap;
5use swc_atoms::atom;
6use swc_common::{Span, SyntaxContext, DUMMY_SP};
7use swc_ecma_ast::*;
8use swc_ecma_utils::{quote_ident, ExprFactory};
9use swc_ecma_visit::{noop_visit_mut_type, VisitMut, VisitMutWith};
10
11use crate::{
12    bundler::{chunk::merge::Ctx, load::TransformedModule},
13    modules::Modules,
14    Bundler, Load, Resolve,
15};
16
17impl<L, R> Bundler<'_, L, R>
18where
19    L: Load,
20    R: Resolve,
21{
22    fn make_cjs_load_var(&self, info: &TransformedModule, span: Span) -> Ident {
23        Ident::new(atom!("load"), span, info.export_ctxt())
24    }
25
26    pub(super) fn replace_cjs_require_calls(
27        &self,
28        info: &TransformedModule,
29        module: &mut Modules,
30        is_entry: bool,
31    ) {
32        if !self.config.require {
33            return;
34        }
35
36        let mut v = RequireReplacer {
37            is_entry,
38            base: info,
39            bundler: self,
40            replaced: false,
41        };
42        module.visit_mut_with(&mut v);
43
44        if v.replaced {
45            info.helpers.require.store(true, Ordering::SeqCst);
46        }
47    }
48
49    /// Creates a variable named `load` (see `make_cjs_load_var`) if it's a
50    /// common js module.
51    pub(super) fn wrap_cjs_module(
52        &self,
53        ctx: &Ctx,
54        info: &TransformedModule,
55        mut module: Modules,
56    ) -> Result<Modules, Error> {
57        if !self.config.require || (!self.scope.is_cjs(info.id) && info.is_es6) {
58            return Ok(module);
59        }
60
61        tracing::debug!("Merging as a common js module: {}", info.fm.name);
62
63        let load_var = self.make_cjs_load_var(info, DUMMY_SP);
64
65        module.visit_mut_with(&mut DefaultHandler {
66            local_ctxt: info.local_ctxt(),
67        });
68        module.sort(info.id, &ctx.graph, &ctx.cycles, &self.cm);
69
70        let stmt = wrap_module(
71            SyntaxContext::empty(),
72            SyntaxContext::empty().apply_mark(self.unresolved_mark),
73            info.local_ctxt(),
74            load_var,
75            module.into(),
76        )
77        .into();
78
79        let wrapped = Modules::from(
80            info.id,
81            Module {
82                span: DUMMY_SP,
83                body: vec![stmt],
84                shebang: None,
85            },
86            self.injected_ctxt,
87        );
88
89        tracing::debug!("Injected a variable named `load` for a common js module");
90
91        Ok(wrapped)
92    }
93}
94
95fn wrap_module(
96    helper_ctxt: SyntaxContext,
97    unresolved_ctxt: SyntaxContext,
98    local_ctxt: SyntaxContext,
99    load_var: Ident,
100    mut dep: Module,
101) -> Stmt {
102    {
103        // Remap syntax context of `module` and `exports`
104        // Those are unresolved, but it's actually an injected variable.
105
106        let mut from = HashMap::default();
107        from.insert((atom!("module"), unresolved_ctxt), local_ctxt);
108        from.insert((atom!("exports"), unresolved_ctxt), local_ctxt);
109
110        dep.visit_mut_with(&mut Remapper { vars: from })
111    }
112
113    // ... body of foo
114    let module_fn: Expr = FnExpr {
115        ident: None,
116        function: Box::new(Function {
117            params: vec![
118                // module
119                Param {
120                    span: DUMMY_SP,
121                    decorators: Default::default(),
122                    pat: Pat::Ident(Ident::new(atom!("module"), DUMMY_SP, local_ctxt).into()),
123                },
124                // exports
125                Param {
126                    span: DUMMY_SP,
127                    decorators: Default::default(),
128                    pat: Pat::Ident(Ident::new(atom!("exports"), DUMMY_SP, local_ctxt).into()),
129                },
130            ],
131            decorators: Vec::new(),
132            span: DUMMY_SP,
133            body: Some(BlockStmt {
134                span: dep.span,
135                stmts: dep
136                    .body
137                    .into_iter()
138                    .map(|v| match v {
139                        ModuleItem::ModuleDecl(i) => {
140                            unreachable!("module item found but is_es6 is false: {:?}", i)
141                        }
142                        ModuleItem::Stmt(s) => s,
143                        #[cfg(swc_ast_unknown)]
144                        _ => panic!("unable to access unknown nodes"),
145                    })
146                    .collect(),
147                ..Default::default()
148            }),
149            is_generator: false,
150            is_async: false,
151            ..Default::default()
152        }),
153    }
154    .into();
155
156    // var load = __swcpack_require__.bind(void 0, moduleDecl)
157
158    VarDecl {
159        span: DUMMY_SP,
160        kind: VarDeclKind::Var,
161        declare: false,
162        decls: vec![VarDeclarator {
163            span: DUMMY_SP,
164            name: Pat::Ident(load_var.into()),
165            init: Some(Box::new(Expr::Call(CallExpr {
166                span: DUMMY_SP,
167                callee: Ident::new(atom!("__swcpack_require__"), DUMMY_SP, helper_ctxt)
168                    .make_member(quote_ident!("bind"))
169                    .as_callee(),
170                args: vec![Expr::undefined(DUMMY_SP).as_arg(), module_fn.as_arg()],
171                ..Default::default()
172            }))),
173            definite: false,
174        }],
175        ..Default::default()
176    }
177    .into()
178}
179
180struct RequireReplacer<'a, 'b, L, R>
181where
182    L: Load,
183    R: Resolve,
184{
185    base: &'a TransformedModule,
186    bundler: &'a Bundler<'b, L, R>,
187    replaced: bool,
188    is_entry: bool,
189}
190
191impl<L, R> VisitMut for RequireReplacer<'_, '_, L, R>
192where
193    L: Load,
194    R: Resolve,
195{
196    noop_visit_mut_type!(fail);
197
198    fn visit_mut_call_expr(&mut self, node: &mut CallExpr) {
199        node.visit_mut_children_with(self);
200
201        if let Callee::Expr(e) = &node.callee {
202            if let Expr::Ident(i) = &**e {
203                // TODO: Check for global mark
204                if i.sym == *"require" && node.args.len() == 1 {
205                    if let Expr::Lit(Lit::Str(module_name)) = &*node.args[0].expr {
206                        let module_atom = module_name.value.to_atom_lossy();
207                        if self.bundler.is_external(module_atom.as_ref()) {
208                            return;
209                        }
210                        let load = CallExpr {
211                            span: node.span,
212                            callee: Ident::new(atom!("load"), i.span, i.ctxt).as_callee(),
213                            args: Vec::new(),
214                            ..Default::default()
215                        };
216                        self.replaced = true;
217                        *node = load;
218
219                        tracing::trace!("Found, and replacing require");
220                    }
221                }
222            }
223        }
224    }
225
226    fn visit_mut_module_item(&mut self, node: &mut ModuleItem) {
227        node.visit_mut_children_with(self);
228
229        if !self.is_entry {
230            return;
231        }
232
233        if let ModuleItem::ModuleDecl(ModuleDecl::Import(i)) = node {
234            let dep_module_id = self
235                .base
236                .imports
237                .specifiers
238                .iter()
239                .find(|(src, _)| src.src.value == i.src.value)
240                .map(|v| v.0.module_id);
241            let dep_module_id = match dep_module_id {
242                Some(v) => v,
243                _ => {
244                    return;
245                }
246            };
247            // Replace imports iff dependency is common js module.
248            let dep_module = self.bundler.scope.get_module(dep_module_id).unwrap();
249            if !self.bundler.scope.is_cjs(dep_module_id) && dep_module.is_es6 {
250                return;
251            }
252
253            let load_var = self.bundler.make_cjs_load_var(&dep_module, i.span);
254            // Replace import progress from 'progress';
255            // Side effect import
256            if i.specifiers.is_empty() {
257                self.replaced = true;
258                *node = CallExpr {
259                    span: DUMMY_SP,
260                    callee: load_var.as_callee(),
261                    args: Vec::new(),
262
263                    ..Default::default()
264                }
265                .into_stmt()
266                .into();
267                return;
268            }
269
270            let mut props = Vec::new();
271            // TODO
272            for spec in i.specifiers.clone() {
273                match spec {
274                    ImportSpecifier::Named(s) => match s.imported {
275                        Some(ModuleExportName::Ident(imported)) => {
276                            props.push(ObjectPatProp::KeyValue(KeyValuePatProp {
277                                key: imported.into(),
278                                value: Box::new(s.local.into()),
279                            }));
280                        }
281                        Some(ModuleExportName::Str(..)) => {
282                            unimplemented!("module string names unimplemented")
283                        }
284                        _ => {
285                            props.push(ObjectPatProp::Assign(AssignPatProp {
286                                span: s.span,
287                                key: s.local.into(),
288                                value: None,
289                            }));
290                        }
291                    },
292                    ImportSpecifier::Default(s) => {
293                        props.push(ObjectPatProp::KeyValue(KeyValuePatProp {
294                            key: PropName::Ident(IdentName::new(atom!("default"), DUMMY_SP)),
295                            value: Box::new(s.local.into()),
296                        }));
297                    }
298                    ImportSpecifier::Namespace(ns) => {
299                        self.replaced = true;
300                        *node = VarDecl {
301                            span: i.span,
302                            kind: VarDeclKind::Var,
303                            declare: false,
304                            decls: vec![VarDeclarator {
305                                span: ns.span,
306                                name: ns.local.into(),
307                                init: Some(Box::new(
308                                    CallExpr {
309                                        span: DUMMY_SP,
310                                        callee: load_var.as_callee(),
311                                        args: Vec::new(),
312
313                                        ..Default::default()
314                                    }
315                                    .into(),
316                                )),
317                                definite: false,
318                            }],
319                            ..Default::default()
320                        }
321                        .into();
322                        return;
323                    }
324                    #[cfg(swc_ast_unknown)]
325                    _ => panic!("unable to access unknown nodes"),
326                }
327            }
328
329            self.replaced = true;
330            *node = VarDecl {
331                span: i.span,
332                kind: VarDeclKind::Var,
333                declare: false,
334                decls: vec![VarDeclarator {
335                    span: i.span,
336                    name: Pat::Object(ObjectPat {
337                        span: DUMMY_SP,
338                        props,
339                        optional: false,
340                        type_ann: None,
341                    }),
342                    init: Some(Box::new(Expr::Call(CallExpr {
343                        span: DUMMY_SP,
344                        callee: load_var.as_callee(),
345                        args: Vec::new(),
346                        ..Default::default()
347                    }))),
348                    definite: false,
349                }],
350                ..Default::default()
351            }
352            .into();
353        }
354    }
355}
356
357struct DefaultHandler {
358    local_ctxt: SyntaxContext,
359}
360
361impl VisitMut for DefaultHandler {
362    noop_visit_mut_type!(fail);
363
364    fn visit_mut_expr(&mut self, e: &mut Expr) {
365        e.visit_mut_children_with(self);
366
367        if let Expr::Ident(i) = e {
368            if i.sym == "default" {
369                *e = MemberExpr {
370                    span: i.span,
371                    obj: Ident::new(atom!("module"), DUMMY_SP, self.local_ctxt).into(),
372                    prop: MemberProp::Ident(quote_ident!("exports")),
373                }
374                .into();
375            }
376        }
377    }
378}
379
380struct Remapper {
381    vars: FxHashMap<Id, SyntaxContext>,
382}
383
384impl VisitMut for Remapper {
385    noop_visit_mut_type!(fail);
386
387    fn visit_mut_ident(&mut self, i: &mut Ident) {
388        if let Some(v) = self.vars.get(&i.to_id()).copied() {
389            i.ctxt = v;
390        }
391    }
392}