From e829192205ce885402ba9e10f03ea1f898d80349 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Donny/=EA=B0=95=EB=8F=99=EC=9C=A4?= Date: Fri, 20 Jan 2023 16:09:31 +0900 Subject: [PATCH 01/32] Module --- packages/next-swc/crates/core/src/lib.rs | 4 ++++ packages/next-swc/crates/core/src/server_actions.rs | 5 +++++ 2 files changed, 9 insertions(+) create mode 100644 packages/next-swc/crates/core/src/server_actions.rs diff --git a/packages/next-swc/crates/core/src/lib.rs b/packages/next-swc/crates/core/src/lib.rs index d708b107e45f6..2f004119f5a22 100644 --- a/packages/next-swc/crates/core/src/lib.rs +++ b/packages/next-swc/crates/core/src/lib.rs @@ -59,6 +59,7 @@ pub mod react_server_components; #[cfg(not(target_arch = "wasm32"))] pub mod relay; pub mod remove_console; +mod server_actions; pub mod shake_exports; mod top_level_binding_collector; @@ -122,6 +123,9 @@ pub struct TransformOptions { #[serde(default)] pub font_loaders: Option, + + #[serde(default)] + pub server_actions: Option, } pub fn custom_before_pass<'a, C: Comments + 'a>( diff --git a/packages/next-swc/crates/core/src/server_actions.rs b/packages/next-swc/crates/core/src/server_actions.rs new file mode 100644 index 0000000000000..d094a640c5afe --- /dev/null +++ b/packages/next-swc/crates/core/src/server_actions.rs @@ -0,0 +1,5 @@ +use serde::Deserialize; + +#[derive(Clone, Debug, Deserialize)] +#[serde(deny_unknown_fields, rename_all = "camelCase")] +pub struct Config {} From fa720826ca094799ddebfa10dfc0f266c55ec4e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Donny/=EA=B0=95=EB=8F=99=EC=9C=A4?= Date: Fri, 20 Jan 2023 16:10:09 +0900 Subject: [PATCH 02/32] Fix compilation --- packages/next-swc/crates/core/tests/full.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/next-swc/crates/core/tests/full.rs b/packages/next-swc/crates/core/tests/full.rs index d45ffe9cc4e78..9bde0c87f03f9 100644 --- a/packages/next-swc/crates/core/tests/full.rs +++ b/packages/next-swc/crates/core/tests/full.rs @@ -70,6 +70,7 @@ fn test(input: &Path, minify: bool) { emotion: Some(assert_json("{}")), modularize_imports: None, font_loaders: None, + server_actions: None, }; let options = options.patch(&fm); From 84e49a8c0bc0ca3ec39dfb2f73ed3e8a1a4b1ef9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Donny/=EA=B0=95=EB=8F=99=EC=9C=A4?= Date: Fri, 20 Jan 2023 16:33:10 +0900 Subject: [PATCH 03/32] init visitor --- packages/next-swc/crates/core/src/server_actions.rs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/packages/next-swc/crates/core/src/server_actions.rs b/packages/next-swc/crates/core/src/server_actions.rs index d094a640c5afe..9176fc5503b2f 100644 --- a/packages/next-swc/crates/core/src/server_actions.rs +++ b/packages/next-swc/crates/core/src/server_actions.rs @@ -1,5 +1,18 @@ +use next_binding::swc::core::ecma::visit::{as_folder, noop_visit_mut_type, Fold, VisitMut}; use serde::Deserialize; #[derive(Clone, Debug, Deserialize)] #[serde(deny_unknown_fields, rename_all = "camelCase")] pub struct Config {} + +pub fn server_actions(config: Config) -> impl VisitMut + Fold { + as_folder(ServerActions { config }) +} + +struct ServerActions { + config: Config, +} + +impl VisitMut for ServerActions { + noop_visit_mut_type!(); +} From c554329447728d80bb8a3c7afc0cc01f124d0f17 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Donny/=EA=B0=95=EB=8F=99=EC=9C=A4?= Date: Fri, 20 Jan 2023 16:35:18 +0900 Subject: [PATCH 04/32] visit_mut_fn_decl --- .../crates/core/src/server_actions.rs | 30 ++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/packages/next-swc/crates/core/src/server_actions.rs b/packages/next-swc/crates/core/src/server_actions.rs index 9176fc5503b2f..209fd0d4aa60b 100644 --- a/packages/next-swc/crates/core/src/server_actions.rs +++ b/packages/next-swc/crates/core/src/server_actions.rs @@ -1,4 +1,10 @@ -use next_binding::swc::core::ecma::visit::{as_folder, noop_visit_mut_type, Fold, VisitMut}; +use next_binding::swc::core::{ + common::errors::HANDLER, + ecma::{ + ast::{Expr, FnDecl, Lit, Stmt, Str}, + visit::{as_folder, noop_visit_mut_type, Fold, VisitMut, VisitMutWith}, + }, +}; use serde::Deserialize; #[derive(Clone, Debug, Deserialize)] @@ -15,4 +21,26 @@ struct ServerActions { impl VisitMut for ServerActions { noop_visit_mut_type!(); + + fn visit_mut_fn_decl(&mut self, f: &mut FnDecl) { + f.visit_mut_children_with(self); + + // Check if the first item is `"use action"`; + if let Some(body) = &f.function.body { + if let Some(Stmt::Expr(first)) = body.stmts.first() { + match &*first.expr { + Expr::Lit(Lit::Str(Str { value, .. })) if value == "use action" => {} + _ => return, + } + } + } + + if !f.function.is_async { + HANDLER.with(|handler| { + handler + .struct_span_err(f.ident.span, "Server actions must be async") + .emit(); + }); + } + } } From 35da9892b4e89ddfeeabdb14defea15b3fa54b4d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Donny/=EA=B0=95=EB=8F=99=EC=9C=A4?= Date: Fri, 20 Jan 2023 16:49:21 +0900 Subject: [PATCH 05/32] annotations --- .../crates/core/src/server_actions.rs | 51 +++++++++++++++++-- 1 file changed, 48 insertions(+), 3 deletions(-) diff --git a/packages/next-swc/crates/core/src/server_actions.rs b/packages/next-swc/crates/core/src/server_actions.rs index 209fd0d4aa60b..56441afb00171 100644 --- a/packages/next-swc/crates/core/src/server_actions.rs +++ b/packages/next-swc/crates/core/src/server_actions.rs @@ -1,7 +1,8 @@ use next_binding::swc::core::{ - common::errors::HANDLER, + common::{errors::HANDLER, DUMMY_SP}, ecma::{ - ast::{Expr, FnDecl, Lit, Stmt, Str}, + ast::{op, AssignExpr, CallExpr, Expr, ExprStmt, FnDecl, Ident, Lit, PatOrExpr, Stmt, Str}, + utils::{quote_ident, ExprFactory}, visit::{as_folder, noop_visit_mut_type, Fold, VisitMut, VisitMutWith}, }, }; @@ -12,11 +13,16 @@ use serde::Deserialize; pub struct Config {} pub fn server_actions(config: Config) -> impl VisitMut + Fold { - as_folder(ServerActions { config }) + as_folder(ServerActions { + config, + annotations: Default::default(), + }) } struct ServerActions { config: Config, + + annotations: Vec, } impl VisitMut for ServerActions { @@ -42,5 +48,44 @@ impl VisitMut for ServerActions { .emit(); }); } + + // myAction.$$typeof = Symbol.for('react.action.reference'); + self.annotations.push(annotate( + &f.ident, + "$$typeof", + CallExpr { + span: DUMMY_SP, + callee: quote_ident!("Symbol") + .make_member(quote_ident!("for")) + .as_callee(), + args: vec!["react.action.reference".as_arg()], + type_args: Default::default(), + } + .into(), + )); + + // myAction.$$filepath = '/app/page.tsx'; + self.annotations + .push(annotate(&f.ident, "$$filepath", "".into())); + + // myAction.$$name = '$ACTION_myAction'; + self.annotations.push(annotate( + &f.ident, + "$$name", + format!("$ACTION_{}", f.ident.sym).into(), + )); } } + +fn annotate(fn_name: &Ident, field_name: &str, value: Box) -> Stmt { + Stmt::Expr(ExprStmt { + span: DUMMY_SP, + expr: AssignExpr { + span: DUMMY_SP, + op: op!("="), + left: PatOrExpr::Expr(fn_name.clone().make_member(quote_ident!(field_name)).into()), + right: value, + } + .into(), + }) +} From 20604fe5bb0e864e0ab2c34a1d7abe4b4326fffc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Donny/=EA=B0=95=EB=8F=99=EC=9C=A4?= Date: Fri, 20 Jan 2023 16:50:06 +0900 Subject: [PATCH 06/32] type --- packages/next-swc/crates/core/src/server_actions.rs | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/packages/next-swc/crates/core/src/server_actions.rs b/packages/next-swc/crates/core/src/server_actions.rs index 56441afb00171..aa72ada364889 100644 --- a/packages/next-swc/crates/core/src/server_actions.rs +++ b/packages/next-swc/crates/core/src/server_actions.rs @@ -1,7 +1,10 @@ use next_binding::swc::core::{ common::{errors::HANDLER, DUMMY_SP}, ecma::{ - ast::{op, AssignExpr, CallExpr, Expr, ExprStmt, FnDecl, Ident, Lit, PatOrExpr, Stmt, Str}, + ast::{ + op, AssignExpr, CallExpr, Expr, ExprStmt, FnDecl, Ident, Lit, ModuleItem, PatOrExpr, + Stmt, Str, + }, utils::{quote_ident, ExprFactory}, visit::{as_folder, noop_visit_mut_type, Fold, VisitMut, VisitMutWith}, }, @@ -22,7 +25,7 @@ pub fn server_actions(config: Config) -> impl VisitMut + Fold { struct ServerActions { config: Config, - annotations: Vec, + annotations: Vec, } impl VisitMut for ServerActions { @@ -77,8 +80,8 @@ impl VisitMut for ServerActions { } } -fn annotate(fn_name: &Ident, field_name: &str, value: Box) -> Stmt { - Stmt::Expr(ExprStmt { +fn annotate(fn_name: &Ident, field_name: &str, value: Box) -> ModuleItem { + ModuleItem::Stmt(Stmt::Expr(ExprStmt { span: DUMMY_SP, expr: AssignExpr { span: DUMMY_SP, @@ -87,5 +90,5 @@ fn annotate(fn_name: &Ident, field_name: &str, value: Box) -> Stmt { right: value, } .into(), - }) + })) } From fccaea8d720038366e558f4c54e9a2cd216c1f64 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Donny/=EA=B0=95=EB=8F=99=EC=9C=A4?= Date: Fri, 20 Jan 2023 16:53:47 +0900 Subject: [PATCH 07/32] export const --- .../crates/core/src/server_actions.rs | 31 ++++++++++++++----- 1 file changed, 24 insertions(+), 7 deletions(-) diff --git a/packages/next-swc/crates/core/src/server_actions.rs b/packages/next-swc/crates/core/src/server_actions.rs index aa72ada364889..c72f6c9fad87f 100644 --- a/packages/next-swc/crates/core/src/server_actions.rs +++ b/packages/next-swc/crates/core/src/server_actions.rs @@ -2,8 +2,8 @@ use next_binding::swc::core::{ common::{errors::HANDLER, DUMMY_SP}, ecma::{ ast::{ - op, AssignExpr, CallExpr, Expr, ExprStmt, FnDecl, Ident, Lit, ModuleItem, PatOrExpr, - Stmt, Str, + op, AssignExpr, CallExpr, Decl, ExportDecl, Expr, ExprStmt, FnDecl, Ident, Lit, + ModuleDecl, ModuleItem, PatOrExpr, Stmt, Str, VarDecl, VarDeclKind, VarDeclarator, }, utils::{quote_ident, ExprFactory}, visit::{as_folder, noop_visit_mut_type, Fold, VisitMut, VisitMutWith}, @@ -18,14 +18,14 @@ pub struct Config {} pub fn server_actions(config: Config) -> impl VisitMut + Fold { as_folder(ServerActions { config, - annotations: Default::default(), + extra_items: Default::default(), }) } struct ServerActions { config: Config, - annotations: Vec, + extra_items: Vec, } impl VisitMut for ServerActions { @@ -53,7 +53,7 @@ impl VisitMut for ServerActions { } // myAction.$$typeof = Symbol.for('react.action.reference'); - self.annotations.push(annotate( + self.extra_items.push(annotate( &f.ident, "$$typeof", CallExpr { @@ -68,15 +68,32 @@ impl VisitMut for ServerActions { )); // myAction.$$filepath = '/app/page.tsx'; - self.annotations + self.extra_items .push(annotate(&f.ident, "$$filepath", "".into())); // myAction.$$name = '$ACTION_myAction'; - self.annotations.push(annotate( + self.extra_items.push(annotate( &f.ident, "$$name", format!("$ACTION_{}", f.ident.sym).into(), )); + + self.extra_items + .push(ModuleItem::ModuleDecl(ModuleDecl::ExportDecl(ExportDecl { + span: DUMMY_SP, + decl: Decl::Var(Box::new(VarDecl { + span: DUMMY_SP, + kind: VarDeclKind::Const, + declare: Default::default(), + decls: vec![VarDeclarator { + span: DUMMY_SP, + name: Ident::new(format!("$ACTION_{}", f.ident.sym).into(), f.ident.span) + .into(), + init: Some(f.ident.clone().into()), + definite: Default::default(), + }], + })), + }))); } } From edc4a78ec048fcec21629a7ceef5a1c624150d5d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Donny/=EA=B0=95=EB=8F=99=EC=9C=A4?= Date: Fri, 20 Jan 2023 16:53:55 +0900 Subject: [PATCH 08/32] cmt --- packages/next-swc/crates/core/src/server_actions.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/next-swc/crates/core/src/server_actions.rs b/packages/next-swc/crates/core/src/server_actions.rs index c72f6c9fad87f..417b44a4b6b85 100644 --- a/packages/next-swc/crates/core/src/server_actions.rs +++ b/packages/next-swc/crates/core/src/server_actions.rs @@ -78,6 +78,7 @@ impl VisitMut for ServerActions { format!("$ACTION_{}", f.ident.sym).into(), )); + // export const $ACTION_myAction = myAction; self.extra_items .push(ModuleItem::ModuleDecl(ModuleDecl::ExportDecl(ExportDecl { span: DUMMY_SP, From 431843fc5c2852fe7032551f1320684cb0f3c52c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Donny/=EA=B0=95=EB=8F=99=EC=9C=A4?= Date: Fri, 20 Jan 2023 16:57:36 +0900 Subject: [PATCH 09/32] Add a test --- .../crates/core/tests/fixture/server-actions/1/input.js | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 packages/next-swc/crates/core/tests/fixture/server-actions/1/input.js diff --git a/packages/next-swc/crates/core/tests/fixture/server-actions/1/input.js b/packages/next-swc/crates/core/tests/fixture/server-actions/1/input.js new file mode 100644 index 0000000000000..b0b7a7ed44248 --- /dev/null +++ b/packages/next-swc/crates/core/tests/fixture/server-actions/1/input.js @@ -0,0 +1,8 @@ +export function Item({ id1, id2 }) { + async function deleteItem() { + "use action"; + await deleteFromDb(id1); + await deleteFromDb(id2); + } + return ; +} \ No newline at end of file From b5248bf328893fe9b9dd958570a667cc3fda3114 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Donny/=EA=B0=95=EB=8F=99=EC=9C=A4?= Date: Fri, 20 Jan 2023 16:58:30 +0900 Subject: [PATCH 10/32] split --- .../next-swc/crates/core/src/server_actions.rs | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/packages/next-swc/crates/core/src/server_actions.rs b/packages/next-swc/crates/core/src/server_actions.rs index 417b44a4b6b85..e9fe5829e68b3 100644 --- a/packages/next-swc/crates/core/src/server_actions.rs +++ b/packages/next-swc/crates/core/src/server_actions.rs @@ -19,12 +19,14 @@ pub fn server_actions(config: Config) -> impl VisitMut + Fold { as_folder(ServerActions { config, extra_items: Default::default(), + annotations: Default::default(), }) } struct ServerActions { config: Config, + annotations: Vec, extra_items: Vec, } @@ -53,7 +55,7 @@ impl VisitMut for ServerActions { } // myAction.$$typeof = Symbol.for('react.action.reference'); - self.extra_items.push(annotate( + self.annotations.push(annotate( &f.ident, "$$typeof", CallExpr { @@ -68,11 +70,11 @@ impl VisitMut for ServerActions { )); // myAction.$$filepath = '/app/page.tsx'; - self.extra_items + self.annotations .push(annotate(&f.ident, "$$filepath", "".into())); // myAction.$$name = '$ACTION_myAction'; - self.extra_items.push(annotate( + self.annotations.push(annotate( &f.ident, "$$name", format!("$ACTION_{}", f.ident.sym).into(), @@ -98,8 +100,8 @@ impl VisitMut for ServerActions { } } -fn annotate(fn_name: &Ident, field_name: &str, value: Box) -> ModuleItem { - ModuleItem::Stmt(Stmt::Expr(ExprStmt { +fn annotate(fn_name: &Ident, field_name: &str, value: Box) -> Stmt { + Stmt::Expr(ExprStmt { span: DUMMY_SP, expr: AssignExpr { span: DUMMY_SP, @@ -108,5 +110,5 @@ fn annotate(fn_name: &Ident, field_name: &str, value: Box) -> ModuleItem { right: value, } .into(), - })) + }) } From 0a7bbe00c75119b57d33829d659fc7d1a41c62b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Donny/=EA=B0=95=EB=8F=99=EC=9C=A4?= Date: Fri, 20 Jan 2023 16:59:08 +0900 Subject: [PATCH 11/32] pub --- packages/next-swc/crates/core/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/next-swc/crates/core/src/lib.rs b/packages/next-swc/crates/core/src/lib.rs index 2f004119f5a22..906e96d2c12a9 100644 --- a/packages/next-swc/crates/core/src/lib.rs +++ b/packages/next-swc/crates/core/src/lib.rs @@ -59,7 +59,7 @@ pub mod react_server_components; #[cfg(not(target_arch = "wasm32"))] pub mod relay; pub mod remove_console; -mod server_actions; +pub mod server_actions; pub mod shake_exports; mod top_level_binding_collector; From bc8fb9b8049048f09d181d2b106aaa1dbd287b7e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Donny/=EA=B0=95=EB=8F=99=EC=9C=A4?= Date: Fri, 20 Jan 2023 17:01:49 +0900 Subject: [PATCH 12/32] test --- packages/next-swc/crates/core/tests/fixture.rs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/packages/next-swc/crates/core/tests/fixture.rs b/packages/next-swc/crates/core/tests/fixture.rs index b362230d95b7f..5e6bdae8b86d4 100644 --- a/packages/next-swc/crates/core/tests/fixture.rs +++ b/packages/next-swc/crates/core/tests/fixture.rs @@ -17,6 +17,7 @@ use next_swc::{ react_server_components::server_components, relay::{relay, Config as RelayConfig, RelayLanguageConfig}, remove_console::remove_console, + server_actions::{self, server_actions}, shake_exports::{shake_exports, Config as ShakeExportsConfig}, }; use std::path::PathBuf; @@ -295,3 +296,15 @@ fn next_font_loaders_fixture(input: PathBuf) { Default::default(), ); } + +#[fixture("tests/fixture/server-actions/**/input.js")] +fn server_actions_fixture(input: PathBuf) { + let output = input.parent().unwrap().join("output.js"); + test_fixture( + syntax(), + &|_tr| server_actions(server_actions::Config {}), + &input, + &output, + Default::default(), + ); +} From 1736e4a4d65a7665eab61e2680e4881e96589010 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Donny/=EA=B0=95=EB=8F=99=EC=9C=A4?= Date: Fri, 20 Jan 2023 17:04:09 +0900 Subject: [PATCH 13/32] else --- packages/next-swc/crates/core/src/server_actions.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/next-swc/crates/core/src/server_actions.rs b/packages/next-swc/crates/core/src/server_actions.rs index e9fe5829e68b3..50c1503276c96 100644 --- a/packages/next-swc/crates/core/src/server_actions.rs +++ b/packages/next-swc/crates/core/src/server_actions.rs @@ -43,7 +43,11 @@ impl VisitMut for ServerActions { Expr::Lit(Lit::Str(Str { value, .. })) if value == "use action" => {} _ => return, } + } else { + return; } + } else { + return; } if !f.function.is_async { From 7c775d810588303caa40c58612f595ab5c2c3b5e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Donny/=EA=B0=95=EB=8F=99=EC=9C=A4?= Date: Fri, 20 Jan 2023 17:04:38 +0900 Subject: [PATCH 14/32] remove --- packages/next-swc/crates/core/src/server_actions.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/next-swc/crates/core/src/server_actions.rs b/packages/next-swc/crates/core/src/server_actions.rs index 50c1503276c96..7358891b88898 100644 --- a/packages/next-swc/crates/core/src/server_actions.rs +++ b/packages/next-swc/crates/core/src/server_actions.rs @@ -37,7 +37,7 @@ impl VisitMut for ServerActions { f.visit_mut_children_with(self); // Check if the first item is `"use action"`; - if let Some(body) = &f.function.body { + if let Some(body) = &mut f.function.body { if let Some(Stmt::Expr(first)) = body.stmts.first() { match &*first.expr { Expr::Lit(Lit::Str(Str { value, .. })) if value == "use action" => {} @@ -46,6 +46,8 @@ impl VisitMut for ServerActions { } else { return; } + + body.stmts.remove(0); } else { return; } From b90b7a7fc9d4302e5948554c95cc01460a05daa2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Donny/=EA=B0=95=EB=8F=99=EC=9C=A4?= Date: Fri, 20 Jan 2023 17:06:43 +0900 Subject: [PATCH 15/32] annotations --- .../crates/core/src/server_actions.rs | 22 ++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/packages/next-swc/crates/core/src/server_actions.rs b/packages/next-swc/crates/core/src/server_actions.rs index 7358891b88898..a6a7ef6a59285 100644 --- a/packages/next-swc/crates/core/src/server_actions.rs +++ b/packages/next-swc/crates/core/src/server_actions.rs @@ -1,5 +1,5 @@ use next_binding::swc::core::{ - common::{errors::HANDLER, DUMMY_SP}, + common::{errors::HANDLER, util::take::Take, DUMMY_SP}, ecma::{ ast::{ op, AssignExpr, CallExpr, Decl, ExportDecl, Expr, ExprStmt, FnDecl, Ident, Lit, @@ -31,8 +31,6 @@ struct ServerActions { } impl VisitMut for ServerActions { - noop_visit_mut_type!(); - fn visit_mut_fn_decl(&mut self, f: &mut FnDecl) { f.visit_mut_children_with(self); @@ -104,6 +102,24 @@ impl VisitMut for ServerActions { })), }))); } + + fn visit_mut_stmts(&mut self, stmts: &mut Vec) { + let old_annotations = self.annotations.take(); + + let mut new = Vec::with_capacity(stmts.len()); + for mut stmt in stmts.take() { + stmt.visit_mut_with(self); + + new.push(stmt); + new.append(&mut self.annotations); + } + + *stmts = new; + + self.annotations = old_annotations; + } + + noop_visit_mut_type!(); } fn annotate(fn_name: &Ident, field_name: &str, value: Box) -> Stmt { From dfdb9ef411b32357a719953e5f7ed69b4a7e9e75 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Donny/=EA=B0=95=EB=8F=99=EC=9C=A4?= Date: Fri, 20 Jan 2023 17:09:48 +0900 Subject: [PATCH 16/32] FileName --- packages/next-swc/crates/core/src/server_actions.rs | 13 +++++++++---- packages/next-swc/crates/core/tests/fixture.rs | 7 ++++++- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/packages/next-swc/crates/core/src/server_actions.rs b/packages/next-swc/crates/core/src/server_actions.rs index a6a7ef6a59285..30f8951e093bc 100644 --- a/packages/next-swc/crates/core/src/server_actions.rs +++ b/packages/next-swc/crates/core/src/server_actions.rs @@ -1,5 +1,5 @@ use next_binding::swc::core::{ - common::{errors::HANDLER, util::take::Take, DUMMY_SP}, + common::{errors::HANDLER, util::take::Take, FileName, DUMMY_SP}, ecma::{ ast::{ op, AssignExpr, CallExpr, Decl, ExportDecl, Expr, ExprStmt, FnDecl, Ident, Lit, @@ -15,9 +15,10 @@ use serde::Deserialize; #[serde(deny_unknown_fields, rename_all = "camelCase")] pub struct Config {} -pub fn server_actions(config: Config) -> impl VisitMut + Fold { +pub fn server_actions(file_name: &FileName, config: Config) -> impl VisitMut + Fold { as_folder(ServerActions { config, + file_name: file_name.clone(), extra_items: Default::default(), annotations: Default::default(), }) @@ -25,6 +26,7 @@ pub fn server_actions(config: Config) -> impl VisitMut + Fold { struct ServerActions { config: Config, + file_name: FileName, annotations: Vec, extra_items: Vec, @@ -74,8 +76,11 @@ impl VisitMut for ServerActions { )); // myAction.$$filepath = '/app/page.tsx'; - self.annotations - .push(annotate(&f.ident, "$$filepath", "".into())); + self.annotations.push(annotate( + &f.ident, + "$$filepath", + self.file_name.to_string().into(), + )); // myAction.$$name = '$ACTION_myAction'; self.annotations.push(annotate( diff --git a/packages/next-swc/crates/core/tests/fixture.rs b/packages/next-swc/crates/core/tests/fixture.rs index 5e6bdae8b86d4..ec1351cd07562 100644 --- a/packages/next-swc/crates/core/tests/fixture.rs +++ b/packages/next-swc/crates/core/tests/fixture.rs @@ -302,7 +302,12 @@ fn server_actions_fixture(input: PathBuf) { let output = input.parent().unwrap().join("output.js"); test_fixture( syntax(), - &|_tr| server_actions(server_actions::Config {}), + &|_tr| { + server_actions( + &FileName::Real("/app/item.js".into()), + server_actions::Config {}, + ) + }, &input, &output, Default::default(), From 3bfd778f1f94f97d8c1baef0819660abe930fcc6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Donny/=EA=B0=95=EB=8F=99=EC=9C=A4?= Date: Fri, 20 Jan 2023 17:11:51 +0900 Subject: [PATCH 17/32] extra_items --- .../next-swc/crates/core/src/server_actions.rs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/packages/next-swc/crates/core/src/server_actions.rs b/packages/next-swc/crates/core/src/server_actions.rs index 30f8951e093bc..ae8c7e455aecb 100644 --- a/packages/next-swc/crates/core/src/server_actions.rs +++ b/packages/next-swc/crates/core/src/server_actions.rs @@ -108,6 +108,23 @@ impl VisitMut for ServerActions { }))); } + fn visit_mut_module_items(&mut self, stmts: &mut Vec) { + let old_annotations = self.annotations.take(); + + let mut new = Vec::with_capacity(stmts.len()); + for mut stmt in stmts.take() { + stmt.visit_mut_with(self); + + new.push(stmt); + new.extend(self.annotations.drain(..).map(ModuleItem::Stmt)); + new.append(&mut self.extra_items); + } + + *stmts = new; + + self.annotations = old_annotations; + } + fn visit_mut_stmts(&mut self, stmts: &mut Vec) { let old_annotations = self.annotations.take(); From 471f5bad2463e7cf04090ccaed52d1b95ca54bc0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Donny/=EA=B0=95=EB=8F=99=EC=9C=A4?= Date: Fri, 20 Jan 2023 17:13:52 +0900 Subject: [PATCH 18/32] top_level --- .../crates/core/src/server_actions.rs | 39 ++++++++++++------- 1 file changed, 25 insertions(+), 14 deletions(-) diff --git a/packages/next-swc/crates/core/src/server_actions.rs b/packages/next-swc/crates/core/src/server_actions.rs index ae8c7e455aecb..822c0de1284f2 100644 --- a/packages/next-swc/crates/core/src/server_actions.rs +++ b/packages/next-swc/crates/core/src/server_actions.rs @@ -19,6 +19,7 @@ pub fn server_actions(file_name: &FileName, config: Config) -> impl VisitMut + F as_folder(ServerActions { config, file_name: file_name.clone(), + top_level: false, extra_items: Default::default(), annotations: Default::default(), }) @@ -28,6 +29,8 @@ struct ServerActions { config: Config, file_name: FileName, + top_level: bool, + annotations: Vec, extra_items: Vec, } @@ -89,23 +92,29 @@ impl VisitMut for ServerActions { format!("$ACTION_{}", f.ident.sym).into(), )); - // export const $ACTION_myAction = myAction; - self.extra_items - .push(ModuleItem::ModuleDecl(ModuleDecl::ExportDecl(ExportDecl { - span: DUMMY_SP, - decl: Decl::Var(Box::new(VarDecl { + if self.top_level { + // export const $ACTION_myAction = myAction; + self.extra_items + .push(ModuleItem::ModuleDecl(ModuleDecl::ExportDecl(ExportDecl { span: DUMMY_SP, - kind: VarDeclKind::Const, - declare: Default::default(), - decls: vec![VarDeclarator { + decl: Decl::Var(Box::new(VarDecl { span: DUMMY_SP, - name: Ident::new(format!("$ACTION_{}", f.ident.sym).into(), f.ident.span) + kind: VarDeclKind::Const, + declare: Default::default(), + decls: vec![VarDeclarator { + span: DUMMY_SP, + name: Ident::new( + format!("$ACTION_{}", f.ident.sym).into(), + f.ident.span, + ) .into(), - init: Some(f.ident.clone().into()), - definite: Default::default(), - }], - })), - }))); + init: Some(f.ident.clone().into()), + definite: Default::default(), + }], + })), + }))); + } else { + } } fn visit_mut_module_items(&mut self, stmts: &mut Vec) { @@ -113,6 +122,7 @@ impl VisitMut for ServerActions { let mut new = Vec::with_capacity(stmts.len()); for mut stmt in stmts.take() { + self.top_level = true; stmt.visit_mut_with(self); new.push(stmt); @@ -130,6 +140,7 @@ impl VisitMut for ServerActions { let mut new = Vec::with_capacity(stmts.len()); for mut stmt in stmts.take() { + self.top_level = false; stmt.visit_mut_with(self); new.push(stmt); From c3e3547461e398afc41df0782defddc793947e17 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Donny/=EA=B0=95=EB=8F=99=EC=9C=A4?= Date: Fri, 20 Jan 2023 17:21:19 +0900 Subject: [PATCH 19/32] hoist --- .../crates/core/src/server_actions.rs | 63 +++++++++++++++---- 1 file changed, 51 insertions(+), 12 deletions(-) diff --git a/packages/next-swc/crates/core/src/server_actions.rs b/packages/next-swc/crates/core/src/server_actions.rs index 822c0de1284f2..143360975c89c 100644 --- a/packages/next-swc/crates/core/src/server_actions.rs +++ b/packages/next-swc/crates/core/src/server_actions.rs @@ -2,9 +2,11 @@ use next_binding::swc::core::{ common::{errors::HANDLER, util::take::Take, FileName, DUMMY_SP}, ecma::{ ast::{ - op, AssignExpr, CallExpr, Decl, ExportDecl, Expr, ExprStmt, FnDecl, Ident, Lit, - ModuleDecl, ModuleItem, PatOrExpr, Stmt, Str, VarDecl, VarDeclKind, VarDeclarator, + op, AssignExpr, BlockStmt, CallExpr, Decl, ExportDecl, Expr, ExprStmt, FnDecl, + Function, Ident, Lit, ModuleDecl, ModuleItem, PatOrExpr, ReturnStmt, Stmt, Str, + VarDecl, VarDeclKind, VarDeclarator, }, + atoms::JsWord, utils::{quote_ident, ExprFactory}, visit::{as_folder, noop_visit_mut_type, Fold, VisitMut, VisitMutWith}, }, @@ -63,6 +65,8 @@ impl VisitMut for ServerActions { }); } + let action_name: JsWord = format!("$ACTION_{}", f.ident.sym).into(); + // myAction.$$typeof = Symbol.for('react.action.reference'); self.annotations.push(annotate( &f.ident, @@ -86,11 +90,8 @@ impl VisitMut for ServerActions { )); // myAction.$$name = '$ACTION_myAction'; - self.annotations.push(annotate( - &f.ident, - "$$name", - format!("$ACTION_{}", f.ident.sym).into(), - )); + self.annotations + .push(annotate(&f.ident, "$$name", action_name.clone().into())); if self.top_level { // export const $ACTION_myAction = myAction; @@ -103,17 +104,55 @@ impl VisitMut for ServerActions { declare: Default::default(), decls: vec![VarDeclarator { span: DUMMY_SP, - name: Ident::new( - format!("$ACTION_{}", f.ident.sym).into(), - f.ident.span, - ) - .into(), + name: Ident::new(action_name, f.ident.span).into(), init: Some(f.ident.clone().into()), definite: Default::default(), }], })), }))); } else { + // Hoist the function to the top level. + + let call = CallExpr { + span: DUMMY_SP, + callee: action_name.clone().as_callee(), + args: vec![f + .ident + .clone() + .make_member(quote_ident!("$$closure")) + .as_arg()], + type_args: Default::default(), + }; + + let new_fn = Box::new(Function { + params: f.function.params.clone(), + decorators: f.function.decorators.take(), + span: f.function.span, + body: Some(BlockStmt { + span: DUMMY_SP, + stmts: vec![Stmt::Return(ReturnStmt { + span: DUMMY_SP, + arg: Some(call.into()), + })], + }), + is_generator: f.function.is_generator, + is_async: f.function.is_async, + type_params: Default::default(), + return_type: Default::default(), + }); + + self.extra_items + .push(ModuleItem::ModuleDecl(ModuleDecl::ExportDecl(ExportDecl { + span: DUMMY_SP, + decl: FnDecl { + ident: Ident::new(action_name, f.ident.span), + function: f.function.take(), + declare: Default::default(), + } + .into(), + }))); + + f.function = new_fn; } } From 4fc19d02ab3378cab109038668baa11d978f46b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Donny/=EA=B0=95=EB=8F=99=EC=9C=A4?= Date: Fri, 20 Jan 2023 17:22:42 +0900 Subject: [PATCH 20/32] Ident --- packages/next-swc/crates/core/src/server_actions.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/next-swc/crates/core/src/server_actions.rs b/packages/next-swc/crates/core/src/server_actions.rs index 143360975c89c..b94b03ca1351b 100644 --- a/packages/next-swc/crates/core/src/server_actions.rs +++ b/packages/next-swc/crates/core/src/server_actions.rs @@ -115,7 +115,7 @@ impl VisitMut for ServerActions { let call = CallExpr { span: DUMMY_SP, - callee: action_name.clone().as_callee(), + callee: quote_ident!(action_name.clone()).as_callee(), args: vec![f .ident .clone() From fd756d15542a852fc71e4a045e3f92daf726ff91 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Donny/=EA=B0=95=EB=8F=99=EC=9C=A4?= Date: Fri, 20 Jan 2023 17:25:29 +0900 Subject: [PATCH 21/32] hygiene --- packages/next-swc/crates/core/src/server_actions.rs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/packages/next-swc/crates/core/src/server_actions.rs b/packages/next-swc/crates/core/src/server_actions.rs index b94b03ca1351b..fcad91b944562 100644 --- a/packages/next-swc/crates/core/src/server_actions.rs +++ b/packages/next-swc/crates/core/src/server_actions.rs @@ -7,7 +7,7 @@ use next_binding::swc::core::{ VarDecl, VarDeclKind, VarDeclarator, }, atoms::JsWord, - utils::{quote_ident, ExprFactory}, + utils::{private_ident, quote_ident, ExprFactory}, visit::{as_folder, noop_visit_mut_type, Fold, VisitMut, VisitMutWith}, }, }; @@ -66,6 +66,7 @@ impl VisitMut for ServerActions { } let action_name: JsWord = format!("$ACTION_{}", f.ident.sym).into(); + let action_ident = private_ident!(action_name.clone()); // myAction.$$typeof = Symbol.for('react.action.reference'); self.annotations.push(annotate( @@ -91,7 +92,7 @@ impl VisitMut for ServerActions { // myAction.$$name = '$ACTION_myAction'; self.annotations - .push(annotate(&f.ident, "$$name", action_name.clone().into())); + .push(annotate(&f.ident, "$$name", action_name.into())); if self.top_level { // export const $ACTION_myAction = myAction; @@ -104,7 +105,7 @@ impl VisitMut for ServerActions { declare: Default::default(), decls: vec![VarDeclarator { span: DUMMY_SP, - name: Ident::new(action_name, f.ident.span).into(), + name: action_ident.into(), init: Some(f.ident.clone().into()), definite: Default::default(), }], @@ -115,7 +116,7 @@ impl VisitMut for ServerActions { let call = CallExpr { span: DUMMY_SP, - callee: quote_ident!(action_name.clone()).as_callee(), + callee: action_ident.clone().as_callee(), args: vec![f .ident .clone() @@ -145,7 +146,7 @@ impl VisitMut for ServerActions { .push(ModuleItem::ModuleDecl(ModuleDecl::ExportDecl(ExportDecl { span: DUMMY_SP, decl: FnDecl { - ident: Ident::new(action_name, f.ident.span), + ident: action_ident, function: f.function.take(), declare: Default::default(), } From ddf58838a27a5ff82d43221a319d2dbc16a3eb13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Donny/=EA=B0=95=EB=8F=99=EC=9C=A4?= Date: Fri, 20 Jan 2023 17:38:59 +0900 Subject: [PATCH 22/32] Closure replacer --- .../crates/core/src/server_actions.rs | 156 +++++++++++++++++- 1 file changed, 148 insertions(+), 8 deletions(-) diff --git a/packages/next-swc/crates/core/src/server_actions.rs b/packages/next-swc/crates/core/src/server_actions.rs index fcad91b944562..7ac7e0a6dca5d 100644 --- a/packages/next-swc/crates/core/src/server_actions.rs +++ b/packages/next-swc/crates/core/src/server_actions.rs @@ -2,13 +2,17 @@ use next_binding::swc::core::{ common::{errors::HANDLER, util::take::Take, FileName, DUMMY_SP}, ecma::{ ast::{ - op, AssignExpr, BlockStmt, CallExpr, Decl, ExportDecl, Expr, ExprStmt, FnDecl, - Function, Ident, Lit, ModuleDecl, ModuleItem, PatOrExpr, ReturnStmt, Stmt, Str, - VarDecl, VarDeclKind, VarDeclarator, + op, ArrayLit, AssignExpr, BlockStmt, CallExpr, ComputedPropName, Decl, ExportDecl, + Expr, ExprStmt, FnDecl, Function, Id, Ident, KeyValueProp, Lit, MemberExpr, MemberProp, + ModuleDecl, ModuleItem, PatOrExpr, Prop, PropName, ReturnStmt, Stmt, Str, VarDecl, + VarDeclKind, VarDeclarator, }, atoms::JsWord, - utils::{private_ident, quote_ident, ExprFactory}, - visit::{as_folder, noop_visit_mut_type, Fold, VisitMut, VisitMutWith}, + utils::{find_pat_ids, private_ident, quote_ident, ExprFactory}, + visit::{ + as_folder, noop_visit_mut_type, noop_visit_type, visit_obj_and_computed, Fold, Visit, + VisitMut, VisitMutWith, VisitWith, + }, }, }; use serde::Deserialize; @@ -22,8 +26,9 @@ pub fn server_actions(file_name: &FileName, config: Config) -> impl VisitMut + F config, file_name: file_name.clone(), top_level: false, - extra_items: Default::default(), + closure_candidates: Default::default(), annotations: Default::default(), + extra_items: Default::default(), }) } @@ -33,13 +38,23 @@ struct ServerActions { top_level: bool, + closure_candidates: Vec, + annotations: Vec, extra_items: Vec, } impl VisitMut for ServerActions { fn visit_mut_fn_decl(&mut self, f: &mut FnDecl) { - f.visit_mut_children_with(self); + { + let mut old_len = self.closure_candidates.len(); + self.closure_candidates + .extend(find_pat_ids(&f.function.params)); + + f.visit_mut_children_with(self); + + self.closure_candidates.truncate(old_len); + } // Check if the first item is `"use action"`; if let Some(body) = &mut f.function.body { @@ -114,6 +129,32 @@ impl VisitMut for ServerActions { } else { // Hoist the function to the top level. + let mut used_ids = idents_used_by(&f.function.body); + + used_ids.retain(|id| self.closure_candidates.contains(id)); + + let closure_arg = private_ident!("closure"); + + f.function.body.visit_mut_with(&mut ClosureReplacer { + closure_arg: &closure_arg, + used_ids: &used_ids, + }); + + // myAction.$$closure = [id1, id2] + self.annotations.push(annotate( + &f.ident, + "$$closure", + ArrayLit { + span: DUMMY_SP, + elems: used_ids + .iter() + .cloned() + .map(|id| Some(id.as_arg())) + .collect(), + } + .into(), + )); + let call = CallExpr { span: DUMMY_SP, callee: action_ident.clone().as_callee(), @@ -147,7 +188,10 @@ impl VisitMut for ServerActions { span: DUMMY_SP, decl: FnDecl { ident: action_ident, - function: f.function.take(), + function: Box::new(Function { + params: vec![closure_arg.into()], + ..*f.function.take() + }), declare: Default::default(), } .into(), @@ -207,3 +251,99 @@ fn annotate(fn_name: &Ident, field_name: &str, value: Box) -> Stmt { .into(), }) } + +fn idents_used_by(n: &N) -> Vec +where + N: VisitWith, +{ + let mut v = IdentUsageCollector { + ..Default::default() + }; + n.visit_with(&mut v); + v.ids +} + +#[derive(Default)] +pub(crate) struct IdentUsageCollector { + ids: Vec, +} + +impl Visit for IdentUsageCollector { + noop_visit_type!(); + + visit_obj_and_computed!(); + + fn visit_ident(&mut self, n: &Ident) { + if self.ids.contains(&n.to_id()) { + return; + } + self.ids.push(n.to_id()); + } + + fn visit_member_prop(&mut self, n: &MemberProp) { + if let MemberProp::Computed(..) = n { + n.visit_children_with(self); + } + } + + fn visit_prop_name(&mut self, n: &PropName) { + if let PropName::Computed(..) = n { + n.visit_children_with(self); + } + } +} + +pub(crate) struct ClosureReplacer<'a> { + closure_arg: &'a Ident, + used_ids: &'a [Id], +} + +impl ClosureReplacer<'_> { + fn index(&self, i: &Ident) -> Option { + self.used_ids + .iter() + .position(|used_id| i.sym == used_id.0 && i.span.ctxt == used_id.1) + } +} + +impl VisitMut for ClosureReplacer<'_> { + fn visit_mut_expr(&mut self, e: &mut Expr) { + e.visit_mut_children_with(self); + + if let Expr::Ident(i) = e { + if let Some(index) = self.index(i) { + *e = Expr::Member(MemberExpr { + span: DUMMY_SP, + obj: self.closure_arg.clone().into(), + prop: MemberProp::Computed(ComputedPropName { + span: DUMMY_SP, + expr: index.into(), + }), + }); + } + } + } + + fn visit_mut_prop(&mut self, p: &mut Prop) { + p.visit_mut_children_with(self); + + if let Prop::Shorthand(i) = p { + if let Some(index) = self.index(i) { + *p = Prop::KeyValue(KeyValueProp { + key: PropName::Ident(i.clone()), + value: MemberExpr { + span: DUMMY_SP, + obj: self.closure_arg.clone().into(), + prop: MemberProp::Computed(ComputedPropName { + span: DUMMY_SP, + expr: index.into(), + }), + } + .into(), + }); + } + } + } + + noop_visit_mut_type!(); +} From e51066ae05fad40c7f9e9aac233fd1d9831e808b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Donny/=EA=B0=95=EB=8F=99=EC=9C=A4?= Date: Fri, 20 Jan 2023 19:44:43 +0900 Subject: [PATCH 23/32] Update test refs --- .../tests/fixture/server-actions/1/output.js | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 packages/next-swc/crates/core/tests/fixture/server-actions/1/output.js diff --git a/packages/next-swc/crates/core/tests/fixture/server-actions/1/output.js b/packages/next-swc/crates/core/tests/fixture/server-actions/1/output.js new file mode 100644 index 0000000000000..2a8001145e2cf --- /dev/null +++ b/packages/next-swc/crates/core/tests/fixture/server-actions/1/output.js @@ -0,0 +1,17 @@ +export function Item({ id1 , id2 }) { + async function deleteItem() { + return $ACTION_deleteItem(deleteItem.$$closure); + } + deleteItem.$$typeof = Symbol.for("react.action.reference"); + deleteItem.$$filepath = "/app/item.js"; + deleteItem.$$name = "$ACTION_deleteItem"; + deleteItem.$$closure = [ + id1, + id2 + ]; + return ; +} +export async function $ACTION_deleteItem(closure) { + await deleteFromDb(closure[0]); + await deleteFromDb(closure[1]); +} From 4ddc3e3c48330b1ad499ade8477e88642cfd59f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Donny/=EA=B0=95=EB=8F=99=EC=9C=A4?= Date: Fri, 20 Jan 2023 19:45:33 +0900 Subject: [PATCH 24/32] lints --- packages/next-swc/crates/core/src/server_actions.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/next-swc/crates/core/src/server_actions.rs b/packages/next-swc/crates/core/src/server_actions.rs index 7ac7e0a6dca5d..f34c1f4ac42ec 100644 --- a/packages/next-swc/crates/core/src/server_actions.rs +++ b/packages/next-swc/crates/core/src/server_actions.rs @@ -33,6 +33,7 @@ pub fn server_actions(file_name: &FileName, config: Config) -> impl VisitMut + F } struct ServerActions { + #[allow(unused)] config: Config, file_name: FileName, @@ -47,7 +48,7 @@ struct ServerActions { impl VisitMut for ServerActions { fn visit_mut_fn_decl(&mut self, f: &mut FnDecl) { { - let mut old_len = self.closure_candidates.len(); + let old_len = self.closure_candidates.len(); self.closure_candidates .extend(find_pat_ids(&f.function.params)); From d07b8d9a64954c91c8fc5cece3f7f9af5a1ef70c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Donny/=EA=B0=95=EB=8F=99=EC=9C=A4?= Date: Fri, 20 Jan 2023 19:46:38 +0900 Subject: [PATCH 25/32] transform --- packages/next-swc/crates/core/src/lib.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/next-swc/crates/core/src/lib.rs b/packages/next-swc/crates/core/src/lib.rs index 906e96d2c12a9..42c9fb72c02ab 100644 --- a/packages/next-swc/crates/core/src/lib.rs +++ b/packages/next-swc/crates/core/src/lib.rs @@ -256,6 +256,11 @@ where Some(config) => Either::Left(next_font_loaders::next_font_loaders(config.clone())), None => Either::Right(noop()), }, + match &opts.server_actions { + Some(config) => + Either::Left(server_actions::server_actions(&file.name, config.clone())), + None => Either::Right(noop()), + }, ) } From 40f1e3d2ca8a3aa0dcbd621b512154e03ceb3ccc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Donny/=EA=B0=95=EB=8F=99=EC=9C=A4?= Date: Fri, 20 Jan 2023 20:00:27 +0900 Subject: [PATCH 26/32] Add a test --- .../crates/core/tests/fixture/server-actions/2/input.js | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 packages/next-swc/crates/core/tests/fixture/server-actions/2/input.js diff --git a/packages/next-swc/crates/core/tests/fixture/server-actions/2/input.js b/packages/next-swc/crates/core/tests/fixture/server-actions/2/input.js new file mode 100644 index 0000000000000..7504c5890f70a --- /dev/null +++ b/packages/next-swc/crates/core/tests/fixture/server-actions/2/input.js @@ -0,0 +1,9 @@ +// /app/page.tsx (Server Component) +async function myAction(a, b, c) { + "use action"; + console.log('a') +} + +export default function Page() { + return ; +} \ No newline at end of file From 975c54d15fee460eee0ada322d2c13952bd40bfb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Donny/=EA=B0=95=EB=8F=99=EC=9C=A4?= Date: Fri, 20 Jan 2023 20:03:52 +0900 Subject: [PATCH 27/32] Stateless --- packages/next-swc/crates/core/src/server_actions.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/next-swc/crates/core/src/server_actions.rs b/packages/next-swc/crates/core/src/server_actions.rs index f34c1f4ac42ec..6041345f04c29 100644 --- a/packages/next-swc/crates/core/src/server_actions.rs +++ b/packages/next-swc/crates/core/src/server_actions.rs @@ -221,6 +221,7 @@ impl VisitMut for ServerActions { } fn visit_mut_stmts(&mut self, stmts: &mut Vec) { + let old_top_level = self.top_level; let old_annotations = self.annotations.take(); let mut new = Vec::with_capacity(stmts.len()); @@ -235,6 +236,7 @@ impl VisitMut for ServerActions { *stmts = new; self.annotations = old_annotations; + self.top_level = old_top_level; } noop_visit_mut_type!(); From 1081da3f021ee23699915d97cc9f968bedaf8b8e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Donny/=EA=B0=95=EB=8F=99=EC=9C=A4?= Date: Fri, 20 Jan 2023 20:04:00 +0900 Subject: [PATCH 28/32] Update test refs --- .../core/tests/fixture/server-actions/2/input.js | 1 - .../core/tests/fixture/server-actions/2/output.js | 10 ++++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) create mode 100644 packages/next-swc/crates/core/tests/fixture/server-actions/2/output.js diff --git a/packages/next-swc/crates/core/tests/fixture/server-actions/2/input.js b/packages/next-swc/crates/core/tests/fixture/server-actions/2/input.js index 7504c5890f70a..b71c4d7ec3430 100644 --- a/packages/next-swc/crates/core/tests/fixture/server-actions/2/input.js +++ b/packages/next-swc/crates/core/tests/fixture/server-actions/2/input.js @@ -1,4 +1,3 @@ -// /app/page.tsx (Server Component) async function myAction(a, b, c) { "use action"; console.log('a') diff --git a/packages/next-swc/crates/core/tests/fixture/server-actions/2/output.js b/packages/next-swc/crates/core/tests/fixture/server-actions/2/output.js new file mode 100644 index 0000000000000..ef6346f84df08 --- /dev/null +++ b/packages/next-swc/crates/core/tests/fixture/server-actions/2/output.js @@ -0,0 +1,10 @@ +async function myAction(a, b, c) { + console.log('a'); +} +myAction.$$typeof = Symbol.for("react.action.reference"); +myAction.$$filepath = "/app/item.js"; +myAction.$$name = "$ACTION_myAction"; +export const $ACTION_myAction = myAction; +export default function Page() { + return ; +} From 5d292ff7c57647653c654ca3bdfabff5f9dd8145 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Donny/=EA=B0=95=EB=8F=99=EC=9C=A4?= Date: Fri, 20 Jan 2023 20:32:41 +0900 Subject: [PATCH 29/32] Add a test --- .../crates/core/tests/fixture/server-actions/3/input.js | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 packages/next-swc/crates/core/tests/fixture/server-actions/3/input.js diff --git a/packages/next-swc/crates/core/tests/fixture/server-actions/3/input.js b/packages/next-swc/crates/core/tests/fixture/server-actions/3/input.js new file mode 100644 index 0000000000000..433b9cadb1ad9 --- /dev/null +++ b/packages/next-swc/crates/core/tests/fixture/server-actions/3/input.js @@ -0,0 +1,5 @@ +// app/send.ts +"use action"; +export async function myAction(a, b, c) { + console.log('a') +} \ No newline at end of file From 55f8bd0d15384fa02cb3f4e39502cbfcc770b9df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Donny/=EA=B0=95=EB=8F=99=EC=9C=A4?= Date: Fri, 20 Jan 2023 20:37:27 +0900 Subject: [PATCH 30/32] file-level --- .../crates/core/src/server_actions.rs | 46 +++++++++++++++---- 1 file changed, 36 insertions(+), 10 deletions(-) diff --git a/packages/next-swc/crates/core/src/server_actions.rs b/packages/next-swc/crates/core/src/server_actions.rs index 6041345f04c29..a735485b4763e 100644 --- a/packages/next-swc/crates/core/src/server_actions.rs +++ b/packages/next-swc/crates/core/src/server_actions.rs @@ -25,6 +25,8 @@ pub fn server_actions(file_name: &FileName, config: Config) -> impl VisitMut + F as_folder(ServerActions { config, file_name: file_name.clone(), + in_action_file: false, + in_export_decl: false, top_level: false, closure_candidates: Default::default(), annotations: Default::default(), @@ -37,6 +39,8 @@ struct ServerActions { config: Config, file_name: FileName, + in_action_file: bool, + in_export_decl: bool, top_level: bool, closure_candidates: Vec, @@ -46,6 +50,13 @@ struct ServerActions { } impl VisitMut for ServerActions { + fn visit_mut_export_decl(&mut self, decl: &mut ExportDecl) { + let old = self.in_export_decl; + self.in_export_decl = true; + decl.decl.visit_mut_with(self); + self.in_export_decl = old; + } + fn visit_mut_fn_decl(&mut self, f: &mut FnDecl) { { let old_len = self.closure_candidates.len(); @@ -57,20 +68,22 @@ impl VisitMut for ServerActions { self.closure_candidates.truncate(old_len); } - // Check if the first item is `"use action"`; - if let Some(body) = &mut f.function.body { - if let Some(Stmt::Expr(first)) = body.stmts.first() { - match &*first.expr { - Expr::Lit(Lit::Str(Str { value, .. })) if value == "use action" => {} - _ => return, + if !(self.in_action_file && self.in_export_decl) { + // Check if the first item is `"use action"`; + if let Some(body) = &mut f.function.body { + if let Some(Stmt::Expr(first)) = body.stmts.first() { + match &*first.expr { + Expr::Lit(Lit::Str(Str { value, .. })) if value == "use action" => {} + _ => return, + } + } else { + return; } + + body.stmts.remove(0); } else { return; } - - body.stmts.remove(0); - } else { - return; } if !f.function.is_async { @@ -203,6 +216,19 @@ impl VisitMut for ServerActions { } fn visit_mut_module_items(&mut self, stmts: &mut Vec) { + if let Some(ModuleItem::Stmt(Stmt::Expr(first))) = stmts.first() { + match &*first.expr { + Expr::Lit(Lit::Str(Str { value, .. })) if value == "use action" => { + self.in_action_file = true; + } + _ => {} + } + } + + if self.in_action_file { + stmts.remove(0); + } + let old_annotations = self.annotations.take(); let mut new = Vec::with_capacity(stmts.len()); From e3ff737413d08b935964246496e2a4ee69b4c8d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Donny/=EA=B0=95=EB=8F=99=EC=9C=A4?= Date: Fri, 20 Jan 2023 20:40:17 +0900 Subject: [PATCH 31/32] Unexport --- .../next-swc/crates/core/src/server_actions.rs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/packages/next-swc/crates/core/src/server_actions.rs b/packages/next-swc/crates/core/src/server_actions.rs index a735485b4763e..43f7ae7281640 100644 --- a/packages/next-swc/crates/core/src/server_actions.rs +++ b/packages/next-swc/crates/core/src/server_actions.rs @@ -215,6 +215,20 @@ impl VisitMut for ServerActions { } } + fn visit_mut_module_item(&mut self, s: &mut ModuleItem) { + s.visit_mut_children_with(self); + + if self.in_action_file { + if let ModuleItem::ModuleDecl(ModuleDecl::ExportDecl(ExportDecl { + decl: decl @ Decl::Fn(..), + .. + })) = s + { + *s = ModuleItem::Stmt(Stmt::Decl(decl.take())); + } + } + } + fn visit_mut_module_items(&mut self, stmts: &mut Vec) { if let Some(ModuleItem::Stmt(Stmt::Expr(first))) = stmts.first() { match &*first.expr { From afc575a76ec51da5ff968026b2007e165d20461e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Donny/=EA=B0=95=EB=8F=99=EC=9C=A4?= Date: Fri, 20 Jan 2023 20:40:21 +0900 Subject: [PATCH 32/32] Update test refs --- .../crates/core/tests/fixture/server-actions/3/output.js | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 packages/next-swc/crates/core/tests/fixture/server-actions/3/output.js diff --git a/packages/next-swc/crates/core/tests/fixture/server-actions/3/output.js b/packages/next-swc/crates/core/tests/fixture/server-actions/3/output.js new file mode 100644 index 0000000000000..2c58682402736 --- /dev/null +++ b/packages/next-swc/crates/core/tests/fixture/server-actions/3/output.js @@ -0,0 +1,8 @@ +// app/send.ts +async function myAction(a, b, c) { + console.log('a'); +} +myAction.$$typeof = Symbol.for("react.action.reference"); +myAction.$$filepath = "/app/item.js"; +myAction.$$name = "$ACTION_myAction"; +export const $ACTION_myAction = myAction;