Fix server component transforms (#49135)

This makes sure that we are treating a module as in the server layer, if
it's under the client layer but with file-level `"use server"`. Also
makes sure we're catching errors like having both `"use server"` and
`"use client"` directives presented.
This commit is contained in:
Shu Ding 2023-05-03 23:20:32 +02:00 committed by GitHub
parent e659653e48
commit d957327d71
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 95 additions and 6 deletions

View file

@ -61,7 +61,8 @@ impl<C: Comments> VisitMut for ReactServerComponents<C> {
noop_visit_mut_type!();
fn visit_mut_module(&mut self, module: &mut Module) {
let (is_client_entry, imports) = self.collect_top_level_directives_and_imports(module);
let (is_client_entry, is_action_file, imports) =
self.collect_top_level_directives_and_imports(module);
let is_cjs = contains_cjs(module);
if self.is_server {
@ -72,7 +73,9 @@ impl<C: Comments> VisitMut for ReactServerComponents<C> {
return;
}
} else {
self.assert_client_graph(&imports, module);
if !is_action_file {
self.assert_client_graph(&imports, module);
}
if is_client_entry {
self.prepend_comment_node(module, is_cjs);
}
@ -87,10 +90,24 @@ impl<C: Comments> ReactServerComponents<C> {
fn collect_top_level_directives_and_imports(
&mut self,
module: &mut Module,
) -> (bool, Vec<ModuleImports>) {
) -> (bool, bool, Vec<ModuleImports>) {
let mut imports: Vec<ModuleImports> = vec![];
let mut finished_directives = false;
let mut is_client_entry = false;
let mut is_action_file = false;
fn panic_both_directives(span: Span) {
// It's not possible to have both directives in the same file.
HANDLER.with(|handler| {
handler
.struct_span_err(
span,
"It's not possible to have both `use client` and `use server` directives \
in the same file.",
)
.emit()
})
}
let _ = &module.body.retain(|item| {
match item {
@ -107,6 +124,10 @@ impl<C: Comments> ReactServerComponents<C> {
if &**value == "use client" {
if !finished_directives {
is_client_entry = true;
if is_action_file {
panic_both_directives(expr_stmt.span)
}
} else {
HANDLER.with(|handler| {
handler
@ -120,6 +141,12 @@ impl<C: Comments> ReactServerComponents<C> {
// Remove the directive.
return false;
} else if &**value == "use server" && !finished_directives {
is_action_file = true;
if is_client_entry {
panic_both_directives(expr_stmt.span)
}
}
}
// Match `ParenthesisExpression` which is some formatting tools
@ -230,7 +257,7 @@ impl<C: Comments> ReactServerComponents<C> {
true
});
(is_client_entry, imports)
(is_client_entry, is_action_file, imports)
}
// Convert the client module to the module reference code and add a special

View file

@ -155,13 +155,21 @@ fn react_server_actions_errors(input: PathBuf) {
let output = input.parent().unwrap().join("output.js");
test_fixture(
syntax(),
&|_tr| {
&|tr| {
chain!(
resolver(Mark::new(), Mark::new(), false),
server_components(
FileName::Real(PathBuf::from("/app/item.js")),
next_swc::react_server_components::Config::WithOptions(
next_swc::react_server_components::Options { is_server: false },
),
tr.comments.as_ref().clone(),
None,
),
server_actions(
&FileName::Real("/app/item.js".into()),
server_actions::Config { is_server: true },
_tr.comments.as_ref().clone(),
tr.comments.as_ref().clone(),
)
)
},

View file

@ -0,0 +1,4 @@
'use server'
'use client'
export async function foo() {}

View file

@ -0,0 +1,9 @@
/* __next_internal_client_entry_do_not_use__ foo auto */ /* __next_internal_action_entry_do_not_use__ foo */ export async function foo() {}
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;

View file

@ -0,0 +1,7 @@
x It's not possible to have both `use client` and `use server` directives in the same file.
,-[input.js:1:1]
1 | 'use server'
2 | 'use client'
: ^^^^^^^^^^^^
`----

View file

@ -0,0 +1,8 @@
'use client'
'use strict'
// This is a comment.
'use server'
export async function foo() {}

View file

@ -0,0 +1,10 @@
/* __next_internal_client_entry_do_not_use__ foo auto */ /* __next_internal_action_entry_do_not_use__ foo */ 'use strict';
export async function foo() {}
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;

View file

@ -0,0 +1,7 @@
x It's not possible to have both `use client` and `use server` directives in the same file.
,-[input.js:5:1]
5 |
6 | 'use server'
: ^^^^^^^^^^^^
`----

View file

@ -0,0 +1,5 @@
'use server'
// It should be allowed to import server APIs here.
import 'server-only'
import { cookies } from 'next/headers'

View file

@ -0,0 +1,4 @@
'use server';
// It should be allowed to import server APIs here.
import 'server-only';
import { cookies } from 'next/headers';