next_custom_transforms/transforms/
named_import_transform.rs

1use std::collections::HashSet;
2
3use serde::Deserialize;
4use swc_core::{
5    common::DUMMY_SP,
6    ecma::{
7        ast::*,
8        visit::{fold_pass, Fold},
9    },
10};
11
12#[derive(Clone, Debug, Deserialize)]
13pub struct Config {
14    pub packages: Vec<String>,
15}
16
17pub fn named_import_transform(config: Config) -> impl Pass {
18    fold_pass(NamedImportTransform {
19        packages: config.packages,
20    })
21}
22
23#[derive(Debug, Default)]
24struct NamedImportTransform {
25    packages: Vec<String>,
26}
27
28/// TODO: Implement this as a [Pass] instead of a full visitor ([Fold])
29impl Fold for NamedImportTransform {
30    fn fold_import_decl(&mut self, decl: ImportDecl) -> ImportDecl {
31        // Match named imports and check if it's included in the packages
32        let src_value = decl.src.value.clone();
33
34        if self.packages.iter().any(|p| src_value == *p) {
35            let mut specifier_names = HashSet::new();
36
37            // Skip the transform if the default or namespace import is present
38            let mut skip_transform = false;
39
40            for specifier in &decl.specifiers {
41                match specifier {
42                    ImportSpecifier::Named(specifier) => {
43                        // Add the import name as string to the set
44                        if let Some(imported) = &specifier.imported {
45                            match imported {
46                                ModuleExportName::Ident(ident) => {
47                                    specifier_names.insert(ident.sym.to_string());
48                                }
49                                ModuleExportName::Str(str_) => {
50                                    specifier_names.insert(str_.value.to_string());
51                                }
52                            }
53                        } else {
54                            specifier_names.insert(specifier.local.sym.to_string());
55                        }
56                    }
57                    ImportSpecifier::Default(_) => {
58                        skip_transform = true;
59                        break;
60                    }
61                    ImportSpecifier::Namespace(_) => {
62                        skip_transform = true;
63                        break;
64                    }
65                }
66            }
67
68            if !skip_transform {
69                let mut names = specifier_names.into_iter().collect::<Vec<_>>();
70                // Sort the names to make sure the order is consistent
71                names.sort();
72
73                let new_src = format!(
74                    "__barrel_optimize__?names={}!=!{}",
75                    names.join(","),
76                    src_value
77                );
78
79                // Create a new import declaration, keep everything the same except the source
80                let mut new_decl = decl.clone();
81                new_decl.src = Box::new(Str {
82                    span: DUMMY_SP,
83                    value: new_src.into(),
84                    raw: None,
85                });
86
87                return new_decl;
88            }
89        }
90
91        decl
92    }
93}