swc_ecma_transforms_module/
amd.rs

1use anyhow::Context;
2use regex::Regex;
3use serde::{Deserialize, Serialize};
4use swc_atoms::{atom, Atom};
5use swc_common::{
6    comments::{CommentKind, Comments},
7    source_map::PURE_SP,
8    util::take::Take,
9    Mark, Span, Spanned, SyntaxContext, DUMMY_SP,
10};
11use swc_ecma_ast::*;
12use swc_ecma_transforms_base::helper_expr;
13use swc_ecma_utils::{
14    member_expr, private_ident, quote_ident, quote_str, ExprFactory, FunctionFactory, IsDirective,
15};
16use swc_ecma_visit::{noop_visit_mut_type, visit_mut_pass, VisitMut, VisitMutWith};
17
18pub use super::util::Config as InnerConfig;
19use crate::{
20    module_decl_strip::{Export, Link, LinkFlag, LinkItem, LinkSpecifierReducer, ModuleDeclStrip},
21    module_ref_rewriter::{rewrite_import_bindings, ImportMap},
22    path::Resolver,
23    top_level_this::top_level_this,
24    util::{
25        define_es_module, emit_export_stmts, local_name_for_src, use_strict, ImportInterop,
26        VecStmtLike,
27    },
28    SpanCtx,
29};
30
31#[derive(Debug, Clone, Default, Serialize, Deserialize)]
32#[serde(deny_unknown_fields, rename_all = "camelCase")]
33pub struct Config {
34    #[serde(default)]
35    pub module_id: Option<String>,
36
37    #[serde(flatten, default)]
38    pub config: InnerConfig,
39}
40
41#[derive(Default)]
42pub struct FeatureFlag {
43    pub support_block_scoping: bool,
44    pub support_arrow: bool,
45}
46
47pub fn amd<C>(
48    resolver: Resolver,
49    unresolved_mark: Mark,
50    config: Config,
51    available_features: FeatureFlag,
52    comments: Option<C>,
53) -> impl Pass
54where
55    C: Comments,
56{
57    let Config { module_id, config } = config;
58
59    visit_mut_pass(Amd {
60        module_id,
61        config,
62        unresolved_mark,
63        resolver,
64        comments,
65
66        support_arrow: available_features.support_arrow,
67        const_var_kind: if available_features.support_block_scoping {
68            VarDeclKind::Const
69        } else {
70            VarDeclKind::Var
71        },
72        dep_list: Default::default(),
73        require: quote_ident!(
74            SyntaxContext::empty().apply_mark(unresolved_mark),
75            "require"
76        ),
77        exports: None,
78        module: None,
79        found_import_meta: false,
80    })
81}
82
83pub struct Amd<C>
84where
85    C: Comments,
86{
87    module_id: Option<String>,
88    config: InnerConfig,
89    unresolved_mark: Mark,
90    resolver: Resolver,
91    comments: Option<C>,
92
93    support_arrow: bool,
94    const_var_kind: VarDeclKind,
95
96    dep_list: Vec<(Ident, Atom, SpanCtx)>,
97    require: Ident,
98    exports: Option<Ident>,
99    module: Option<Ident>,
100    found_import_meta: bool,
101}
102
103impl<C> VisitMut for Amd<C>
104where
105    C: Comments,
106{
107    noop_visit_mut_type!(fail);
108
109    fn visit_mut_module(&mut self, n: &mut Module) {
110        if self.module_id.is_none() {
111            self.module_id = self.get_amd_module_id_from_comments(n.span);
112        }
113
114        let mut stmts: Vec<Stmt> = Vec::with_capacity(n.body.len() + 4);
115
116        // Collect directives
117        stmts.extend(
118            &mut n
119                .body
120                .iter_mut()
121                .take_while(|i| i.directive_continue())
122                .map(|i| i.take())
123                .map(ModuleItem::expect_stmt),
124        );
125
126        // "use strict";
127        if self.config.strict_mode && !stmts.has_use_strict() {
128            stmts.push(use_strict());
129        }
130
131        if !self.config.allow_top_level_this {
132            top_level_this(&mut n.body, *Expr::undefined(DUMMY_SP));
133        }
134
135        let import_interop = self.config.import_interop();
136
137        let mut strip = ModuleDeclStrip::new(self.const_var_kind);
138        n.body.visit_mut_with(&mut strip);
139
140        let ModuleDeclStrip {
141            link,
142            export,
143            export_assign,
144            has_module_decl,
145            ..
146        } = strip;
147
148        let is_export_assign = export_assign.is_some();
149
150        if has_module_decl && !import_interop.is_none() && !is_export_assign {
151            stmts.push(define_es_module(self.exports()))
152        }
153
154        let mut import_map = Default::default();
155
156        stmts.extend(self.handle_import_export(&mut import_map, link, export, is_export_assign));
157
158        stmts.extend(n.body.take().into_iter().filter_map(|item| match item {
159            ModuleItem::Stmt(stmt) if !stmt.is_empty() => Some(stmt),
160            _ => None,
161        }));
162
163        if let Some(export_assign) = export_assign {
164            let return_stmt = ReturnStmt {
165                span: DUMMY_SP,
166                arg: Some(export_assign),
167            };
168
169            stmts.push(return_stmt.into())
170        }
171
172        if !self.config.ignore_dynamic || !self.config.preserve_import_meta {
173            stmts.visit_mut_children_with(self);
174        }
175
176        rewrite_import_bindings(&mut stmts, import_map, Default::default());
177
178        // ====================
179        //  Emit
180        // ====================
181
182        let mut elems = vec![Some(quote_str!("require").as_arg())];
183        let mut params = vec![self.require.clone().into()];
184
185        if let Some(exports) = self.exports.take() {
186            elems.push(Some(quote_str!("exports").as_arg()));
187            params.push(exports.into())
188        }
189
190        if let Some(module) = self.module.clone() {
191            elems.push(Some(quote_str!("module").as_arg()));
192            params.push(module.into())
193        }
194
195        self.dep_list
196            .take()
197            .into_iter()
198            .for_each(|(ident, src_path, src_span)| {
199                let src_path = match &self.resolver {
200                    Resolver::Real { resolver, base } => resolver
201                        .resolve_import(base, &src_path)
202                        .with_context(|| format!("failed to resolve `{src_path}`"))
203                        .unwrap(),
204                    Resolver::Default => src_path,
205                };
206
207                elems.push(Some(quote_str!(src_span.0, src_path).as_arg()));
208                params.push(ident.into());
209            });
210
211        let mut amd_call_args = Vec::with_capacity(3);
212        if let Some(module_id) = self.module_id.clone() {
213            amd_call_args.push(quote_str!(module_id).as_arg());
214        }
215        amd_call_args.push(
216            ArrayLit {
217                span: DUMMY_SP,
218                elems,
219            }
220            .as_arg(),
221        );
222
223        amd_call_args.push(
224            Function {
225                params,
226                decorators: Default::default(),
227                span: DUMMY_SP,
228                body: Some(BlockStmt {
229                    stmts,
230                    ..Default::default()
231                }),
232                is_generator: false,
233                is_async: false,
234                ..Default::default()
235            }
236            .into_fn_expr(None)
237            .as_arg(),
238        );
239
240        n.body = vec![quote_ident!(
241            SyntaxContext::empty().apply_mark(self.unresolved_mark),
242            "define"
243        )
244        .as_call(DUMMY_SP, amd_call_args)
245        .into_stmt()
246        .into()];
247    }
248
249    fn visit_mut_script(&mut self, _: &mut Script) {
250        // skip script
251    }
252
253    fn visit_mut_expr(&mut self, n: &mut Expr) {
254        match n {
255            Expr::Call(CallExpr {
256                span,
257                callee:
258                    Callee::Import(Import {
259                        span: import_span,
260                        phase: ImportPhase::Evaluation,
261                    }),
262                args,
263                ..
264            }) if !self.config.ignore_dynamic => {
265                args.visit_mut_with(self);
266
267                args.get_mut(0).into_iter().for_each(|x| {
268                    if let ExprOrSpread { spread: None, expr } = x {
269                        if let Expr::Lit(Lit::Str(Str { value, raw, .. })) = &mut **expr {
270                            *value = self.resolver.resolve(value.clone());
271                            *raw = None;
272                        }
273                    }
274                });
275
276                let mut require = self.require.clone();
277                require.span = *import_span;
278
279                *n = amd_dynamic_import(
280                    *span,
281                    args.take(),
282                    require,
283                    self.config.import_interop(),
284                    self.support_arrow,
285                );
286            }
287            Expr::Member(MemberExpr { span, obj, prop })
288                if !self.config.preserve_import_meta
289                    && obj
290                        .as_meta_prop()
291                        .map(|p| p.kind == MetaPropKind::ImportMeta)
292                        .unwrap_or_default() =>
293            {
294                let p = match prop {
295                    MemberProp::Ident(IdentName { sym, .. }) => &**sym,
296                    MemberProp::Computed(ComputedPropName { expr, .. }) => match &**expr {
297                        Expr::Lit(Lit::Str(s)) => &s.value,
298                        _ => return,
299                    },
300                    MemberProp::PrivateName(..) => return,
301                };
302                self.found_import_meta = true;
303
304                match p {
305                    // new URL(module.uri, document.baseURI).href
306                    "url" => {
307                        *n = amd_import_meta_url(*span, self.module());
308                    }
309                    // require.toUrl()
310                    "resolve" => {
311                        let mut require = self.require.clone();
312                        require.span = obj.span();
313                        *obj = require.into();
314
315                        match prop {
316                            MemberProp::Ident(IdentName { sym, .. }) => *sym = atom!("toUrl"),
317                            MemberProp::Computed(ComputedPropName { expr, .. }) => {
318                                match &mut **expr {
319                                    Expr::Lit(Lit::Str(s)) => {
320                                        s.value = atom!("toUrl");
321                                        s.raw = None;
322                                    }
323                                    _ => unreachable!(),
324                                }
325                            }
326                            MemberProp::PrivateName(..) => unreachable!(),
327                        }
328                    }
329                    // module.uri.split("/").pop()
330                    "filename" => {
331                        *n = amd_import_meta_filename(*span, self.module());
332                    }
333                    // require.toUrl(".")
334                    "dirname" => {
335                        let mut require = self.require.clone();
336                        require.span = obj.span();
337                        *obj = require.into();
338
339                        match prop {
340                            MemberProp::Ident(IdentName { sym, .. }) => *sym = atom!("toUrl"),
341                            MemberProp::Computed(ComputedPropName { expr, .. }) => {
342                                match &mut **expr {
343                                    Expr::Lit(Lit::Str(s)) => {
344                                        s.value = atom!("toUrl");
345                                        s.raw = None;
346                                    }
347                                    _ => unreachable!(),
348                                }
349                            }
350                            MemberProp::PrivateName(..) => unreachable!(),
351                        }
352
353                        *n = n.take().as_call(n.span(), vec![quote_str!(".").as_arg()]);
354                    }
355                    "main" => {
356                        *n = BinExpr {
357                            span: *span,
358                            left: self.module().make_member(quote_ident!("id")).into(),
359                            op: op!("=="),
360                            right: quote_str!("main").into(),
361                        }
362                        .into();
363                    }
364                    _ => {}
365                }
366            }
367            Expr::OptChain(OptChainExpr { base, .. }) if !self.config.preserve_import_meta => {
368                if let OptChainBase::Member(member) = &mut **base {
369                    if member
370                        .obj
371                        .as_meta_prop()
372                        .is_some_and(|meta_prop| meta_prop.kind == MetaPropKind::ImportMeta)
373                    {
374                        *n = member.take().into();
375                        n.visit_mut_with(self);
376                        return;
377                    }
378                };
379
380                n.visit_mut_children_with(self);
381            }
382            _ => n.visit_mut_children_with(self),
383        }
384    }
385}
386
387impl<C> Amd<C>
388where
389    C: Comments,
390{
391    fn handle_import_export(
392        &mut self,
393        import_map: &mut ImportMap,
394        link: Link,
395        export: Export,
396        is_export_assign: bool,
397    ) -> impl Iterator<Item = Stmt> {
398        let import_interop = self.config.import_interop();
399
400        let mut stmts = Vec::with_capacity(link.len());
401
402        let mut export_obj_prop_list = export.into_iter().collect();
403
404        link.into_iter().for_each(
405            |(src, LinkItem(src_span, link_specifier_set, mut link_flag))| {
406                let is_node_default = !link_flag.has_named() && import_interop.is_node();
407
408                if import_interop.is_none() {
409                    link_flag -= LinkFlag::NAMESPACE;
410                }
411
412                let need_re_export = link_flag.export_star();
413                let need_interop = link_flag.interop();
414                let need_new_var = link_flag.need_raw_import();
415
416                let mod_ident = private_ident!(local_name_for_src(&src));
417                let new_var_ident = if need_new_var {
418                    private_ident!(local_name_for_src(&src))
419                } else {
420                    mod_ident.clone()
421                };
422
423                self.dep_list.push((mod_ident.clone(), src, src_span));
424
425                link_specifier_set.reduce(
426                    import_map,
427                    &mut export_obj_prop_list,
428                    &new_var_ident,
429                    &Some(mod_ident.clone()),
430                    &mut false,
431                    is_node_default,
432                );
433
434                // _export_star(mod, exports);
435                let mut import_expr: Expr = if need_re_export {
436                    helper_expr!(export_star).as_call(
437                        DUMMY_SP,
438                        vec![mod_ident.clone().as_arg(), self.exports().as_arg()],
439                    )
440                } else {
441                    mod_ident.clone().into()
442                };
443
444                // _introp(mod);
445                if need_interop {
446                    import_expr = match import_interop {
447                        ImportInterop::Swc if link_flag.interop() => if link_flag.namespace() {
448                            helper_expr!(interop_require_wildcard)
449                        } else {
450                            helper_expr!(interop_require_default)
451                        }
452                        .as_call(PURE_SP, vec![import_expr.as_arg()]),
453                        ImportInterop::Node if link_flag.namespace() => {
454                            helper_expr!(interop_require_wildcard)
455                                .as_call(PURE_SP, vec![import_expr.as_arg(), true.as_arg()])
456                        }
457                        _ => import_expr,
458                    }
459                };
460
461                // mod = _introp(mod);
462                // var mod1 = _introp(mod);
463                if need_new_var {
464                    let stmt: Stmt = import_expr
465                        .into_var_decl(self.const_var_kind, new_var_ident.into())
466                        .into();
467
468                    stmts.push(stmt)
469                } else if need_interop {
470                    let stmt = import_expr
471                        .make_assign_to(op!("="), mod_ident.into())
472                        .into_stmt();
473                    stmts.push(stmt);
474                } else if need_re_export {
475                    stmts.push(import_expr.into_stmt());
476                };
477            },
478        );
479
480        let mut export_stmts = Default::default();
481
482        if !export_obj_prop_list.is_empty() && !is_export_assign {
483            export_obj_prop_list.sort_by_cached_key(|(key, ..)| key.clone());
484
485            let exports = self.exports();
486
487            export_stmts = emit_export_stmts(exports, export_obj_prop_list);
488        }
489
490        export_stmts.into_iter().chain(stmts)
491    }
492
493    fn module(&mut self) -> Ident {
494        self.module
495            .get_or_insert_with(|| private_ident!("module"))
496            .clone()
497    }
498
499    fn exports(&mut self) -> Ident {
500        self.exports
501            .get_or_insert_with(|| private_ident!("exports"))
502            .clone()
503    }
504
505    fn get_amd_module_id_from_comments(&self, span: Span) -> Option<String> {
506        // https://github.com/microsoft/TypeScript/blob/1b9c8a15adc3c9a30e017a7048f98ef5acc0cada/src/compiler/parser.ts#L9648-L9658
507        let amd_module_re =
508            Regex::new(r#"(?i)^/\s*<amd-module.*?name\s*=\s*(?:(?:'([^']*)')|(?:"([^"]*)")).*?/>"#)
509                .unwrap();
510
511        self.comments.as_ref().and_then(|comments| {
512            comments
513                .get_leading(span.lo)
514                .iter()
515                .flatten()
516                .rev()
517                .find_map(|cmt| {
518                    if cmt.kind != CommentKind::Line {
519                        return None;
520                    }
521                    amd_module_re
522                        .captures(&cmt.text)
523                        .and_then(|cap| cap.get(1).or_else(|| cap.get(2)))
524                })
525                .map(|m| m.as_str().to_string())
526        })
527    }
528}
529
530/// new Promise((resolve, reject) => require([arg], m => resolve(m), reject))
531pub(crate) fn amd_dynamic_import(
532    span: Span,
533    args: Vec<ExprOrSpread>,
534    require: Ident,
535    import_interop: ImportInterop,
536    support_arrow: bool,
537) -> Expr {
538    let resolve = private_ident!("resolve");
539    let reject = private_ident!("reject");
540    let arg = args[..1].iter().cloned().map(Option::Some).collect();
541
542    let module = private_ident!("m");
543
544    let resolved_module: Expr = match import_interop {
545        ImportInterop::None => module.clone().into(),
546        ImportInterop::Swc => {
547            helper_expr!(interop_require_wildcard).as_call(PURE_SP, vec![module.clone().as_arg()])
548        }
549        ImportInterop::Node => helper_expr!(interop_require_wildcard)
550            .as_call(PURE_SP, vec![module.clone().as_arg(), true.as_arg()]),
551    };
552
553    let resolve_callback = resolve
554        .clone()
555        .as_call(DUMMY_SP, vec![resolved_module.as_arg()])
556        .into_lazy_auto(vec![module.into()], support_arrow);
557
558    let require_call = require.as_call(
559        DUMMY_SP,
560        vec![
561            ArrayLit {
562                span: DUMMY_SP,
563                elems: arg,
564            }
565            .as_arg(),
566            resolve_callback.as_arg(),
567            reject.clone().as_arg(),
568        ],
569    );
570
571    let promise_executer =
572        require_call.into_lazy_auto(vec![resolve.into(), reject.into()], support_arrow);
573
574    NewExpr {
575        span,
576        callee: Box::new(quote_ident!("Promise").into()),
577        args: Some(vec![promise_executer.as_arg()]),
578        ..Default::default()
579    }
580    .into()
581}
582
583/// new URL(module.uri, document.baseURI).href
584fn amd_import_meta_url(span: Span, module: Ident) -> Expr {
585    MemberExpr {
586        span,
587        obj: quote_ident!("URL")
588            .into_new_expr(
589                DUMMY_SP,
590                Some(vec![
591                    module.make_member(quote_ident!("uri")).as_arg(),
592                    member_expr!(Default::default(), DUMMY_SP, document.baseURI).as_arg(),
593                ]),
594            )
595            .into(),
596        prop: MemberProp::Ident(atom!("href").into()),
597    }
598    .into()
599}
600
601// module.uri.split("/").pop()
602fn amd_import_meta_filename(span: Span, module: Ident) -> Expr {
603    module
604        .make_member(quote_ident!("uri"))
605        .make_member(quote_ident!("split"))
606        .as_call(DUMMY_SP, vec![quote_str!("/").as_arg()])
607        .make_member(quote_ident!("pop"))
608        .as_call(span, vec![])
609}