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