swc_ecma_transforms_module/
umd.rs

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        // Collect directives
77        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        // "use strict";
86        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        // ====================
137        //  Emit
138        // ====================
139
140        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                // _export_star(mod, exports);
214                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                // _introp(mod);
224                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                // mod = _introp(mod);
241                // var mod1 = _introp(mod);
242                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    /// - Without `export =`
279    /// ```javascript
280    /// (function (global, factory) {
281    ///   if (typeof module === "object" && typeof module.exports === "object") {
282    ///     factory(exports, require("mod"));
283    ///   } else if (typeof define === "function" && define.amd) {
284    ///     define(["exports", "mod"], factory);
285    ///   } else if (global = typeof globalThis !== "undefined" ? globalThis : global || self) {
286    ///     factory((global.lib = {}), global.mod);
287    ///   }
288    /// })(this, function (exports, mod) {
289    ///   ...
290    /// });
291    /// ```
292    /// - With `export =`
293    /// ```javascript
294    /// (function (global, factory) {
295    ///   if (typeof module === "object" && typeof module.exports === "object") {
296    ///     module.exports = factory(require("mod"));
297    ///   } else if (typeof define === "function" && define.amd) {
298    ///     define(["mod"], factory);
299    ///   } else if (global = typeof globalThis !== "undefined" ? globalThis : global || self) {
300    ///     global.lib = factory(global.mod);
301    ///   }
302    /// })(this, function (mod) {
303    ///   ...
304    /// });
305    /// ```
306    /// Return: adapter expr and factory params
307    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        // define unresolved ref
329        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        // adapter arguments
352        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}