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