swc_ecma_transforms_module/
common_js.rs

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