swc_ecma_transforms_module/
common_js.rs

1use std::borrow::Cow;
2
3use rustc_hash::FxHashSet;
4use swc_common::{
5    source_map::PURE_SP, util::take::Take, Mark, Span, Spanned, SyntaxContext, DUMMY_SP,
6};
7use swc_ecma_ast::*;
8use swc_ecma_transforms_base::helper_expr;
9use swc_ecma_utils::{
10    member_expr, private_ident, quote_expr, quote_ident, ExprFactory, FunctionFactory, IsDirective,
11};
12use swc_ecma_visit::{noop_visit_mut_type, visit_mut_pass, VisitMut, VisitMutWith};
13
14pub use super::util::Config;
15use crate::{
16    module_decl_strip::{
17        Export, ExportKV, Link, LinkFlag, LinkItem, LinkSpecifierReducer, ModuleDeclStrip,
18    },
19    module_ref_rewriter::{rewrite_import_bindings, ImportMap},
20    path::Resolver,
21    top_level_this::top_level_this,
22    util::{
23        define_es_module, emit_export_stmts, local_name_for_src, prop_name, use_strict,
24        ImportInterop, VecStmtLike,
25    },
26};
27
28#[derive(Default)]
29pub struct FeatureFlag {
30    pub support_block_scoping: bool,
31    pub support_arrow: bool,
32}
33
34pub fn common_js(
35    resolver: Resolver,
36    unresolved_mark: Mark,
37    config: Config,
38    available_features: FeatureFlag,
39) -> impl Pass {
40    visit_mut_pass(Cjs {
41        config,
42        resolver,
43        unresolved_mark,
44        support_arrow: available_features.support_arrow,
45        const_var_kind: if available_features.support_block_scoping {
46            VarDeclKind::Const
47        } else {
48            VarDeclKind::Var
49        },
50    })
51}
52
53pub struct Cjs {
54    config: Config,
55    resolver: Resolver,
56    unresolved_mark: Mark,
57    support_arrow: bool,
58    const_var_kind: VarDeclKind,
59}
60
61impl VisitMut for Cjs {
62    noop_visit_mut_type!(fail);
63
64    fn visit_mut_module(&mut self, n: &mut Module) {
65        let mut stmts: Vec<ModuleItem> = Vec::with_capacity(n.body.len() + 6);
66
67        // Collect directives
68        stmts.extend(
69            &mut n
70                .body
71                .iter_mut()
72                .take_while(|i| i.directive_continue())
73                .map(|i| i.take()),
74        );
75
76        // "use strict";
77        if self.config.strict_mode && !stmts.has_use_strict() {
78            stmts.push(use_strict().into());
79        }
80
81        if !self.config.allow_top_level_this {
82            top_level_this(&mut n.body, *Expr::undefined(DUMMY_SP));
83        }
84
85        let import_interop = self.config.import_interop();
86
87        let mut module_map = Default::default();
88
89        let mut has_ts_import_equals = false;
90
91        // handle `import foo = require("mod")`
92        n.body.iter_mut().for_each(|item| {
93            if let ModuleItem::ModuleDecl(module_decl) = item {
94                *item = self.handle_ts_import_equals(
95                    module_decl.take(),
96                    &mut module_map,
97                    &mut has_ts_import_equals,
98                );
99            }
100        });
101
102        let mut strip = ModuleDeclStrip::new(self.const_var_kind);
103        n.body.visit_mut_with(&mut strip);
104
105        let ModuleDeclStrip {
106            link,
107            export,
108            export_assign,
109            has_module_decl,
110            ..
111        } = strip;
112
113        let has_module_decl = has_module_decl || has_ts_import_equals;
114
115        let is_export_assign = export_assign.is_some();
116
117        if has_module_decl && !import_interop.is_none() && !is_export_assign {
118            stmts.push(define_es_module(self.exports()).into())
119        }
120
121        let mut lazy_record = Default::default();
122
123        // `import` -> `require`
124        // `export` -> `_export(exports, {});`
125        stmts.extend(
126            self.handle_import_export(
127                &mut module_map,
128                &mut lazy_record,
129                link,
130                export,
131                is_export_assign,
132            )
133            .map(From::from),
134        );
135
136        stmts.extend(n.body.take().into_iter().filter(|item| match item {
137            ModuleItem::Stmt(stmt) => !stmt.is_empty(),
138            _ => false,
139        }));
140
141        // `export = expr;` -> `module.exports = expr;`
142        if let Some(export_assign) = export_assign {
143            stmts.push(
144                export_assign
145                    .make_assign_to(
146                        op!("="),
147                        member_expr!(
148                            SyntaxContext::empty().apply_mark(self.unresolved_mark),
149                            Default::default(),
150                            module.exports
151                        )
152                        .into(),
153                    )
154                    .into_stmt()
155                    .into(),
156            )
157        }
158
159        if !self.config.ignore_dynamic || !self.config.preserve_import_meta {
160            stmts.visit_mut_children_with(self);
161        }
162
163        rewrite_import_bindings(&mut stmts, module_map, lazy_record);
164
165        n.body = stmts;
166    }
167
168    fn visit_mut_script(&mut self, _: &mut Script) {
169        // skip script
170    }
171
172    fn visit_mut_expr(&mut self, n: &mut Expr) {
173        match n {
174            Expr::Call(CallExpr {
175                span,
176                callee:
177                    Callee::Import(Import {
178                        span: import_span,
179                        phase: ImportPhase::Evaluation,
180                    }),
181                args,
182                ..
183            }) if !self.config.ignore_dynamic => {
184                args.visit_mut_with(self);
185
186                let mut is_lit_path = false;
187
188                args.get_mut(0).into_iter().for_each(|x| {
189                    if let ExprOrSpread { spread: None, expr } = x {
190                        if let Expr::Lit(Lit::Str(Str { value, raw, .. })) = &mut **expr {
191                            is_lit_path = true;
192
193                            *value = self
194                                .resolver
195                                .resolve(value.to_atom_lossy().into_owned())
196                                .into();
197                            *raw = None;
198                        }
199                    }
200                });
201
202                let unresolved_ctxt = SyntaxContext::empty().apply_mark(self.unresolved_mark);
203
204                *n = cjs_dynamic_import(
205                    *span,
206                    args.take(),
207                    quote_ident!(unresolved_ctxt, *import_span, "require"),
208                    self.config.import_interop(),
209                    self.support_arrow,
210                    is_lit_path,
211                );
212            }
213            Expr::Member(MemberExpr { span, obj, prop })
214                if !self.config.preserve_import_meta
215                    && obj
216                        .as_meta_prop()
217                        .map(|p| p.kind == MetaPropKind::ImportMeta)
218                        .unwrap_or_default() =>
219            {
220                let p = match prop {
221                    MemberProp::Ident(IdentName { sym, .. }) => Cow::Borrowed(&**sym),
222                    MemberProp::Computed(ComputedPropName { expr, .. }) => match &**expr {
223                        Expr::Lit(Lit::Str(s)) => s.value.to_string_lossy(),
224                        _ => return,
225                    },
226                    MemberProp::PrivateName(..) => return,
227                    #[cfg(swc_ast_unknown)]
228                    _ => panic!("unable to access unknown nodes"),
229                };
230
231                match &*p {
232                    "url" => {
233                        let require = quote_ident!(
234                            SyntaxContext::empty().apply_mark(self.unresolved_mark),
235                            "require"
236                        );
237                        *n = cjs_import_meta_url(*span, require, self.unresolved_mark);
238                    }
239                    "resolve" => {
240                        let require = quote_ident!(
241                            SyntaxContext::empty().apply_mark(self.unresolved_mark),
242                            obj.span(),
243                            "require"
244                        );
245
246                        *obj = Box::new(require.into());
247                    }
248                    "filename" => {
249                        *n = quote_ident!(
250                            SyntaxContext::empty().apply_mark(self.unresolved_mark),
251                            *span,
252                            "__filename"
253                        )
254                        .into();
255                    }
256                    "dirname" => {
257                        *n = quote_ident!(
258                            SyntaxContext::empty().apply_mark(self.unresolved_mark),
259                            *span,
260                            "__dirname"
261                        )
262                        .into();
263                    }
264                    "main" => {
265                        let ctxt = SyntaxContext::empty().apply_mark(self.unresolved_mark);
266                        let require = quote_ident!(ctxt, "require");
267                        let require_main = require.make_member(quote_ident!("main"));
268                        let module = quote_ident!(ctxt, "module");
269
270                        *n = BinExpr {
271                            span: *span,
272                            op: op!("=="),
273                            left: require_main.into(),
274                            right: module.into(),
275                        }
276                        .into();
277                    }
278                    _ => {}
279                }
280            }
281            Expr::OptChain(OptChainExpr { base, .. }) if !self.config.preserve_import_meta => {
282                if let OptChainBase::Member(member) = &mut **base {
283                    if member
284                        .obj
285                        .as_meta_prop()
286                        .is_some_and(|meta_prop| meta_prop.kind == MetaPropKind::ImportMeta)
287                    {
288                        *n = member.take().into();
289                        n.visit_mut_with(self);
290                        return;
291                    }
292                };
293
294                n.visit_mut_children_with(self);
295            }
296            _ => n.visit_mut_children_with(self),
297        }
298    }
299}
300
301impl Cjs {
302    fn handle_import_export(
303        &mut self,
304        import_map: &mut ImportMap,
305        lazy_record: &mut FxHashSet<Id>,
306        link: Link,
307        export: Export,
308        is_export_assign: bool,
309    ) -> impl Iterator<Item = Stmt> {
310        let import_interop = self.config.import_interop();
311        let export_interop_annotation = self.config.export_interop_annotation();
312        let is_node = import_interop.is_node();
313
314        let mut stmts = Vec::with_capacity(link.len());
315
316        let mut export_obj_prop_list = export.into_iter().collect();
317
318        let lexer_reexport = if export_interop_annotation {
319            self.emit_lexer_reexport(&link)
320        } else {
321            None
322        };
323
324        link.into_iter().for_each(
325            |(src, LinkItem(src_span, link_specifier_set, mut link_flag))| {
326                let is_node_default = !link_flag.has_named() && is_node;
327
328                if import_interop.is_none() {
329                    link_flag -= LinkFlag::NAMESPACE;
330                }
331
332                let mod_ident = private_ident!(local_name_for_src(&src));
333
334                let mut decl_mod_ident = false;
335
336                link_specifier_set.reduce(
337                    import_map,
338                    &mut export_obj_prop_list,
339                    &mod_ident,
340                    &None,
341                    &mut decl_mod_ident,
342                    is_node_default,
343                );
344
345                let is_lazy =
346                    decl_mod_ident && !link_flag.export_star() && self.config.lazy.is_lazy(&src);
347
348                if is_lazy {
349                    lazy_record.insert(mod_ident.to_id());
350                }
351
352                // require("mod");
353                let import_expr =
354                    self.resolver
355                        .make_require_call(self.unresolved_mark, src, src_span.0);
356
357                // _export_star(require("mod"), exports);
358                let import_expr = if link_flag.export_star() {
359                    helper_expr!(export_star).as_call(
360                        DUMMY_SP,
361                        vec![import_expr.as_arg(), self.exports().as_arg()],
362                    )
363                } else {
364                    import_expr
365                };
366
367                // _introp(require("mod"));
368                let import_expr = {
369                    match import_interop {
370                        ImportInterop::Swc if link_flag.interop() => if link_flag.namespace() {
371                            helper_expr!(interop_require_wildcard)
372                        } else {
373                            helper_expr!(interop_require_default)
374                        }
375                        .as_call(PURE_SP, vec![import_expr.as_arg()]),
376                        ImportInterop::Node if link_flag.namespace() => {
377                            helper_expr!(interop_require_wildcard)
378                                .as_call(PURE_SP, vec![import_expr.as_arg(), true.as_arg()])
379                        }
380                        _ => import_expr,
381                    }
382                };
383
384                if decl_mod_ident {
385                    let stmt = if is_lazy {
386                        lazy_require(import_expr, mod_ident, self.const_var_kind).into()
387                    } else {
388                        import_expr
389                            .into_var_decl(self.const_var_kind, mod_ident.into())
390                            .into()
391                    };
392
393                    stmts.push(stmt);
394                } else {
395                    stmts.push(import_expr.into_stmt());
396                }
397            },
398        );
399
400        let mut export_stmts: Vec<Stmt> = Default::default();
401
402        if !export_obj_prop_list.is_empty() && !is_export_assign {
403            export_obj_prop_list.sort_by_cached_key(|(key, ..)| key.clone());
404
405            let exports = self.exports();
406
407            if export_interop_annotation && export_obj_prop_list.len() > 1 {
408                export_stmts.extend(self.emit_lexer_exports_init(&export_obj_prop_list));
409            }
410
411            export_stmts.extend(emit_export_stmts(exports, export_obj_prop_list));
412        }
413
414        export_stmts.extend(lexer_reexport);
415
416        export_stmts.into_iter().chain(stmts)
417    }
418
419    fn handle_ts_import_equals(
420        &self,
421        module_decl: ModuleDecl,
422        module_map: &mut ImportMap,
423        has_ts_import_equals: &mut bool,
424    ) -> ModuleItem {
425        match module_decl {
426            ModuleDecl::TsImportEquals(v)
427                if matches!(
428                    &*v,
429                    TsImportEqualsDecl {
430                        is_type_only: false,
431                        module_ref: TsModuleRef::TsExternalModuleRef(TsExternalModuleRef { .. }),
432                        ..
433                    }
434                ) =>
435            {
436                let TsImportEqualsDecl {
437                    span,
438                    is_export,
439                    id,
440                    module_ref,
441                    ..
442                } = *v;
443                let Str {
444                    span: src_span,
445                    value: src,
446                    ..
447                } = module_ref.expect_ts_external_module_ref().expr;
448
449                *has_ts_import_equals = true;
450
451                let require = self.resolver.make_require_call(
452                    self.unresolved_mark,
453                    src.to_atom_lossy().into_owned(),
454                    src_span,
455                );
456
457                if is_export {
458                    // exports.foo = require("mod")
459                    module_map.insert(id.to_id(), (self.exports(), Some(id.sym.clone())));
460
461                    let assign_expr = AssignExpr {
462                        span,
463                        op: op!("="),
464                        left: self.exports().make_member(id.into()).into(),
465                        right: Box::new(require),
466                    };
467
468                    assign_expr.into_stmt()
469                } else {
470                    // const foo = require("mod")
471                    let mut var_decl = require.into_var_decl(self.const_var_kind, id.into());
472                    var_decl.span = span;
473
474                    var_decl.into()
475                }
476                .into()
477            }
478            _ => module_decl.into(),
479        }
480    }
481
482    fn exports(&self) -> Ident {
483        quote_ident!(
484            SyntaxContext::empty().apply_mark(self.unresolved_mark),
485            "exports"
486        )
487    }
488
489    /// emit [cjs-module-lexer](https://github.com/nodejs/cjs-module-lexer) friendly exports list
490    /// ```javascript
491    /// 0 && (exports.foo = 0);
492    /// 0 && (module.exports = { foo: _, bar: _ });
493    /// ```
494    fn emit_lexer_exports_init(&mut self, export_id_list: &[ExportKV]) -> Option<Stmt> {
495        match export_id_list.len() {
496            0 => None,
497            1 => {
498                let expr: Expr = 0.into();
499
500                let (key, export_item) = &export_id_list[0];
501                let prop = prop_name(key, Default::default()).into();
502                let export_binding = MemberExpr {
503                    obj: Box::new(self.exports().into()),
504                    span: export_item.export_name_span().0,
505                    prop,
506                };
507                let expr = expr.make_assign_to(op!("="), export_binding.into());
508                let expr = BinExpr {
509                    span: DUMMY_SP,
510                    op: op!("&&"),
511                    left: 0.into(),
512                    right: Box::new(expr),
513                };
514
515                Some(expr.into_stmt())
516            }
517            _ => {
518                let props = export_id_list
519                    .iter()
520                    .map(|(key, ..)| prop_name(key, Default::default()))
521                    .map(|key| KeyValueProp {
522                        key: key.into(),
523                        // `cjs-module-lexer` only support identifier as value
524                        // `null` is treated as identifier in `cjs-module-lexer`
525                        value: quote_expr!(DUMMY_SP, null).into(),
526                    })
527                    .map(Prop::KeyValue)
528                    .map(Box::new)
529                    .map(PropOrSpread::Prop)
530                    .collect();
531
532                let module_exports_assign = ObjectLit {
533                    span: DUMMY_SP,
534                    props,
535                }
536                .make_assign_to(
537                    op!("="),
538                    member_expr!(
539                        SyntaxContext::empty().apply_mark(self.unresolved_mark),
540                        Default::default(),
541                        module.exports
542                    )
543                    .into(),
544                );
545
546                let expr = BinExpr {
547                    span: DUMMY_SP,
548                    op: op!("&&"),
549                    left: 0.into(),
550                    right: Box::new(module_exports_assign),
551                };
552
553                Some(expr.into_stmt())
554            }
555        }
556    }
557
558    /// emit [cjs-module-lexer](https://github.com/nodejs/cjs-module-lexer) friendly exports list
559    /// ```javascript
560    /// 0 && __export(require("foo")) && __export(require("bar"));
561    /// ```
562    fn emit_lexer_reexport(&self, link: &Link) -> Option<Stmt> {
563        link.iter()
564            .filter(|(.., LinkItem(.., link_flag))| link_flag.export_star())
565            .map(|(src, ..)| {
566                let import_expr =
567                    self.resolver
568                        .make_require_call(self.unresolved_mark, src.clone(), DUMMY_SP);
569
570                quote_ident!("__export").as_call(DUMMY_SP, vec![import_expr.as_arg()])
571            })
572            .reduce(|left, right| {
573                BinExpr {
574                    span: DUMMY_SP,
575                    op: op!("&&"),
576                    left: left.into(),
577                    right: right.into(),
578                }
579                .into()
580            })
581            .map(|expr| {
582                BinExpr {
583                    span: DUMMY_SP,
584                    op: op!("&&"),
585                    left: 0.into(),
586                    right: expr.into(),
587                }
588                .into_stmt()
589            })
590    }
591}
592
593/// ```javascript
594/// Promise.resolve(args).then(p => require(p))
595/// // for literial dynamic import:
596/// Promise.resolve().then(() => require(args))
597/// ```
598pub(crate) fn cjs_dynamic_import(
599    span: Span,
600    args: Vec<ExprOrSpread>,
601    require: Ident,
602    import_interop: ImportInterop,
603    support_arrow: bool,
604    is_lit_path: bool,
605) -> Expr {
606    let p = private_ident!("p");
607
608    let (resolve_args, callback_params, require_args) = if is_lit_path {
609        (Vec::new(), Vec::new(), args)
610    } else {
611        (args, vec![p.clone().into()], vec![p.as_arg()])
612    };
613
614    let then = member_expr!(Default::default(), Default::default(), Promise.resolve)
615        // TODO: handle import assert
616        .as_call(DUMMY_SP, resolve_args)
617        .make_member(quote_ident!("then"));
618
619    let import_expr = {
620        let require = require.as_call(DUMMY_SP, require_args);
621
622        match import_interop {
623            ImportInterop::None => require,
624            ImportInterop::Swc => {
625                helper_expr!(interop_require_wildcard).as_call(PURE_SP, vec![require.as_arg()])
626            }
627            ImportInterop::Node => helper_expr!(interop_require_wildcard)
628                .as_call(PURE_SP, vec![require.as_arg(), true.as_arg()]),
629        }
630    };
631
632    then.as_call(
633        span,
634        vec![import_expr
635            .into_lazy_auto(callback_params, support_arrow)
636            .as_arg()],
637    )
638}
639
640/// require('url').pathToFileURL(__filename).toString()
641fn cjs_import_meta_url(span: Span, require: Ident, unresolved_mark: Mark) -> Expr {
642    require
643        .as_call(DUMMY_SP, vec!["url".as_arg()])
644        .make_member(quote_ident!("pathToFileURL"))
645        .as_call(
646            DUMMY_SP,
647            vec![quote_ident!(
648                SyntaxContext::empty().apply_mark(unresolved_mark),
649                "__filename"
650            )
651            .as_arg()],
652        )
653        .make_member(quote_ident!("toString"))
654        .as_call(span, Default::default())
655}
656
657/// ```javascript
658/// function foo() {
659///   const data = expr;
660///
661///   foo = () => data;
662///
663///   return data;
664/// }
665/// ```
666pub fn lazy_require(expr: Expr, mod_ident: Ident, var_kind: VarDeclKind) -> FnDecl {
667    let data = private_ident!("data");
668    let data_decl = expr.into_var_decl(var_kind, data.clone().into());
669    let data_stmt = data_decl.into();
670    let overwrite_stmt = data
671        .clone()
672        .into_lazy_fn(Default::default())
673        .into_fn_expr(None)
674        .make_assign_to(op!("="), mod_ident.clone().into())
675        .into_stmt();
676    let return_stmt = data.into_return_stmt().into();
677
678    FnDecl {
679        ident: mod_ident,
680        declare: false,
681        function: Function {
682            params: Default::default(),
683            decorators: Default::default(),
684            span: DUMMY_SP,
685            body: Some(BlockStmt {
686                span: DUMMY_SP,
687                stmts: vec![data_stmt, overwrite_stmt, return_stmt],
688                ..Default::default()
689            }),
690            is_generator: false,
691            is_async: false,
692            ..Default::default()
693        }
694        .into(),
695    }
696}