1use anyhow::Context;
2use swc_atoms::Atom;
3use swc_common::{
4 source_map::PURE_SP, sync::Lrc, util::take::Take, Mark, SourceMap, Span, SyntaxContext,
5 DUMMY_SP,
6};
7use swc_ecma_ast::*;
8use swc_ecma_transforms_base::{feature::FeatureFlag, helper_expr};
9use swc_ecma_utils::{
10 is_valid_prop_ident, private_ident, quote_ident, quote_str, ExprFactory, IsDirective,
11};
12use swc_ecma_visit::{noop_visit_mut_type, visit_mut_pass, VisitMut, VisitMutWith};
13
14use self::config::BuiltConfig;
15pub use self::config::Config;
16use crate::{
17 module_decl_strip::{Export, Link, LinkFlag, LinkItem, LinkSpecifierReducer, ModuleDeclStrip},
18 module_ref_rewriter::{rewrite_import_bindings, ImportMap},
19 path::Resolver,
20 top_level_this::top_level_this,
21 util::{
22 define_es_module, emit_export_stmts, local_name_for_src, use_strict, ImportInterop,
23 VecStmtLike,
24 },
25 SpanCtx,
26};
27
28mod config;
29
30pub fn umd(
31 cm: Lrc<SourceMap>,
32 resolver: Resolver,
33 unresolved_mark: Mark,
34 config: Config,
35 available_features: FeatureFlag,
36) -> impl Pass {
37 visit_mut_pass(Umd {
38 config: config.build(cm.clone()),
39 unresolved_mark,
40 cm,
41 resolver,
42
43 const_var_kind: if caniuse!(available_features.BlockScoping) {
44 VarDeclKind::Const
45 } else {
46 VarDeclKind::Var
47 },
48
49 dep_list: Default::default(),
50
51 exports: None,
52 })
53}
54
55pub struct Umd {
56 cm: Lrc<SourceMap>,
57 unresolved_mark: Mark,
58 config: BuiltConfig,
59 resolver: Resolver,
60
61 const_var_kind: VarDeclKind,
62
63 dep_list: Vec<(Ident, Atom, SpanCtx)>,
64
65 exports: Option<Ident>,
66}
67
68impl VisitMut for Umd {
69 noop_visit_mut_type!(fail);
70
71 fn visit_mut_module(&mut self, module: &mut Module) {
72 let module_items = &mut module.body;
73
74 let mut stmts: Vec<Stmt> = Vec::with_capacity(module_items.len() + 4);
75
76 stmts.extend(
78 module_items
79 .iter_mut()
80 .take_while(|i| i.directive_continue())
81 .map(|i| i.take())
82 .map(ModuleItem::expect_stmt),
83 );
84
85 if self.config.config.strict_mode && !stmts.has_use_strict() {
87 stmts.push(use_strict());
88 }
89
90 if !self.config.config.allow_top_level_this {
91 top_level_this(module_items, *Expr::undefined(DUMMY_SP));
92 }
93
94 let import_interop = self.config.config.import_interop();
95
96 let mut strip = ModuleDeclStrip::new(self.const_var_kind);
97 module_items.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 is_export_assign = export_assign.is_some();
108
109 if has_module_decl && !import_interop.is_none() && !is_export_assign {
110 stmts.push(define_es_module(self.exports()))
111 }
112
113 let mut import_map = Default::default();
114
115 stmts.extend(
116 self.handle_import_export(&mut import_map, link, export, is_export_assign)
117 .map(From::from),
118 );
119
120 stmts.extend(module_items.take().into_iter().filter_map(|i| match i {
121 ModuleItem::Stmt(stmt) if !stmt.is_empty() => Some(stmt),
122 _ => None,
123 }));
124
125 if let Some(export_assign) = export_assign {
126 let return_stmt = ReturnStmt {
127 span: DUMMY_SP,
128 arg: Some(export_assign),
129 };
130
131 stmts.push(return_stmt.into())
132 }
133
134 rewrite_import_bindings(&mut stmts, import_map, Default::default());
135
136 let (adapter_fn_expr, factory_params) = self.adapter(module.span, is_export_assign);
141
142 let factory_fn_expr: Expr = Function {
143 params: factory_params,
144 decorators: Default::default(),
145 span: DUMMY_SP,
146 body: Some(BlockStmt {
147 stmts,
148 ..Default::default()
149 }),
150 is_generator: false,
151 is_async: false,
152 ..Default::default()
153 }
154 .into();
155
156 *module_items = vec![adapter_fn_expr
157 .as_call(
158 DUMMY_SP,
159 vec![
160 ThisExpr { span: DUMMY_SP }.as_arg(),
161 factory_fn_expr.as_arg(),
162 ],
163 )
164 .into_stmt()
165 .into()]
166 }
167}
168
169impl Umd {
170 fn handle_import_export(
171 &mut self,
172 import_map: &mut ImportMap,
173 link: Link,
174 export: Export,
175 is_export_assign: bool,
176 ) -> impl Iterator<Item = Stmt> {
177 let import_interop = self.config.config.import_interop();
178
179 let mut stmts = Vec::with_capacity(link.len());
180
181 let mut export_obj_prop_list = export.into_iter().collect();
182
183 link.into_iter().for_each(
184 |(src, LinkItem(src_span, link_specifier_set, mut link_flag))| {
185 let is_node_default = !link_flag.has_named() && import_interop.is_node();
186
187 if import_interop.is_none() {
188 link_flag -= LinkFlag::NAMESPACE;
189 }
190
191 let need_re_export = link_flag.export_star();
192 let need_interop = link_flag.interop();
193 let need_new_var = link_flag.need_raw_import();
194
195 let mod_ident = private_ident!(local_name_for_src(&src));
196 let new_var_ident = if need_new_var {
197 private_ident!(local_name_for_src(&src))
198 } else {
199 mod_ident.clone()
200 };
201
202 self.dep_list.push((mod_ident.clone(), src, src_span));
203
204 link_specifier_set.reduce(
205 import_map,
206 &mut export_obj_prop_list,
207 &new_var_ident,
208 &Some(mod_ident.clone()),
209 &mut false,
210 is_node_default,
211 );
212
213 let mut import_expr: Expr = if need_re_export {
215 helper_expr!(export_star).as_call(
216 DUMMY_SP,
217 vec![mod_ident.clone().as_arg(), self.exports().as_arg()],
218 )
219 } else {
220 mod_ident.clone().into()
221 };
222
223 if need_interop {
225 import_expr = match import_interop {
226 ImportInterop::Swc if link_flag.interop() => if link_flag.namespace() {
227 helper_expr!(interop_require_wildcard)
228 } else {
229 helper_expr!(interop_require_default)
230 }
231 .as_call(PURE_SP, vec![import_expr.as_arg()]),
232 ImportInterop::Node if link_flag.namespace() => {
233 helper_expr!(interop_require_wildcard)
234 .as_call(PURE_SP, vec![import_expr.as_arg(), true.as_arg()])
235 }
236 _ => import_expr,
237 }
238 };
239
240 if need_new_var {
243 let stmt: Stmt = import_expr
244 .into_var_decl(self.const_var_kind, new_var_ident.into())
245 .into();
246
247 stmts.push(stmt)
248 } else if need_interop {
249 let stmt = import_expr
250 .make_assign_to(op!("="), mod_ident.into())
251 .into_stmt();
252 stmts.push(stmt);
253 } else if need_re_export {
254 stmts.push(import_expr.into_stmt());
255 }
256 },
257 );
258
259 let mut export_stmts = Default::default();
260
261 if !export_obj_prop_list.is_empty() && !is_export_assign {
262 export_obj_prop_list.sort_by_cached_key(|(key, ..)| key.clone());
263
264 let exports = self.exports();
265
266 export_stmts = emit_export_stmts(exports, export_obj_prop_list);
267 }
268
269 export_stmts.into_iter().chain(stmts)
270 }
271
272 fn exports(&mut self) -> Ident {
273 self.exports
274 .get_or_insert_with(|| private_ident!("exports"))
275 .clone()
276 }
277
278 fn adapter(&mut self, module_span: Span, is_export_assign: bool) -> (FnExpr, Vec<Param>) {
308 macro_rules! js_typeof {
309 ($test:expr =>! $type:expr) => {
310 Expr::Unary(UnaryExpr {
311 span: DUMMY_SP,
312 op: op!("typeof"),
313 arg: Box::new(Expr::from($test)),
314 })
315 .make_bin(op!("!=="), quote_str!($type))
316 };
317
318 ($test:expr => $type:expr) => {
319 Expr::Unary(UnaryExpr {
320 span: DUMMY_SP,
321 op: op!("typeof"),
322 arg: Box::new(Expr::from($test)),
323 })
324 .make_bin(op!("==="), quote_str!($type))
325 };
326 }
327
328 let module = quote_ident!(
330 SyntaxContext::empty().apply_mark(self.unresolved_mark),
331 "module"
332 );
333
334 let require = quote_ident!(
335 SyntaxContext::empty().apply_mark(self.unresolved_mark),
336 "require"
337 );
338 let define = quote_ident!(
339 SyntaxContext::empty().apply_mark(self.unresolved_mark),
340 "define"
341 );
342 let global_this = quote_ident!(
343 SyntaxContext::empty().apply_mark(self.unresolved_mark),
344 "globalThis"
345 );
346 let js_self = quote_ident!(
347 SyntaxContext::empty().apply_mark(self.unresolved_mark),
348 "self"
349 );
350
351 let global = private_ident!("global");
353 let factory = private_ident!("factory");
354
355 let module_exports = module.clone().make_member(quote_ident!("exports"));
356 let define_amd = define.clone().make_member(quote_ident!("amd"));
357
358 let mut cjs_args = Vec::new();
359 let mut amd_dep_list = Vec::new();
360 let mut browser_args = Vec::new();
361
362 let mut factory_params = Vec::new();
363
364 if !is_export_assign && self.exports.is_some() {
365 let filename = self.cm.span_to_filename(module_span);
366 let exported_name = self.config.determine_export_name(filename);
367 let global_lib = global.clone().make_member(exported_name.into());
368
369 cjs_args.push(quote_ident!("exports").as_arg());
370 amd_dep_list.push(Some(quote_str!("exports").as_arg()));
371 browser_args.push(
372 ObjectLit {
373 span: DUMMY_SP,
374 props: Default::default(),
375 }
376 .make_assign_to(op!("="), global_lib.into())
377 .as_arg(),
378 );
379 factory_params.push(self.exports().into());
380 }
381
382 self.dep_list
383 .take()
384 .into_iter()
385 .for_each(|(ident, src_path, src_span)| {
386 let src_path = match &self.resolver {
387 Resolver::Real { resolver, base } => resolver
388 .resolve_import(base, &src_path)
389 .with_context(|| format!("failed to resolve `{}`", src_path))
390 .unwrap(),
391 Resolver::Default => src_path,
392 };
393
394 cjs_args.push(
395 require
396 .clone()
397 .as_call(
398 DUMMY_SP,
399 vec![quote_str!(src_span.0, src_path.clone()).as_arg()],
400 )
401 .as_arg(),
402 );
403 amd_dep_list.push(Some(quote_str!(src_span.0, src_path.clone()).as_arg()));
404
405 let global_dep = {
406 let dep_name = self.config.global_name(&src_path);
407 let global = global.clone();
408 if is_valid_prop_ident(&dep_name) {
409 global.make_member(quote_ident!(dep_name))
410 } else {
411 global.computed_member(quote_str!(dep_name))
412 }
413 };
414 browser_args.push(global_dep.as_arg());
415 factory_params.push(ident.into());
416 });
417
418 let cjs_if_test = js_typeof!(module => "object")
419 .make_bin(op!("&&"), js_typeof!(module_exports.clone() => "object"));
420 let mut cjs_if_body = factory.clone().as_call(DUMMY_SP, cjs_args);
421 if is_export_assign {
422 cjs_if_body = cjs_if_body.make_assign_to(op!("="), module_exports.clone().into());
423 }
424
425 let amd_if_test = js_typeof!(define.clone() => "function").make_bin(op!("&&"), define_amd);
426 let amd_if_body = define.as_call(
427 DUMMY_SP,
428 vec![
429 ArrayLit {
430 span: DUMMY_SP,
431 elems: amd_dep_list,
432 }
433 .as_arg(),
434 factory.clone().as_arg(),
435 ],
436 );
437
438 let browser_if_test = CondExpr {
439 span: DUMMY_SP,
440 test: Box::new(js_typeof!(global_this.clone() =>! "undefined")),
441 cons: Box::new(global_this.into()),
442 alt: Box::new(global.clone().make_bin(op!("||"), js_self)),
443 }
444 .make_assign_to(op!("="), global.clone().into());
445
446 let mut browser_if_body = factory.clone().as_call(DUMMY_SP, browser_args);
447 if is_export_assign {
448 browser_if_body = browser_if_body.make_assign_to(op!("="), module_exports.into());
449 }
450
451 let adapter_body = BlockStmt {
452 span: DUMMY_SP,
453 stmts: vec![IfStmt {
454 span: DUMMY_SP,
455 test: Box::new(cjs_if_test),
456 cons: Box::new(cjs_if_body.into_stmt()),
457 alt: Some(Box::new(
458 IfStmt {
459 span: DUMMY_SP,
460 test: Box::new(amd_if_test),
461 cons: Box::new(amd_if_body.into_stmt()),
462 alt: Some(Box::new(
463 IfStmt {
464 span: DUMMY_SP,
465 test: Box::new(browser_if_test),
466 cons: Box::new(browser_if_body.into_stmt()),
467 alt: None,
468 }
469 .into(),
470 )),
471 }
472 .into(),
473 )),
474 }
475 .into()],
476 ..Default::default()
477 };
478
479 let adapter_fn_expr = Function {
480 params: vec![global.into(), factory.into()],
481 span: DUMMY_SP,
482 body: Some(adapter_body),
483 is_generator: false,
484 is_async: false,
485 ..Default::default()
486 }
487 .into();
488
489 (adapter_fn_expr, factory_params)
490 }
491}