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:
parent
ca65fd8f87
commit
d5aa0387b9
15 changed files with 207 additions and 6 deletions
12
packages/next/build/swc/Cargo.lock
generated
12
packages/next/build/swc/Cargo.lock
generated
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
|
|
151
packages/next/build/swc/src/page_config.rs
Normal file
151
packages/next/build/swc/src/page_config.rs
Normal 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
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
export const config = { amp: 'hybrid' }
|
||||
|
||||
function About(props) {
|
||||
return <h3>My AMP About Page!</h3>
|
||||
}
|
||||
|
||||
export default About
|
|
@ -0,0 +1,7 @@
|
|||
export const config = {
|
||||
amp: "hybrid"
|
||||
};
|
||||
function About(props) {
|
||||
return <h3 >My AMP About Page!</h3>;
|
||||
}
|
||||
export default About;
|
|
@ -0,0 +1,7 @@
|
|||
export const config = { amp: true }
|
||||
|
||||
function About(props) {
|
||||
return <h3>My AMP About Page!</h3>
|
||||
}
|
||||
|
||||
export default About
|
|
@ -0,0 +1 @@
|
|||
const __NEXT_DROP_CLIENT_FILE__ = "__NEXT_DROP_CLIENT_FILE__ mock_timestamp";
|
|
@ -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,
|
||||
}
|
||||
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -1,5 +0,0 @@
|
|||
module.exports = {
|
||||
experimental: {
|
||||
swcLoader: false,
|
||||
},
|
||||
}
|
Loading…
Reference in a new issue