next_custom_transforms/transforms/
next_ssg.rs1use std::{cell::RefCell, mem::take, rc::Rc};
2
3use easy_error::{bail, Error};
4use rustc_hash::FxHashSet;
5use swc_core::{
6 atoms::Atom,
7 common::{
8 errors::HANDLER,
9 pass::{Repeat, Repeated},
10 DUMMY_SP,
11 },
12 ecma::{
13 ast::*,
14 visit::{fold_pass, noop_fold_type, Fold, FoldWith},
15 },
16};
17
18static SSG_EXPORTS: &[&str; 3] = &["getStaticProps", "getStaticPaths", "getServerSideProps"];
19
20pub fn next_ssg(eliminated_packages: Rc<RefCell<FxHashSet<Atom>>>) -> impl Pass {
22 fold_pass(Repeat::new(NextSsg {
23 state: State {
24 eliminated_packages,
25 ..Default::default()
26 },
27 in_lhs_of_var: false,
28 }))
29}
30
31#[derive(Debug, Default)]
33struct State {
34 refs_from_other: FxHashSet<Id>,
39
40 refs_from_data_fn: FxHashSet<Id>,
45
46 cur_declaring: FxHashSet<Id>,
47
48 is_prerenderer: bool,
49 is_server_props: bool,
50 done: bool,
51
52 should_run_again: bool,
53
54 pub eliminated_packages: Rc<RefCell<FxHashSet<Atom>>>,
57}
58
59impl State {
60 #[allow(clippy::wrong_self_convention)]
61 fn is_data_identifier(&mut self, i: &Ident) -> Result<bool, Error> {
62 if SSG_EXPORTS.contains(&&*i.sym) {
63 if &*i.sym == "getServerSideProps" {
64 if self.is_prerenderer {
65 HANDLER.with(|handler| {
66 handler
67 .struct_span_err(
68 i.span,
69 "You can not use getStaticProps or getStaticPaths with \
70 getServerSideProps. To use SSG, please remove getServerSideProps",
71 )
72 .emit()
73 });
74 bail!("both ssg and ssr functions present");
75 }
76
77 self.is_server_props = true;
78 } else {
79 if self.is_server_props {
80 HANDLER.with(|handler| {
81 handler
82 .struct_span_err(
83 i.span,
84 "You can not use getStaticProps or getStaticPaths with \
85 getServerSideProps. To use SSG, please remove getServerSideProps",
86 )
87 .emit()
88 });
89 bail!("both ssg and ssr functions present");
90 }
91
92 self.is_prerenderer = true;
93 }
94
95 Ok(true)
96 } else {
97 Ok(false)
98 }
99 }
100}
101
102struct Analyzer<'a> {
103 state: &'a mut State,
104 in_lhs_of_var: bool,
105 in_data_fn: bool,
106}
107
108impl Analyzer<'_> {
109 fn add_ref(&mut self, id: Id) {
110 tracing::trace!("add_ref({}{:?}, data = {})", id.0, id.1, self.in_data_fn);
111 if self.in_data_fn {
112 self.state.refs_from_data_fn.insert(id);
113 } else {
114 if self.state.cur_declaring.contains(&id) {
115 return;
116 }
117
118 self.state.refs_from_other.insert(id);
119 }
120 }
121}
122
123impl Fold for Analyzer<'_> {
124 noop_fold_type!();
126
127 fn fold_binding_ident(&mut self, i: BindingIdent) -> BindingIdent {
128 if !self.in_lhs_of_var || self.in_data_fn {
129 self.add_ref(i.id.to_id());
130 }
131
132 i
133 }
134
135 fn fold_export_named_specifier(&mut self, s: ExportNamedSpecifier) -> ExportNamedSpecifier {
136 if let ModuleExportName::Ident(id) = &s.orig {
137 if !SSG_EXPORTS.contains(&&*id.sym) {
138 self.add_ref(id.to_id());
139 }
140 }
141
142 s
143 }
144
145 fn fold_export_decl(&mut self, s: ExportDecl) -> ExportDecl {
146 if let Decl::Var(d) = &s.decl {
147 if d.decls.is_empty() {
148 return s;
149 }
150
151 if let Pat::Ident(id) = &d.decls[0].name {
152 if !SSG_EXPORTS.contains(&&*id.id.sym) {
153 self.add_ref(id.to_id());
154 }
155 }
156 }
157
158 s.fold_children_with(self)
159 }
160
161 fn fold_expr(&mut self, e: Expr) -> Expr {
162 let e = e.fold_children_with(self);
163
164 if let Expr::Ident(i) = &e {
165 self.add_ref(i.to_id());
166 }
167
168 e
169 }
170
171 fn fold_jsx_element(&mut self, jsx: JSXElement) -> JSXElement {
172 fn get_leftmost_id_member_expr(e: &JSXMemberExpr) -> Id {
173 match &e.obj {
174 JSXObject::Ident(i) => i.to_id(),
175 JSXObject::JSXMemberExpr(e) => get_leftmost_id_member_expr(e),
176 }
177 }
178
179 match &jsx.opening.name {
180 JSXElementName::Ident(i) => {
181 self.add_ref(i.to_id());
182 }
183 JSXElementName::JSXMemberExpr(e) => {
184 self.add_ref(get_leftmost_id_member_expr(e));
185 }
186 _ => {}
187 }
188
189 jsx.fold_children_with(self)
190 }
191
192 fn fold_fn_decl(&mut self, f: FnDecl) -> FnDecl {
193 let old_in_data = self.in_data_fn;
194
195 self.state.cur_declaring.insert(f.ident.to_id());
196
197 if let Ok(is_data_identifier) = self.state.is_data_identifier(&f.ident) {
198 self.in_data_fn |= is_data_identifier;
199 } else {
200 return f;
201 }
202 tracing::trace!(
203 "ssg: Handling `{}{:?}`; in_data_fn = {:?}",
204 f.ident.sym,
205 f.ident.ctxt,
206 self.in_data_fn
207 );
208
209 let f = f.fold_children_with(self);
210
211 self.state.cur_declaring.remove(&f.ident.to_id());
212
213 self.in_data_fn = old_in_data;
214
215 f
216 }
217
218 fn fold_fn_expr(&mut self, f: FnExpr) -> FnExpr {
219 let f = f.fold_children_with(self);
220
221 if let Some(id) = &f.ident {
222 self.add_ref(id.to_id());
223 }
224
225 f
226 }
227
228 fn fold_module_item(&mut self, s: ModuleItem) -> ModuleItem {
230 match s {
231 ModuleItem::ModuleDecl(ModuleDecl::ExportNamed(e)) if !e.specifiers.is_empty() => {
232 let e = e.fold_with(self);
233
234 if e.specifiers.is_empty() {
235 return ModuleItem::Stmt(Stmt::Empty(EmptyStmt { span: DUMMY_SP }));
236 }
237
238 return ModuleItem::ModuleDecl(ModuleDecl::ExportNamed(e));
239 }
240 _ => {}
241 };
242
243 let s = s.fold_children_with(self);
245
246 if let ModuleItem::ModuleDecl(ModuleDecl::ExportDecl(e)) = &s {
247 match &e.decl {
248 Decl::Fn(f) => {
249 if let Ok(is_data_identifier) = self.state.is_data_identifier(&f.ident) {
251 if is_data_identifier {
252 return ModuleItem::Stmt(Stmt::Empty(EmptyStmt { span: DUMMY_SP }));
253 }
254 } else {
255 return s;
256 }
257 }
258
259 Decl::Var(d) => {
260 if d.decls.is_empty() {
261 return ModuleItem::Stmt(Stmt::Empty(EmptyStmt { span: DUMMY_SP }));
262 }
263 }
264 _ => {}
265 }
266 }
267
268 s
269 }
270
271 fn fold_named_export(&mut self, mut n: NamedExport) -> NamedExport {
272 if n.src.is_some() {
273 n.specifiers = n.specifiers.fold_with(self);
274 }
275
276 n
277 }
278
279 fn fold_prop(&mut self, p: Prop) -> Prop {
280 let p = p.fold_children_with(self);
281
282 if let Prop::Shorthand(i) = &p {
283 self.add_ref(i.to_id());
284 }
285
286 p
287 }
288
289 fn fold_var_declarator(&mut self, mut v: VarDeclarator) -> VarDeclarator {
290 let old_in_data = self.in_data_fn;
291
292 if let Pat::Ident(name) = &v.name {
293 if let Ok(is_data_identifier) = self.state.is_data_identifier(&name.id) {
294 if is_data_identifier {
295 self.in_data_fn = true;
296 }
297 } else {
298 return v;
299 }
300 }
301
302 let old_in_lhs_of_var = self.in_lhs_of_var;
303
304 self.in_lhs_of_var = true;
305 v.name = v.name.fold_with(self);
306
307 self.in_lhs_of_var = false;
308 v.init = v.init.fold_with(self);
309
310 self.in_lhs_of_var = old_in_lhs_of_var;
311
312 self.in_data_fn = old_in_data;
313
314 v
315 }
316}
317
318struct NextSsg {
320 pub state: State,
321 in_lhs_of_var: bool,
322}
323
324impl NextSsg {
325 fn should_remove(&self, id: Id) -> bool {
326 self.state.refs_from_data_fn.contains(&id) && !self.state.refs_from_other.contains(&id)
327 }
328
329 fn mark_as_candidate<N>(&mut self, n: N) -> N
331 where
332 N: for<'aa> FoldWith<Analyzer<'aa>>,
333 {
334 tracing::debug!("mark_as_candidate");
335
336 let mut v = Analyzer {
339 state: &mut self.state,
340 in_lhs_of_var: false,
341 in_data_fn: true,
342 };
343
344 let n = n.fold_with(&mut v);
345 self.state.should_run_again = true;
346 n
347 }
348}
349
350impl Repeated for NextSsg {
351 fn changed(&self) -> bool {
352 self.state.should_run_again
353 }
354
355 fn reset(&mut self) {
356 self.state.refs_from_other.clear();
357 self.state.cur_declaring.clear();
358 self.state.should_run_again = false;
359 }
360}
361
362impl Fold for NextSsg {
367 noop_fold_type!();
369
370 fn fold_import_decl(&mut self, mut i: ImportDecl) -> ImportDecl {
371 if i.specifiers.is_empty() {
373 return i;
374 }
375
376 let import_src = &i.src.value;
377
378 i.specifiers.retain(|s| match s {
379 ImportSpecifier::Named(ImportNamedSpecifier { local, .. })
380 | ImportSpecifier::Default(ImportDefaultSpecifier { local, .. })
381 | ImportSpecifier::Namespace(ImportStarAsSpecifier { local, .. }) => {
382 if self.should_remove(local.to_id()) {
383 if self.state.is_server_props
384 && import_src.starts_with(|c: char| c.is_ascii_lowercase() || c == '@')
387 {
388 self.state
389 .eliminated_packages
390 .borrow_mut()
391 .insert(import_src.clone());
392 }
393 tracing::trace!(
394 "Dropping import `{}{:?}` because it should be removed",
395 local.sym,
396 local.ctxt
397 );
398
399 self.state.should_run_again = true;
400 false
401 } else {
402 true
403 }
404 }
405 });
406
407 i
408 }
409
410 fn fold_module(&mut self, mut m: Module) -> Module {
411 tracing::info!("ssg: Start");
412 {
413 let mut v = Analyzer {
415 state: &mut self.state,
416 in_lhs_of_var: false,
417 in_data_fn: false,
418 };
419 m = m.fold_with(&mut v);
420 }
421
422 m.fold_children_with(self)
428 }
429
430 fn fold_module_item(&mut self, i: ModuleItem) -> ModuleItem {
431 if let ModuleItem::ModuleDecl(ModuleDecl::Import(i)) = i {
432 let is_for_side_effect = i.specifiers.is_empty();
433 let i = i.fold_with(self);
434
435 if !is_for_side_effect && i.specifiers.is_empty() {
436 return ModuleItem::Stmt(Stmt::Empty(EmptyStmt { span: DUMMY_SP }));
437 }
438
439 return ModuleItem::ModuleDecl(ModuleDecl::Import(i));
440 }
441
442 let i = i.fold_children_with(self);
443
444 match &i {
445 ModuleItem::ModuleDecl(ModuleDecl::ExportNamed(e)) if e.specifiers.is_empty() => {
446 return ModuleItem::Stmt(Stmt::Empty(EmptyStmt { span: DUMMY_SP }))
447 }
448 _ => {}
449 }
450
451 i
452 }
453
454 fn fold_module_items(&mut self, mut items: Vec<ModuleItem>) -> Vec<ModuleItem> {
455 items = items.fold_children_with(self);
456
457 items.retain(|s| !matches!(s, ModuleItem::Stmt(Stmt::Empty(..))));
459
460 if !self.state.done
461 && !self.state.should_run_again
462 && (self.state.is_prerenderer || self.state.is_server_props)
463 {
464 self.state.done = true;
465
466 if items.iter().any(|s| s.is_module_decl()) {
467 let mut var = Some(VarDeclarator {
468 span: DUMMY_SP,
469 name: Pat::Ident(
470 IdentName::new(
471 if self.state.is_prerenderer {
472 "__N_SSG".into()
473 } else {
474 "__N_SSP".into()
475 },
476 DUMMY_SP,
477 )
478 .into(),
479 ),
480 init: Some(Box::new(Expr::Lit(Lit::Bool(Bool {
481 span: DUMMY_SP,
482 value: true,
483 })))),
484 definite: Default::default(),
485 });
486
487 let mut new = Vec::with_capacity(items.len() + 1);
488 for item in take(&mut items) {
489 if let ModuleItem::ModuleDecl(
490 ModuleDecl::ExportNamed(..)
491 | ModuleDecl::ExportDecl(..)
492 | ModuleDecl::ExportDefaultDecl(..)
493 | ModuleDecl::ExportDefaultExpr(..),
494 ) = &item
495 {
496 if let Some(var) = var.take() {
497 new.push(ModuleItem::ModuleDecl(ModuleDecl::ExportDecl(ExportDecl {
498 span: DUMMY_SP,
499 decl: Decl::Var(Box::new(VarDecl {
500 span: DUMMY_SP,
501 kind: VarDeclKind::Var,
502 decls: vec![var],
503 ..Default::default()
504 })),
505 })))
506 }
507 }
508
509 new.push(item);
510 }
511
512 return new;
513 }
514 }
515
516 items
517 }
518
519 fn fold_named_export(&mut self, mut n: NamedExport) -> NamedExport {
520 n.specifiers = n.specifiers.fold_with(self);
521
522 n.specifiers.retain(|s| {
523 let preserve = match s {
524 ExportSpecifier::Namespace(ExportNamespaceSpecifier {
525 name: ModuleExportName::Ident(exported),
526 ..
527 })
528 | ExportSpecifier::Default(ExportDefaultSpecifier { exported, .. })
529 | ExportSpecifier::Named(ExportNamedSpecifier {
530 exported: Some(ModuleExportName::Ident(exported)),
531 ..
532 }) => self
533 .state
534 .is_data_identifier(exported)
535 .map(|is_data_identifier| !is_data_identifier),
536 ExportSpecifier::Named(ExportNamedSpecifier {
537 orig: ModuleExportName::Ident(orig),
538 ..
539 }) => self
540 .state
541 .is_data_identifier(orig)
542 .map(|is_data_identifier| !is_data_identifier),
543
544 _ => Ok(true),
545 };
546
547 match preserve {
548 Ok(false) => {
549 tracing::trace!("Dropping a export specifier because it's a data identifier");
550
551 if let ExportSpecifier::Named(ExportNamedSpecifier {
552 orig: ModuleExportName::Ident(orig),
553 ..
554 }) = s
555 {
556 self.state.should_run_again = true;
557 self.state.refs_from_data_fn.insert(orig.to_id());
558 }
559
560 false
561 }
562 Ok(true) => true,
563 Err(_) => false,
564 }
565 });
566
567 n
568 }
569
570 fn fold_pat(&mut self, mut p: Pat) -> Pat {
572 p = p.fold_children_with(self);
573
574 if self.in_lhs_of_var {
575 match &mut p {
576 Pat::Ident(name) => {
577 if self.should_remove(name.id.to_id()) {
578 self.state.should_run_again = true;
579 tracing::trace!(
580 "Dropping var `{}{:?}` because it should be removed",
581 name.id.sym,
582 name.id.ctxt
583 );
584
585 return Pat::Invalid(Invalid { span: DUMMY_SP });
586 }
587 }
588 Pat::Array(arr) => {
589 if !arr.elems.is_empty() {
590 arr.elems.retain(|e| !matches!(e, Some(Pat::Invalid(..))));
591
592 if arr.elems.is_empty() {
593 return Pat::Invalid(Invalid { span: DUMMY_SP });
594 }
595 }
596 }
597 Pat::Object(obj) => {
598 if !obj.props.is_empty() {
599 obj.props = take(&mut obj.props)
600 .into_iter()
601 .filter_map(|prop| match prop {
602 ObjectPatProp::KeyValue(prop) => {
603 if prop.value.is_invalid() {
604 None
605 } else {
606 Some(ObjectPatProp::KeyValue(prop))
607 }
608 }
609 ObjectPatProp::Assign(prop) => {
610 if self.should_remove(prop.key.to_id()) {
611 self.mark_as_candidate(prop.value);
612
613 None
614 } else {
615 Some(ObjectPatProp::Assign(prop))
616 }
617 }
618 ObjectPatProp::Rest(prop) => {
619 if prop.arg.is_invalid() {
620 None
621 } else {
622 Some(ObjectPatProp::Rest(prop))
623 }
624 }
625 })
626 .collect();
627
628 if obj.props.is_empty() {
629 return Pat::Invalid(Invalid { span: DUMMY_SP });
630 }
631 }
632 }
633 Pat::Rest(rest) => {
634 if rest.arg.is_invalid() {
635 return Pat::Invalid(Invalid { span: DUMMY_SP });
636 }
637 }
638 _ => {}
639 }
640 }
641
642 p
643 }
644
645 #[allow(clippy::single_match)]
646 fn fold_stmt(&mut self, mut s: Stmt) -> Stmt {
647 match s {
648 Stmt::Decl(Decl::Fn(f)) => {
649 if self.should_remove(f.ident.to_id()) {
650 self.mark_as_candidate(f.function);
651 return Stmt::Empty(EmptyStmt { span: DUMMY_SP });
652 }
653
654 s = Stmt::Decl(Decl::Fn(f));
655 }
656 _ => {}
657 }
658
659 let s = s.fold_children_with(self);
660 match s {
661 Stmt::Decl(Decl::Var(v)) if v.decls.is_empty() => {
662 return Stmt::Empty(EmptyStmt { span: DUMMY_SP });
663 }
664 _ => {}
665 }
666
667 s
668 }
669
670 fn fold_var_declarator(&mut self, mut d: VarDeclarator) -> VarDeclarator {
673 let old = self.in_lhs_of_var;
674 self.in_lhs_of_var = true;
675 let name = d.name.fold_with(self);
676
677 self.in_lhs_of_var = false;
678 if name.is_invalid() {
679 d.init = self.mark_as_candidate(d.init);
680 }
681 let init = d.init.fold_with(self);
682 self.in_lhs_of_var = old;
683
684 VarDeclarator { name, init, ..d }
685 }
686
687 fn fold_var_declarators(&mut self, mut decls: Vec<VarDeclarator>) -> Vec<VarDeclarator> {
688 decls = decls.fold_children_with(self);
689 decls.retain(|d| !d.name.is_invalid());
690
691 decls
692 }
693}