Skip to content

Commit 5fce570

Browse files
committed
Server actions transform data-urls
1 parent e5156f5 commit 5fce570

File tree

8 files changed

+153
-47
lines changed

8 files changed

+153
-47
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/next-core/src/next_shared/transforms/server_actions.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
use anyhow::Result;
22
use async_trait::async_trait;
3-
use next_custom_transforms::transforms::server_actions::{server_actions, Config};
3+
use next_custom_transforms::transforms::server_actions::{
4+
server_actions, Config, ServerActionMode,
5+
};
46
use swc_core::{common::FileName, ecma::ast::Program};
57
use turbo_rcstr::RcStr;
68
use turbo_tasks::{ResolvedVc, Vc};
@@ -63,6 +65,7 @@ impl CustomTransformer for NextServerActions {
6365
use_cache_enabled: self.use_cache_enabled,
6466
hash_salt: self.encryption_key.await?.to_string(),
6567
cache_kinds: self.cache_kinds.owned().await?,
68+
mode: ServerActionMode::Turbopack,
6669
},
6770
ctx.comments.clone(),
6871
Default::default(),

crates/next-custom-transforms/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ swc_core = { workspace = true, features = [
3838
"cached",
3939
"common_concurrent",
4040
"ecma_ast",
41+
"ecma_codegen",
4142
"ecma_loader_lru",
4243
"ecma_loader_node",
4344
"ecma_minifier",
@@ -59,6 +60,7 @@ swc_emotion = { workspace = true }
5960
swc_relay = { workspace = true }
6061
turbopack-ecmascript-plugins = { workspace = true, optional = true }
6162
turbo-rcstr = { workspace = true }
63+
urlencoding = { workspace = true }
6264

6365
react_remove_properties = "0.33.0"
6466
remove_console = "0.34.0"

crates/next-custom-transforms/src/transforms/server_actions.rs

Lines changed: 118 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ use std::{
44
convert::{TryFrom, TryInto},
55
mem::{replace, take},
66
rc::Rc,
7+
sync::Arc,
78
};
89

910
use hex::encode as hex_encode;
@@ -12,23 +13,32 @@ use rustc_hash::{FxHashMap, FxHashSet};
1213
use serde::Deserialize;
1314
use sha1::{Digest, Sha1};
1415
use swc_core::{
15-
atoms::Atom,
16+
atoms::{atom, Atom},
1617
common::{
1718
comments::{Comment, CommentKind, Comments},
1819
errors::HANDLER,
1920
source_map::PURE_SP,
2021
util::take::Take,
21-
BytePos, FileName, Mark, Span, SyntaxContext, DUMMY_SP,
22+
BytePos, FileName, Mark, SourceMap, Span, SyntaxContext, DUMMY_SP,
2223
},
2324
ecma::{
2425
ast::*,
26+
codegen::{text_writer::JsWriter, Emitter},
2527
utils::{private_ident, quote_ident, ExprFactory},
2628
visit::{noop_visit_mut_type, visit_mut_pass, VisitMut, VisitMutWith},
2729
},
2830
quote,
2931
};
3032
use turbo_rcstr::RcStr;
3133

34+
use crate::FxIndexMap;
35+
36+
#[derive(Clone, Copy, Debug, Deserialize)]
37+
pub enum ServerActionMode {
38+
Webpack,
39+
Turbopack,
40+
}
41+
3242
#[derive(Clone, Debug, Deserialize)]
3343
#[serde(deny_unknown_fields, rename_all = "camelCase")]
3444
pub struct Config {
@@ -37,6 +47,7 @@ pub struct Config {
3747
pub use_cache_enabled: bool,
3848
pub hash_salt: String,
3949
pub cache_kinds: FxHashSet<RcStr>,
50+
pub mode: ServerActionMode,
4051
}
4152

4253
#[derive(Clone, Debug)]
@@ -1809,46 +1820,49 @@ impl<C: Comments> VisitMut for ServerActions<C> {
18091820
let call_server_ident = private_ident!("callServer");
18101821
let find_source_map_url_ident = private_ident!("findSourceMapURL");
18111822

1812-
if (self.has_action || self.has_cache) && !self.config.is_react_server_layer {
1813-
// import {
1814-
// createServerReference,
1815-
// callServer,
1816-
// findSourceMapURL
1817-
// } from 'private-next-rsc-action-client-wrapper'
1818-
// createServerReference("action_id")
1819-
new.push(ModuleItem::ModuleDecl(ModuleDecl::Import(ImportDecl {
1820-
span: DUMMY_SP,
1821-
specifiers: vec![
1822-
ImportSpecifier::Named(ImportNamedSpecifier {
1823-
span: DUMMY_SP,
1824-
local: create_ref_ident.clone(),
1825-
imported: None,
1826-
is_type_only: false,
1827-
}),
1828-
ImportSpecifier::Named(ImportNamedSpecifier {
1823+
let client_layer_import = ((self.has_action || self.has_cache)
1824+
&& !self.config.is_react_server_layer)
1825+
.then(|| {
1826+
// import {
1827+
// createServerReference,
1828+
// callServer,
1829+
// findSourceMapURL
1830+
// } from 'private-next-rsc-action-client-wrapper'
1831+
// createServerReference("action_id")
1832+
ModuleItem::ModuleDecl(ModuleDecl::Import(ImportDecl {
1833+
span: DUMMY_SP,
1834+
specifiers: vec![
1835+
ImportSpecifier::Named(ImportNamedSpecifier {
1836+
span: DUMMY_SP,
1837+
local: create_ref_ident.clone(),
1838+
imported: None,
1839+
is_type_only: false,
1840+
}),
1841+
ImportSpecifier::Named(ImportNamedSpecifier {
1842+
span: DUMMY_SP,
1843+
local: call_server_ident.clone(),
1844+
imported: None,
1845+
is_type_only: false,
1846+
}),
1847+
ImportSpecifier::Named(ImportNamedSpecifier {
1848+
span: DUMMY_SP,
1849+
local: find_source_map_url_ident.clone(),
1850+
imported: None,
1851+
is_type_only: false,
1852+
}),
1853+
],
1854+
src: Box::new(Str {
18291855
span: DUMMY_SP,
1830-
local: call_server_ident.clone(),
1831-
imported: None,
1832-
is_type_only: false,
1856+
value: "private-next-rsc-action-client-wrapper".into(),
1857+
raw: None,
18331858
}),
1834-
ImportSpecifier::Named(ImportNamedSpecifier {
1835-
span: DUMMY_SP,
1836-
local: find_source_map_url_ident.clone(),
1837-
imported: None,
1838-
is_type_only: false,
1839-
}),
1840-
],
1841-
src: Box::new(Str {
1842-
span: DUMMY_SP,
1843-
value: "private-next-rsc-action-client-wrapper".into(),
1844-
raw: None,
1845-
}),
1846-
type_only: false,
1847-
with: None,
1848-
phase: Default::default(),
1849-
})));
1850-
new.rotate_right(1);
1851-
}
1859+
type_only: false,
1860+
with: None,
1861+
phase: Default::default(),
1862+
}))
1863+
});
1864+
1865+
let mut client_layer_exports = FxIndexMap::default();
18521866

18531867
// If it's a "use server" or a "use cache" file, all exports need to be annotated.
18541868
if should_track_exports {
@@ -1885,7 +1899,7 @@ impl<C: Comments> VisitMut for ServerActions<C> {
18851899
})),
18861900
},
18871901
));
1888-
new.push(export_expr);
1902+
client_layer_exports.insert(atom!("default"), export_expr);
18891903
} else {
18901904
let export_expr =
18911905
ModuleItem::ModuleDecl(ModuleDecl::ExportDecl(ExportDecl {
@@ -1932,7 +1946,7 @@ impl<C: Comments> VisitMut for ServerActions<C> {
19321946
..Default::default()
19331947
})),
19341948
}));
1935-
new.push(export_expr);
1949+
client_layer_exports.insert(export_name.clone(), export_expr);
19361950
}
19371951
} else if !in_cache_file {
19381952
self.annotations.push(Stmt::Expr(ExprStmt {
@@ -2098,6 +2112,50 @@ impl<C: Comments> VisitMut for ServerActions<C> {
20982112
new.rotate_right(2);
20992113
}
21002114

2115+
if (self.has_action || self.has_cache) && !self.config.is_react_server_layer {
2116+
match self.config.mode {
2117+
ServerActionMode::Webpack => {
2118+
new.push(client_layer_import.unwrap());
2119+
new.rotate_right(1);
2120+
new.extend(client_layer_exports.into_iter().map(|(_, v)| v));
2121+
}
2122+
ServerActionMode::Turbopack => {
2123+
for (export, stmt) in client_layer_exports {
2124+
new.push(ModuleItem::ModuleDecl(ModuleDecl::ExportNamed(
2125+
NamedExport {
2126+
specifiers: vec![ExportSpecifier::Named(ExportNamedSpecifier {
2127+
span: DUMMY_SP,
2128+
orig: ModuleExportName::Ident(export.into()),
2129+
exported: None,
2130+
is_type_only: false,
2131+
})],
2132+
src: Some(Box::new(
2133+
program_to_data_url(&Program::Module(Module {
2134+
span: DUMMY_SP,
2135+
body: vec![
2136+
ModuleItem::Stmt(Stmt::Expr(ExprStmt {
2137+
expr: Box::new(Expr::Lit(Lit::Str(
2138+
"use turbopack no side effects".into(),
2139+
))),
2140+
span: DUMMY_SP,
2141+
})),
2142+
client_layer_import.clone().unwrap(),
2143+
stmt,
2144+
],
2145+
shebang: None,
2146+
}))
2147+
.into(),
2148+
)),
2149+
span: DUMMY_SP,
2150+
type_only: false,
2151+
with: None,
2152+
},
2153+
)));
2154+
}
2155+
}
2156+
}
2157+
}
2158+
21012159
*stmts = new;
21022160

21032161
self.annotations = old_annotations;
@@ -3102,3 +3160,21 @@ fn emit_error(error_kind: ServerActionsErrorKind) {
31023160

31033161
HANDLER.with(|handler| handler.struct_span_err(span, &msg).emit());
31043162
}
3163+
3164+
fn program_to_data_url(program: &Program) -> String {
3165+
let mut output = vec![];
3166+
let sourcemap = Arc::new(SourceMap::default());
3167+
let mut emitter = Emitter {
3168+
cfg: Default::default(),
3169+
cm: sourcemap.clone(),
3170+
wr: Box::new(JsWriter::new(sourcemap.clone(), " ", &mut output, None)),
3171+
comments: None,
3172+
};
3173+
3174+
// println!("Emitting: {:?}", module);
3175+
emitter.emit_program(program).unwrap();
3176+
drop(emitter);
3177+
3178+
let output = String::from_utf8(output).expect("codegen generated non-utf8 output");
3179+
format!("data:text/javascript,{}", urlencoding::encode(&output))
3180+
}

crates/next-custom-transforms/tests/errors.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,7 @@ use next_custom_transforms::transforms::{
66
fonts::{next_font_loaders, Config as FontLoaderConfig},
77
next_ssg::next_ssg,
88
react_server_components::server_components,
9-
server_actions::{
10-
server_actions, {self},
11-
},
9+
server_actions::{self, server_actions, ServerActionMode},
1210
strip_page_exports::{next_transform_strip_page_exports, ExportFilter},
1311
};
1412
use rustc_hash::FxHashSet;
@@ -169,6 +167,7 @@ fn react_server_actions_errors(input: PathBuf) {
169167
use_cache_enabled: true,
170168
hash_salt: "".into(),
171169
cache_kinds: FxHashSet::default(),
170+
mode: ServerActionMode::Webpack,
172171
},
173172
tr.comments.as_ref().clone(),
174173
Default::default(),
@@ -230,6 +229,7 @@ fn use_cache_not_allowed(input: PathBuf) {
230229
use_cache_enabled: false,
231230
hash_salt: "".into(),
232231
cache_kinds: FxHashSet::from_iter(["x".into()]),
232+
mode: ServerActionMode::Webpack,
233233
},
234234
tr.comments.as_ref().clone(),
235235
Default::default(),

crates/next-custom-transforms/tests/fixture.rs

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ use next_custom_transforms::transforms::{
1818
page_config::page_config_test,
1919
pure::pure_magic,
2020
react_server_components::server_components,
21-
server_actions::{self, server_actions},
21+
server_actions::{self, server_actions, ServerActionMode},
2222
shake_exports::{shake_exports, Config as ShakeExportsConfig},
2323
strip_page_exports::{next_transform_strip_page_exports, ExportFilter},
2424
warn_for_edge_runtime::warn_for_edge_runtime,
@@ -542,6 +542,11 @@ fn server_actions_fixture(input: PathBuf) {
542542
let output = input.parent().unwrap().join("output.js");
543543
let is_react_server_layer = input.iter().any(|s| s.to_str() == Some("server-graph"));
544544
let is_development = input.iter().any(|s| s.to_str() == Some("development"));
545+
let mode = if input.iter().any(|s| s.to_str() == Some("turbopack")) {
546+
ServerActionMode::Turbopack
547+
} else {
548+
ServerActionMode::Webpack
549+
};
545550
test_fixture(
546551
syntax(),
547552
&|_tr| {
@@ -555,6 +560,7 @@ fn server_actions_fixture(input: PathBuf) {
555560
use_cache_enabled: true,
556561
hash_salt: "".into(),
557562
cache_kinds: FxHashSet::from_iter(["x".into()]),
563+
mode,
558564
},
559565
_tr.comments.as_ref().clone(),
560566
Default::default(),
@@ -590,6 +596,7 @@ fn next_font_with_directive_fixture(input: PathBuf) {
590596
use_cache_enabled: true,
591597
hash_salt: "".into(),
592598
cache_kinds: FxHashSet::default(),
599+
mode: ServerActionMode::Webpack,
593600
},
594601
_tr.comments.as_ref().clone(),
595602
Default::default(),
@@ -881,6 +888,11 @@ fn test_source_maps(input: PathBuf) {
881888
let output: PathBuf = input.parent().unwrap().join("output.js");
882889
let is_react_server_layer = input.iter().any(|s| s.to_str() == Some("server-graph"));
883890
let is_development = input.iter().any(|s| s.to_str() == Some("development"));
891+
let mode = if input.iter().any(|s| s.to_str() == Some("turbopack")) {
892+
ServerActionMode::Turbopack
893+
} else {
894+
ServerActionMode::Webpack
895+
};
884896

885897
test_fixture(
886898
syntax(),
@@ -895,6 +907,7 @@ fn test_source_maps(input: PathBuf) {
895907
use_cache_enabled: true,
896908
hash_salt: "".into(),
897909
cache_kinds: FxHashSet::from_iter([]),
910+
mode,
898911
},
899912
_tr.comments.as_ref().clone(),
900913
Default::default(),
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
'use server'
2+
3+
export async function foo() {
4+
console.log('action foo')
5+
}
6+
7+
export async function bar() {
8+
console.log('action bar')
9+
}

crates/next-custom-transforms/tests/fixture/server-actions/client-graph/turbopack/1/output.js

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)