Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Change Server Reference creation on client #48824

Merged
merged 11 commits into from
Apr 27, 2023
227 changes: 124 additions & 103 deletions packages/next-swc/crates/core/src/server_actions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -855,59 +855,46 @@ impl<C: Comments> VisitMut for ServerActions<C> {

// If it's a "use server" file, all exports need to be annotated as actions.
if self.in_action_file {
// If it's compiled in the client layer, each export field needs to be
// wrapped by a reference creation call.
let create_ref_ident = private_ident!("createServerReference");
if !self.config.is_server {
// import createServerReference from 'private-next-rsc-action-client-wrapper'
// createServerReference("action_id")
new.push(ModuleItem::ModuleDecl(ModuleDecl::Import(ImportDecl {
span: DUMMY_SP,
specifiers: vec![ImportSpecifier::Default(ImportDefaultSpecifier {
span: DUMMY_SP,
local: create_ref_ident.clone(),
})],
src: Box::new(Str {
span: DUMMY_SP,
value: "private-next-rsc-action-client-wrapper".into(),
raw: None,
}),
type_only: false,
asserts: None,
})));
}

for (id, export_name) in self.exported_idents.iter() {
let ident = Ident::new(id.0.clone(), DUMMY_SP.with_ctxt(id.1));
annotate_ident_as_action(
&mut self.annotations,
ident.clone(),
Vec::new(),
self.file_name.to_string(),
export_name.to_string(),
false,
None,
);

if !self.config.is_server {
let params_ident = private_ident!("args");
let noop_fn = Box::new(Function {
params: vec![Param {
span: DUMMY_SP,
decorators: Default::default(),
pat: Pat::Rest(RestPat {
span: DUMMY_SP,
dot3_token: DUMMY_SP,
arg: Box::new(Pat::Ident(params_ident.clone().into())),
type_ann: None,
}),
}],
decorators: Vec::new(),
span: DUMMY_SP,
body: Some(BlockStmt {
span: DUMMY_SP,
stmts: vec![Stmt::Return(ReturnStmt {
span: DUMMY_SP,
arg: Some(Box::new(Expr::Call(CallExpr {
span: DUMMY_SP,
callee: Callee::Expr(Box::new(Expr::Ident(private_ident!(
"__build_action__"
)))),
args: vec![ident.clone().as_arg(), params_ident.as_arg()],
type_args: None,
}))),
})],
}),
is_generator: false,
is_async: true,
type_params: None,
return_type: None,
});
let action_id =
generate_action_id(self.file_name.to_string(), export_name.to_string());

if export_name == "default" {
let export_expr = ModuleItem::ModuleDecl(ModuleDecl::ExportDefaultExpr(
ExportDefaultExpr {
span: DUMMY_SP,
expr: Box::new(Expr::Fn(FnExpr {
ident: Some(ident),
function: noop_fn,
expr: Box::new(Expr::Call(CallExpr {
span: DUMMY_SP,
callee: Callee::Expr(Box::new(Expr::Ident(
create_ref_ident.clone(),
))),
args: vec![action_id.as_arg()],
type_args: None,
})),
},
));
Expand All @@ -916,66 +903,92 @@ impl<C: Comments> VisitMut for ServerActions<C> {
let export_expr =
ModuleItem::ModuleDecl(ModuleDecl::ExportDecl(ExportDecl {
span: DUMMY_SP,
decl: Decl::Fn(FnDecl {
ident,
decl: Decl::Var(Box::new(VarDecl {
span: DUMMY_SP,
kind: VarDeclKind::Const,
declare: false,
function: noop_fn,
}),
decls: vec![VarDeclarator {
span: DUMMY_SP,
name: Pat::Ident(ident.into()),
init: Some(Box::new(Expr::Call(CallExpr {
span: DUMMY_SP,
callee: Callee::Expr(Box::new(Expr::Ident(
create_ref_ident.clone(),
))),
args: vec![action_id.as_arg()],
type_args: None,
}))),
definite: false,
}],
})),
}));
new.push(export_expr);
}
} else {
annotate_ident_as_action(
&mut self.annotations,
ident.clone(),
Vec::new(),
self.file_name.to_string(),
export_name.to_string(),
false,
None,
);
}
}
new.append(&mut self.extra_items);

// Ensure that the exports are valid by appending a check
// import { ensureServerEntryExports } from 'private-next-rsc-action-proxy'
// ensureServerEntryExports([action1, action2, ...])
let ensure_ident = private_ident!("ensureServerEntryExports");
new.push(ModuleItem::ModuleDecl(ModuleDecl::Import(ImportDecl {
span: DUMMY_SP,
specifiers: vec![ImportSpecifier::Default(ImportDefaultSpecifier {
span: DUMMY_SP,
local: ensure_ident.clone(),
})],
src: Box::new(Str {
if self.config.is_server {
new.append(&mut self.extra_items);

// Ensure that the exports are valid by appending a check
// import { ensureServerEntryExports } from 'private-next-rsc-action-proxy'
// ensureServerEntryExports([action1, action2, ...])
let ensure_ident = private_ident!("ensureServerEntryExports");
new.push(ModuleItem::ModuleDecl(ModuleDecl::Import(ImportDecl {
span: DUMMY_SP,
value: "private-next-rsc-action-proxy".into(),
raw: None,
}),
type_only: false,
asserts: None,
})));
new.push(ModuleItem::Stmt(Stmt::Expr(ExprStmt {
span: DUMMY_SP,
expr: Box::new(Expr::Call(CallExpr {
specifiers: vec![ImportSpecifier::Default(ImportDefaultSpecifier {
span: DUMMY_SP,
local: ensure_ident.clone(),
})],
src: Box::new(Str {
span: DUMMY_SP,
value: "private-next-rsc-action-proxy".into(),
raw: None,
}),
type_only: false,
asserts: None,
})));
new.push(ModuleItem::Stmt(Stmt::Expr(ExprStmt {
span: DUMMY_SP,
callee: Callee::Expr(Box::new(Expr::Ident(ensure_ident))),
args: vec![ExprOrSpread {
spread: None,
expr: Box::new(Expr::Array(ArrayLit {
span: DUMMY_SP,
elems: self
.exported_idents
.iter()
.map(|e| {
Some(ExprOrSpread {
spread: None,
expr: Box::new(Expr::Ident(Ident::new(
e.0 .0.clone(),
DUMMY_SP.with_ctxt(e.0 .1),
))),
expr: Box::new(Expr::Call(CallExpr {
span: DUMMY_SP,
callee: Callee::Expr(Box::new(Expr::Ident(ensure_ident))),
args: vec![ExprOrSpread {
spread: None,
expr: Box::new(Expr::Array(ArrayLit {
span: DUMMY_SP,
elems: self
.exported_idents
.iter()
.map(|e| {
Some(ExprOrSpread {
spread: None,
expr: Box::new(Expr::Ident(Ident::new(
e.0 .0.clone(),
DUMMY_SP.with_ctxt(e.0 .1),
))),
})
})
})
.collect(),
})),
}],
type_args: None,
})),
})));
.collect(),
})),
}],
type_args: None,
})),
})));

// Append annotations to the end of the file.
new.extend(self.annotations.drain(..).map(ModuleItem::Stmt));
// Append annotations to the end of the file.
new.extend(self.annotations.drain(..).map(ModuleItem::Stmt));
}
}

*stmts = new;
Expand Down Expand Up @@ -1149,6 +1162,18 @@ fn pat_to_assign_pat(
}
}

fn generate_action_id(file_name: String, export_name: String) -> String {
// Attach a checksum to the action using sha1:
// $$id = sha1('file_name' + ':' + 'export_name');
let mut hasher = Sha1::new();
hasher.update(file_name.as_bytes());
hasher.update(b":");
hasher.update(export_name.as_bytes());
let result = hasher.finalize();

hex_encode(result).into()
}

fn annotate_ident_as_action(
annotations: &mut Vec<Stmt>,
ident: Ident,
Expand All @@ -1173,16 +1198,12 @@ fn annotate_ident_as_action(
.into(),
));

// Attach a checksum to the action using sha1:
// myAction.$$id = sha1('file_name' + ':' + 'export_name');
let mut hasher = Sha1::new();
hasher.update(file_name.as_bytes());
hasher.update(b":");
hasher.update(export_name.as_bytes());
let result = hasher.finalize();

// Convert result to hex string
annotations.push(annotate(&ident, "$$id", hex_encode(result).into()));
annotations.push(annotate(
&ident,
"$$id",
generate_action_id(file_name, export_name).into(),
));

// myAction.$$bound = [];
annotations.push(annotate(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,20 +1,4 @@
// app/send.ts
/* __next_internal_action_entry_do_not_use__ myAction,default */ export async function myAction(...args) {
return __build_action__(myAction, args);
}
export default async function $$ACTION_0(...args) {
return __build_action__($$ACTION_0, args);
};
import ensureServerEntryExports from "private-next-rsc-action-proxy";
ensureServerEntryExports([
myAction,
$$ACTION_0
]);
myAction.$$typeof = Symbol.for("react.server.reference");
myAction.$$id = "e10665baac148856374b2789aceb970f66fec33e";
myAction.$$bound = [];
myAction.$$with_bound = false;
$$ACTION_0.$$typeof = Symbol.for("react.server.reference");
$$ACTION_0.$$id = "c18c215a6b7cdc64bf709f3a714ffdef1bf9651d";
$$ACTION_0.$$bound = [];
$$ACTION_0.$$with_bound = false;
/* __next_internal_action_entry_do_not_use__ myAction,default */ import createServerReference from "private-next-rsc-action-client-wrapper";
export const myAction = createServerReference("e10665baac148856374b2789aceb970f66fec33e");
export default createServerReference("c18c215a6b7cdc64bf709f3a714ffdef1bf9651d");
Original file line number Diff line number Diff line change
@@ -1,12 +1,3 @@
// app/send.ts
/* __next_internal_action_entry_do_not_use__ foo */ export async function foo(...args) {
return __build_action__(foo, args);
}
import ensureServerEntryExports from "private-next-rsc-action-proxy";
ensureServerEntryExports([
foo
]);
foo.$$typeof = Symbol.for("react.server.reference");
foo.$$id = "ab21efdafbe611287bc25c0462b1e0510d13e48b";
foo.$$bound = [];
foo.$$with_bound = false;
/* __next_internal_action_entry_do_not_use__ foo */ import createServerReference from "private-next-rsc-action-client-wrapper";
export const foo = createServerReference("ab21efdafbe611287bc25c0462b1e0510d13e48b");
6 changes: 5 additions & 1 deletion packages/next/src/build/webpack-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
APP_DIR_ALIAS,
WEBPACK_LAYERS,
RSC_ACTION_PROXY_ALIAS,
RSC_ACTION_CLIENT_WRAPPER_ALIAS,
} from '../lib/constants'
import { fileExists } from '../lib/file-exists'
import { CustomRoutes } from '../lib/load-custom-routes.js'
Expand Down Expand Up @@ -1058,6 +1059,9 @@ export default async function getBaseWebpackConfig(
[RSC_ACTION_PROXY_ALIAS]:
'next/dist/build/webpack/loaders/next-flight-loader/action-proxy',

[RSC_ACTION_CLIENT_WRAPPER_ALIAS]:
'next/dist/build/webpack/loaders/next-flight-loader/action-client-wrapper',

...(isClient || isEdgeServer
? {
[clientResolveRewrites]: hasRewrites
Expand Down Expand Up @@ -1263,7 +1267,7 @@ export default async function getBaseWebpackConfig(
}

const notExternalModules =
/^(?:private-next-pages\/|next\/(?:dist\/pages\/|(?:app|document|link|image|legacy\/image|constants|dynamic|script|navigation|headers)$)|string-hash|private-next-rsc-action-proxy$)/
/^(?:private-next-pages\/|next\/(?:dist\/pages\/|(?:app|document|link|image|legacy\/image|constants|dynamic|script|navigation|headers)$)|string-hash|private-next-rsc-action-proxy|private-next-rsc-action-client-wrapper$)/
if (notExternalModules.test(request)) {
return
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// This file must be bundled in the app's client layer.

import { createServerReference } from 'next/dist/compiled/react-server-dom-webpack/client'
import { callServer } from 'next/dist/client/app-call-server'

// A noop wrapper to let the Flight client create the server reference.
// See also: https://github.com/facebook/react/pull/26632
export default function (id: string) {
return createServerReference(id, callServer)
}
2 changes: 2 additions & 0 deletions packages/next/src/lib/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ export const ROOT_DIR_ALIAS = 'private-next-root-dir'
export const APP_DIR_ALIAS = 'private-next-app-dir'
export const RSC_MOD_REF_PROXY_ALIAS = 'private-next-rsc-mod-ref-proxy'
export const RSC_ACTION_PROXY_ALIAS = 'private-next-rsc-action-proxy'
export const RSC_ACTION_CLIENT_WRAPPER_ALIAS =
'private-next-rsc-action-client-wrapper'

export const PUBLIC_DIR_MIDDLEWARE_CONFLICT = `You can not have a '_next' folder inside of your public folder. This conflicts with the internal '/_next' route. https://nextjs.org/docs/messages/public-next-folder-conflict`

Expand Down