diff --git a/CHANGELOG.md b/CHANGELOG.md index 1e014fd81c..6d408b7770 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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) diff --git a/jscomp/frontend/ast_attributes.ml b/jscomp/frontend/ast_attributes.ml index 3345bcf65e..ebcf5ac05d 100644 --- a/jscomp/frontend/ast_attributes.ml +++ b/jscomp/frontend/ast_attributes.ml @@ -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] diff --git a/jscomp/frontend/ast_async.ml b/jscomp/ml/ast_async.ml similarity index 91% rename from jscomp/frontend/ast_async.ml rename to jscomp/ml/ast_async.ml index 34a59d62a5..d16b193a4c 100644 --- a/jscomp/frontend/ast_async.ml +++ b/jscomp/ml/ast_async.ml @@ -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 diff --git a/jscomp/frontend/ast_await.ml b/jscomp/ml/ast_await.ml similarity index 92% rename from jscomp/frontend/ast_await.ml rename to jscomp/ml/ast_await.ml index 2906ea4690..410f3b9c70 100644 --- a/jscomp/frontend/ast_await.ml +++ b/jscomp/ml/ast_await.ml @@ -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 = diff --git a/jscomp/others/jsxPPXReactSupportC.res b/jscomp/others/jsxPPXReactSupportC.res index 4930472ed4..a024d44718 100644 --- a/jscomp/others/jsxPPXReactSupportC.res +++ b/jscomp/others/jsxPPXReactSupportC.res @@ -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 = "%identity" diff --git a/jscomp/others/jsxPPXReactSupportU.res b/jscomp/others/jsxPPXReactSupportU.res index 8987f68e4d..a850efc120 100644 --- a/jscomp/others/jsxPPXReactSupportU.res +++ b/jscomp/others/jsxPPXReactSupportU.res @@ -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 = "%identity" diff --git a/jscomp/syntax/src/react_jsx_common.ml b/jscomp/syntax/src/react_jsx_common.ml index 0cfe798a78..51c4711032 100644 --- a/jscomp/syntax/src/react_jsx_common.ml +++ b/jscomp/syntax/src/react_jsx_common.ml @@ -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 diff --git a/jscomp/syntax/src/reactjs_jsx_v4.ml b/jscomp/syntax/src/reactjs_jsx_v4.ml index a80da10366..1801edd011 100644 --- a/jscomp/syntax/src/reactjs_jsx_v4.ml +++ b/jscomp/syntax/src/reactjs_jsx_v4.ml @@ -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 @@ -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" } *) @@ -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 diff --git a/jscomp/syntax/tests/ppx/react/asyncAwait.res b/jscomp/syntax/tests/ppx/react/asyncAwait.res new file mode 100644 index 0000000000..9a253a04a2 --- /dev/null +++ b/jscomp/syntax/tests/ppx/react/asyncAwait.res @@ -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) +
{React.int(a)}
+ } +} + +module C1 = { + @react.component + let make = async (~status) => { + switch status { + | #on => React.string("on") + | #off => React.string("off") + } + } +} \ No newline at end of file diff --git a/jscomp/syntax/tests/ppx/react/expected/asyncAwait.res.txt b/jscomp/syntax/tests/ppx/react/expected/asyncAwait.res.txt new file mode 100644 index 0000000000..d8d86dccca --- /dev/null +++ b/jscomp/syntax/tests/ppx/react/expected/asyncAwait.res.txt @@ -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" + } +}