From 71c20233ff79e696d0eeca2ce1462d3083fbcfed Mon Sep 17 00:00:00 2001 From: polwex Date: Sun, 15 Jun 2025 04:59:49 +0700 Subject: and were done, just like that --- bs5/server/pages/Comments.re | 118 +++++++++++++++++++ bs5/server/pages/Home.re | 40 +++++++ bs5/server/pages/Hydrate.re | 3 + bs5/server/pages/NoteItem.re | 66 +++++++++++ bs5/server/pages/NoteList.re | 52 +++++++++ bs5/server/pages/RouterRSC.re | 186 ++++++++++++++++++++++++++++++ bs5/server/pages/ServerOnlyRSC.re | 46 ++++++++ bs5/server/pages/SidebarNote.re | 32 ++++++ bs5/server/pages/SinglePageRSC.re | 232 ++++++++++++++++++++++++++++++++++++++ 9 files changed, 775 insertions(+) create mode 100644 bs5/server/pages/Comments.re create mode 100644 bs5/server/pages/Home.re create mode 100644 bs5/server/pages/Hydrate.re create mode 100644 bs5/server/pages/NoteItem.re create mode 100644 bs5/server/pages/NoteList.re create mode 100644 bs5/server/pages/RouterRSC.re create mode 100644 bs5/server/pages/ServerOnlyRSC.re create mode 100644 bs5/server/pages/SidebarNote.re create mode 100644 bs5/server/pages/SinglePageRSC.re (limited to 'bs5/server/pages') diff --git a/bs5/server/pages/Comments.re b/bs5/server/pages/Comments.re new file mode 100644 index 0000000..6af39ff --- /dev/null +++ b/bs5/server/pages/Comments.re @@ -0,0 +1,118 @@ +module Post = { + let make = () => { +
+

+ {React.string( + "Notice how HTML for comments 'streams in' before the JavaScript (or React) has loaded on the page. In fact, the demo is entirely rendered in the server and doesn't use client-side JavaScript at all", + )} +

+

+ {React.string("This demo is ")} + {React.string("artificially slowed down")} + {React.string(" while loading the comments data.")} +

+
; + }; +}; + +module Data = { + let delay = 4.0; + + let fakeData = [ + "Wait, it doesn't wait for React to load?", + "How does this even work?", + "I like marshmallows", + "!1!1!1! This is a comment", + "This is actually static from the server", + "But, imagine it's dynamic", + ]; + + let get = () => fakeData; + + let cached = ref(false); + let destroy = () => cached := false; + let promise = () => { + cached.contents + ? Lwt.return(fakeData) + : { + let%lwt () = Lwt_unix.sleep(delay); + cached.contents = true; + Lwt.return(fakeData); + }; + }; +}; + +module Comments = { + [@react.async.component] + let make = () => { + let comments = React.Experimental.use(Data.promise()); + + Lwt.return( +
+ {comments + |> List.mapi((i, comment) => +

+ {React.string(comment)} +

+ ) + |> React.list} +
, + ); + }; +}; + +module Page = { + [@react.component] + let make = () => { + +
+
+

+ {React.string("Rendering React.Suspense on the server")} +

+ +
+

+ {React.string("Comments")} +

+ }> + + +
+

{React.string("Thanks for reading!")}

+
+
+
; + }; +}; + +let handler = _request => { + Dream.stream( + ~headers=[("Content-Type", "text/html")], + response_stream => { + Data.destroy(); + + let pipe = data => { + let%lwt () = Dream.write(response_stream, data); + Dream.flush(response_stream); + }; + + let%lwt (stream, _abort) = + ReactDOM.renderToStream( ); + + Lwt_stream.iter_s(pipe, stream); + }, + ); +}; diff --git a/bs5/server/pages/Home.re b/bs5/server/pages/Home.re new file mode 100644 index 0000000..494a7b7 --- /dev/null +++ b/bs5/server/pages/Home.re @@ -0,0 +1,40 @@ +let handler = _request => { + let app = + +
+
+

+ {React.string("Demos for server-reason-react")} +

+
+ + "This is a list of links to all the demos for server-reason-react's features" + +
+ + "If you want to learn more about server-reason-react, check out the " + + + "documentation" + + " or " + + "repository" + + "." +
+
+ +
+
; + + Dream.html(ReactDOM.renderToStaticMarkup(app)); +}; diff --git a/bs5/server/pages/Hydrate.re b/bs5/server/pages/Hydrate.re new file mode 100644 index 0000000..f887e34 --- /dev/null +++ b/bs5/server/pages/Hydrate.re @@ -0,0 +1,3 @@ +let doc = ; +let toString = ReactDOM.renderToString(doc); +let toStatic = ReactDOM.renderToStaticMarkup(doc); diff --git a/bs5/server/pages/NoteItem.re b/bs5/server/pages/NoteItem.re new file mode 100644 index 0000000..f241cc1 --- /dev/null +++ b/bs5/server/pages/NoteItem.re @@ -0,0 +1,66 @@ +open Rsc; +open Lwt.Syntax; + +module NoteView = { + [@react.component] + let make = (~note: Note.t) => { +
+
+
+

+ {React.string(note.title)} +

+ + {"Last updated on " ++ Date.format_date(note.updated_at)} + +
+ + +
+ +
; + }; +}; + +[@react.async.component] +let make = (~selectedId: option(int), ~isEditing: bool) => { + switch (selectedId) { + | None when isEditing => + Lwt.return( + , + ) + | None => + Lwt.return( +
+ "🥺" + "Click a note on the left to view something!" +
, + ) + | Some(id) => + let+ note: result(Note.t, string) = DB.fetch_note(id); + + switch (note) { + | Ok(note) when !isEditing => + | Ok(note) => + + | Error(error) => +
+
+ "❌" + "There's an error while loading a single note" + error +
+
+ }; + }; +}; diff --git a/bs5/server/pages/NoteList.re b/bs5/server/pages/NoteList.re new file mode 100644 index 0000000..d284f0f --- /dev/null +++ b/bs5/server/pages/NoteList.re @@ -0,0 +1,52 @@ +open Lwt.Syntax; + +let is_substring = (a, b) => { + let len_a = String.length(a); + let len_b = String.length(b); + if (len_a > len_b) { + false; + } else { + let rec check = start => + if (start > len_b - len_a) { + false; + } else if (String.sub(b, start, len_a) == a) { + true; + } else { + check(start + 1); + }; + check(0); + }; +}; + +[@react.async.component] +let make = (~searchText: string) => { + let+ notes = DB.read_notes(); + + switch (notes) { + | Error(error) => +
+ "❌" + "Couldn't read notes file" + error +
+ | Ok(notes) when notes->List.length == 0 => +
+ "There's no notes created yet!" +
+ | Ok(notes) => +
    + {notes + |> List.filter((note: Note.t) => + is_substring( + String.lowercase_ascii(searchText), + String.lowercase_ascii(note.title), + ) + ) + |> List.map((note: Note.t) => +
  • + ) + |> React.list} +
+ }; +}; diff --git a/bs5/server/pages/RouterRSC.re b/bs5/server/pages/RouterRSC.re new file mode 100644 index 0000000..390a8db --- /dev/null +++ b/bs5/server/pages/RouterRSC.re @@ -0,0 +1,186 @@ +// let markdownStyles = (~background, ~text) => { +// Printf.sprintf( +// {| +// .markdown h1 { +// font-size: 2.25rem; +// font-weight: bold; +// line-height: 2.5; +// } + +// .markdown h2 { +// font-size: 1.875rem; +// font-weight: bold; +// line-height: 2.5; +// } + +// .markdown h3 { +// font-size: 1.5rem; +// font-weight: bold; +// line-height: 2.5; +// } + +// .markdown h4 { +// font-size: 1.25rem; +// font-weight: bold; +// line-height: 2.5; +// } + +// .markdown h5 { +// font-size: 1.125rem; +// font-weight: bold; +// line-height: 2.5; +// } + +// .markdown h6 { +// font-size: 1rem; +// font-weight: bold; +// line-height: 2.5; +// } + +// .markdown p { +// font-size: 1rem; +// margin-bottom: 1rem; +// } + +// .markdown ul, .markdown ol { +// padding-left: 2rem; +// margin-bottom: 1rem; +// } + +// .markdown li { +// margin-bottom: 0.5rem; +// } + +// .markdown blockquote { +// border-left: 4px solid %s; +// padding-left: 1rem; +// margin: 1.5rem 0; +// font-style: italic; +// } + +// .markdown pre { +// padding: 1rem; +// margin: 1.5rem 0; +// background-color: %s; +// color: %s; +// border-radius: 0.375rem; +// } + +// .markdown code { +// display: block; +// margin: 1rem; +// padding-left: 1rem; +// padding-right: 1rem; +// font-family: monospace; +// background-color: %s; +// color: %s; +// padding: 0.25rem 0.5rem; +// border-radius: 0.25rem; +// } +// |}, +// background, +// background, +// text, +// background, +// text, +// ); +// }; + +module App = { + [@react.async.component] + let make = (~selectedId, ~isEditing, ~searchText) => { + Lwt.return( + + + + + + + //