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