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 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 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 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 }
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 "url" => {
314 *n = amd_import_meta_url(*span, self.module());
315 }
316 "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 "filename" => {
340 *n = amd_import_meta_filename(*span, self.module());
341 }
342 "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 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 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 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 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
541pub(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
594fn 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
612fn 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}