Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/main' into feat/platform-on-expr…
Browse files Browse the repository at this point in the history
…essions

* origin/main:
  Use the right extension syntax on promise.js (#194)
  Battle test RSC with Suspense + client components (#190)
  Move ppx transformation to function body (not last expression) (#192)
  fix: format missing mel.raw
  chore: update reason to 3.14
  Update README.md
  • Loading branch information
pedrobslisboa committed Dec 11, 2024
2 parents ff118b8 + 44f532a commit 34fe287
Show file tree
Hide file tree
Showing 30 changed files with 676 additions and 473 deletions.
9 changes: 4 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
> **Warning**
> This repo contains a few parts that are considered experimental. The stable parts are used in production at [app.ahrefs.com](https://app.ahrefs.com) for all users and [wordcount.com](https://wordcount.com), but `Belt`, `Js` modules have missing APIs. non-implemented functions and unsafe code. Use it at your own risk.
> This project enables sharing ReasonReact code between native (compiled to machine code) and Melange (compiled to JavaScript). There are a lot of interesting pieces from this architecture and stack. If you are interested, feel free to contact me in [Discord](https://discord.com/users/122441959414431745) or [Twitter](https://www.twitter.com/davesnx).
# server-reason-react

Re-implementation of `react`, `react-dom` and `react-dom/server` to run on the server and also, a [few related libraries](https://ml-in-barcelona.github.io/server-reason-react/local/server-reason-react/index.html#other-libraries) to enable Server-side Rendering for reason-react applications.
Re-implementation of `react`, `react-dom` and `react-dom/server` to run on the server and also, a [few related libraries](https://ml-in-barcelona.github.io/server-reason-react/local/server-reason-react/index.html#other-libraries) to enable Server-side rendering for reason-react applications, also contains a [few libraries](https://ml-in-barcelona.github.io/server-reason-react/local/server-reason-react/universal-code.html) and a [ppx](https://ml-in-barcelona.github.io/server-reason-react/local/server-reason-react/browser_only.html) to share code between native (compiled to machine code) and JavaScript (compiled by [Melange](https://melange.re)).

> **Warning**
> This repo contains a few parts that are considered experimental. The stable parts are used in production at [app.ahrefs.com](https://app.ahrefs.com) for all users and [wordcount.com](https://wordcount.com), but `Belt`, `Js` modules have missing APIs, non-implemented functions and unsafe code. Use it at your own risk.
## Why
Explained more details in this blog post [sancho.dev/blog/server-side-rendering-react-in-ocaml](https://sancho.dev/blog/server-side-rendering-react-in-ocaml)
Expand Down
3 changes: 2 additions & 1 deletion arch/server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
"version": "0.0.1",
"scripts": {
"react-dom-server": "node react-dom-server.js",
"render-to-stream": "bun render-to-stream.js",
"render-html-to-stream": "bun render-html-to-stream.js",
"render-rsc-to-stream": "bun --conditions react-server render-rsc-to-stream.js",
"react-server-dom-webpack": "node --conditions react-server react-server-dom-webpack.js"
},
"license": "MIT",
Expand Down
106 changes: 0 additions & 106 deletions arch/server/react-server-dom-webpack.js

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -26,27 +26,28 @@ const debug = (readableStream) => {
reader.read().then(debugReader);
};

/* const app = () => (
<div>
<React.Suspense fallback="Fallback 1">
<DefferedComponent sleep={1}>"lol"</DefferedComponent>
</React.Suspense>
</div>
); */

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

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

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


ReactDOM.renderToReadableStream(<App />).then((stream) => {
debug(stream);
});
40 changes: 40 additions & 0 deletions arch/server/render-rsc-to-stream.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import React from "react";
import { renderToPipeableStream } from "react-server-dom-webpack/server";

const DefferedComponent = async ({ sleep, children }) => {
await new Promise((res) => setTimeout(() => res(), sleep * 1000));
return <span>Sleep {sleep}s, {children}</span>;
};

const decoder = new TextDecoder();

const debug = (readableStream) => {
const reader = readableStream.getReader();
const debugReader = ({ done, value }) => {
if (done) {
console.log("Stream complete");
return;
}
console.log(decoder.decode(value));
console.log(" ");
return reader.read().then(debugReader);
};
reader.read().then(debugReader);
};

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

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

const { pipe } = renderToPipeableStream(<App />);

pipe(process.stdout);
2 changes: 1 addition & 1 deletion demo/client/dune
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
(enabled_if
(= %{profile} "dev"))
(modules index)
(libraries melange demo_shared_js reason-react)
(libraries melange demo_shared_js reason-react melange.dom melange-webapi)
(preprocess
(pps reason-react-ppx browser_ppx -js melange.ppx))
(module_systems es6))
Expand Down
4 changes: 3 additions & 1 deletion demo/client/index.re
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
let _ = MelRaw.mockInitWebsocket();

switch (ReactDOM.querySelector("#root")) {
let element = Webapi.Dom.Document.querySelector("#root", Webapi.Dom.document);

switch (element) {
| Some(el) =>
let _ = ReactDOM.Client.hydrateRoot(el, <App />);
();
Expand Down
35 changes: 20 additions & 15 deletions demo/client/runtime-with-client.jsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
window.__webpack_require__ = (id) => {
const component = window.__client_manifest_map[id];
console.log("REQUIRE ---");
const component = window.__client_manifest_map[id];
console.log(id);
console.log(component);
console.log("---");
/* return { __esModule: true, default: component }; */
return component;
return { __esModule: true, default: component };
};

const React = require("react");
Expand All @@ -21,17 +20,20 @@ const register = (name, render) => {
};

register(
"Note_editor",
React.lazy(() => import("./app/demo/universal/js/Note_editor.js")),
"Counter",
React.lazy(() => import("./app/demo/universal/js/Counter.js"))
);

register(
"Counter",
React.lazy(() => import("./app/demo/universal/js/Counter.js")),
"Note_editor",
React.lazy(() => import("./app/demo/universal/js/Note_editor.js")),
);
register(

/* register(
"Promise_renderer",
React.lazy(() => import("./app/demo/universal/js/Promise_renderer.js")),
);
); */

/* end bootstrap.js */

class ErrorBoundary extends React.Component {
Expand Down Expand Up @@ -68,14 +70,17 @@ try {
const stream = window.srr_stream.readable_stream;
const promise = ReactServerDOM.createFromReadableStream(stream);
const element = document.getElementById("root");
const app = (
<ErrorBoundary>
<Use promise={promise} />
</ErrorBoundary>
);

React.startTransition(() => {
const app = (
<ErrorBoundary>
<Use promise={promise} />
</ErrorBoundary>
);
ReactDOM.hydrateRoot(element, app);
});
} catch (e) {
console.error(e);
console.error("Error type:", e.constructor.name);
console.error("Full error:", e);
console.error("Stack:", e.stack);
}
33 changes: 27 additions & 6 deletions demo/server/server.re
Original file line number Diff line number Diff line change
Expand Up @@ -74,12 +74,34 @@ let stream_rsc = fn => {
);
};

let serverComponentsHandler = request => {
let sleep = (~ms, value) => {
let%lwt () = Lwt_unix.sleep(ms /. 1000.);
Lwt.return(value);
module Page = {
[@react.async.component]
let make = () => {
let%lwt () = Lwt_unix.sleep(1.0);
Lwt.return(
<Layout background=Theme.Color.black>
<Stack gap=8 justify=`start>
<p
className={Cx.make([
"text-3xl",
"font-bold",
Theme.text(Theme.Color.white),
])}>
{React.string("This is a small form")}
</p>
/* TODO: payload is wrong in client components */
<Note_editor title="Hello" body="World" />
<Hr />
<Counter initial=123 onClick={_ => print_endline("Clicked")} />
<Hr />
</Stack>
</Layout>,
);
};
let app = <Noter valueIn3seconds={sleep(~ms=300., "PROMISE VALUE HERE")} />;
};

let serverComponentsHandler = request => {
let app = <Page />;
switch (Dream.header(request, "Accept")) {
| Some(accept) when is_react_component_header(accept) =>
stream_rsc(stream => {
Expand Down Expand Up @@ -202,7 +224,6 @@ let router = [

let () = {
Dream.run(
~adjust_terminal=true,
~port=8080,
~interface={
switch (Sys.getenv_opt("SERVER_INTERFACE")) {
Expand Down
2 changes: 1 addition & 1 deletion demo/universal/native/lib/App.re
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ let make = () => {
<Stack gap=8 justify=`start>
<Title />
<Hr />
<Counter initial=23 onClick={_ => print_endline("Clicked")} />
<Counter initial=22 onClick={_ => print_endline("Clicked")} />
</Stack>
</Layout>;
};
17 changes: 1 addition & 16 deletions demo/universal/native/lib/Counter.re
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ let make = (~initial, ~onClick as [@browser_only] onClick) => {
};

<div className={Theme.text(Theme.Color.white)}>
<Spacer bottom=3>
<Spacer bottom=0>
<div
className={Cx.make([
"flex",
Expand All @@ -28,12 +28,6 @@ let make = (~initial, ~onClick as [@browser_only] onClick) => {
</button>
</div>
</Spacer>
<p className="text-lg">
{React.string(
"The HTML comes from the server"
++ " then is updated by the client after React runs. Via render or hydration (when using ReactDOM.hydrateRoot).",
)}
</p>
</div>;
};

Expand All @@ -51,12 +45,3 @@ let make = (~initial, ~onClick as [@browser_only] onClick) =>
};

let default = make;

/* switch%platform (Runtime.platform) {
| Server => ()
| Client =>
Components.register("Counter", (props: Js.t({..})) => {
React.jsx(make, makeProps(~initial=props##initial, ()))
})
};
*/
11 changes: 11 additions & 0 deletions demo/universal/native/lib/Hr.re
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
[@react.component]
let make = () => {
<hr
className={Cx.make([
"block",
"w-full",
"h-px",
Theme.border("gray-700"),
])}
/>;
};
Loading

0 comments on commit 34fe287

Please sign in to comment.