next_custom_transforms/transforms/
page_config.rs

1use 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
29/// TODO: Implement this as a [Pass] instead of a full visitor ([Fold])
30impl 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}