Skip to content

Support async component for React Server component in JSX v4 #6399

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

Merged
merged 10 commits into from
Oct 3, 2023
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
- Add builtin abstract types for File and Blob APIs. https://github.com/rescript-lang/rescript-compiler/pull/6383
- Untagged variants: Support `promise`, RegExes, Dates, File and Blob. https://github.com/rescript-lang/rescript-compiler/pull/6383
- Support aliased types as payloads to untagged variants. https://github.com/rescript-lang/rescript-compiler/pull/6394
- Support the async component for React Server Component in JSX V4. https://github.com/rescript-lang/rescript-compiler/pull/6399

#### :nail_care: Polish

Expand Down
2 changes: 2 additions & 0 deletions jscomp/others/jsxPPXReactSupportC.res
Original file line number Diff line number Diff line change
Expand Up @@ -48,3 +48,5 @@ let createElementWithKey = (~key=?, component, props) =>

let createElementVariadicWithKey = (~key=?, component, props, elements) =>
createElementVariadic(component, addKeyProp(~key?, props), elements)

external asyncComponent: promise<Jsx.element> => Jsx.element = "%identity"
2 changes: 2 additions & 0 deletions jscomp/others/jsxPPXReactSupportU.res
Original file line number Diff line number Diff line change
Expand Up @@ -50,3 +50,5 @@ let createElementWithKey = (~key=?, component, props) =>

let createElementVariadicWithKey = (~key=?, component, props, elements) =>
createElementVariadic(component, addKeyProp(~key?, props), elements)

external asyncComponent: promise<Jsx.element> => Jsx.element = "%identity"
25 changes: 25 additions & 0 deletions jscomp/syntax/src/react_jsx_common.ml
Original file line number Diff line number Diff line change
Expand Up @@ -63,3 +63,28 @@ let removeArity binding =
| _ -> expr
in
{binding with pvb_expr = removeArityRecord binding.pvb_expr}

let add_async_attribute ~async (body : Parsetree.expression) =
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Aren't these functions already in the file that deals with async transformations?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you mean the files that deal with async transformation are ast_async.ml or ast_attributes.ml, the syntax module doesn't have the frontend as dependency yet. That's why I added here again.
Do you want me to add the frontend module to deps?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oops, not able to do that due do circular deps: frontend -> common -> syntax

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like front-end already accesses ml:

(library
 (name frontend)
 (wrapped false)
 (flags
  (:standard -w +a-4-9-40-42-70))
 (libraries common ml))

and ml is where e.g. ast_untagged_variants.ml is.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe I'm missing something. syntax can access ml, but not frontend that has async transformation functions.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps my wording is confusing. Actually, this is correct: frontend <- common <- syntax

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Put it where ast_untagged_variants.ml is, and change nothing else.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not allowed to use anything that is not already in ml, obviously.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Got it. 8126a13
I was going to put only ast_async.ml in ml, but I thought it would be nice to have ast_await.ml in the same place, so I moved it. 4d1e792

if async then
{
body with
pexp_attributes =
({txt = "res.async"; loc = Location.none}, PStr [])
:: body.pexp_attributes;
}
else body

let is_async : Parsetree.attribute -> bool =
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would you move this function too?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How does it look? 9d283ba
I moved is_async and is_await to ast_async and ast_await instead of moving ast_attributes.ml to ml. If we are going to move ast_attributes.ml, then other modules need to be moved too such as external_arg_spec.ml, bs_syntaxerr.ml. I think that is a bit too much.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks great.
Good to merge

fun ({txt}, _) -> txt = "async" || txt = "res.async"

let async_component ~async expr =
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this should not move as it's specific to the ppx

if async then
let open Ast_helper in
Exp.apply
(Exp.ident
{
loc = Location.none;
txt = Ldot (Lident "JsxPPXReactSupport", "asyncComponent");
})
[(Nolabel, expr)]
else expr
11 changes: 11 additions & 0 deletions jscomp/syntax/src/reactjs_jsx_v4.ml
Original file line number Diff line number Diff line change
Expand Up @@ -909,6 +909,11 @@ let mapBinding ~config ~emptyLoc ~pstr_loc ~fileName ~recFlag binding =
let bindingWrapper, hasForwardRef, expression =
modifiedBinding ~bindingLoc ~bindingPatLoc ~fnName binding
in
let isAsync =
Ext_list.find_first binding.pvb_expr.pexp_attributes
React_jsx_common.is_async
|> Option.is_some
in
(* do stuff here! *)
let namedArgList, newtypes, _typeConstraints =
recursivelyTransformNamedArgsForMake
Expand Down Expand Up @@ -942,6 +947,9 @@ let mapBinding ~config ~emptyLoc ~pstr_loc ~fileName ~recFlag binding =
(Pat.var @@ Location.mknoloc "props")
(Typ.constr (Location.mknoloc @@ Lident "props") [Typ.any ()])
in
let innerExpression =
React_jsx_common.async_component ~async:isAsync innerExpression
in
let fullExpression =
(* React component name should start with uppercase letter *)
(* let make = { let \"App" = props => make(props); \"App" } *)
Expand Down Expand Up @@ -1083,6 +1091,9 @@ let mapBinding ~config ~emptyLoc ~pstr_loc ~fileName ~recFlag binding =
| _ -> [Typ.any ()]))))
expression
in
let expression =
React_jsx_common.add_async_attribute ~async:isAsync expression
in
let expression =
(* Add new tupes (type a,b,c) to make's definition *)
newtypes
Expand Down
19 changes: 19 additions & 0 deletions jscomp/syntax/tests/ppx/react/asyncAwait.res
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
let f = a => Js.Promise.resolve(a + a)

module C0 = {
@react.component
let make = async (~a) => {
let a = await f(a)
<div> {React.int(a)} </div>
}
}

module C1 = {
@react.component
let make = async (~status) => {
switch status {
| #on => React.string("on")
| #off => React.string("off")
}
}
}
31 changes: 31 additions & 0 deletions jscomp/syntax/tests/ppx/react/expected/asyncAwait.res.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
let f = a => Js.Promise.resolve(a + a)

module C0 = {
type props<'a> = {a: 'a}

let make = async ({a, _}: props<_>) => {
let a = await f(a)
ReactDOM.jsx("div", {children: ?ReactDOM.someElement({React.int(a)})})
}
let make = {
let \"AsyncAwait$C0" = (props: props<_>) => JsxPPXReactSupport.asyncComponent(make(props))

\"AsyncAwait$C0"
}
}

module C1 = {
type props<'status> = {status: 'status}

let make = async ({status, _}: props<_>) => {
switch status {
| #on => React.string("on")
| #off => React.string("off")
}
}
let make = {
let \"AsyncAwait$C1" = (props: props<_>) => JsxPPXReactSupport.asyncComponent(make(props))

\"AsyncAwait$C1"
}
}