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