Skip to content

Commit ee1ea9a

Browse files
committed
Merge remote-tracking branch 'origin/main' into feat/navigation
* origin/main: Specify model values at React (#309)
2 parents 018c399 + cc50f18 commit ee1ea9a

File tree

8 files changed

+109
-160
lines changed

8 files changed

+109
-160
lines changed

documentation/universal-code.mld

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ module ServerReasonReact = {
7474
| Fragment(array(element));
7575

7676
// createElement is a function that returns a React.element
77-
let createElement = name => React.Element(string);
77+
let createElement = name => React.Model.Element(string);
7878
};
7979
};
8080
]}

packages/react/src/React.ml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -402,6 +402,7 @@ type element =
402402

403403
and lower_case_element = { key : string option; tag : string; attributes : JSX.prop list; children : element list }
404404
and client_props = (string * element Model.t) list
405+
and model_value = element Model.t
405406

406407
exception Invalid_children of string
407408

packages/react/src/React.mli

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -585,6 +585,7 @@ type element =
585585

586586
and lower_case_element = { key : string option; tag : string; attributes : JSX.prop list; children : element list }
587587
and client_props = (string * element Model.t) list
588+
and model_value = element Model.t
588589

589590
exception Invalid_children of string
590591

packages/reactDom/src/ReactServerDOM.ml

