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