From 460b4ba6bf25bf3e6198147b4f2f3ca8f7071d0d Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa Date: Sun, 28 Jul 2024 13:07:56 +0900 Subject: [PATCH 1/4] chore(rsbuild-rsc): tweak demo --- rsbuild-rsc/src/entry-server.tsx | 21 ++++++---- rsbuild-rsc/src/routes/layout.tsx | 19 ++++++++- rsbuild-rsc/src/routes/page.tsx | 2 +- rsbuild-rsc/src/routes/stream/_client3.tsx | 5 +++ rsbuild-rsc/src/routes/stream/page.tsx | 47 ++++++++++++++++++++++ rsbuild-rsc/src/style.css | 10 +++-- 6 files changed, 90 insertions(+), 14 deletions(-) create mode 100644 rsbuild-rsc/src/routes/stream/_client3.tsx create mode 100644 rsbuild-rsc/src/routes/stream/page.tsx diff --git a/rsbuild-rsc/src/entry-server.tsx b/rsbuild-rsc/src/entry-server.tsx index 23ef4fb8..a7ed4bf9 100644 --- a/rsbuild-rsc/src/entry-server.tsx +++ b/rsbuild-rsc/src/entry-server.tsx @@ -16,7 +16,7 @@ export async function handler(request: Request): Promise { } // [react node -> flight] react server - const node = ; + const node = ; const { browserManifest } = await getClientManifest(); const flightStream = ReactServer.renderToReadableStream( { node }, @@ -25,12 +25,17 @@ export async function handler(request: Request): Promise { return { flightStream }; } -async function Router() { +async function Router(props: { request: Request }) { + const url = new URL(props.request.url); const { default: Layout } = await import("./routes/layout"); - const { default: Page } = await import("./routes/page"); - return ( - - - - ); + let page =

Not Found

