next_custom_transforms/transforms/
optimize_server_react.rs1use serde::Deserialize;
6use swc_core::{
7 common::DUMMY_SP,
8 ecma::{
9 ast::*,
10 visit::{fold_pass, Fold, FoldWith},
11 },
12};
13
14#[derive(Clone, Debug, Deserialize)]
15pub struct Config {
16 pub optimize_use_state: bool,
17}
18
19pub fn optimize_server_react(config: Config) -> impl Pass {
20 fold_pass(OptimizeServerReact {
21 optimize_use_state: config.optimize_use_state,
22 ..Default::default()
23 })
24}
25
26#[derive(Debug, Default)]
27struct OptimizeServerReact {
28 optimize_use_state: bool,
29 react_ident: Option<Id>,
30 use_state_ident: Option<Id>,
31 use_effect_ident: Option<Id>,
32 use_layout_effect_ident: Option<Id>,
33}
34
35fn effect_has_side_effect_deps(call: &CallExpr) -> bool {
36 if call.args.len() != 2 {
37 return false;
38 }
39
40 if let box Expr::Call(_) = &call.args[1].expr {
43 return true;
44 }
45
46 if let box Expr::Array(arr) = &call.args[1].expr {
49 for elem in arr.elems.iter().flatten() {
50 if let ExprOrSpread {
51 expr: box Expr::Call(_),
52 ..
53 } = elem
54 {
55 return true;
56 }
57 }
58 }
59
60 false
61}
62
63fn wrap_expr_with_env_prod_condition(call: CallExpr) -> Expr {
64 Expr::Bin(BinExpr {
70 span: DUMMY_SP,
71 left: Box::new(Expr::Member(MemberExpr {
72 obj: (Box::new(Expr::Member(MemberExpr {
73 obj: (Box::new(Expr::Ident(Ident {
74 sym: "process".into(),
75 span: DUMMY_SP,
76 ..Default::default()
77 }))),
78 prop: MemberProp::Ident(IdentName {
79 sym: "env".into(),
80 span: DUMMY_SP,
81 }),
82 span: DUMMY_SP,
83 }))),
84 prop: (MemberProp::Ident(IdentName {
85 sym: "__NEXT_PRIVATE_MINIMIZE_MACRO_FALSE".into(),
86 span: DUMMY_SP,
87 })),
88 span: DUMMY_SP,
89 })),
90 op: op!("&&"),
91 right: Box::new(Expr::Call(call)),
92 })
93}
94
95impl Fold for OptimizeServerReact {
96 fn fold_module_items(&mut self, items: Vec<ModuleItem>) -> Vec<ModuleItem> {
97 let mut new_items = vec![];
98
99 for item in items {
100 new_items.push(item.clone().fold_with(self));
101
102 if let ModuleItem::ModuleDecl(ModuleDecl::Import(import_decl)) = &item {
103 if import_decl.src.value != "react" {
104 continue;
105 }
106 for specifier in &import_decl.specifiers {
107 if let ImportSpecifier::Named(named_import) = specifier {
108 let name = match &named_import.imported {
109 Some(n) => match &n {
110 ModuleExportName::Ident(n) => n.sym.to_string(),
111 ModuleExportName::Str(n) => n.value.to_string(),
112 },
113 None => named_import.local.sym.to_string(),
114 };
115
116 if name == "useState" {
117 self.use_state_ident = Some(named_import.local.to_id());
118 } else if name == "useEffect" {
119 self.use_effect_ident = Some(named_import.local.to_id());
120 } else if name == "useLayoutEffect" {
121 self.use_layout_effect_ident = Some(named_import.local.to_id());
122 }
123 } else if let ImportSpecifier::Default(default_import) = specifier {
124 self.react_ident = Some(default_import.local.to_id());
125 }
126 }
127 }
128 }
129
130 new_items
131 }
132
133 fn fold_expr(&mut self, expr: Expr) -> Expr {
134 if let Expr::Call(call) = &expr {
135 if let Callee::Expr(box Expr::Ident(f)) = &call.callee {
136 if let Some(use_effect_ident) = &self.use_effect_ident {
138 if &f.to_id() == use_effect_ident && !effect_has_side_effect_deps(call) {
139 return wrap_expr_with_env_prod_condition(call.clone());
141 }
142 }
143 if let Some(use_layout_effect_ident) = &self.use_layout_effect_ident {
145 if &f.to_id() == use_layout_effect_ident && !effect_has_side_effect_deps(call) {
146 return wrap_expr_with_env_prod_condition(call.clone());
147 }
148 }
149 } else if let Some(react_ident) = &self.react_ident {
150 if let Callee::Expr(box Expr::Member(member)) = &call.callee {
151 if let box Expr::Ident(f) = &member.obj {
152 if &f.to_id() == react_ident {
153 if let MemberProp::Ident(i) = &member.prop {
154 if i.sym == "useEffect" || i.sym == "useLayoutEffect" {
157 return wrap_expr_with_env_prod_condition(call.clone());
158 }
159 }
160 }
161 }
162 }
163 }
164 }
165
166 expr.fold_children_with(self)
167 }
168
169 fn fold_var_declarator(&mut self, decl: VarDeclarator) -> VarDeclarator {
172 if !self.optimize_use_state {
173 return decl;
174 }
175
176 if let Pat::Array(array_pat) = &decl.name {
177 if array_pat.elems.len() == 2 {
178 if let Some(box Expr::Call(call)) = &decl.init {
179 if let Callee::Expr(box Expr::Ident(f)) = &call.callee {
180 if let Some(use_state_ident) = &self.use_state_ident {
181 if &f.to_id() == use_state_ident && call.args.len() == 1 {
182 match &call.args[0].expr {
188 box Expr::Lit(_) | box Expr::Object(_) | box Expr::Array(_) => {
189 return VarDeclarator {
191 definite: false,
192 name: decl.name.clone(),
193 init: Some(Box::new(Expr::Array(ArrayLit {
194 elems: vec![
195 Some(call.args[0].expr.clone().into()),
196 Some(
197 Expr::Arrow(ArrowExpr {
198 span: DUMMY_SP,
199 body: Box::new(BlockStmtOrExpr::Expr(
200 Box::new(Expr::Lit(Lit::Null(
201 Null { span: DUMMY_SP },
202 ))),
203 )),
204 is_async: false,
205 is_generator: false,
206 params: vec![],
207 ..Default::default()
208 })
209 .into(),
210 ),
211 ],
212 span: DUMMY_SP,
213 }))),
214 span: DUMMY_SP,
215 };
216 }
217 _ => {}
218 }
219 }
220 }
221 }
222 }
223 }
224 }
225
226 decl.fold_children_with(self)
227 }
228}