-
-
Notifications
You must be signed in to change notification settings - Fork 9
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
7 changed files
with
209 additions
and
94 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
server-reason-react use the same API as [reason-react] (explained in their {{:https://reasonml.github.io/reason-react/docs/en/components}official documentation}). | ||
|
||
Components are functions that return a [React.element] and are annotated with [@react.component]. By convention, they are named `make`, so when they are used inside JSX, they can be written without the `make` prefix and just the name of the module. If another name is used, it can be specified later in the JSX. For example, [<Counter />] and [<Counter.component />] are both valid. | ||
|
||
{[ | ||
module Counter = { | ||
[@react.component] | ||
let make = (~name) => { | ||
let (count, setCount) = React.useState(() => 0); | ||
|
||
<div> | ||
<p> {React.string(name ++ " clicked " ++ Int.to_string(count) ++ " times")} </p> | ||
<button onClick={_ => setCount(_ => count + 1)}> | ||
{React.string("Click me")} | ||
</button> | ||
</div> | ||
}; | ||
}; | ||
|
||
module App = { | ||
let make = () => { | ||
<Counter name="John" /> | ||
}; | ||
}; | ||
]} | ||
|
||
{[ | ||
/* Imagine that this is a handler for an HTTP request, inside your server */ | ||
let handler = (_request) => { | ||
/* If we want to render the component to a string and return the HTML to the client: */ | ||
let html = ReactDOM.renderToString(<App />); | ||
respond(html); | ||
}; | ||
]} | ||
|
||
Hooks like [React.useState] or [React.useEffect] are available in the [React] module, but when running in the server, they are no-ops. Since react components don't re-render on the server, hooks like [React.useCallback], [React.useMemo] have no memoization and return the same value just once. | ||
|
||
{1:render-to-string Server-side rendering with renderToString/renderToStaticMarkup} | ||
|
||
[ReactDOM.renderToString] renders a React tree to an HTML string. It does not support streaming or waiting for data. You can use [renderToStream] instead. | ||
|
||
{[ | ||
let html = ReactDOM.renderToString(<App />); | ||
]} | ||
|
||
[ReactDOM.renderToStaticMarkup] renders a non-interactive React tree to an HTML string. Non-interactive means that the component should not be hydrated on the client. This can be useful to for Server-side generation of HTML (SSG). | ||
|
||
{[ | ||
let html = ReactDOM.renderToStaticMarkup(<App />); | ||
]} | ||
|
||
{1:render-to-stream Server-side rendering with renderToStream} | ||
|
||
[ReactDOM.renderToStream] renders a React tree to a {{:https://ocsigen.org/lwt/3.1.0/api/Lwt_stream}Lwt_stream.t}. with type [Lwt_stream.t(string)]. | ||
|
||
This snippet assumes that you are using [Dream](https://dream.ocsigen.org/) to serve the HTML, and [lwt_ppx](https://github.com/ocsigen/lwt_ppx) to handle the [let%lwt] syntax. | ||
|
||
Those are optional choices, you can use any other server framework and not use [lwt_ppx]. | ||
{[ | ||
let%lwt (stream, abort) = ReactDOM.renderToStream(<App />); | ||
stream |> Lwt_stream.iter_s((chunk => { | ||
/* Dream.write pushes the chunk into the response stream */ | ||
let%lwt () = Dream.write(response_stream, chunk); | ||
Dream.flush(response_stream); | ||
})); | ||
|
||
/* abort is a function that can be called to stop the stream, and leaves the client to render the rest of the page */ | ||
]} | ||
|
||
Note that [Lwt] is a required library. More details {{:https://github.com/ml-in-barcelona/server-reason-react/issues/205}in this issue}. | ||
|
||
{1:rsc React Server Components} | ||
|
||
React Server Components (RSC) is an architecture that allows you to render React components exclusively on the server, using Server-side code (such as Database queries or Filesystem operations). It allows to strip all client components (those components that require interactivity) from the JavaScript bundle sent to the client. | ||
|
||
There's a entire area of improvements that RSC bring to the table, such as lazy loading client components only when they are needed, remove fetching data with useEffect hooks (by passing promises to those client components), and removing state by lifting it to the URL. | ||
|
||
Half of the JavaScript ecosystem is losing their hair over this, but with [server-reason-react] you would be able to use from another language. | ||
|
||
This library supports it, but many pieces are being polished, check the {{:https://github.com/ml-in-barcelona/server-reason-react/tree/main/demo}demo folder} for more information or the {{:https://github.com/ml-in-barcelona/server-reason-react/issues/204}umbrella issue}. |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,122 @@ | ||
{0 Exclude client code from the native build} | ||
|
||
[browser_only] is the ppx to exclude client code from the server build, making sure a library can run on both build targets. It also allows to conditionally execute code based on the platform. | ||
|
||
For example, if you're using Webapi to query the DOM and extract some data from it. This code should only run on the client, and there's no equivalent or fallback on the server. | ||
|
||
In order to compile and run successfully on the server, you can use [let%browser_only],[switch%platform] or the [@platform]. This page explains how to use them. | ||
|
||
{1 Example} | ||
|
||
The ppx expose a [%browser_only] attribute that can be used to discard a function, and [switch%platform] to conditionally execute code based on the platform. | ||
|
||
{[ | ||
let%browser_only countDomNodes = (id) => { | ||
let elements = Webapi.Element.querySelector("#" ++ id); | ||
let arr_elements = Webapi.Element.toArray(elements); | ||
Array.length(arr_elements); | ||
} | ||
]} | ||
|
||
{[ | ||
switch%platform (Runtime.platform) { | ||
| Server => print_endline("This prints to the terminal") | ||
| Client => Js.log("This prints to the console") | ||
}; | ||
]} | ||
|
||
{1 Installation} | ||
|
||
Add [server-reason-react.browser_ppx] into to your pps field under a dune stanzas (melange.emit, libraries or executable) in your dune files. | ||
|
||
You would need to add it on both "server" and "client" dune files. Adding the [-js] flag [server-reason-react.browser_ppx -js] for the client and without for the server: | ||
{[ | ||
; server exectuable | ||
(executable | ||
(name server) | ||
(preprocess | ||
(pps server-reason-react.browser_ppx))) | ||
|
||
; melange emitting JavaScript | ||
(melange.emit | ||
(target app) | ||
(preprocess | ||
(pps server-reason-react.browser_ppx -js))) | ||
]} | ||
|
||
{1 Usage} | ||
|
||
{2 let%browser_only to discard functions} | ||
{[ | ||
let%browser_only countDomNodes = (id) => { | ||
let elements = Webapi.Element.querySelector("#" ++ id); | ||
let arr_elements = Webapi.Element.toArray(elements); | ||
Array.length(arr_elements); | ||
}; | ||
]} | ||
|
||
The method tagged by [browser_only] will keep the function for the client build, but will be discarded for the server build. In more detail, the body of function will be transformed to a [Runtime.Impossible_in_ssr] exception (and | ||
|
||
If this function ever runs on the server by accident, it will raise the exception. If this exception isn't caught, the server will crash. Calling a function on the server that should only run on the client, it's very unlikely to happen. | ||
|
||
In any case, there may be cases where catch the exception and provide a default behaviour/value, can be useful. | ||
|
||
Following with the example from above: | ||
{[ | ||
let%browser_only countDomNodes = (id) => { | ||
let elements = Webapi.Element.querySelector("#" ++ id); | ||
let arr_elements = Webapi.Element.toArray(elements); | ||
Array.length(arr_elements); | ||
} | ||
|
||
let main = id => | ||
switch (countDomNodes(id)) { | ||
| exception Runtime.Impossible_in_ssr(_message) => 0 | ||
}; | ||
]} | ||
|
||
{2 switch%platform to conditionally exclude an expression for each platform} | ||
|
||
[switch%platform] allows to conditionally execute code based on the platform. There are some cases where you need to run a specific code only on the server or only on the client. | ||
|
||
{[ | ||
switch%platform (Runtime.platform) { | ||
| Server => print_endline("This prints to the terminal") | ||
| Client => Js.log("This prints to the console") | ||
}; | ||
]} | ||
|
||
Not only executing code, but any expression can be part of the switch. | ||
|
||
{[ | ||
let howManyColumns = | ||
switch%platform (Runtime.platform) { | ||
| Server => 0 | ||
| Client => 12 | ||
}; | ||
]} | ||
|
||
Note that the expression is evaluated for each platform, but the type needs to be the same for all the branches | ||
|
||
{2 @platform attribute} | ||
|
||
The [@platform] attribute allows to specify code blocks that should only be included in the JavaScript or native build, respectively. This is useful when you have code that is specific to one platform and should not be included in the other. | ||
|
||
For example, you can define two modules, but only one of them should be kept in the final build based on the platform. | ||
{[ | ||
[@platform js] | ||
module X = { | ||
type t = Js.Json.t; | ||
|
||
let a = 2 + 2; | ||
}; | ||
|
||
[@platform native] | ||
module Y = { | ||
type t = Js.Json.t; | ||
|
||
let a = 4 + 4; | ||
}; | ||
]} | ||
|
||
When compiling with the `-js` flag, only the block with [[@platform js]] (module X) is kept, and when compiling without it, only the block with [[@platform native]] (module Y) is kept. If you name the modules the same, the compiler won't complain and you would get a single module available in both targets. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters