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