swc_bundler/bundler/import/
mod.rs

1use anyhow::{Context, Error};
2use rustc_hash::{FxHashMap, FxHashSet};
3use swc_atoms::Atom;
4use swc_common::{sync::Lrc, FileName, Mark, Spanned, SyntaxContext, DUMMY_SP};
5use swc_ecma_ast::*;
6use swc_ecma_utils::find_pat_ids;
7use swc_ecma_visit::{noop_visit_mut_type, VisitMut, VisitMutWith};
8
9use super::Bundler;
10use crate::{load::Load, resolve::Resolve, util::ExportMetadata};
11
12#[cfg(test)]
13mod tests;
14
15impl<L, R> Bundler<'_, L, R>
16where
17    L: Load,
18    R: Resolve,
19{
20    /// This method de-globs imports if possible and colorizes imported values.
21    pub(super) fn extract_import_info(
22        &self,
23        path: &FileName,
24        module: &mut Module,
25        module_local_mark: Mark,
26    ) -> RawImports {
27        self.run(|| {
28            let mut v = ImportHandler {
29                module_ctxt: SyntaxContext::empty().apply_mark(module_local_mark),
30                path,
31                bundler: self,
32                top_level: false,
33                info: Default::default(),
34                usages: Default::default(),
35                imported_idents: Default::default(),
36                deglob_phase: false,
37                idents_to_deglob: Default::default(),
38                in_obj_of_member: false,
39            };
40            module.body.visit_mut_with(&mut v);
41            v.deglob_phase = true;
42            module.body.visit_mut_with(&mut v);
43
44            v.info
45        })
46    }
47
48    pub(super) fn resolve(
49        &self,
50        base: &FileName,
51        module_specifier: &str,
52    ) -> Result<Lrc<FileName>, Error> {
53        self.run(|| {
54            let path = self
55                .resolver
56                .resolve(base, module_specifier)
57                .map(|v| v.filename)
58                .with_context(|| format!("failed to resolve {module_specifier} from {base}"))?;
59
60            let path = Lrc::new(path);
61
62            Ok(path)
63        })
64    }
65}
66
67#[derive(Debug, Default)]
68pub(super) struct RawImports {
69    /// Unconditional imports. This includes require on top level.
70    pub imports: Vec<ImportDecl>,
71
72    /// Non-top-level imports.
73    ///
74    /// # Example
75    ///
76    /// ```js
77    /// try{
78    ///     const { watch } = require('watcher');
79    /// } catch (e) {
80    /// }
81    /// ```
82    pub lazy_imports: Vec<ImportDecl>,
83    pub dynamic_imports: Vec<Str>,
84
85    /// Contains namespace imports accessed with computed key.
86    ///
87    ///
88    /// e.g.
89    ///
90    ///```js
91    /// import * as foo from './foo';
92    /// function bar() {}
93    /// foo[bar()]
94    /// ```
95    pub forced_ns: FxHashSet<Atom>,
96}
97
98/// This type implements two operation (analysis, deglobbing) to reduce binary
99/// size.
100struct ImportHandler<'a, 'b, L, R>
101where
102    L: Load,
103    R: Resolve,
104{
105    /// The [SyntaxContext] for the top level module items.
106    //// The top level module items includes imported bindings.
107    module_ctxt: SyntaxContext,
108    path: &'a FileName,
109    bundler: &'a Bundler<'b, L, R>,
110    top_level: bool,
111    info: RawImports,
112
113    /// HashMap from the local identifier of a namespace import to used
114    /// properties.
115    usages: FxHashMap<Id, Vec<Id>>,
116
117    /// While deglobbing, we also marks imported identifiers.
118    imported_idents: FxHashMap<Id, SyntaxContext>,
119
120    deglob_phase: bool,
121    idents_to_deglob: FxHashSet<Id>,
122
123    /// `true` while folding objects of a member expression.
124    ///
125    /// This is used to distinguish usage of `a` in `console.log(a)` and
126    /// `a.join()`.
127    in_obj_of_member: bool,
128}
129
130impl RawImports {
131    fn insert(&mut self, import: &ImportDecl) {
132        for prev in self.imports.iter_mut() {
133            if prev.src.value == import.src.value {
134                prev.specifiers.extend(import.specifiers.clone());
135                return;
136            }
137        }
138
139        self.imports.push(import.clone());
140    }
141}
142
143impl<L, R> ImportHandler<'_, '_, L, R>
144where
145    L: Load,
146    R: Resolve,
147{
148    /// Returns (local, export)
149    fn ctxt_for(&self, src: &Atom) -> Option<(SyntaxContext, SyntaxContext)> {
150        // Don't apply mark if it's a core module.
151        if self.bundler.is_external(src) {
152            return None;
153        }
154        let path = self.bundler.resolve(self.path, src).ok()?;
155        let (_, local_mark, export_mark) = self.bundler.scope.module_id_gen.gen(&path);
156
157        Some((
158            SyntaxContext::empty().apply_mark(local_mark),
159            SyntaxContext::empty().apply_mark(export_mark),
160        ))
161    }
162
163    fn mark_as_wrapping_required(&self, src: &Atom) {
164        // Don't apply mark if it's a core module.
165        if self.bundler.is_external(src) {
166            return;
167        }
168        let path = self.bundler.resolve(self.path, src);
169        let path = match path {
170            Ok(v) => v,
171            Err(_) => return,
172        };
173        let (id, _, _) = self.bundler.scope.module_id_gen.gen(&path);
174
175        self.bundler.scope.mark_as_wrapping_required(id);
176    }
177
178    fn mark_as_cjs(&self, src: &Atom) {
179        let path = self.bundler.resolve(self.path, src);
180        let path = match path {
181            Ok(v) => v,
182            Err(_) => return,
183        };
184        let (id, _, _) = self.bundler.scope.module_id_gen.gen(&path);
185
186        self.bundler.scope.mark_as_cjs(id);
187    }
188
189    fn add_forced_ns_for(&mut self, id: Id) {
190        self.idents_to_deglob.remove(&id);
191        self.imported_idents.remove(&id);
192        self.usages.remove(&id);
193
194        if let Some(src) = self
195            .info
196            .imports
197            .iter()
198            .find(|import| {
199                import.specifiers.iter().any(|specifier| match specifier {
200                    ImportSpecifier::Namespace(ns) => ns.local.sym == id.0 && ns.local.ctxt == id.1,
201                    _ => false,
202                })
203            })
204            .map(|import| import.src.value.clone())
205        {
206            self.info.forced_ns.insert(src.to_atom_lossy().into_owned());
207        }
208    }
209
210    fn find_require(&mut self, e: &mut Expr) {
211        match e {
212            Expr::Call(e) if e.args.len() == 1 => {
213                let src = match e.args.first().unwrap() {
214                    ExprOrSpread { spread: None, expr } => match &**expr {
215                        Expr::Lit(Lit::Str(s)) => s,
216                        _ => return,
217                    },
218                    _ => return,
219                };
220
221                match &mut e.callee {
222                    Callee::Expr(callee)
223                        if self.bundler.config.require && callee.is_ident_ref_to("require") =>
224                    {
225                        let src_atom = src.value.to_atom_lossy();
226                        if self.bundler.is_external(src_atom.as_ref()) {
227                            return;
228                        }
229                        if let Expr::Ident(i) = &mut **callee {
230                            self.mark_as_cjs(src_atom.as_ref());
231                            if let Some((_, export_ctxt)) = self.ctxt_for(src_atom.as_ref()) {
232                                i.ctxt = export_ctxt;
233                            }
234                        }
235
236                        let span = callee.span();
237
238                        let decl = ImportDecl {
239                            span,
240                            specifiers: Vec::new(),
241                            src: Box::new(src.clone()),
242                            type_only: false,
243                            with: None,
244                            phase: Default::default(),
245                        };
246
247                        if self.top_level {
248                            self.info.insert(&decl);
249                            return;
250                        }
251
252                        self.info.lazy_imports.push(decl);
253                    }
254
255                    // TODO: Uncomment this after implementing an option to make swc_bundler
256                    // includes dynamic imports
257                    //
258                    //
259                    // ExprOrSuper::Expr(ref e) => match &**e {
260                    //     Expr::Ident(Ident {
261                    //         sym: "import",
262                    //         ..
263                    //     }) => {
264                    //         self.info.dynamic_imports.push(src.clone());
265                    //     }
266                    //     _ => {}
267                    // },
268                    _ => {}
269                }
270            }
271            _ => {}
272        }
273    }
274
275    fn analyze_usage(&mut self, e: &mut Expr) {
276        if let Expr::Member(e) = e {
277            if let Expr::Ident(obj) = &*e.obj {
278                if !self.imported_idents.contains_key(&obj.to_id()) {
279                    // If it's not imported, just abort the usage analysis.
280                    return;
281                }
282
283                if e.prop.is_computed() {
284                    // If a module is accessed with unknown key, we should import
285                    // everything from it.
286                    self.add_forced_ns_for(obj.to_id());
287                    return;
288                }
289
290                // Store usages of obj
291                let import = self.info.imports.iter().find(|import| {
292                    for s in &import.specifiers {
293                        if let ImportSpecifier::Namespace(n) = s {
294                            return obj.sym == n.local.sym
295                                && (obj.ctxt == self.module_ctxt || obj.ctxt == n.local.ctxt);
296                        }
297                    }
298
299                    false
300                });
301                let import = match import {
302                    Some(v) => v,
303                    None => return,
304                };
305
306                let src_atom = import.src.value.to_atom_lossy();
307                let mark = self.ctxt_for(src_atom.as_ref());
308                let exported_ctxt = match mark {
309                    None => return,
310                    Some(ctxts) => ctxts.1,
311                };
312                let prop = match &e.prop {
313                    MemberProp::Ident(i) => {
314                        let mut i = Ident::from(i.clone());
315                        i.ctxt = exported_ctxt;
316                        i
317                    }
318                    _ => unreachable!(
319                        "Non-computed member expression with property other than ident is invalid"
320                    ),
321                };
322
323                self.usages
324                    .entry(obj.to_id())
325                    .or_default()
326                    .push(prop.to_id());
327            }
328        }
329    }
330
331    fn try_deglob(&mut self, e: &mut Expr) {
332        let me = match e {
333            Expr::Member(e) => e,
334            _ => return,
335        };
336        if me.prop.is_computed() {
337            return;
338        }
339
340        let obj = match &*me.obj {
341            Expr::Ident(obj) => obj,
342            _ => return,
343        };
344
345        let usages = self.usages.get(&obj.to_id());
346
347        match usages {
348            Some(..) => {}
349            _ => return,
350        };
351
352        let mut prop = match &me.prop {
353            MemberProp::Ident(v) => Ident::from(v.clone()),
354            _ => return,
355        };
356        prop.ctxt = self.imported_idents.get(&obj.to_id()).copied().unwrap();
357
358        *e = prop.into();
359    }
360}
361
362impl<L, R> VisitMut for ImportHandler<'_, '_, L, R>
363where
364    L: Load,
365    R: Resolve,
366{
367    noop_visit_mut_type!(fail);
368
369    fn visit_mut_export_named_specifier(&mut self, s: &mut ExportNamedSpecifier) {
370        let orig = match &s.orig {
371            ModuleExportName::Ident(ident) => ident,
372            ModuleExportName::Str(..) => unimplemented!("module string names unimplemented"),
373            #[cfg(swc_ast_unknown)]
374            _ => panic!("unable to access unknown nodes"),
375        };
376
377        self.add_forced_ns_for(orig.to_id());
378
379        match &mut s.exported {
380            Some(ModuleExportName::Ident(exported)) => {
381                // PR 3139 (https://github.com/swc-project/swc/pull/3139) removes the syntax context from any named exports from other sources.
382                exported.ctxt = self.module_ctxt;
383            }
384            Some(ModuleExportName::Str(..)) => unimplemented!("module string names unimplemented"),
385            #[cfg(swc_ast_unknown)]
386            Some(_) => panic!("unable to access unknown nodes"),
387            None => {
388                let exported = Ident::new(orig.sym.clone(), orig.span, self.module_ctxt);
389                s.exported = Some(ModuleExportName::Ident(exported));
390            }
391        }
392    }
393
394    fn visit_mut_expr(&mut self, e: &mut Expr) {
395        e.visit_mut_children_with(self);
396
397        if !self.deglob_phase {
398            // Firstly, we check for usages of imported namespaces.
399            // Code like below are handled by this check.
400            //
401            // import * as log from './log';
402            // console.log(log)
403            // console.log(log.getLogger())
404            if !self.in_obj_of_member {
405                if let Expr::Ident(i) = &e {
406                    if !self.in_obj_of_member {
407                        self.add_forced_ns_for(i.to_id());
408                        return;
409                    }
410                }
411            }
412
413            self.analyze_usage(e);
414            self.find_require(e);
415        } else {
416            self.try_deglob(e);
417        }
418    }
419
420    fn visit_mut_import_decl(&mut self, import: &mut ImportDecl) {
421        let src_atom = import.src.value.to_atom_lossy().into_owned();
422        // Ignore if it's a core module.
423        if self.bundler.is_external(&src_atom) {
424            return;
425        }
426
427        if !self.deglob_phase {
428            if let Some((_, export_ctxt)) = self.ctxt_for(&src_atom) {
429                // Firstly we attach proper syntax contexts.
430                ExportMetadata {
431                    export_ctxt: Some(export_ctxt),
432                    ..Default::default()
433                }
434                .encode(&mut import.with);
435
436                // Then we store list of imported identifiers.
437                for specifier in &mut import.specifiers {
438                    match specifier {
439                        ImportSpecifier::Named(n) => {
440                            self.imported_idents.insert(n.local.to_id(), export_ctxt);
441                            match &mut n.imported {
442                                Some(ModuleExportName::Ident(imported)) => {
443                                    imported.ctxt = export_ctxt;
444                                }
445                                Some(ModuleExportName::Str(..)) => {
446                                    unimplemented!("module string names unimplemented")
447                                }
448                                #[cfg(swc_ast_unknown)]
449                                Some(_) => panic!("unable to access unknown nodes"),
450                                None => {
451                                    let mut imported: Ident = n.local.clone();
452                                    imported.ctxt = export_ctxt;
453                                    n.imported = Some(ModuleExportName::Ident(imported));
454                                }
455                            }
456                        }
457                        ImportSpecifier::Default(n) => {
458                            self.imported_idents.insert(n.local.to_id(), n.local.ctxt);
459                        }
460                        ImportSpecifier::Namespace(n) => {
461                            self.imported_idents.insert(n.local.to_id(), export_ctxt);
462                        }
463                        #[cfg(swc_ast_unknown)]
464                        _ => panic!("unable to access unknown nodes"),
465                    }
466                }
467            }
468
469            self.info.insert(import);
470            return;
471        }
472
473        // Now we are in deglobbing phase.
474
475        // We cannot deglob this.
476        if self.info.forced_ns.contains(&import.src.value) {
477            return;
478        }
479
480        // deglob namespace imports
481        if import.specifiers.len() == 1 {
482            if let ImportSpecifier::Namespace(ns) = &import.specifiers[0] {
483                //
484                let specifiers = self
485                    .usages
486                    .get(&ns.local.to_id())
487                    .cloned()
488                    .map(|ids| {
489                        //
490                        let specifiers: Vec<_> = ids
491                            .into_iter()
492                            .map(|id| {
493                                self.idents_to_deglob.insert(id.clone());
494                                ImportSpecifier::Named(ImportNamedSpecifier {
495                                    span: DUMMY_SP,
496                                    local: Ident::new(id.0, DUMMY_SP, id.1),
497                                    imported: None,
498                                    is_type_only: false,
499                                })
500                            })
501                            .collect();
502
503                        for import_info in &mut self.info.imports {
504                            if import_info.src != import.src {
505                                continue;
506                            }
507
508                            import_info.specifiers.extend(specifiers.clone());
509                        }
510
511                        specifiers
512                    })
513                    .unwrap_or_else(Vec::new);
514
515                if !specifiers.is_empty() {
516                    import.specifiers = specifiers;
517                    return;
518                }
519
520                // We failed to found property usage.
521                self.info
522                    .forced_ns
523                    .insert(import.src.value.clone().to_atom_lossy().into_owned());
524            }
525        }
526    }
527
528    fn visit_mut_member_expr(&mut self, e: &mut MemberExpr) {
529        let old = self.in_obj_of_member;
530        self.in_obj_of_member = true;
531        e.obj.visit_mut_with(self);
532
533        if let MemberProp::Computed(c) = &mut e.prop {
534            self.in_obj_of_member = false;
535            c.visit_mut_with(self);
536        }
537
538        self.in_obj_of_member = old;
539    }
540
541    fn visit_mut_module_items(&mut self, items: &mut Vec<ModuleItem>) {
542        self.top_level = true;
543        items.visit_mut_children_with(self);
544
545        items.retain_mut(|item| match item {
546            ModuleItem::Stmt(Stmt::Empty(..)) => false,
547            ModuleItem::Stmt(Stmt::Decl(Decl::Var(var))) => {
548                var.decls.retain(|d| !matches!(d.name, Pat::Invalid(..)));
549
550                !var.decls.is_empty()
551            }
552
553            _ => true,
554        });
555
556        if self.deglob_phase {
557            let mut wrapping_required = Vec::new();
558            for import in self.info.imports.iter_mut() {
559                let src_atom = import.src.value.to_atom_lossy().into_owned();
560                let use_ns = self.info.forced_ns.contains(&import.src.value)
561                    || self.bundler.config.external_modules.contains(&src_atom);
562
563                if use_ns {
564                    wrapping_required.push(import.src.value.clone());
565                } else {
566                    // De-glob namespace imports
567                    import
568                        .specifiers
569                        .retain(|s| !matches!(s, ImportSpecifier::Namespace(_)));
570                }
571            }
572
573            for id in wrapping_required {
574                let id_atom = id.to_atom_lossy();
575                self.mark_as_wrapping_required(id_atom.as_ref());
576            }
577        }
578    }
579
580    fn visit_mut_stmts(&mut self, items: &mut Vec<Stmt>) {
581        self.top_level = false;
582        items.visit_mut_children_with(self)
583    }
584
585    fn visit_mut_super_prop_expr(&mut self, e: &mut SuperPropExpr) {
586        let old = self.in_obj_of_member;
587
588        if let SuperProp::Computed(c) = &mut e.prop {
589            self.in_obj_of_member = false;
590            c.visit_mut_with(self);
591        }
592
593        self.in_obj_of_member = old;
594    }
595
596    /// ```js
597    /// const { readFile } = required('fs');
598    /// ```
599    ///
600    /// is treated as
601    ///
602    /// ```js
603    /// import { readFile } from 'fs';
604    /// ```
605    fn visit_mut_var_declarator(&mut self, node: &mut VarDeclarator) {
606        node.visit_mut_children_with(self);
607
608        if let Some(init) = &mut node.init {
609            match &mut **init {
610                Expr::Call(CallExpr {
611                    span,
612                    callee: Callee::Expr(ref mut callee),
613                    ref args,
614                    ..
615                }) if self.bundler.config.require
616                    && callee.is_ident_ref_to("require")
617                    && args.len() == 1 =>
618                {
619                    let span = *span;
620                    let src = match args.first().unwrap() {
621                        ExprOrSpread { spread: None, expr } => match &**expr {
622                            Expr::Lit(Lit::Str(s)) => s.clone(),
623                            _ => return,
624                        },
625                        _ => return,
626                    };
627                    // Ignore core modules.
628                    let src_atom = src.value.to_atom_lossy();
629                    if self
630                        .bundler
631                        .config
632                        .external_modules
633                        .contains(src_atom.as_ref())
634                    {
635                        return;
636                    }
637
638                    self.mark_as_cjs(src_atom.as_ref());
639
640                    if let Expr::Ident(i) = &mut **callee {
641                        if let Some((_, export_ctxt)) = self.ctxt_for(src_atom.as_ref()) {
642                            i.ctxt = export_ctxt;
643                        }
644                    }
645
646                    let ids: Vec<Ident> = find_pat_ids(&node.name);
647
648                    let decl = ImportDecl {
649                        span,
650                        specifiers: ids
651                            .into_iter()
652                            .map(|ident| {
653                                ImportSpecifier::Named(ImportNamedSpecifier {
654                                    span,
655                                    local: ident,
656                                    imported: None,
657                                    is_type_only: false,
658                                })
659                            })
660                            .collect(),
661                        src: Box::new(src),
662                        type_only: false,
663                        with: None,
664                        phase: Default::default(),
665                    };
666
667                    // if self.top_level {
668                    //     self.info.imports.push(decl);
669                    //     node.init = None;
670                    //     node.name = Pat::Invalid(Invalid { span: DUMMY_SP });
671                    //     return node;
672                    // }
673
674                    self.info.lazy_imports.push(decl);
675                }
676
677                _ => {}
678            }
679        }
680    }
681}