; // TODO: 404 status + if (url.pathname === "/") { + const { default: Page } = await import("./routes/page"); + page = ; + } + if (url.pathname === "/stream") { + const { default: Page } = await import("./routes/stream/page"); + page = ; + } + return {page}; } diff --git a/rsbuild-rsc/src/routes/layout.tsx b/rsbuild-rsc/src/routes/layout.tsx index 20e030ed..c1a7df0b 100644 --- a/rsbuild-rsc/src/routes/layout.tsx +++ b/rsbuild-rsc/src/routes/layout.tsx @@ -10,8 +10,25 @@ export default function Layout(props: React.PropsWithChildren) { rel="icon" href="https://assets.rspack.dev/rsbuild/favicon-128x128.png" /> + - {props.children} + +
+ Menu: + Home + Stream +
+
+ {props.children} +
+ ); } diff --git a/rsbuild-rsc/src/routes/page.tsx b/rsbuild-rsc/src/routes/page.tsx index ef2b636b..5f6fadb1 100644 --- a/rsbuild-rsc/src/routes/page.tsx +++ b/rsbuild-rsc/src/routes/page.tsx @@ -3,7 +3,7 @@ import { Counter, Hydrated } from "./_client"; export default function Page() { return ( -
+

Rsbuild RSC

{React.version}
diff --git a/rsbuild-rsc/src/routes/stream/_client3.tsx b/rsbuild-rsc/src/routes/stream/_client3.tsx new file mode 100644 index 00000000..07c12365 --- /dev/null +++ b/rsbuild-rsc/src/routes/stream/_client3.tsx @@ -0,0 +1,5 @@ +"use client"; + +export function Client3() { + return test-client3; +} diff --git a/rsbuild-rsc/src/routes/stream/page.tsx b/rsbuild-rsc/src/routes/stream/page.tsx new file mode 100644 index 00000000..e830892b --- /dev/null +++ b/rsbuild-rsc/src/routes/stream/page.tsx @@ -0,0 +1,47 @@ +import React from "react"; +import { Client3 } from "./_client3"; + +export default function Page() { + return ( +
+

Stream

+ +
+
Outer
+
[rendered at {new Date().toISOString()}]
+
+
+ Sleeping 1 sec...
}> + + +
+
+ ); +} + +async function Sleep() { + await new Promise((r) => setTimeout(r, 1000)); + return ( +
+
Inner
+
[rendered at {new Date().toISOString()}]
+
+ ); +} diff --git a/rsbuild-rsc/src/style.css b/rsbuild-rsc/src/style.css index d9fc092c..907928b9 100644 --- a/rsbuild-rsc/src/style.css +++ b/rsbuild-rsc/src/style.css @@ -1,3 +1,5 @@ +/* based on https://github.com/vitejs/vite/blob/86cf1b4b497557f09a0d9a81dc304e7a081d6198/packages/create-vite/template-react/src/index.css */ + :root { font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif; line-height: 1.5; @@ -25,9 +27,10 @@ a:hover { body { margin: 0; display: flex; - place-items: center; + flex-direction: column; + padding: 1rem 0; + gap: 1rem 0; min-width: 320px; - min-height: 100vh; } h1 { @@ -70,7 +73,6 @@ button:focus-visible { #root { max-width: 1280px; margin: 0 auto; - padding: 2rem; text-align: center; } @@ -103,7 +105,7 @@ button:focus-visible { } .card { - padding: 2em; + padding: 1rem; } .read-the-docs { From 484d0fec1ad727b4101a972d65cb316fbad53061 Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa Date: Sun, 28 Jul 2024 13:14:30 +0900 Subject: [PATCH 2/4] chore: fix preview --- rsbuild-rsc/vite.config.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/rsbuild-rsc/vite.config.ts b/rsbuild-rsc/vite.config.ts index c4cb76bd..c045c241 100644 --- a/rsbuild-rsc/vite.config.ts +++ b/rsbuild-rsc/vite.config.ts @@ -5,10 +5,17 @@ import { defineConfig } from "vite"; // use vite preview server for local build export default defineConfig({ + appType: "custom", plugins: [ { name: "preview-middleware", async configurePreviewServer(server) { + server.middlewares.use((req, _res, next) => { + // disable compression entirely since it breaks streaming + delete req.headers["accept-encoding"]; + next(); + }); + const mod = await import(path.resolve("./dist/ssr/index.cjs")); return () => { server.middlewares.use(webToNodeHandler(mod.default.default)); From 2b6c8bda3626cb4ce5382be963b34a7c24a52ab6 Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa Date: Sun, 28 Jul 2024 13:18:10 +0900 Subject: [PATCH 3/4] chore: client side navigation --- rsbuild-rsc/src/entry-browser.tsx | 32 ++++++++++++++++++--- rsbuild-rsc/src/lib/router/browser.ts | 41 +++++++++++++++++++++++++++ 2 files changed, 69 insertions(+), 4 deletions(-) create mode 100644 rsbuild-rsc/src/lib/router/browser.ts diff --git a/rsbuild-rsc/src/entry-browser.tsx b/rsbuild-rsc/src/entry-browser.tsx index 3e5269d4..e78634ad 100644 --- a/rsbuild-rsc/src/entry-browser.tsx +++ b/rsbuild-rsc/src/entry-browser.tsx @@ -4,6 +4,7 @@ import ReactClient from "react-server-dom-webpack/client.browser"; import type { FlightData } from "./entry-server"; import "./lib/virtual-client-references-browser.js"; import "./style.css"; +import { setupBrowserRouter } from "./lib/router/browser"; async function main() { const url = new URL(window.location.href); @@ -11,14 +12,37 @@ async function main() { return; } + // TODO + const callServer = () => {}; + // [flight => react node] react client - const initialFlight = await ReactClient.createFromReadableStream( + const initialFlight = ReactClient.createFromReadableStream( (self as any).__flightStream, - // TODO - { callServer: () => {} }, + { callServer }, ); - let browserRoot = initialFlight.node; + function BrowserRoot() { + const [flight, setFlight] = + React.useState>(initialFlight); + + React.useEffect(() => { + return setupBrowserRouter(() => { + const url = new URL(window.location.href); + url.searchParams.set("__f", ""); + React.startTransition(() => + setFlight( + ReactClient.createFromFetch(fetch(url), { + callServer, + }), + ), + ); + }); + }, []); + + return <>{React.use(flight).node}; + } + + let browserRoot = ; if (!url.searchParams.has("__nostrict")) { browserRoot = {browserRoot}; } diff --git a/rsbuild-rsc/src/lib/router/browser.ts b/rsbuild-rsc/src/lib/router/browser.ts new file mode 100644 index 00000000..804d9947 --- /dev/null +++ b/rsbuild-rsc/src/lib/router/browser.ts @@ -0,0 +1,41 @@ +export function setupBrowserRouter(onNavigation: () => void) { + window.addEventListener("pushstate", onNavigation); + window.addEventListener("popstate", onNavigation); + + const oldPushState = window.history.pushState; + window.history.pushState = function (...args) { + const res = oldPushState.apply(this, args); + onNavigation(); + return res; + }; + + const oldReplaceState = window.history.replaceState; + window.history.replaceState = function (...args) { + const res = oldReplaceState.apply(this, args); + onNavigation(); + return res; + }; + + function linkHandler(e: MouseEvent) { + const el = e.target; + if ( + e.button === 0 && + !(e.metaKey || e.altKey || e.ctrlKey || e.shiftKey) && + el instanceof HTMLAnchorElement && + (!el.target || el.target === "_self") && + new URL(el.href).origin === window.location.origin + ) { + e.preventDefault(); + window.history.pushState({}, "", el.href); + } + } + document.addEventListener("click", linkHandler); + + return () => { + document.removeEventListener("click", linkHandler); + window.removeEventListener("pushstate", onNavigation); + window.removeEventListener("popstate", onNavigation); + window.history.pushState = oldPushState; + window.history.replaceState = oldReplaceState; + }; +} From 47da63dbfd505a8fe71a2ee2be25215920d535fa Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa Date: Sun, 28 Jul 2024 13:19:24 +0900 Subject: [PATCH 4/4] chore: minify esbuild --- rsbuild-rsc/misc/vercel/build.js | 1 + 1 file changed, 1 insertion(+) diff --git a/rsbuild-rsc/misc/vercel/build.js b/rsbuild-rsc/misc/vercel/build.js index e5906423..55a8fd3c 100644 --- a/rsbuild-rsc/misc/vercel/build.js +++ b/rsbuild-rsc/misc/vercel/build.js @@ -60,6 +60,7 @@ async function main() { entryPoints: [join(import.meta.dirname, "entry.js")], outfile: join(outDir, "functions/index.func/index.js"), bundle: true, + minify: true, format: "esm", platform: "browser", });