1use std::path::{Path, PathBuf};
2
3use anyhow::Error;
4use relative_path::RelativePath;
5use rustc_hash::FxHashMap;
6use swc_atoms::atom;
7use swc_common::{util::move_map::MoveMap, FileName, Mark, DUMMY_SP};
8use swc_ecma_ast::*;
9use swc_ecma_transforms_base::{
10 fixer::fixer,
11 helpers::{inject_helpers, Helpers, HELPERS},
12 hygiene::hygiene,
13};
14use swc_ecma_utils::{contains_top_level_await, find_pat_ids, private_ident, ExprFactory};
15use swc_ecma_visit::{noop_fold_type, Fold, FoldWith, VisitMutWith};
16
17use crate::{hash::calc_hash, Bundle, BundleKind, Bundler, Load, ModuleType, Resolve};
18
19impl<L, R> Bundler<'_, L, R>
20where
21 L: Load,
22 R: Resolve,
23{
24 pub(super) fn finalize(
30 &self,
31 bundles: Vec<Bundle>,
32 unresolved_mark: Mark,
33 ) -> Result<Vec<Bundle>, Error> {
34 self.run(|| {
35 let mut new = Vec::with_capacity(bundles.len());
36 let mut renamed = FxHashMap::default();
37
38 for mut bundle in bundles {
39 bundle.module = self.optimize(bundle.module);
40
41 if !self.config.disable_hygiene {
42 bundle.module.visit_mut_with(&mut hygiene());
43 }
44
45 bundle.module = self.may_wrap_with_iife(bundle.module);
46
47 if !self.config.disable_fixer {
48 bundle.module.visit_mut_with(&mut fixer(None));
49 }
50
51 {
52 let swc_helpers = *self
54 .scope
55 .get_module(bundle.id)
56 .expect("module should exist at this point")
57 .swc_helpers
58 .lock();
59
60 HELPERS.set(&Helpers::from_data(swc_helpers), || {
61 bundle
62 .module
63 .visit_mut_with(&mut inject_helpers(unresolved_mark));
64 });
65 }
66
67 match bundle.kind {
68 BundleKind::Named { .. } => {
69 let helpers = self
71 .scope
72 .get_module(bundle.id)
73 .expect("module should exist at this point")
74 .helpers;
75
76 helpers.add_to(&mut bundle.module.body);
77
78 new.push(bundle);
79 }
80 BundleKind::Lib { name } => {
81 let hash = calc_hash(self.cm.clone(), &bundle.module)?;
82 let mut new_name = PathBuf::from(name);
83 let key = new_name.clone();
84 let file_name = new_name
85 .file_name()
86 .map(|path| -> PathBuf {
87 let path = Path::new(path);
88 let ext = path.extension();
89 if let Some(ext) = ext {
90 return format!(
91 "{}-{}.{}",
92 path.file_stem().unwrap().to_string_lossy(),
93 hash,
94 ext.to_string_lossy()
95 )
96 .into();
97 }
98 format!("{}-{}", path.file_stem().unwrap().to_string_lossy(), hash,)
99 .into()
100 })
101 .expect("javascript file should have name");
102 new_name.pop();
103 new_name = new_name.join(file_name.clone());
104
105 renamed.insert(key, new_name.to_string_lossy().to_string());
106
107 new.push(Bundle {
108 kind: BundleKind::Named {
109 name: file_name.display().to_string(),
110 },
111 ..bundle
112 })
113 }
114 _ => new.push(bundle),
115 }
116 }
117
118 if new.len() == 1 {
119 return Ok(new);
120 }
121
122 new = new.move_map(|bundle| {
123 let path = match &*self.scope.get_module(bundle.id).unwrap().fm.name {
124 FileName::Real(ref v) => v.clone(),
125 _ => {
126 tracing::error!("Cannot rename: not a real file");
127 return bundle;
128 }
129 };
130
131 let module = {
132 let mut v = Renamer {
134 resolver: &self.resolver,
135 base: &path,
136 renamed: &renamed,
137 };
138 bundle.module.fold_with(&mut v)
139 };
140
141 Bundle { module, ..bundle }
142 });
143
144 Ok(new)
145 })
146 }
147
148 fn may_wrap_with_iife(&self, module: Module) -> Module {
149 if self.config.module != ModuleType::Iife {
150 return module;
151 }
152
153 let is_async = contains_top_level_await(&module);
154
155 let mut props = Vec::new();
157
158 let mut body = BlockStmt {
159 span: module.span,
160 stmts: module
161 .body
162 .into_iter()
163 .filter_map(|item| {
164 let decl = match item {
165 ModuleItem::ModuleDecl(v) => v,
166 ModuleItem::Stmt(stmt) => return Some(stmt),
167 #[cfg(swc_ast_unknown)]
168 _ => panic!("unable to access unknown nodes"),
169 };
170
171 match decl {
172 ModuleDecl::ExportNamed(NamedExport { src: Some(..), .. })
173 | ModuleDecl::TsImportEquals(_)
174 | ModuleDecl::TsExportAssignment(_)
175 | ModuleDecl::TsNamespaceExport(_)
176 | ModuleDecl::Import(_) => None,
177
178 ModuleDecl::ExportDecl(export) => {
179 match &export.decl {
180 Decl::Class(ClassDecl { ident, .. })
181 | Decl::Fn(FnDecl { ident, .. }) => {
182 props.push(PropOrSpread::Prop(Box::new(Prop::Shorthand(
183 ident.clone(),
184 ))));
185 }
186 Decl::Var(decl) => {
187 let ids: Vec<Ident> = find_pat_ids(decl);
188 props.extend(
189 ids.into_iter()
190 .map(Prop::Shorthand)
191 .map(Box::new)
192 .map(PropOrSpread::Prop),
193 );
194 }
195 _ => unreachable!(),
196 }
197
198 Some(export.decl.into())
199 }
200
201 ModuleDecl::ExportNamed(NamedExport {
202 specifiers,
203 src: None,
204 ..
205 }) => {
206 for s in specifiers {
207 match s {
208 ExportSpecifier::Namespace(..) => {
209 }
211 ExportSpecifier::Default(s) => {
212 props.push(PropOrSpread::Prop(Box::new(Prop::KeyValue(
213 KeyValueProp {
214 key: PropName::Ident(IdentName::new(
215 atom!("default"),
216 DUMMY_SP,
217 )),
218 value: s.exported.into(),
219 },
220 ))));
221 }
222 ExportSpecifier::Named(s) => match s.exported {
223 Some(ModuleExportName::Ident(exported)) => {
224 let orig = match s.orig {
225 ModuleExportName::Ident(ident) => ident,
226 ModuleExportName::Str(..) => unimplemented!(
227 "module string names unimplemented"
228 ),
229 #[cfg(swc_ast_unknown)]
230 _ => panic!("unable to access unknown nodes"),
231 };
232 props.push(PropOrSpread::Prop(Box::new(
233 Prop::KeyValue(KeyValueProp {
234 key: PropName::Ident(exported.into()),
235 value: orig.into(),
236 }),
237 )));
238 }
239 Some(ModuleExportName::Str(..)) => {
240 unimplemented!("module string names unimplemented")
241 }
242 #[cfg(swc_ast_unknown)]
243 Some(_) => panic!("unable to access unknown nodes"),
244 None => {
245 let orig = match s.orig {
246 ModuleExportName::Ident(ident) => ident,
247 ModuleExportName::Str(..) => unimplemented!(
248 "module string names unimplemented"
249 ),
250 #[cfg(swc_ast_unknown)]
251 _ => panic!("unable to access unknown nodes"),
252 };
253 props.push(PropOrSpread::Prop(Box::new(
254 Prop::Shorthand(orig),
255 )));
256 }
257 },
258 #[cfg(swc_ast_unknown)]
259 _ => panic!("unable to access unknown nodes"),
260 }
261 }
262
263 None
264 }
265
266 ModuleDecl::ExportDefaultDecl(export) => match export.decl {
267 DefaultDecl::Class(expr) => {
268 let ident = expr.ident;
269 let ident =
270 ident.unwrap_or_else(|| private_ident!("_default_decl"));
271
272 props.push(PropOrSpread::Prop(Box::new(Prop::KeyValue(
273 KeyValueProp {
274 key: PropName::Ident(IdentName::new(
275 atom!("default"),
276 export.span,
277 )),
278 value: ident.clone().into(),
279 },
280 ))));
281
282 Some(
283 ClassDecl {
284 ident,
285 class: expr.class,
286 declare: false,
287 }
288 .into(),
289 )
290 }
291 DefaultDecl::Fn(expr) => {
292 let ident = expr.ident;
293 let ident =
294 ident.unwrap_or_else(|| private_ident!("_default_decl"));
295
296 props.push(PropOrSpread::Prop(Box::new(Prop::KeyValue(
297 KeyValueProp {
298 key: PropName::Ident(IdentName::new(
299 atom!("default"),
300 export.span,
301 )),
302 value: ident.clone().into(),
303 },
304 ))));
305
306 Some(
307 FnDecl {
308 ident,
309 function: expr.function,
310 declare: false,
311 }
312 .into(),
313 )
314 }
315 DefaultDecl::TsInterfaceDecl(_) => None,
316 #[cfg(swc_ast_unknown)]
317 _ => panic!("unable to access unknown nodes"),
318 },
319 ModuleDecl::ExportDefaultExpr(export) => {
320 let default_var = private_ident!("default");
321 props.push(PropOrSpread::Prop(Box::new(Prop::Shorthand(
322 default_var.clone(),
323 ))));
324 let var = VarDeclarator {
325 span: DUMMY_SP,
326 name: default_var.into(),
327 init: Some(export.expr),
328 definite: false,
329 };
330 Some(
331 VarDecl {
332 span: DUMMY_SP,
333 kind: VarDeclKind::Const,
334 declare: false,
335 decls: vec![var],
336 ..Default::default()
337 }
338 .into(),
339 )
340 }
341
342 ModuleDecl::ExportAll(_) => None,
343
344 #[cfg(swc_ast_unknown)]
345 _ => panic!("unable to access unknown nodes"),
346 }
347 })
348 .collect(),
349 ..Default::default()
350 };
351 body.stmts.push(
352 ReturnStmt {
353 span: DUMMY_SP,
354 arg: Some(
355 ObjectLit {
356 span: DUMMY_SP,
357 props,
358 }
359 .into(),
360 ),
361 }
362 .into(),
363 );
364
365 let f = Function {
366 is_generator: false,
367 is_async,
368 span: DUMMY_SP,
369 body: Some(body),
370 ..Default::default()
371 };
372
373 let invoked_fn_expr = FnExpr {
374 ident: None,
375 function: Box::new(f),
376 };
377
378 let iife = CallExpr {
379 span: DUMMY_SP,
380 callee: invoked_fn_expr.as_callee(),
381 args: Default::default(),
382 ..Default::default()
383 }
384 .into();
385
386 Module {
387 span: DUMMY_SP,
388 shebang: None,
389 body: vec![ModuleItem::Stmt(Stmt::Expr(ExprStmt {
390 span: DUMMY_SP,
391 expr: iife,
392 }))],
393 }
394 }
395}
396
397struct Renamer<'a, R>
399where
400 R: Resolve,
401{
402 resolver: R,
403 base: &'a PathBuf,
404 renamed: &'a FxHashMap<PathBuf, String>,
405}
406
407impl<R> Fold for Renamer<'_, R>
408where
409 R: Resolve,
410{
411 noop_fold_type!();
412
413 fn fold_import_decl(&mut self, import: ImportDecl) -> ImportDecl {
414 let resolved = match self.resolver.resolve(
415 &FileName::Real(self.base.clone()),
416 &import.src.value.to_string_lossy(),
417 ) {
418 Ok(v) => match v.filename {
419 FileName::Real(v) => v,
420 _ => panic!("rename_bundles called with non-path module"),
421 },
422 Err(_) => return import,
423 };
424
425 if let Some(v) = self.renamed.get(&resolved) {
426 let base = self
432 .base
433 .parent()
434 .unwrap_or(self.base)
435 .as_os_str()
436 .to_string_lossy();
437 let base = RelativePath::new(&*base);
438 let v = base.relative(v);
439 let value = v.as_str();
440 return ImportDecl {
441 src: Box::new(Str {
442 value: if value.starts_with('.') {
443 value.into()
444 } else {
445 format!("./{value}").into()
446 },
447 ..*import.src
448 }),
449 ..import
450 };
451 }
452
453 import
454 }
455}