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::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        // Collect directives
82        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        // "use strict";
91        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        // ====================
139        //  Emit
140        // ====================
141
142        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                // _export_star(mod, exports);
216                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                // _introp(mod);
226                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                // mod = _introp(mod);
243                // var mod1 = _introp(mod);
244                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    /// - Without `export =`
281    /// ```javascript
282    /// (function (global, factory) {
283    ///   if (typeof module === "object" && typeof module.exports === "object") {
284    ///     factory(exports, require("mod"));
285    ///   } else if (typeof define === "function" && define.amd) {
286    ///     define(["exports", "mod"], factory);
287    ///   } else if (global = typeof globalThis !== "undefined" ? globalThis : global || self) {
288    ///     factory((global.lib = {}), global.mod);
289    ///   }
290    /// })(this, function (exports, mod) {
291    ///   ...
292    /// });
293    /// ```
294    /// - With `export =`
295    /// ```javascript
296    /// (function (global, factory) {
297    ///   if (typeof module === "object" && typeof module.exports === "object") {
298    ///     module.exports = factory(require("mod"));
299    ///   } else if (typeof define === "function" && define.amd) {
300    ///     define(["mod"], factory);
301    ///   } else if (global = typeof globalThis !== "undefined" ? globalThis : global || self) {
302    ///     global.lib = factory(global.mod);
303    ///   }
304    /// })(this, function (mod) {
305    ///   ...
306    /// });
307    /// ```
308    /// Return: adapter expr and factory params
309    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        // define unresolved ref
331        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        // adapter arguments
354        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}