Lines changed: 32 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -245,10 +245,8 @@ module Model = struct
245245
context.push (to_chunk (Debug_ref (`String debug_info_ref)) current_index) |> ignore
246246
else ()
247247

248-
let rec element_to_payload ~context ?(debug = false) ~to_chunk ~env element =
249-
let is_root = ref true in
250-
251-
let rec turn_element_into_payload ~context element =
248+
let rec element_to_payload ?(debug = false) ~context ~is_root ~to_chunk ~env element =
249+
let rec turn_element_into_payload ~context ~is_root element =
252250
match (element : React.element) with
253251
| Empty -> `Null
254252
| DangerouslyInnerHtml _ ->
@@ -272,35 +270,28 @@ module Model = struct
272270
attributes
273271
in
274272
let props = props_to_json attributes in
275-
node ~key ~tag ~props (List.map (turn_element_into_payload ~context) children)
276-
| Fragment children ->
277-
if is_root.contents then is_root := false;
278-
turn_element_into_payload ~context children
279-
| List children ->
280-
if is_root.contents then is_root := false;
281-
`List (List.map (turn_element_into_payload ~context) children)
282-
| Array children ->
283-
if is_root.contents then is_root := false;
284-
`List (Array.map (turn_element_into_payload ~context) children |> Array.to_list)
273+
node ~key ~tag ~props (List.map (turn_element_into_payload ~context ~is_root) children)
274+
| Fragment children -> turn_element_into_payload ~context ~is_root:false children
275+
| List children -> `List (List.map (turn_element_into_payload ~context ~is_root:false) children)
276+
| Array children -> `List (Array.map (turn_element_into_payload ~context ~is_root:false) children |> Array.to_list)
285277
| Upper_case_component (name, component) -> (
286278
(* TODO: Get the stack info from component *)
287279
match component () with
288280
| element ->
289281
(* TODO: Can we remove the is_root difference. It currently align with react.js behavior, but it's not clear what is the purpose of it *)
290-
if is_root.contents then (
291-
is_root := false;
282+
if is_root then (
292283
(*
293284
If it's the root element, React returns the element payload instead of a reference value.
294285
Root is a special case: https://github.com/facebook/react/blob/f3a803617ec4ba9d14bf5205ffece28ed1496a1d/packages/react-server/src/ReactFlightServer.js#L756-L766
295286
*)
296287
if debug then push_debug_info ~context ~to_chunk ~env ~index:0 ~ownerName:name else ();
297-
turn_element_into_payload ~context element)
288+
turn_element_into_payload ~context ~is_root:false element)
298289
else
299290
(* If it's not the root React push the element to the stream and return the reference value *)
300291
let element_index =
301292
Stream.push ~context (fun index ->
302293
if debug then push_debug_info ~context ~to_chunk ~env ~index ~ownerName:name else ();
303-
let payload = turn_element_into_payload ~context element in
294+
let payload = turn_element_into_payload ~context ~is_root element in
304295
to_chunk (Value payload) index)
305296
in
306297
`String (ref_value element_index)
@@ -318,12 +309,12 @@ module Model = struct
318309
let error = make_error ~message ~stack ~digest:"" in
319310
let index = Stream.push ~context (to_chunk (Error (env, error))) in
320311
`String (lazy_value index)
321-
| Return element -> turn_element_into_payload ~context element
312+
| Return element -> turn_element_into_payload ~context ~is_root:false element
322313
| Sleep ->
323314
let promise =
324315
try%lwt
325316
let%lwt element = promise in
326-
Lwt.return (to_chunk (Value (turn_element_into_payload ~context element)))
317+
Lwt.return (to_chunk (Value (turn_element_into_payload ~context ~is_root element)))
327318
with exn ->
328319
let message = Printexc.to_string exn in
329320
let stack = create_stack_trace () in
@@ -335,29 +326,26 @@ module Model = struct
335326
| Suspense { key; children; fallback } ->
336327
(* TODO: Need to check is_root? *)
337328
(* TODO: Maybe we need to push suspense index and suspense node separately *)
338-
let fallback = turn_element_into_payload ~context fallback in
339-
suspense_node ~key ~fallback [ turn_element_into_payload ~context children ]
329+
let fallback = turn_element_into_payload ~context ~is_root fallback in
330+
suspense_node ~key ~fallback [ turn_element_into_payload ~context ~is_root children ]
340331
| Client_component { import_module; import_name; props; client = _ } ->
341332
let ref = component_ref ~module_:import_module ~name:import_name in
342333
let index = Stream.push ~context (to_chunk (Component_ref ref)) in
343-
let client_props = client_values_to_json ~context ~to_chunk ~env props in
334+
let client_props = models_to_payload ~context ~to_chunk ~env props in
344335
node ~tag:(ref_value index) ~props:client_props []
345336
(* TODO: Do we need to do anything with Provider and Consumer? *)
346-
| Provider children -> turn_element_into_payload ~context children
347-
| Consumer children -> turn_element_into_payload ~context children
337+
| Provider children -> turn_element_into_payload ~context ~is_root children
338+
| Consumer children -> turn_element_into_payload ~context ~is_root children
348339
in
349-
turn_element_into_payload ~context element
340+
turn_element_into_payload ~context ~is_root element
350341

351-
and client_value_to_json ~context ?debug ~to_chunk ~env value =
352-
match (value : React.element React.Model.t) with
342+
and model_to_payload ~context ?debug ~is_root ~to_chunk ~env value =
343+
match (value : React.model_value) with
353344
| Json json -> json
354345
| Error error ->
355346
let index = Stream.push ~context (to_chunk (Error (env, error))) in
356347
`String (error_value index)
357-
| Element element ->
358-
let payload = element_to_payload ~context ?debug ~to_chunk ~env element in
359-
let index = Stream.push ~context (to_chunk (Value payload)) in
360-
`String (ref_value index)
348+
| Element element -> element_to_payload ~context ?debug ~is_root ~to_chunk ~env element
361349
| Promise (promise, value_to_json) -> (
362350
match Lwt.state promise with
363351
| Return value ->
@@ -381,27 +369,27 @@ module Model = struct
381369
(* TODO: https://github.com/ml-in-barcelona/server-reason-react/issues/251 *)
382370
raise exn)
383371
| List list ->
384-
let list = List.map (fun element -> client_value_to_json ?debug ~context ~to_chunk ~env element) list in
372+
let list = List.map (fun element -> model_to_payload ~context ~is_root ~to_chunk ~env element) list in
385373
`List list
386374
| Assoc assoc ->
387375
let assoc =
388-
List.map (fun (name, value) -> (name, client_value_to_json ?debug ~context ~to_chunk ~env value)) assoc
376+
List.map (fun (name, value) -> (name, model_to_payload ~context ~is_root ~to_chunk ~env value)) assoc
389377
in
390378
`Assoc assoc
391379
| Function action ->
392380
let index = Stream.push ~context (to_chunk (Value (`Assoc [ ("id", `String action.id); ("bound", `Null) ]))) in
393381
`String (action_value index)
394382

395-
and client_values_to_json ~context ~to_chunk ~env props =
396-
List.map (fun (name, value) -> (name, client_value_to_json ~context ~to_chunk ~env value)) props
383+
and models_to_payload ~context ~to_chunk ~env props =
384+
List.map (fun (name, value) -> (name, model_to_payload ~context ~is_root:false ~to_chunk ~env value)) props
397385

398-
let render ?(env = `Dev) ?(debug = false) ?subscribe element =
386+
let render ?(env = `Dev) ?(debug = false) ?subscribe model =
399387
let stream, context = Stream.make () in
400-
let to_root_chunk element id =
401-
let payload = client_value_to_json ~debug ~context ~to_chunk ~env element in
388+
let to_root_chunk model id =
389+
let payload = model_to_payload ~debug ~is_root:true ~context ~to_chunk ~env model in
402390
to_chunk (Value payload) id
403391
in
404-
Stream.push ~context (to_root_chunk element) |> ignore;
392+
Stream.push ~context (to_root_chunk model) |> ignore;
405393
if context.pending = 0 then context.close ();
406394
match subscribe with None -> Lwt.return () | Some subscribe -> Lwt_stream.iter_s subscribe stream
407395

@@ -417,7 +405,7 @@ module Model = struct
417405
in
418406
let stream, context = Stream.make () in
419407
let to_root_chunk value id =
420-
let payload = client_value_to_json ~debug ~context ~to_chunk ~env value in
408+
let payload = model_to_payload ~debug ~is_root:true ~context ~to_chunk ~env value in
421409
to_chunk (Value payload) id
422410
in
423411
Stream.push ~context (to_root_chunk response) |> ignore;
@@ -571,7 +559,7 @@ let rec render_element_to_html ~(fiber : Fiber.t) (element : React.element) : (H
571559
| Client_component { import_module; import_name; props; client } ->
572560
let context = fiber.context in
573561
let env = fiber.env in
574-
let props = Model.client_values_to_json ~context ~to_chunk:model_to_chunk ~env props in
562+
let props = Model.models_to_payload ~context ~to_chunk:model_to_chunk ~env props in
575563
let%lwt html = client_to_html ~fiber client in
576564
let ref : json = Model.component_ref ~module_:import_module ~name:import_name in
577565
let index = Stream.push ~context (model_to_chunk (Component_ref ref)) in
@@ -936,8 +924,8 @@ let decodeFormDataReply formData =
936924
(args, aux (Js.FormData.make ()) formDataEntries)
937925

938926
type server_function =
939-
| FormData of (Yojson.Basic.t array -> Js.FormData.t -> React.element React.Model.t Lwt.t)
940-
| Body of (Yojson.Basic.t array -> React.element React.Model.t Lwt.t)
927+
| FormData of (Yojson.Basic.t array -> Js.FormData.t -> React.model_value Lwt.t)
928+
| Body of (Yojson.Basic.t array -> React.model_value Lwt.t)
941929

942930
module type FunctionReferences = sig
943931
type t

packages/reactDom/src/ReactServerDOM.mli

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,18 +9,14 @@ val render_html :
99
(string * ((string -> unit Lwt.t) -> unit Lwt.t)) Lwt.t
1010

1111
val render_model :
12-
?env:[ `Dev | `Prod ] -> ?debug:bool -> ?subscribe:(string -> unit Lwt.t) -> React.element React.Model.t -> unit Lwt.t
12+
?env:[ `Dev | `Prod ] -> ?debug:bool -> ?subscribe:(string -> unit Lwt.t) -> React.model_value -> unit Lwt.t
1313

1414
val create_action_response :
15-
?env:[ `Dev | `Prod ] ->
16-
?debug:bool ->
17-
?subscribe:(string -> unit Lwt.t) ->
18-
React.element React.Model.t Lwt.t ->
19-
unit Lwt.t
15+
?env:[ `Dev | `Prod ] -> ?debug:bool -> ?subscribe:(string -> unit Lwt.t) -> React.model_value Lwt.t -> unit Lwt.t
2016

2117
type server_function =
22-
| FormData of (Yojson.Basic.t array -> Js.FormData.t -> React.element React.Model.t Lwt.t)
23-
| Body of (Yojson.Basic.t array -> React.element React.Model.t Lwt.t)
18+
| FormData of (Yojson.Basic.t array -> Js.FormData.t -> React.model_value Lwt.t)
19+
| Body of (Yojson.Basic.t array -> React.model_value Lwt.t)
2420

2521
val decodeReply : string -> Yojson.Basic.t array
2622
val decodeFormDataReply : Js.FormData.t -> Yojson.Basic.t array * Js.FormData.t

packages/reactDom/test/test_RSC_html.ml

Lines changed: 32 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -341,16 +341,43 @@ let client_with_element_props () =
341341
in
342342
assert_html (app ())
343343
~shell:
344-
"Client with elment prop<script data-payload='0:[\"$\",\"$2\",null,{\"element\":\"$1\"},null,[],{}]\n\
344+
"Client with elment prop<script \
345+
data-payload='0:[\"$\",\"$1\",null,{\"element\":[\"$\",\"span\",null,{\"children\":\"server-component-as-props-to-client-component\"},null,[],{}]},null,[],{}]\n\
345346
'>window.srr_stream.push()</script>"
346347
[
347-
"<script \
348-
data-payload='1:[\"$\",\"span\",null,{\"children\":\"server-component-as-props-to-client-component\"},null,[],{}]\n\
349-
'>window.srr_stream.push()</script>";
350-
"<script data-payload='2:I[\"./client-with-props.js\",[],\"ClientWithProps\"]\n\
348+
"<script data-payload='1:I[\"./client-with-props.js\",[],\"ClientWithProps\"]\n\
351349
'>window.srr_stream.push()</script>";
352350
]
353351

352+
let client_component_with_async_component () =
353+
let children =
354+
React.Async_component
355+
( __FUNCTION__,
356+
fun () ->
357+
let%lwt () = sleep ~ms:10 in
358+
Lwt.return (React.string "Async Component") )
359+
in
360+
let app ~children =
361+
React.Upper_case_component
362+
( "app",
363+
fun () ->
364+
React.Client_component
365+
{
366+
import_module = "./client.js";
367+
import_name = "Client";
368+
props = [ ("children", React.Model.Element children) ];
369+
client = children;
370+
} )
371+
in
372+
assert_html (app ~children)
373+
~shell:
374+
"Async Component<script data-payload='0:[\"$\",\"$2\",null,{\"children\":\"$L1\"},null,[],{}]\n\
375+
'>window.srr_stream.push()</script>"
376+
[
377+
"<script data-payload='1:\"Async Component\"\n'>window.srr_stream.push()</script>";
378+
"<script data-payload='2:I[\"./client.js\",[],\"Client\"]\n'>window.srr_stream.push()</script>";
379+
]
380+
354381
let suspense_with_error () =
355382
let app () =
356383
React.Suspense.make ~fallback:(React.string "Loading...")
@@ -630,88 +657,6 @@ let nested_context () =
630657
"<script data-payload='a:I[\"./provider.js\",[],\"Provider\"]\n'>window.srr_stream.push()</script>";
631658
]
632659

633-
let client_component_with_async_component () =
634-
let children =
635-
React.Async_component
636-
( __FUNCTION__,
637-
fun () ->
638-
let%lwt () = sleep ~ms:10 in
639-
Lwt.return (React.string "Async Component") )
640-
in
641-
let app ~children =
642-
React.Client_component
643-
{
644-
import_module = "./client.js";
645-
import_name = "Client";
646-
props = [ ("children", React.Model.Element children) ];
647-
client = children;
648-
}
649-
in
650-
assert_html (app ~children)
651-
~shell:
652-
"Async Component<script data-payload='0:[\"$\",\"$1\",null,{\"children\":\"Async Component\"},null,[],{}]\n\
653-
'>window.srr_stream.push()</script>"
654-
[ "<script data-payload='1:I[\"./client.js\",[],\"Client\"]\n'>window.srr_stream.push()</script>" ]
655-
656-
(* This test ensures that we don't push multiple scripts for the Suspense component *)
657-
let client_component_with_nested_suspense_client_component () =
658-
let async_suspense ~children =
659-
React.Suspense.make ~fallback:(React.string "Loading...")
660-
~children:
661-
(React.Async_component
662-
( "async_suspense",
663-
fun () ->
664-
let%lwt () = sleep ~ms:10 in
665-
Lwt.return children ))
666-
()
667-
in
668-
let client_component ~children =
669-
React.Client_component
670-
{
671-
import_module = "./client.js";
672-
import_name = "Client";
673-
props = [ ("children", React.Model.Element children) ];
674-
client = children;
675-
}
676-
in
677-
assert_html
678-
(client_component
679-
~children:
680-
(React.array
681-
[|
682-
React.string "Root";
683-
async_suspense
684-
~children:
685-
(React.array
686-
[|
687-
React.string "Level 2";
688-
client_component
689-
~children:
690-
(React.array
691-
[|
692-
React.string "Level 3";
693-
client_component ~children:(async_suspense ~children:(React.string "Level 4"));
694-
|]);
695-
|]);
696-
|]))
697-
~shell:
698-
"Root<!--$?--><template id=\"B:1\"></template>Loading...<!--/$--><script \
699-
data-payload='0:[\"$\",\"$3\",null,{\"children\":[\"Root\",[\"$\",\"$Sreact.suspense\",null,{\"fallback\":\"Loading...\",\"children\":\"$L2\"},null,[],{}]]},null,[],{}]\n\
700-
'>window.srr_stream.push()</script>"
701-
[
702-
"<script data-payload='3:I[\"./client.js\",[],\"Client\"]\n'>window.srr_stream.push()</script>";
703-
"<div hidden=\"true\" id=\"S:1\">Level 2<!-- -->Level 3<!--$?--><template \
704-
id=\"B:4\"></template>Loading...<!--/$--></div>\n\
705-
<script>$RC('B:1', 'S:1')</script>";
706-
"<script data-payload='8:I[\"./client.js\",[],\"Client\"]\n'>window.srr_stream.push()</script>";
707-
"<script data-payload='9:I[\"./client.js\",[],\"Client\"]\n'>window.srr_stream.push()</script>";
708-
"<script data-payload='2:[\"Level 2\",[\"$\",\"$9\",null,{\"children\":[\"Level \
709-
3\",[\"$\",\"$8\",null,{\"children\":[\"$\",\"$Sreact.suspense\",null,{\"fallback\":\"Loading...\",\"children\":\"$L7\"},null,[],{}]},null,[],{}]]},null,[],{}]]\n\
710-
'>window.srr_stream.push()</script>";
711-
"<div hidden=\"true\" id=\"S:4\">Level 4</div>\n<script>$RC('B:4', 'S:4')</script>";
712-
"<script data-payload='7:\"Level 4\"\n'>window.srr_stream.push()</script>";
713-
]
714-
715660
let tests =
716661
[
717662
(* test "debug_adds_debug_info" debug_adds_debug_info; *)
@@ -734,8 +679,6 @@ let tests =
734679
test "error_in_toplevel_in_async" error_in_toplevel_in_async;
735680
test "suspense_in_a_list_with_error" suspense_in_a_list_with_error;
736681
test "server_function_as_action" server_function_as_action;
737-
test "client_component_with_async_component" client_component_with_async_component;
738-
test "client_component_with_nested_suspense_client_component" client_component_with_nested_suspense_client_component;
739682
(* test "nested_context" nested_context; *)
740683
(* test "client_component_with_suspense_prop" client_component_with_suspense_prop; *)
741684
(* test "page_with_resources" page_with_resources;

0 commit comments

Comments
 (0)