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 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 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 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 }
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 "url" => {
307 *n = amd_import_meta_url(*span, self.module());
308 }
309 "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 "filename" => {
331 *n = amd_import_meta_filename(*span, self.module());
332 }
333 "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 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 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 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 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
530pub(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
583fn 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
601fn 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}