Skip to content

Commit

Permalink
Support async component for React Server component in JSX v4 (#6399)
Browse files Browse the repository at this point in the history
* add tests

* support async in JSX ppx

* update changelog

* convert async component type

* eof

* use asyncComponent func

* fix polymorphic type inference

* remove duplicated function

* relocate ast_await into ml

* remove duplicated is_async function
  • Loading branch information
mununki authored Oct 3, 2023
1 parent 4b06ba9 commit b8c4157
Show file tree
Hide file tree
Showing 10 changed files with 83 additions and 8 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
- Untagged variants: Support `promise`, RegExes, Dates, File and Blob. https://github.com/rescript-lang/rescript-compiler/pull/6383
- Untagged variants: Support `bool`. https://github.com/rescript-lang/rescript-compiler/pull/6368
- 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

#### :boom: Breaking Change
- Add smart printer for pipe-chains. https://github.com/rescript-lang/rescript-compiler/pull/6411 (the formatter will reformat existing code in certain cases)
Expand Down
10 changes: 2 additions & 8 deletions jscomp/frontend/ast_attributes.ml
Original file line number Diff line number Diff line change
Expand Up @@ -166,14 +166,8 @@ let is_inline : attr -> bool =

let has_inline_payload (attrs : t) = Ext_list.find_first attrs is_inline

let is_await : attr -> bool =
fun ({txt}, _) -> txt = "await" || txt = "res.await"

let is_async : attr -> bool =
fun ({txt}, _) -> txt = "async" || txt = "res.async"

let has_await_payload (attrs : t) = Ext_list.find_first attrs is_await
let has_async_payload (attrs : t) = Ext_list.find_first attrs is_async
let has_await_payload (attrs : t) = Ext_list.find_first attrs Ast_await.is_await
let has_async_payload (attrs : t) = Ext_list.find_first attrs Ast_async.is_async

type derive_attr = {bs_deriving: Ast_payload.action list option} [@@unboxed]

Expand Down
3 changes: 3 additions & 0 deletions jscomp/frontend/ast_async.ml → jscomp/ml/ast_async.ml
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
let is_async : Parsetree.attribute -> bool =
fun ({txt}, _) -> txt = "async" || txt = "res.async"

let add_promise_type ?(loc = Location.none) ~async
(result : Parsetree.expression) =
if async then
Expand Down
3 changes: 3 additions & 0 deletions jscomp/frontend/ast_await.ml → jscomp/ml/ast_await.ml
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
let is_await : Parsetree.attribute -> bool =
fun ({txt}, _) -> txt = "await" || txt = "res.await"

let create_await_expression (e : Parsetree.expression) =
let loc = e.pexp_loc in
let unsafe_await =
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"
12 changes: 12 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,15 @@ let removeArity binding =
| _ -> expr
in
{binding with pvb_expr = removeArityRecord binding.pvb_expr}

let async_component ~async expr =
if async then
let open Ast_helper in
Exp.apply
(Exp.ident
{
loc = Location.none;
txt = Ldot (Lident "JsxPPXReactSupport", "asyncComponent");
})
[(Nolabel, expr)]
else expr
8 changes: 8 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,10 @@ 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 Ast_async.is_async
|> Option.is_some
in
(* do stuff here! *)
let namedArgList, newtypes, _typeConstraints =
recursivelyTransformNamedArgsForMake
Expand Down Expand Up @@ -942,6 +946,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 +1090,7 @@ let mapBinding ~config ~emptyLoc ~pstr_loc ~fileName ~recFlag binding =
| _ -> [Typ.any ()]))))
expression
in
let expression = Ast_async.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"
}
}

0 comments on commit b8c4157

Please sign in to comment.