swc_ecma_transforms_module/
amd.rs

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