Skip to content
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

Fix renderToStream #200

Merged
merged 29 commits into from
Jan 22, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
3b83a0f
Try resources on arch
davesnx Jan 17, 2025
527daaf
ship extract cli as a esbuild plugin
davesnx Jan 17, 2025
fee5f1a
Fix test with-stdout-to
davesnx Jan 17, 2025
c7fa14d
improve error messages of extract-cc
davesnx Jan 17, 2025
4419a64
extract function as a package
davesnx Jan 17, 2025
6c349fe
only generate bootstrap if file is different
davesnx Jan 17, 2025
1486514
esbuild plugin adds bootstrap and webpack_require
davesnx Jan 17, 2025
97f7aa3
random readme changes
davesnx Jan 17, 2025
ad9d1e2
fix alias
davesnx Jan 17, 2025
341e05b
fix alias strings
davesnx Jan 20, 2025
69d4fd8
don't extract for all client
davesnx Jan 20, 2025
2e0d5db
install v20 node
davesnx Jan 20, 2025
54ee294
specify cache-dependency-path
davesnx Jan 20, 2025
1cc1e92
update without banner
davesnx Jan 20, 2025
49bf226
fix with rendering_resolved async components
davesnx Jan 21, 2025
7c15447
add nested suspense with errors
davesnx Jan 21, 2025
7e21588
Merge branch 'main' of github.com:ml-in-barcelona/server-reason-react…
davesnx Jan 21, 2025
d715f77
improve traces in the tests
davesnx Jan 21, 2025
8b86da2
remove Lwt.Infix
davesnx Jan 21, 2025
98e55cc
remove logging in server
davesnx Jan 21, 2025
6023487
fix issue where lowercase with innerhtml
davesnx Jan 21, 2025
0e660c9
improve bench by looking at gc.stat
davesnx Jan 21, 2025
d1ea0d3
raise on client c with import_module
davesnx Jan 22, 2025
82f7d9a
fix create-from-fetch
davesnx Jan 22, 2025
2e91428
remove cc from renderToStream
davesnx Jan 22, 2025
e84ef60
push list of suspenses as test
davesnx Jan 22, 2025
38f7726
ensure reorder works
davesnx Jan 22, 2025
9cb4348
add more tests
davesnx Jan 22, 2025
6525beb
minimize sleeps in test_stream
davesnx Jan 22, 2025
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
6 changes: 3 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,15 @@ help: ## Print this help message

.PHONY: build
build: ## Build the project, including non installable libraries and executables
$(DUNE) build @all --profile=dev
$(DUNE) build --profile=dev

.PHONY: build-prod
build-prod: ## Build for production (--profile=prod)
$(DUNE) build @all --profile=prod
$(DUNE) build --profile=prod

.PHONY: dev
dev: ## Build in watch mode
$(DUNE) build -w --profile=dev @all
$(DUNE) build -w --profile=dev

.PHONY: clean
clean: ## Clean artifacts
Expand Down
49 changes: 36 additions & 13 deletions arch/server/render-html-to-stream.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import React from "react";
import React, { Suspense } from "react";
import ReactDOM from "react-dom/server";

const sleep = (seconds) =>
new Promise((res) => setTimeout(res, seconds * 1000));

