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:
parent
e659653e48
commit
d957327d71
10 changed files with 95 additions and 6 deletions
|
@ -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
|
||||
|
|
|
@ -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(),
|
||||
)
|
||||
)
|
||||
},
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
'use server'
|
||||
'use client'
|
||||
|
||||
export async function foo() {}
|
|
@ -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;
|
|
@ -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'
|
||||
: ^^^^^^^^^^^^
|
||||
`----
|
|
@ -0,0 +1,8 @@
|
|||
'use client'
|
||||
'use strict'
|
||||
|
||||
// This is a comment.
|
||||
|
||||
'use server'
|
||||
|
||||
export async function foo() {}
|
|
@ -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;
|
|
@ -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'
|
||||
: ^^^^^^^^^^^^
|
||||
`----
|
|
@ -0,0 +1,5 @@
|
|||
'use server'
|
||||
|
||||
// It should be allowed to import server APIs here.
|
||||
import 'server-only'
|
||||
import { cookies } from 'next/headers'
|
|
@ -0,0 +1,4 @@
|
|||
'use server';
|
||||
// It should be allowed to import server APIs here.
|
||||
import 'server-only';
|
||||
import { cookies } from 'next/headers';
|
Loading…
Reference in a new issue