1use swc_core::{
6 atoms::Atom,
7 common::DUMMY_SP,
8 ecma::{
9 ast::{
10 ArrayLit,
11 BindingIdent,
12 Decl,
13 ExportDecl,
14 ExportSpecifier,
15 Expr,
16 ExprOrSpread,
17 Ident,
18 Lit,
19 ModuleDecl,
20 ModuleItem,
21 ObjectPatProp,
22 Pat,
23 Program,
24 Str,
25 VarDecl,
26 VarDeclKind,
27 VarDeclarator,
28 },
29 visit::{VisitMut, VisitMutWith},
30 },
31 plugin::{plugin_transform, proxies::TransformPluginProgramMetadata},
32};
33
34const NAMED_EXPORTS_ORDER: &str = "__namedExportsOrder";
36
37#[derive(Debug, Clone)]
41struct ExportOrderVisitor;
42
43impl VisitMut for ExportOrderVisitor {
44 fn visit_mut_module_items(&mut self, items: &mut Vec<ModuleItem>) {
45 items.visit_mut_children_with(self);
46
47 let mut names: Vec<Atom> = Vec::new();
48
49 for item in items.iter() {
50 let ModuleItem::ModuleDecl(decl) = item else {
51 continue;
52 };
53
54 match decl {
55 ModuleDecl::ExportNamed(named) => {
57 for specifier in &named.specifiers {
58 let ExportSpecifier::Named(specifier) = specifier else {
59 continue;
60 };
61 if specifier.is_type_only {
62 continue;
63 }
64 let export_name = specifier.exported.as_ref().unwrap_or(&specifier.orig);
65 names.push(export_name.atom().clone());
66 }
67 }
68 ModuleDecl::ExportDecl(export) => match &export.decl {
70 Decl::Var(var) => {
72 for decl in &var.decls {
73 names.append(&mut extract_bindings(&decl.name));
74 }
75 }
76 Decl::Fn(function) => {
78 names.push(function.ident.sym.clone());
79 }
80 Decl::Class(class) => {
82 names.push(class.ident.sym.clone());
83 }
84 _ => (),
85 },
86 _ => (),
87 }
88 }
89
90 let export_exprs: Vec<_> = names
92 .drain(..)
93 .map(|atom| {
94 Some(ExprOrSpread {
95 spread: None,
96 expr: Box::new(Expr::Lit(Lit::Str(Str::from(atom)))),
97 })
98 })
99 .collect();
100
101 let declaration = ModuleItem::ModuleDecl(ModuleDecl::ExportDecl(ExportDecl {
103 span: DUMMY_SP,
104 decl: Decl::Var(Box::new(VarDecl {
105 kind: VarDeclKind::Const,
106 decls: vec![VarDeclarator {
107 name: Pat::Ident(BindingIdent {
108 id: Ident {
109 sym: Atom::new(NAMED_EXPORTS_ORDER),
110 optional: false,
111 ..Default::default()
112 },
113 type_ann: None,
114 }),
115 init: Some(Box::new(Expr::Array(ArrayLit {
116 elems: export_exprs,
117 ..Default::default()
118 }))),
119 span: DUMMY_SP,
120 definite: false,
121 }],
122 ..Default::default()
123 })),
124 }));
125
126 items.push(declaration);
128 }
129}
130
131pub fn extract_bindings(pat: &Pat) -> Vec<Atom> {
134 let mut names: Vec<Atom> = Vec::new();
135 let mut stack: Vec<&Pat> = vec![pat];
136 while let Some(item) = stack.pop() {
137 match item {
138 Pat::Ident(ident) => names.push(ident.sym.clone()),
140 Pat::Array(array) => {
142 for elem in array.elems.iter().rev().flatten() {
144 stack.push(elem);
145 }
146 }
147 Pat::Object(object) => {
149 for prop in object.props.iter().rev() {
150 match prop {
151 ObjectPatProp::Assign(assign) => names.push(assign.key.sym.clone()),
152 ObjectPatProp::KeyValue(kv) => stack.push(&*kv.value),
153 ObjectPatProp::Rest(rest) => stack.push(&*rest.arg),
154 }
155 }
156 }
157 Pat::Assign(assign) => stack.push(&*assign.left),
158 Pat::Rest(rest) => stack.push(&*rest.arg),
159 _ => (),
160 }
161 }
162 names
163}
164
165#[plugin_transform]
167pub fn process_transform(
168 mut program: Program,
169 _metadata: TransformPluginProgramMetadata,
170) -> Program {
171 program.visit_mut_with(&mut ExportOrderVisitor);
172 program
173}
174
175#[cfg(test)]
176mod tests {
177 use swc_core::ecma::{transforms::testing::test_inline, visit::visit_mut_pass};
178
179 use super::ExportOrderVisitor;
180
181 test_inline!(
182 Default::default(),
184 |_| visit_mut_pass(ExportOrderVisitor),
186 test,
188 r#"
190 const z = 'zoo';
191 const y = 5;
192 const x = () => 5;
193
194 export { z }
195 export { y, x }
196
197 export const [w, v] = [1, 2]
198 export const {u: U = 1, T: { t }} = {u: 1, T: { t: 1 }}
199 export const s = 's'
200 export function r() {}
201 export class q {}
202 "#,
203 r#"
205 const z = 'zoo';
206 const y = 5;
207 const x = () => 5;
208
209 export { z }
210 export { y, x }
211
212 export const [w, v] = [1, 2]
213 export const {u: U = 1, T: { t }} = {u: 1, T: { t: 1 }}
214 export const s = 's'
215 export function r() {}
216 export class q {}
217 export const __namedExportsOrder = ["z", "y", "x", "w", "v", "U", "t", "s", "r", "q"];
218 "#
219 );
220}