Skip to content

Commit

Permalink
Support re-exporting unnamed function expression (#46936)
Browse files Browse the repository at this point in the history
Add support for cases like:

```js
const foo = async function () {}
export default foo

const bar = async function() {}
export { bar }
```

Also fix a bug where nested async function declarations are mistakenly
counted as actions.

Fixes NEXT-800.
  • Loading branch information
shuding committed Mar 8, 2023
1 parent 80f2293 commit ddff41a
Show file tree
Hide file tree
Showing 7 changed files with 126 additions and 34 deletions.
120 changes: 95 additions & 25 deletions packages/next-swc/crates/core/src/server_actions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -126,19 +126,11 @@ impl<C: Comments> ServerActions<C> {

fn add_action_annotations(
&mut self,
ident: &mut Ident,
ident: &Ident,
function: &mut Box<Function>,
is_exported: bool,
is_default_export: bool,
) -> Option<Box<Function>> {
if !function.is_async {
HANDLER.with(|handler| {
handler
.struct_span_err(ident.span, "Server actions must be async functions")
.emit();
});
}

let need_rename_export = self.in_action_file && (self.in_export_decl || is_exported);
let action_name: JsWord = if need_rename_export {
ident.sym.clone()
Expand Down Expand Up @@ -336,28 +328,45 @@ impl<C: Comments> VisitMut for ServerActions<C> {
let old_in_action_fn = self.in_action_fn;
let old_in_module = self.in_module;
let old_should_add_name = self.should_add_name;
let old_in_export_decl = self.in_export_decl;
let old_in_default_export_decl = self.in_default_export_decl;
self.in_action_fn = is_action_fn;
self.in_module = false;
self.should_add_name = true;
self.in_export_decl = false;
self.in_default_export_decl = false;
f.visit_mut_children_with(self);
self.in_action_fn = old_in_action_fn;
self.in_module = old_in_module;
self.should_add_name = old_should_add_name;
self.in_export_decl = old_in_export_decl;
self.in_default_export_decl = old_in_default_export_decl;
}

if !is_action_fn {
return;
}

let maybe_new_fn = self.add_action_annotations(
f.ident.as_mut().unwrap(),
&mut f.function,
is_exported,
is_default_export,
);
if !f.function.is_async {
HANDLER.with(|handler| {
handler
.struct_span_err(
f.ident.as_mut().unwrap().span,
"Server actions must be async functions",
)
.emit();
});
} else {
let maybe_new_fn = self.add_action_annotations(
f.ident.as_mut().unwrap(),
&mut f.function,
is_exported,
is_default_export,
);

if let Some(new_fn) = maybe_new_fn {
f.function = new_fn;
if let Some(new_fn) = maybe_new_fn {
f.function = new_fn;
}
}
}

Expand All @@ -379,29 +388,89 @@ impl<C: Comments> VisitMut for ServerActions<C> {
let old_in_action_fn = self.in_action_fn;
let old_in_module = self.in_module;
let old_should_add_name = self.should_add_name;
let old_in_export_decl = self.in_export_decl;
let old_in_default_export_decl = self.in_default_export_decl;
self.in_action_fn = is_action_fn;
self.in_module = false;
self.should_add_name = true;
self.in_export_decl = false;
self.in_default_export_decl = false;
f.visit_mut_children_with(self);
self.in_action_fn = old_in_action_fn;
self.in_module = old_in_module;
self.should_add_name = old_should_add_name;
self.in_export_decl = old_in_export_decl;
self.in_default_export_decl = old_in_default_export_decl;
}

if !is_action_fn {
return;
}

let maybe_new_fn = self.add_action_annotations(
&mut f.ident,
&mut f.function,
is_exported,
is_default_export,
);
if !f.function.is_async {
HANDLER.with(|handler| {
handler
.struct_span_err(f.ident.span, "Server actions must be async functions")
.emit();
});
} else {
let maybe_new_fn = self.add_action_annotations(
&f.ident,
&mut f.function,
is_exported,
is_default_export,
);

if let Some(new_fn) = maybe_new_fn {
f.function = new_fn;
}
}
}

fn visit_mut_var_decl(&mut self, n: &mut VarDecl) {
if self.in_action_file {
for decl in n.decls.iter_mut() {
if decl.init.is_none() {
continue;
}

let init = decl.init.as_mut().unwrap();
if let Pat::Ident(ident) = &mut decl.name {
if let Some(fn_expr) = init.as_mut_fn_expr() {
// Collect `const foo = async function () {}` declarations. For now we
// ignore other types of assignments.
if fn_expr.function.is_async {
if self.in_prepass {
self.async_fn_idents.push(ident.id.to_id());
} else if let Some(exported_ident) = self
.exported_idents
.iter()
.find(|(id, _)| id == &ident.id.to_id())
{
// It's an action function, we need to add the
// name to the function if missing.
if fn_expr.ident.is_none() {
let action_name: JsWord =
format!("$ACTION_fn_{}", self.action_index).into();
self.action_index += 1;
fn_expr.ident = Some(Ident::new(action_name, DUMMY_SP));
}
self.exported_idents.push((
fn_expr.ident.as_ref().unwrap().to_id(),
exported_ident.1,
));
}
}
}
}
}

if let Some(new_fn) = maybe_new_fn {
f.function = new_fn;
if self.in_prepass {
return;
}
}

n.visit_mut_children_with(self);
}

fn visit_mut_module(&mut self, m: &mut Module) {
Expand Down Expand Up @@ -541,6 +610,7 @@ impl<C: Comments> VisitMut for ServerActions<C> {
if let Some(init) = &decl.init {
match &**init {
Expr::Fn(_f) => {}
Expr::Arrow(_f) => {}
_ => {
disallowed_export_span = *span;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1 @@
/* __next_internal_action_entry_do_not_use__ foo */ export function foo() {}
foo.$$typeof = Symbol.for("react.server.reference");
foo.$$id = "ab21efdafbe611287bc25c0462b1e0510d13e48b";
foo.$$bound = [];
/* __next_internal_action_entry_do_not_use__ */ export function foo() {}
Original file line number Diff line number Diff line change
@@ -1,5 +1,2 @@
/* __next_internal_action_entry_do_not_use__ bar */ 'use strict';
export function bar() {}
bar.$$typeof = Symbol.for("react.server.reference");
bar.$$id = "ac840dcaf5e8197cb02b7f3a43c119b7a770b272";
bar.$$bound = [];
/* __next_internal_action_entry_do_not_use__ */ 'use strict';
export function bar() {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
'use server'

const foo = async function () {}
export default foo

const bar = async function() {}
export { bar }
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/* __next_internal_action_entry_do_not_use__ default,$ACTION_fn_1 */ const foo = async function $ACTION_fn_0() {};
$ACTION_fn_0.$$typeof = Symbol.for("react.server.reference");
$ACTION_fn_0.$$id = "c18c215a6b7cdc64bf709f3a714ffdef1bf9651d";
$ACTION_fn_0.$$bound = [];
export default foo;
const bar = async function $ACTION_fn_1() {};
$ACTION_fn_1.$$typeof = Symbol.for("react.server.reference");
$ACTION_fn_1.$$id = "5bde18329eb1c98fc2d6dea0a22639861a18ce65";
$ACTION_fn_1.$$bound = [];
export { bar };
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
'use server'

export async function foo() {
async function bar() {}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
/* __next_internal_action_entry_do_not_use__ foo */ export async function foo() {
async function bar() {}
}
foo.$$typeof = Symbol.for("react.server.reference");
foo.$$id = "ab21efdafbe611287bc25c0462b1e0510d13e48b";
foo.$$bound = [];

0 comments on commit ddff41a

Please sign in to comment.