1use std::{collections::HashMap, sync::atomic::Ordering};
2
3use anyhow::Error;
4use rustc_hash::FxHashMap;
5use swc_atoms::atom;
6use swc_common::{Span, SyntaxContext, DUMMY_SP};
7use swc_ecma_ast::*;
8use swc_ecma_utils::{quote_ident, ExprFactory};
9use swc_ecma_visit::{noop_visit_mut_type, VisitMut, VisitMutWith};
10
11use crate::{
12 bundler::{chunk::merge::Ctx, load::TransformedModule},
13 modules::Modules,
14 Bundler, Load, Resolve,
15};
16
17impl<L, R> Bundler<'_, L, R>
18where
19 L: Load,
20 R: Resolve,
21{
22 fn make_cjs_load_var(&self, info: &TransformedModule, span: Span) -> Ident {
23 Ident::new(atom!("load"), span, info.export_ctxt())
24 }
25
26 pub(super) fn replace_cjs_require_calls(
27 &self,
28 info: &TransformedModule,
29 module: &mut Modules,
30 is_entry: bool,
31 ) {
32 if !self.config.require {
33 return;
34 }
35
36 let mut v = RequireReplacer {
37 is_entry,
38 base: info,
39 bundler: self,
40 replaced: false,
41 };
42 module.visit_mut_with(&mut v);
43
44 if v.replaced {
45 info.helpers.require.store(true, Ordering::SeqCst);
46 }
47 }
48
49 pub(super) fn wrap_cjs_module(
52 &self,
53 ctx: &Ctx,
54 info: &TransformedModule,
55 mut module: Modules,
56 ) -> Result<Modules, Error> {
57 if !self.config.require || (!self.scope.is_cjs(info.id) && info.is_es6) {
58 return Ok(module);
59 }
60
61 tracing::debug!("Merging as a common js module: {}", info.fm.name);
62
63 let load_var = self.make_cjs_load_var(info, DUMMY_SP);
64
65 module.visit_mut_with(&mut DefaultHandler {
66 local_ctxt: info.local_ctxt(),
67 });
68 module.sort(info.id, &ctx.graph, &ctx.cycles, &self.cm);
69
70 let stmt = wrap_module(
71 SyntaxContext::empty(),
72 SyntaxContext::empty().apply_mark(self.unresolved_mark),
73 info.local_ctxt(),
74 load_var,
75 module.into(),
76 )
77 .into();
78
79 let wrapped = Modules::from(
80 info.id,
81 Module {
82 span: DUMMY_SP,
83 body: vec![stmt],
84 shebang: None,
85 },
86 self.injected_ctxt,
87 );
88
89 tracing::debug!("Injected a variable named `load` for a common js module");
90
91 Ok(wrapped)
92 }
93}
94
95fn wrap_module(
96 helper_ctxt: SyntaxContext,
97 unresolved_ctxt: SyntaxContext,
98 local_ctxt: SyntaxContext,
99 load_var: Ident,
100 mut dep: Module,
101) -> Stmt {
102 {
103 let mut from = HashMap::default();
107 from.insert((atom!("module"), unresolved_ctxt), local_ctxt);
108 from.insert((atom!("exports"), unresolved_ctxt), local_ctxt);
109
110 dep.visit_mut_with(&mut Remapper { vars: from })
111 }
112
113 let module_fn: Expr = FnExpr {
115 ident: None,
116 function: Box::new(Function {
117 params: vec![
118 Param {
120 span: DUMMY_SP,
121 decorators: Default::default(),
122 pat: Pat::Ident(Ident::new(atom!("module"), DUMMY_SP, local_ctxt).into()),
123 },
124 Param {
126 span: DUMMY_SP,
127 decorators: Default::default(),
128 pat: Pat::Ident(Ident::new(atom!("exports"), DUMMY_SP, local_ctxt).into()),
129 },
130 ],
131 decorators: Vec::new(),
132 span: DUMMY_SP,
133 body: Some(BlockStmt {
134 span: dep.span,
135 stmts: dep
136 .body
137 .into_iter()
138 .map(|v| match v {
139 ModuleItem::ModuleDecl(i) => {
140 unreachable!("module item found but is_es6 is false: {:?}", i)
141 }
142 ModuleItem::Stmt(s) => s,
143 #[cfg(swc_ast_unknown)]
144 _ => panic!("unable to access unknown nodes"),
145 })
146 .collect(),
147 ..Default::default()
148 }),
149 is_generator: false,
150 is_async: false,
151 ..Default::default()
152 }),
153 }
154 .into();
155
156 VarDecl {
159 span: DUMMY_SP,
160 kind: VarDeclKind::Var,
161 declare: false,
162 decls: vec![VarDeclarator {
163 span: DUMMY_SP,
164 name: Pat::Ident(load_var.into()),
165 init: Some(Box::new(Expr::Call(CallExpr {
166 span: DUMMY_SP,
167 callee: Ident::new(atom!("__swcpack_require__"), DUMMY_SP, helper_ctxt)
168 .make_member(quote_ident!("bind"))
169 .as_callee(),
170 args: vec![Expr::undefined(DUMMY_SP).as_arg(), module_fn.as_arg()],
171 ..Default::default()
172 }))),
173 definite: false,
174 }],
175 ..Default::default()
176 }
177 .into()
178}
179
180struct RequireReplacer<'a, 'b, L, R>
181where
182 L: Load,
183 R: Resolve,
184{
185 base: &'a TransformedModule,
186 bundler: &'a Bundler<'b, L, R>,
187 replaced: bool,
188 is_entry: bool,
189}
190
191impl<L, R> VisitMut for RequireReplacer<'_, '_, L, R>
192where
193 L: Load,
194 R: Resolve,
195{
196 noop_visit_mut_type!(fail);
197
198 fn visit_mut_call_expr(&mut self, node: &mut CallExpr) {
199 node.visit_mut_children_with(self);
200
201 if let Callee::Expr(e) = &node.callee {
202 if let Expr::Ident(i) = &**e {
203 if i.sym == *"require" && node.args.len() == 1 {
205 if let Expr::Lit(Lit::Str(module_name)) = &*node.args[0].expr {
206 let module_atom = module_name.value.to_atom_lossy();
207 if self.bundler.is_external(module_atom.as_ref()) {
208 return;
209 }
210 let load = CallExpr {
211 span: node.span,
212 callee: Ident::new(atom!("load"), i.span, i.ctxt).as_callee(),
213 args: Vec::new(),
214 ..Default::default()
215 };
216 self.replaced = true;
217 *node = load;
218
219 tracing::trace!("Found, and replacing require");
220 }
221 }
222 }
223 }
224 }
225
226 fn visit_mut_module_item(&mut self, node: &mut ModuleItem) {
227 node.visit_mut_children_with(self);
228
229 if !self.is_entry {
230 return;
231 }
232
233 if let ModuleItem::ModuleDecl(ModuleDecl::Import(i)) = node {
234 let dep_module_id = self
235 .base
236 .imports
237 .specifiers
238 .iter()
239 .find(|(src, _)| src.src.value == i.src.value)
240 .map(|v| v.0.module_id);
241 let dep_module_id = match dep_module_id {
242 Some(v) => v,
243 _ => {
244 return;
245 }
246 };
247 let dep_module = self.bundler.scope.get_module(dep_module_id).unwrap();
249 if !self.bundler.scope.is_cjs(dep_module_id) && dep_module.is_es6 {
250 return;
251 }
252
253 let load_var = self.bundler.make_cjs_load_var(&dep_module, i.span);
254 if i.specifiers.is_empty() {
257 self.replaced = true;
258 *node = CallExpr {
259 span: DUMMY_SP,
260 callee: load_var.as_callee(),
261 args: Vec::new(),
262
263 ..Default::default()
264 }
265 .into_stmt()
266 .into();
267 return;
268 }
269
270 let mut props = Vec::new();
271 for spec in i.specifiers.clone() {
273 match spec {
274 ImportSpecifier::Named(s) => match s.imported {
275 Some(ModuleExportName::Ident(imported)) => {
276 props.push(ObjectPatProp::KeyValue(KeyValuePatProp {
277 key: imported.into(),
278 value: Box::new(s.local.into()),
279 }));
280 }
281 Some(ModuleExportName::Str(..)) => {
282 unimplemented!("module string names unimplemented")
283 }
284 _ => {
285 props.push(ObjectPatProp::Assign(AssignPatProp {
286 span: s.span,
287 key: s.local.into(),
288 value: None,
289 }));
290 }
291 },
292 ImportSpecifier::Default(s) => {
293 props.push(ObjectPatProp::KeyValue(KeyValuePatProp {
294 key: PropName::Ident(IdentName::new(atom!("default"), DUMMY_SP)),
295 value: Box::new(s.local.into()),
296 }));
297 }
298 ImportSpecifier::Namespace(ns) => {
299 self.replaced = true;
300 *node = VarDecl {
301 span: i.span,
302 kind: VarDeclKind::Var,
303 declare: false,
304 decls: vec![VarDeclarator {
305 span: ns.span,
306 name: ns.local.into(),
307 init: Some(Box::new(
308 CallExpr {
309 span: DUMMY_SP,
310 callee: load_var.as_callee(),
311 args: Vec::new(),
312
313 ..Default::default()
314 }
315 .into(),
316 )),
317 definite: false,
318 }],
319 ..Default::default()
320 }
321 .into();
322 return;
323 }
324 #[cfg(swc_ast_unknown)]
325 _ => panic!("unable to access unknown nodes"),
326 }
327 }
328
329 self.replaced = true;
330 *node = VarDecl {
331 span: i.span,
332 kind: VarDeclKind::Var,
333 declare: false,
334 decls: vec![VarDeclarator {
335 span: i.span,
336 name: Pat::Object(ObjectPat {
337 span: DUMMY_SP,
338 props,
339 optional: false,
340 type_ann: None,
341 }),
342 init: Some(Box::new(Expr::Call(CallExpr {
343 span: DUMMY_SP,
344 callee: load_var.as_callee(),
345 args: Vec::new(),
346 ..Default::default()
347 }))),
348 definite: false,
349 }],
350 ..Default::default()
351 }
352 .into();
353 }
354 }
355}
356
357struct DefaultHandler {
358 local_ctxt: SyntaxContext,
359}
360
361impl VisitMut for DefaultHandler {
362 noop_visit_mut_type!(fail);
363
364 fn visit_mut_expr(&mut self, e: &mut Expr) {
365 e.visit_mut_children_with(self);
366
367 if let Expr::Ident(i) = e {
368 if i.sym == "default" {
369 *e = MemberExpr {
370 span: i.span,
371 obj: Ident::new(atom!("module"), DUMMY_SP, self.local_ctxt).into(),
372 prop: MemberProp::Ident(quote_ident!("exports")),
373 }
374 .into();
375 }
376 }
377 }
378}
379
380struct Remapper {
381 vars: FxHashMap<Id, SyntaxContext>,
382}
383
384impl VisitMut for Remapper {
385 noop_visit_mut_type!(fail);
386
387 fn visit_mut_ident(&mut self, i: &mut Ident) {
388 if let Some(v) = self.vars.get(&i.to_id()).copied() {
389 i.ctxt = v;
390 }
391 }
392}