next_custom_transforms/transforms/
page_config.rs1use chrono::Utc;
2use swc_core::{
3 common::{errors::HANDLER, Span, DUMMY_SP},
4 ecma::{
5 ast::*,
6 visit::{fold_pass, Fold, FoldWith},
7 },
8};
9
10pub fn page_config(is_development: bool, is_page_file: bool) -> impl Pass {
11 fold_pass(PageConfig {
12 is_development,
13 is_page_file,
14 ..Default::default()
15 })
16}
17
18#[derive(Debug, Default)]
19struct PageConfig {
20 drop_bundle: bool,
21 in_test: bool,
22 is_development: bool,
23 is_page_file: bool,
24}
25
26const STRING_LITERAL_DROP_BUNDLE: &str = "__NEXT_DROP_CLIENT_FILE__";
27const CONFIG_KEY: &str = "config";
28
29impl Fold for PageConfig {
31 fn fold_module_items(&mut self, items: Vec<ModuleItem>) -> Vec<ModuleItem> {
32 let mut new_items = vec![];
33 for item in items {
34 new_items.push(item.fold_with(self));
35 if !self.is_development && self.drop_bundle {
36 let timestamp = match self.in_test {
37 true => String::from("mock_timestamp"),
38 false => Utc::now().timestamp().to_string(),
39 };
40 return vec![ModuleItem::Stmt(Stmt::Decl(Decl::Var(Box::new(VarDecl {
41 decls: vec![VarDeclarator {
42 name: Pat::Ident(BindingIdent {
43 id: Ident {
44 sym: STRING_LITERAL_DROP_BUNDLE.into(),
45 ..Default::default()
46 },
47 type_ann: None,
48 }),
49 init: Some(Box::new(Expr::Lit(Lit::Str(Str {
50 value: format!("{STRING_LITERAL_DROP_BUNDLE} {timestamp}").into(),
51 span: DUMMY_SP,
52 raw: None,
53 })))),
54 span: DUMMY_SP,
55 definite: false,
56 }],
57 span: DUMMY_SP,
58 kind: VarDeclKind::Const,
59 ..Default::default()
60 }))))];
61 }
62 }
63
64 new_items
65 }
66
67 fn fold_export_decl(&mut self, export: ExportDecl) -> ExportDecl {
68 if let Decl::Var(var_decl) = &export.decl {
69 for decl in &var_decl.decls {
70 let mut is_config = false;
71 if let Pat::Ident(ident) = &decl.name {
72 if ident.id.sym == CONFIG_KEY {
73 is_config = true;
74 }
75 }
76
77 if is_config {
78 if let Some(expr) = &decl.init {
79 if let Expr::Object(obj) = &**expr {
80 for prop in &obj.props {
81 if let PropOrSpread::Prop(prop) = prop {
82 if let Prop::KeyValue(kv) = &**prop {
83 match &kv.key {
84 PropName::Ident(_) => {}
85 _ => {
86 self.handle_error(
87 "Invalid property found.",
88 export.span,
89 );
90 }
91 }
92 } else {
93 self.handle_error(
94 "Invalid property or value.",
95 export.span,
96 );
97 }
98 } else {
99 self.handle_error(
100 "Property spread is not allowed.",
101 export.span,
102 );
103 }
104 }
105 } else {
106 self.handle_error("Expected config to be an object.", export.span);
107 }
108 } else {
109 self.handle_error("Expected config to be an object.", export.span);
110 }
111 }
112 }
113 }
114 export
115 }
116
117 fn fold_export_named_specifier(
118 &mut self,
119 specifier: ExportNamedSpecifier,
120 ) -> ExportNamedSpecifier {
121 match &specifier.exported {
122 Some(ident) => {
123 if let ModuleExportName::Ident(ident) = ident {
124 if ident.sym == CONFIG_KEY {
125 self.handle_error("Config cannot be re-exported.", specifier.span)
126 }
127 }
128 }
129 None => {
130 if let ModuleExportName::Ident(ident) = &specifier.orig {
131 if ident.sym == CONFIG_KEY {
132 self.handle_error("Config cannot be re-exported.", specifier.span)
133 }
134 }
135 }
136 }
137 specifier
138 }
139}
140
141impl PageConfig {
142 fn handle_error(&mut self, details: &str, span: Span) {
143 if self.is_page_file {
144 let message = format!("Invalid page config export found. {details} \
145 See: https://nextjs.org/docs/messages/invalid-page-config");
146 HANDLER.with(|handler| handler.struct_span_err(span, &message).emit());
147 }
148 }
149}