Add page config swc transform (#30183)

* Add page config swc transform

* Build next-swc binaries

* Disable page config transform in some scenarios

* Build next-swc binaries

* Only skip dropping bundle in dev mode

* Build next-swc binaries

* Allow for amp 'hybrid' configuration

* Build next-swc binaries
This commit is contained in:
Maia Teegarden 2021-10-22 16:08:09 -07:00 committed by GitHub
parent ca65fd8f87
commit d5aa0387b9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 207 additions and 6 deletions

View file

@ -192,6 +192,7 @@ dependencies = [
"libc",
"num-integer",
"num-traits",
"time",
"winapi",
]
@ -715,6 +716,7 @@ version = "0.0.0"
dependencies = [
"anyhow",
"backtrace",
"chrono",
"easy-error",
"fxhash",
"napi",
@ -2308,6 +2310,16 @@ dependencies = [
"once_cell",
]
[[package]]
name = "time"
version = "0.1.43"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ca8a50ef2360fbd1eeb0ecd46795a87a19024eb4b53c5dc916ca1fd95fe62438"
dependencies = [
"libc",
"winapi",
]
[[package]]
name = "tinyvec"
version = "1.5.0"

View file

@ -9,6 +9,7 @@ crate-type = ["cdylib", "rlib"]
[dependencies]
anyhow = "1.0"
backtrace = "0.3"
chrono = "0.4"
easy-error = "1.0.0"
napi = { version = "1", features = ["serde-json"] }
napi-derive = "1"

View file

@ -47,6 +47,7 @@ pub mod hook_optimizer;
pub mod minify;
pub mod next_dynamic;
pub mod next_ssg;
pub mod page_config;
pub mod styled_jsx;
mod transform;
mod util;
@ -60,8 +61,14 @@ pub struct TransformOptions {
#[serde(default)]
pub disable_next_ssg: bool,
#[serde(default)]
pub disable_page_config: bool,
#[serde(default)]
pub pages_dir: Option<PathBuf>,
#[serde(default)]
pub is_development: bool,
}
pub fn custom_before_pass(name: &FileName, opts: &TransformOptions) -> impl Fold {
@ -71,6 +78,10 @@ pub fn custom_before_pass(name: &FileName, opts: &TransformOptions) -> impl Fold
Optional::new(next_ssg::next_ssg(), !opts.disable_next_ssg),
amp_attributes::amp_attributes(),
next_dynamic::next_dynamic(name.clone(), opts.pages_dir.clone()),
Optional::new(
page_config::page_config(opts.is_development),
!opts.disable_page_config
)
)
}

View file

@ -0,0 +1,151 @@
use chrono::Utc;
use swc_common::{Span, DUMMY_SP};
use swc_ecmascript::ast::*;
use swc_ecmascript::utils::HANDLER;
use swc_ecmascript::visit::{Fold, FoldWith};
pub fn page_config(is_development: bool) -> impl Fold {
PageConfig {
is_development,
..Default::default()
}
}
pub fn page_config_test() -> impl Fold {
PageConfig {
drop_bundle: false,
in_test: true,
is_development: false,
}
}
#[derive(Debug, Default)]
struct PageConfig {
drop_bundle: bool,
in_test: bool,
is_development: bool,
}
fn handle_error(details: &str, span: Span) {
let message = format!("Invalid page config export found. {} \
See: https://nextjs.org/docs/messages/invalid-page-config", details);
HANDLER.with(|handler| handler.struct_span_err(span, &message).emit());
}
const STRING_LITERAL_DROP_BUNDLE: &str = "__NEXT_DROP_CLIENT_FILE__";
const CONFIG_KEY: &str = "config";
impl Fold for PageConfig {
fn fold_module_items(&mut self, items: Vec<ModuleItem>) -> Vec<ModuleItem> {
let mut new_items = vec![];
for item in items {
new_items.push(item.fold_with(self));
if !self.is_development && self.drop_bundle {
let timestamp = match self.in_test {
true => String::from("mock_timestamp"),
false => Utc::now().timestamp().to_string(),
};
return vec![ModuleItem::Stmt(Stmt::Decl(Decl::Var(VarDecl {
decls: vec![VarDeclarator {
name: Pat::Ident(BindingIdent {
id: Ident {
sym: STRING_LITERAL_DROP_BUNDLE.into(),
span: DUMMY_SP,
optional: false,
},
type_ann: None,
}),
init: Some(Box::new(Expr::Lit(Lit::Str(Str {
value: format!("{} {}", STRING_LITERAL_DROP_BUNDLE, timestamp).into(),
span: DUMMY_SP,
kind: StrKind::Synthesized {},
has_escape: false,
})))),
span: DUMMY_SP,
definite: false,
}],
span: DUMMY_SP,
kind: VarDeclKind::Const,
declare: false,
})))];
}
}
new_items
}
fn fold_export_decl(&mut self, export: ExportDecl) -> ExportDecl {
match &export.decl {
Decl::Var(var_decl) => {
for decl in &var_decl.decls {
let mut is_config = false;
if let Pat::Ident(ident) = &decl.name {
if &ident.id.sym == CONFIG_KEY {
is_config = true;
}
}
if is_config {
if let Some(expr) = &decl.init {
if let Expr::Object(obj) = &**expr {
for prop in &obj.props {
if let PropOrSpread::Prop(prop) = prop {
if let Prop::KeyValue(kv) = &**prop {
match &kv.key {
PropName::Ident(ident) => {
if &ident.sym == "amp" {
if let Expr::Lit(Lit::Bool(Bool { value, .. })) = &*kv.value {
if *value {
self.drop_bundle = true;
}
} else if let Expr::Lit(Lit::Str(_)) = &*kv.value {
// Do not replace bundle
} else {
handle_error("Invalid value found.", export.span);
}
}
}
_ => {
handle_error("Invalid property found.", export.span);
}
}
} else {
handle_error("Invalid property or value.", export.span);
}
} else {
handle_error("Property spread is not allowed.", export.span);
}
}
} else {
handle_error("Expected config to be an object.", export.span);
}
} else {
handle_error("Expected config to be an object.", export.span);
}
}
}
}
_ => {}
}
export
}
fn fold_export_named_specifier(
&mut self,
specifier: ExportNamedSpecifier,
) -> ExportNamedSpecifier {
match &specifier.exported {
Some(ident) => {
if &ident.sym == CONFIG_KEY {
handle_error("Config cannot be re-exported.", specifier.span)
}
}
None => {
if &specifier.orig.sym == CONFIG_KEY {
handle_error("Config cannot be re-exported.", specifier.span)
}
}
}
specifier
}
}

View file

@ -1,6 +1,6 @@
use next_swc::{
amp_attributes::amp_attributes, next_dynamic::next_dynamic, next_ssg::next_ssg,
styled_jsx::styled_jsx,
page_config::page_config_test, styled_jsx::styled_jsx,
};
use std::path::PathBuf;
use swc_common::{chain, comments::SingleThreadedComments, FileName, Mark, Span, DUMMY_SP};
@ -113,3 +113,9 @@ fn styled_jsx_span_should_not_affect_hash(input: PathBuf) {
&output,
);
}
#[fixture("tests/fixture/page-config/**/input.js")]
fn page_config_fixture(input: PathBuf) {
let output = input.parent().unwrap().join("output.js");
test_fixture(syntax(), &|_tr| page_config_test(), &input, &output);
}

View file

@ -0,0 +1,7 @@
export const config = { amp: 'hybrid' }
function About(props) {
return <h3>My AMP About Page!</h3>
}
export default About

View file

@ -0,0 +1,7 @@
export const config = {
amp: "hybrid"
};
function About(props) {
return <h3 >My AMP About Page!</h3>;
}
export default About;

View file

@ -0,0 +1,7 @@
export const config = { amp: true }
function About(props) {
return <h3>My AMP About Page!</h3>
}
export default About

View file

@ -0,0 +1 @@
const __NEXT_DROP_CLIENT_FILE__ = "__NEXT_DROP_CLIENT_FILE__ mock_timestamp";

View file

@ -74,6 +74,8 @@ function getSWCOptions({
: {}),
// Disables getStaticProps/getServerSideProps tree shaking on the server compilation for pages
disableNextSsg: true,
disablePageConfig: true,
isDevelopment: development,
pagesDir,
env: {
targets: {
@ -95,6 +97,7 @@ function getSWCOptions({
}
: {}),
disableNextSsg: !isPageFile,
isDevelopment: development,
pagesDir,
jsc,
}

View file

@ -1,5 +0,0 @@
module.exports = {
experimental: {
swcLoader: false,
},
}