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