const DefferedComponent = async ({ by, children }) => {
const DeferredComponent = async ({ by, children }) => {
await sleep(by);
return (
<div>
Expand All @@ -31,30 +31,53 @@ const debug = (readableStream) => {

/* const App = () => (
<React.Suspense fallback="Fallback 1">
<DefferedComponent by={1}>
<DeferredComponent by={1}>
<React.Suspense fallback="Fallback 2">
<DefferedComponent by={1}>"lol"</DefferedComponent>
<DeferredComponent by={1}>"lol"</DeferredComponent>
</React.Suspense>
</DefferedComponent>
</DeferredComponent>
</React.Suspense>
); */

/* const App = () => (
<div>
<React.Suspense fallback="Fallback 1">
<DefferedComponent by={0}>"lol"</DefferedComponent>
<DeferredComponent by={0}>"lol"</DeferredComponent>
</React.Suspense>
</div>
); */

const App = () =>
<head>
<main>
<span>{"Hi"}</span>
<link href="/static/demo/client/app.css" rel="stylesheet" />
</main>
</head>
/* const AlwaysThrow = () => {
throw new Error("always throwing");
};

const App = () => (
<React.Suspense fallback="Fallback 1">
<DeferredComponent by={1}>
<React.Suspense fallback="Fallback 2">
<AlwaysThrow/>
</React.Suspense>
</DeferredComponent>
</React.Suspense>
); */

function App() {
return React.createElement(
Suspense,
{ fallback: "Fallback 1" },
React.createElement(DeferredComponent,
{ by: 0.02 },
React.createElement(
Suspense,
{ fallback: "Fallback 2" },
React.createElement(DeferredComponent,
{ by: 0.02 },
"lol"
)
)
)
);
}

ReactDOM.renderToReadableStream(<App />).then((stream) => {
debug(stream);
Expand Down
2 changes: 2 additions & 0 deletions bench/dune
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
(modules once)
(libraries
unix
lwt
lwt.unix
server-reason-react.react
server-reason-react.reactDom
demo_shared_native)
Expand Down
55 changes: 55 additions & 0 deletions bench/once.ml
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
let measure_alloc title f =
let before = Gc.stat () in
let result = f () in
let after = Gc.stat () in
Printf.printf "\n=== %s ===\n" title;
Printf.printf "Minor words: %f\n" (after.minor_words -. before.minor_words);
Printf.printf "Major words: %f\n" (after.major_words -. before.major_words);
Printf.printf "Minor collections: %d\n" (after.minor_collections - before.minor_collections);
result

let loop n f =
for _ = 1 to n do
f ()
done

let main () =
let filter_map_style () =
let element =
React.createElement "div"
(Stdlib.List.filter_map Fun.id
(List.init 50 (fun i ->
Some
(React.JSX.String (Printf.sprintf "prop%d" i, Printf.sprintf "prop%d" i, Printf.sprintf "value%d" i)))))
[]
in
let _ = ReactDOM.renderToStaticMarkup element in
()
in

let direct_style () =
let props =
List.init 50 (fun i ->
React.JSX.String (Printf.sprintf "prop%d" i, Printf.sprintf "prop%d" i, Printf.sprintf "value%d" i))
in
let element = React.createElement "div" props [] in
let _ = ReactDOM.renderToStaticMarkup element in
()
in

let render_hello_world () =
let _ = ReactDOM.renderToStaticMarkup (HelloWorld.make ()) in
()
in
let render_app () =
let _ = ReactDOM.renderToStaticMarkup (App.make ()) in
()
in

measure_alloc "Use filter_map" (fun () -> loop 10000 filter_map_style);
measure_alloc "Use list direct style" (fun () -> loop 10000 direct_style);
measure_alloc "Render <HelloWorld />" (fun () -> loop 10000 render_hello_world);
measure_alloc "Render <App />" (fun () -> loop 10000 render_app);
Lwt.return ()

let () = Lwt_main.run (main ())
5 changes: 0 additions & 5 deletions bench/once.re

This file was deleted.

4 changes: 4 additions & 0 deletions demo/client/create-from-fetch.jsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
window.__webpack_require__ = () => {
throw new Error("__webpack_require__ should not be called on this demo");
};

let ReactDOM = require("react-dom/client");
let ReactServerDOM = require("react-server-dom-webpack/client");

Expand Down
46 changes: 28 additions & 18 deletions demo/server/comments.re
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ module Post = {
};

module Data = {
let delay = 1.0;
let delay = 4.0;

let fakeData = [
"Wait, it doesn't wait for React to load?",
Expand All @@ -47,29 +47,39 @@ module Data = {
"But, imagine it's dynamic",
];

let get = () => fakeData;

let cached = ref(false);
let destroy = () => cached := false;
let promise = () => {
let%lwt () = Lwt_unix.sleep(delay);
Lwt.return(fakeData);
cached.contents
? Lwt.return(fakeData)
: {
let%lwt () = Lwt_unix.sleep(delay);
cached.contents = true;
Lwt.return(fakeData);
};
};
};

module Comments = {
[@react.async.component]
let make = () => {
/* Sincronous data: let comments = Data.get(); */
/* let comments = React.Experimental.use(Data.promise()); */
let comments = ["a", "b"];
let comments = React.Experimental.use(Data.promise());

<div className="flex gap-4 flex-col">
{comments
|> List.mapi((i, comment) =>
<p
key={Int.to_string(i)}
className="font-semibold border-2 border-yellow-200 rounded-lg p-2 bg-yellow-600 text-slate-900">
{React.string(comment)}
</p>
)
|> React.list}
</div>;
Lwt.return(
<div className="flex gap-4 flex-col">
{comments
|> List.mapi((i, comment) =>
<p
key={Int.to_string(i)}
className="font-semibold border-2 border-yellow-200 rounded-lg p-2 bg-yellow-600 text-slate-900">
{React.string(comment)}
</p>
)
|> React.list}
</div>,
);
};
};

Expand All @@ -85,7 +95,7 @@ let make = () => {
"text-4xl font-bold ",
Theme.text(Theme.Color.white),
])}>
{React.string("Hello world")}
{React.string("Rendering React.Suspense on the server")}
</h1>
<Post />
<section>
Expand Down
18 changes: 10 additions & 8 deletions demo/server/server.re
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,22 @@ let renderToStreamHandler = _ =>
Dream.stream(
~headers=[("Content-Type", "text/html")],
response_stream => {
Comments.Data.destroy();

let pipe = data => {
Dream.log("Pipe: %s", data);
let%lwt () = Dream.write(response_stream, data);
Dream.flush(response_stream);
};

let%lwt (stream, _abort) =
ReactDOM.renderToStream(~pipe, <Document> <Comments /> </Document>);
Lwt.return();

Lwt_stream.iter_s(pipe, stream);
},
);

let serverComponentsWithoutClientHandler = request => {
let createFromFetchHandler = request => {
let isRSCheader =
Dream.header(request, "Accept") == Some("text/x-component");

Expand Down Expand Up @@ -174,7 +179,7 @@ module Page = {
};
};

let serverComponentsHandler = request => {
let createFromReadableStreamHandler = request => {
let app = <AppRouter> <Page /> </AppRouter>;
switch (Dream.header(request, "Accept")) {
| Some(accept) when is_react_component_header(accept) =>
Expand Down Expand Up @@ -292,11 +297,8 @@ let router = [
)
),
Dream.get(Router.renderToStream, renderToStreamHandler),
Dream.get(
Router.serverComponentsWithoutClient,
serverComponentsWithoutClientHandler,
),
Dream.get(Router.serverComponents, serverComponentsHandler),
Dream.get(Router.createFromFetch, createFromFetchHandler),
Dream.get(Router.createFromReadableStream, createFromReadableStreamHandler),
];

let () = {
Expand Down
12 changes: 6 additions & 6 deletions demo/universal/native/lib/Router.re
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,18 @@ let home = "/";
let renderToStaticMarkup = "/demo/renderToStaticMarkup";
let renderToString = "/demo/renderToString";
let renderToStream = "/demo/renderToStream";
let serverComponentsWithoutClient = "/demo/server-components-without-client";
let serverComponents = "/demo/server-components";
let createFromFetch = "/demo/server-components-without-client";
let createFromReadableStream = "/demo/server-components";

let links = [|
("Render to static markup (SSR)", renderToStaticMarkup),
("Render to string (SSR)", renderToString),
("Render to Stream (SSR)", renderToStream),
("Render to stream (SSR)", renderToStream),
("Server components without client (createFromFetch)", createFromFetch),
(
"Server components without client (createFromFetch)",
serverComponentsWithoutClient,
"Server components with createFromReadableStream (RSC + SSR)",
createFromReadableStream,
),
("Server components (RSC + SSR)", serverComponents),
|];

module Menu = {
Expand Down
8 changes: 0 additions & 8 deletions packages/extract-client-components/dune
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,3 @@
(name extract_client_components)
(public_name server_reason_react.extract_client_components)
(libraries unix cmdliner))

; add a rule to `all` to copy the plugin to the build directory
; so demo/client/build.mjs can import it

(rule
(alias all)
(deps ./esbuild-plugin.mjs)
(action (progn)))
Loading
Loading