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