From b7993622d2f6d7410c9fc7eb928bcb65a173edf3 Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa Date: Fri, 27 Sep 2024 18:55:32 +0900 Subject: [PATCH 1/8] feat: add web-worker-rsc example --- examples/web-worker-rsc/README.md | 5 ++ examples/web-worker-rsc/e2e/basic.test.ts | 8 +++ examples/web-worker-rsc/index.html | 15 ++++ examples/web-worker-rsc/package.json | 25 +++++++ examples/web-worker-rsc/playwright.config.ts | 26 +++++++ examples/web-worker-rsc/src/app.tsx | 33 +++++++++ examples/web-worker-rsc/src/entry-client.tsx | 9 +++ examples/web-worker-rsc/src/lib/ambient.d.ts | 4 ++ .../src/lib/fetch-module-client.ts | 12 ++++ .../src/lib/fetch-module-server.ts | 21 ++++++ examples/web-worker-rsc/src/lib/runner.ts | 20 ++++++ examples/web-worker-rsc/src/worker/dep.tsx | 1 + examples/web-worker-rsc/src/worker/entry.tsx | 12 ++++ examples/web-worker-rsc/tsconfig.json | 15 ++++ examples/web-worker-rsc/vite.config.ts | 70 +++++++++++++++++++ 15 files changed, 276 insertions(+) create mode 100644 examples/web-worker-rsc/README.md create mode 100644 examples/web-worker-rsc/e2e/basic.test.ts create mode 100644 examples/web-worker-rsc/index.html create mode 100644 examples/web-worker-rsc/package.json create mode 100644 examples/web-worker-rsc/playwright.config.ts create mode 100644 examples/web-worker-rsc/src/app.tsx create mode 100644 examples/web-worker-rsc/src/entry-client.tsx create mode 100644 examples/web-worker-rsc/src/lib/ambient.d.ts create mode 100644 examples/web-worker-rsc/src/lib/fetch-module-client.ts create mode 100644 examples/web-worker-rsc/src/lib/fetch-module-server.ts create mode 100644 examples/web-worker-rsc/src/lib/runner.ts create mode 100644 examples/web-worker-rsc/src/worker/dep.tsx create mode 100644 examples/web-worker-rsc/src/worker/entry.tsx create mode 100644 examples/web-worker-rsc/tsconfig.json create mode 100644 examples/web-worker-rsc/vite.config.ts diff --git a/examples/web-worker-rsc/README.md b/examples/web-worker-rsc/README.md new file mode 100644 index 00000000..4b94af81 --- /dev/null +++ b/examples/web-worker-rsc/README.md @@ -0,0 +1,5 @@ +# web-worker-rsc + +```sh +pnpm dev +``` diff --git a/examples/web-worker-rsc/e2e/basic.test.ts b/examples/web-worker-rsc/e2e/basic.test.ts new file mode 100644 index 00000000..bc925d45 --- /dev/null +++ b/examples/web-worker-rsc/e2e/basic.test.ts @@ -0,0 +1,8 @@ +import { expect, test } from "@playwright/test"; + +test("basic", async ({ page }) => { + await page.goto("/"); + await expect(page.getByTestId("worker-message")).toContainText( + "Rendered in web worker", + ); +}); diff --git a/examples/web-worker-rsc/index.html b/examples/web-worker-rsc/index.html new file mode 100644 index 00000000..d06403cb --- /dev/null +++ b/examples/web-worker-rsc/index.html @@ -0,0 +1,15 @@ + + + + + Worker + + + +
+ + + diff --git a/examples/web-worker-rsc/package.json b/examples/web-worker-rsc/package.json new file mode 100644 index 00000000..cc1face5 --- /dev/null +++ b/examples/web-worker-rsc/package.json @@ -0,0 +1,25 @@ +{ + "name": "@hiogawa/vite-environment-examples-web-worker", + "private": true, + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build --app", + "preview": "vite preview", + "test-e2e": "playwright test", + "test-e2e-preview": "E2E_PREVIEW=1 playwright test" + }, + "dependencies": { + "react": "19.0.0-rc-eb3ad065-20240822", + "react-dom": "19.0.0-rc-eb3ad065-20240822" + }, + "devDependencies": { + "@hiogawa/vite-plugin-ssr-middleware-alpha": "workspace:*", + "@hiogawa/vite-plugin-workerd": "workspace:*", + "@types/react": "18.3.3", + "@types/react-dom": "18.3.0" + }, + "volta": { + "extends": "../../package.json" + } +} diff --git a/examples/web-worker-rsc/playwright.config.ts b/examples/web-worker-rsc/playwright.config.ts new file mode 100644 index 00000000..51ebbc7a --- /dev/null +++ b/examples/web-worker-rsc/playwright.config.ts @@ -0,0 +1,26 @@ +import { defineConfig, devices } from "@playwright/test"; + +const port = Number(process.env["E2E_PORT"] || 6174); +const command = process.env["E2E_PREVIEW"] + ? `pnpm preview --port ${port} --strict-port` + : `pnpm dev --port ${port} --strict-port`; + +export default defineConfig({ + testDir: "e2e", + use: { + trace: "on-first-retry", + }, + projects: [ + { + name: "chromium", + use: devices["Desktop Chrome"], + }, + ], + webServer: { + command, + port, + }, + forbidOnly: !!process.env["CI"], + retries: process.env["CI"] ? 2 : 0, + reporter: "list", +}); diff --git a/examples/web-worker-rsc/src/app.tsx b/examples/web-worker-rsc/src/app.tsx new file mode 100644 index 00000000..2402fae2 --- /dev/null +++ b/examples/web-worker-rsc/src/app.tsx @@ -0,0 +1,33 @@ +import React from "react"; +import workerUrl from "./worker/entry?worker-runner"; + +export function App() { + const [count, setCount] = React.useState(0); + const [workerMessage, setWorkerMessage] = React.useState( + "Waiting for worker...", + ); + + React.useEffect(() => { + const worker = new Worker(workerUrl, { type: "module" }); + worker.addEventListener("message", (e) => { + setWorkerMessage(e.data); + }); + worker.postMessage("ping"); + return () => { + worker.terminate(); + }; + }, []); + + return ( +
+
Count: {count}
+ + +
+
+
+ ); +} diff --git a/examples/web-worker-rsc/src/entry-client.tsx b/examples/web-worker-rsc/src/entry-client.tsx new file mode 100644 index 00000000..d1461c76 --- /dev/null +++ b/examples/web-worker-rsc/src/entry-client.tsx @@ -0,0 +1,9 @@ +import ReactDomClient from "react-dom/client"; +import { App } from "./app"; + +async function main() { + const el = document.getElementById("root"); + ReactDomClient.createRoot(el!).render(); +} + +main(); diff --git a/examples/web-worker-rsc/src/lib/ambient.d.ts b/examples/web-worker-rsc/src/lib/ambient.d.ts new file mode 100644 index 00000000..8e14e1ed --- /dev/null +++ b/examples/web-worker-rsc/src/lib/ambient.d.ts @@ -0,0 +1,4 @@ +declare module "*?worker-runner" { + const src: string; + export default src; +} diff --git a/examples/web-worker-rsc/src/lib/fetch-module-client.ts b/examples/web-worker-rsc/src/lib/fetch-module-client.ts new file mode 100644 index 00000000..157c65d3 --- /dev/null +++ b/examples/web-worker-rsc/src/lib/fetch-module-client.ts @@ -0,0 +1,12 @@ +import type { FetchFunction } from "vite"; + +export function fetchClientFetchModule(environmentName: string): FetchFunction { + return async (...args) => { + const payload = JSON.stringify([environmentName, ...args]); + const response = await fetch( + "/@vite/fetchModule?" + new URLSearchParams({ payload }), + ); + const result = response.json(); + return result as any; + }; +} diff --git a/examples/web-worker-rsc/src/lib/fetch-module-server.ts b/examples/web-worker-rsc/src/lib/fetch-module-server.ts new file mode 100644 index 00000000..c9f83f74 --- /dev/null +++ b/examples/web-worker-rsc/src/lib/fetch-module-server.ts @@ -0,0 +1,21 @@ +import type { FetchFunction, Plugin } from "vite"; + +export function vitePluginFetchModuleServer(): Plugin { + return { + name: vitePluginFetchModuleServer.name, + configureServer(server) { + server.middlewares.use(async (req, res, next) => { + const url = new URL(req.url ?? "/", "https://any.local"); + if (url.pathname === "/@vite/fetchModule") { + const [name, ...args] = JSON.parse(url.searchParams.get("payload")!); + const result = await server.environments[name]!.fetchModule( + ...(args as Parameters), + ); + res.end(JSON.stringify(result)); + return; + } + next(); + }); + }, + }; +} diff --git a/examples/web-worker-rsc/src/lib/runner.ts b/examples/web-worker-rsc/src/lib/runner.ts new file mode 100644 index 00000000..93209025 --- /dev/null +++ b/examples/web-worker-rsc/src/lib/runner.ts @@ -0,0 +1,20 @@ +import { ESModulesEvaluator, ModuleRunner } from "vite/module-runner"; +import { fetchClientFetchModule } from "./fetch-module-client"; + +export function createFetchRunner(options: { + root: string; + environmentName: string; +}) { + const runner = new ModuleRunner( + { + root: options.root, + sourcemapInterceptor: false, + transport: { + fetchModule: fetchClientFetchModule(options.environmentName), + }, + hmr: false, + }, + new ESModulesEvaluator(), + ); + return runner; +} diff --git a/examples/web-worker-rsc/src/worker/dep.tsx b/examples/web-worker-rsc/src/worker/dep.tsx new file mode 100644 index 00000000..c332443c --- /dev/null +++ b/examples/web-worker-rsc/src/worker/dep.tsx @@ -0,0 +1 @@ +export default "dep-ok"; diff --git a/examples/web-worker-rsc/src/worker/entry.tsx b/examples/web-worker-rsc/src/worker/entry.tsx new file mode 100644 index 00000000..03015476 --- /dev/null +++ b/examples/web-worker-rsc/src/worker/entry.tsx @@ -0,0 +1,12 @@ +import ReactDomServer from "react-dom/server"; +import dep from "./dep"; + +const root = ( +
+
Rendered in web worker
+
{dep}
+
+); + +const result = ReactDomServer.renderToString(root); +self.postMessage(result); diff --git a/examples/web-worker-rsc/tsconfig.json b/examples/web-worker-rsc/tsconfig.json new file mode 100644 index 00000000..09584e24 --- /dev/null +++ b/examples/web-worker-rsc/tsconfig.json @@ -0,0 +1,15 @@ +{ + "extends": "@tsconfig/strictest/tsconfig.json", + "include": ["src", "e2e", "*.ts"], + "compilerOptions": { + "exactOptionalPropertyTypes": false, + "verbatimModuleSyntax": true, + "noEmit": true, + "moduleResolution": "Bundler", + "module": "ESNext", + "target": "ESNext", + "lib": ["ESNext", "DOM"], + "types": ["vite/client"], + "jsx": "react-jsx" + } +} diff --git a/examples/web-worker-rsc/vite.config.ts b/examples/web-worker-rsc/vite.config.ts new file mode 100644 index 00000000..8c143d9e --- /dev/null +++ b/examples/web-worker-rsc/vite.config.ts @@ -0,0 +1,70 @@ +import react from "@vitejs/plugin-react"; +import { type Plugin, defineConfig } from "vite"; +import { vitePluginFetchModuleServer } from "./src/lib/fetch-module-server"; + +export default defineConfig((_env) => ({ + clearScreen: false, + plugins: [react(), vitePluginWorkerRunner(), vitePluginFetchModuleServer()], + environments: { + client: { + dev: { + optimizeDeps: { + exclude: ["vite/module-runner"], + }, + }, + }, + worker: { + webCompatible: true, + resolve: { + conditions: ["react-server", "worker"], + noExternal: true, + }, + dev: { + optimizeDeps: { + include: [ + "react", + "react/jsx-runtime", + "react/jsx-dev-runtime", + ], + }, + }, + }, + }, +})); + +function vitePluginWorkerRunner(): Plugin[] { + const workerEntryPlugin: Plugin = { + name: vitePluginWorkerRunner.name + ":entry", + transform(_code, id) { + // rewrite ?worker-runner import + if (id.endsWith("?worker-runner")) { + const workerUrl = id.replace("?worker-runner", "?worker-runner-file"); + return `export default ${JSON.stringify(workerUrl)}`; + } + + // rewrite worker entry to import it from runner + if (id.endsWith("?worker-runner-file")) { + const options = { + root: this.environment.config.root, + environmentName: "worker", + }; + const entryId = id.replace("?worker-runner-file", ""); + const output = ` + import { createFetchRunner } from "/src/lib/runner"; + const runner = createFetchRunner(${JSON.stringify(options)}); + runner.import(${JSON.stringify(entryId)}); + `; + return { code: output }; + } + return; + }, + hotUpdate(ctx) { + // full reload browser on worker code change + if (this.environment.name === "worker" && ctx.modules.length > 0) { + ctx.server.ws.send({ type: "full-reload", path: ctx.file }); + } + }, + }; + + return [workerEntryPlugin]; +} From d9142b75204cc5eaec5206a4c288f7b446afb110 Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa Date: Fri, 27 Sep 2024 19:02:44 +0900 Subject: [PATCH 2/8] wip: cleanup --- examples/web-worker-rsc/index.html | 2 +- examples/web-worker-rsc/vite.config.ts | 46 ++++---------------------- examples/web-worker/vite.config.ts | 2 +- pnpm-lock.yaml | 22 ++++++++++++ 4 files changed, 31 insertions(+), 41 deletions(-) diff --git a/examples/web-worker-rsc/index.html b/examples/web-worker-rsc/index.html index d06403cb..fa685205 100644 --- a/examples/web-worker-rsc/index.html +++ b/examples/web-worker-rsc/index.html @@ -2,7 +2,7 @@ - Worker + Web Worker RSC ({ @@ -16,7 +17,10 @@ export default defineConfig((_env) => ({ worker: { webCompatible: true, resolve: { - conditions: ["react-server", "worker"], + conditions: [ + // "react-server", + "worker", + ], noExternal: true, }, dev: { @@ -25,46 +29,10 @@ export default defineConfig((_env) => ({ "react", "react/jsx-runtime", "react/jsx-dev-runtime", + "react-dom/server", ], }, }, }, }, })); - -function vitePluginWorkerRunner(): Plugin[] { - const workerEntryPlugin: Plugin = { - name: vitePluginWorkerRunner.name + ":entry", - transform(_code, id) { - // rewrite ?worker-runner import - if (id.endsWith("?worker-runner")) { - const workerUrl = id.replace("?worker-runner", "?worker-runner-file"); - return `export default ${JSON.stringify(workerUrl)}`; - } - - // rewrite worker entry to import it from runner - if (id.endsWith("?worker-runner-file")) { - const options = { - root: this.environment.config.root, - environmentName: "worker", - }; - const entryId = id.replace("?worker-runner-file", ""); - const output = ` - import { createFetchRunner } from "/src/lib/runner"; - const runner = createFetchRunner(${JSON.stringify(options)}); - runner.import(${JSON.stringify(entryId)}); - `; - return { code: output }; - } - return; - }, - hotUpdate(ctx) { - // full reload browser on worker code change - if (this.environment.name === "worker" && ctx.modules.length > 0) { - ctx.server.ws.send({ type: "full-reload", path: ctx.file }); - } - }, - }; - - return [workerEntryPlugin]; -} diff --git a/examples/web-worker/vite.config.ts b/examples/web-worker/vite.config.ts index fb944bb2..8cc3c694 100644 --- a/examples/web-worker/vite.config.ts +++ b/examples/web-worker/vite.config.ts @@ -33,7 +33,7 @@ export default defineConfig((_env) => ({ }, })); -function vitePluginWorkerRunner(): Plugin[] { +export function vitePluginWorkerRunner(): Plugin[] { const workerEntryPlugin: Plugin = { name: vitePluginWorkerRunner.name + ":entry", transform(_code, id) { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 78c11214..aed728af 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -219,6 +219,28 @@ importers: specifier: 18.3.0 version: 18.3.0 + examples/web-worker-rsc: + dependencies: + react: + specifier: 19.0.0-rc-eb3ad065-20240822 + version: 19.0.0-rc-eb3ad065-20240822 + react-dom: + specifier: 19.0.0-rc-eb3ad065-20240822 + version: 19.0.0-rc-eb3ad065-20240822(react@19.0.0-rc-eb3ad065-20240822) + devDependencies: + '@hiogawa/vite-plugin-ssr-middleware-alpha': + specifier: workspace:* + version: link:../../packages/ssr-middleware + '@hiogawa/vite-plugin-workerd': + specifier: workspace:* + version: link:../../packages/workerd + '@types/react': + specifier: 18.3.3 + version: 18.3.3 + '@types/react-dom': + specifier: 18.3.0 + version: 18.3.0 + examples/workerd-cli: devDependencies: '@hiogawa/vite-plugin-workerd': From 7aa4c430904f0fd681f011f9efcbd2a431568802 Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa Date: Fri, 27 Sep 2024 19:13:32 +0900 Subject: [PATCH 3/8] wip: react-server in web worker --- examples/web-worker-rsc/package.json | 3 ++- examples/web-worker-rsc/src/lib/ambient.d.ts | 8 ++++++++ examples/web-worker-rsc/src/worker/entry.tsx | 8 +++++--- examples/web-worker-rsc/src/worker/polyfill.ts | 3 +++ examples/web-worker-rsc/vite.config.ts | 7 ++----- pnpm-lock.yaml | 3 +++ 6 files changed, 23 insertions(+), 9 deletions(-) create mode 100644 examples/web-worker-rsc/src/worker/polyfill.ts diff --git a/examples/web-worker-rsc/package.json b/examples/web-worker-rsc/package.json index cc1face5..fd48cf40 100644 --- a/examples/web-worker-rsc/package.json +++ b/examples/web-worker-rsc/package.json @@ -11,7 +11,8 @@ }, "dependencies": { "react": "19.0.0-rc-eb3ad065-20240822", - "react-dom": "19.0.0-rc-eb3ad065-20240822" + "react-dom": "19.0.0-rc-eb3ad065-20240822", + "react-server-dom-webpack": "19.0.0-rc-eb3ad065-20240822" }, "devDependencies": { "@hiogawa/vite-plugin-ssr-middleware-alpha": "workspace:*", diff --git a/examples/web-worker-rsc/src/lib/ambient.d.ts b/examples/web-worker-rsc/src/lib/ambient.d.ts index 8e14e1ed..a69b3792 100644 --- a/examples/web-worker-rsc/src/lib/ambient.d.ts +++ b/examples/web-worker-rsc/src/lib/ambient.d.ts @@ -2,3 +2,11 @@ declare module "*?worker-runner" { const src: string; export default src; } + +declare module "react-server-dom-webpack/server" { + export function renderToReadableStream( + data: T, + bundlerConfig: unknown, + opitons?: unknown, + ): ReadableStream; +} diff --git a/examples/web-worker-rsc/src/worker/entry.tsx b/examples/web-worker-rsc/src/worker/entry.tsx index 03015476..7d480ca2 100644 --- a/examples/web-worker-rsc/src/worker/entry.tsx +++ b/examples/web-worker-rsc/src/worker/entry.tsx @@ -1,4 +1,5 @@ -import ReactDomServer from "react-dom/server"; +import "./polyfill"; +import ReactServer from "react-server-dom-webpack/server"; import dep from "./dep"; const root = ( @@ -8,5 +9,6 @@ const root = ( ); -const result = ReactDomServer.renderToString(root); -self.postMessage(result); +const stream = ReactServer.renderToReadableStream(root, {}, {}); +console.log(stream); +self.postMessage("TODO"); diff --git a/examples/web-worker-rsc/src/worker/polyfill.ts b/examples/web-worker-rsc/src/worker/polyfill.ts new file mode 100644 index 00000000..1b0ad131 --- /dev/null +++ b/examples/web-worker-rsc/src/worker/polyfill.ts @@ -0,0 +1,3 @@ +Object.assign(globalThis, { + __webpack_require__: () => {}, +}); diff --git a/examples/web-worker-rsc/vite.config.ts b/examples/web-worker-rsc/vite.config.ts index 27bf9028..3004d792 100644 --- a/examples/web-worker-rsc/vite.config.ts +++ b/examples/web-worker-rsc/vite.config.ts @@ -17,10 +17,7 @@ export default defineConfig((_env) => ({ worker: { webCompatible: true, resolve: { - conditions: [ - // "react-server", - "worker", - ], + conditions: ["react-server"], noExternal: true, }, dev: { @@ -29,7 +26,7 @@ export default defineConfig((_env) => ({ "react", "react/jsx-runtime", "react/jsx-dev-runtime", - "react-dom/server", + "react-server-dom-webpack/server", ], }, }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index aed728af..4f65435e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -227,6 +227,9 @@ importers: react-dom: specifier: 19.0.0-rc-eb3ad065-20240822 version: 19.0.0-rc-eb3ad065-20240822(react@19.0.0-rc-eb3ad065-20240822) + react-server-dom-webpack: + specifier: 19.0.0-rc-eb3ad065-20240822 + version: 19.0.0-rc-eb3ad065-20240822(react-dom@19.0.0-rc-eb3ad065-20240822(react@19.0.0-rc-eb3ad065-20240822))(react@19.0.0-rc-eb3ad065-20240822)(webpack@5.93.0(esbuild@0.23.0)) devDependencies: '@hiogawa/vite-plugin-ssr-middleware-alpha': specifier: workspace:* From e464e3c6299f41d577f04dd20d209fd0e84f9843 Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa Date: Fri, 27 Sep 2024 19:15:04 +0900 Subject: [PATCH 4/8] chore: readme --- examples/web-worker-rsc/README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/examples/web-worker-rsc/README.md b/examples/web-worker-rsc/README.md index 4b94af81..6b4d9244 100644 --- a/examples/web-worker-rsc/README.md +++ b/examples/web-worker-rsc/README.md @@ -1,5 +1,7 @@ # web-worker-rsc +Based on [`../web-worker`](../web-worker) + ```sh pnpm dev ``` From 144c3b5bebeb7db99c36fa9deee8d0fe48ce7b15 Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa Date: Fri, 27 Sep 2024 19:27:28 +0900 Subject: [PATCH 5/8] wip: rsc stream demo --- examples/web-worker-rsc/index.html | 2 +- examples/web-worker-rsc/src/app.tsx | 3 ++- .../src/{entry-client.tsx => entry.tsx} | 1 + .../polyfill.ts => polyfill-webpack.ts} | 0 examples/web-worker-rsc/src/worker/entry.tsx | 16 ++++------------ examples/web-worker-rsc/src/worker/root.tsx | 18 ++++++++++++++++++ examples/web-worker-rsc/tsconfig.json | 2 +- 7 files changed, 27 insertions(+), 15 deletions(-) rename examples/web-worker-rsc/src/{entry-client.tsx => entry.tsx} (87%) rename examples/web-worker-rsc/src/{worker/polyfill.ts => polyfill-webpack.ts} (100%) create mode 100644 examples/web-worker-rsc/src/worker/root.tsx diff --git a/examples/web-worker-rsc/index.html b/examples/web-worker-rsc/index.html index fa685205..2c7ded77 100644 --- a/examples/web-worker-rsc/index.html +++ b/examples/web-worker-rsc/index.html @@ -10,6 +10,6 @@
- + diff --git a/examples/web-worker-rsc/src/app.tsx b/examples/web-worker-rsc/src/app.tsx index 2402fae2..521455b1 100644 --- a/examples/web-worker-rsc/src/app.tsx +++ b/examples/web-worker-rsc/src/app.tsx @@ -12,7 +12,6 @@ export function App() { worker.addEventListener("message", (e) => { setWorkerMessage(e.data); }); - worker.postMessage("ping"); return () => { worker.terminate(); }; @@ -20,10 +19,12 @@ export function App() { return (
+

Client

Count: {count}

+

Worker

-
Rendered in web worker
-
{dep}
-
-); - -const stream = ReactServer.renderToReadableStream(root, {}, {}); -console.log(stream); -self.postMessage("TODO"); +const stream = ReactServer.renderToReadableStream(, {}, {}); +self.postMessage(stream, { transfer: [stream] }); diff --git a/examples/web-worker-rsc/src/worker/root.tsx b/examples/web-worker-rsc/src/worker/root.tsx new file mode 100644 index 00000000..86b9065f --- /dev/null +++ b/examples/web-worker-rsc/src/worker/root.tsx @@ -0,0 +1,18 @@ +import React from "react"; + +export function Root() { + return ( +
+
Rendered in web worker
+
[rendered at {new Date().toISOString()}]
+ Sleeping 1 sec...
}> + + +
+ ); +} + +async function Sleep(props: { ms: number }) { + await new Promise((r) => setTimeout(r, props.ms)); + return
[rendered at {new Date().toISOString()}]
; +} diff --git a/examples/web-worker-rsc/tsconfig.json b/examples/web-worker-rsc/tsconfig.json index 09584e24..2f9026d4 100644 --- a/examples/web-worker-rsc/tsconfig.json +++ b/examples/web-worker-rsc/tsconfig.json @@ -9,7 +9,7 @@ "module": "ESNext", "target": "ESNext", "lib": ["ESNext", "DOM"], - "types": ["vite/client"], + "types": ["vite/client", "react/experimental"], "jsx": "react-jsx" } } From a1f84f2abd92ee4e41b3ac1e1333c41728bd58b5 Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa Date: Fri, 27 Sep 2024 20:33:00 +0900 Subject: [PATCH 6/8] wip: react client --- examples/web-worker-rsc/src/app.tsx | 15 ++++++++------- examples/web-worker-rsc/src/lib/ambient.d.ts | 7 +++++++ examples/web-worker-rsc/src/worker/dep.tsx | 1 - examples/web-worker-rsc/src/worker/root.tsx | 8 ++++++-- 4 files changed, 21 insertions(+), 10 deletions(-) delete mode 100644 examples/web-worker-rsc/src/worker/dep.tsx diff --git a/examples/web-worker-rsc/src/app.tsx b/examples/web-worker-rsc/src/app.tsx index 521455b1..ea83f33f 100644 --- a/examples/web-worker-rsc/src/app.tsx +++ b/examples/web-worker-rsc/src/app.tsx @@ -1,16 +1,20 @@ import React from "react"; +import ReactClient from "react-server-dom-webpack/client"; import workerUrl from "./worker/entry?worker-runner"; export function App() { const [count, setCount] = React.useState(0); - const [workerMessage, setWorkerMessage] = React.useState( + const [workerMessage, setWorkerMessage] = React.useState( "Waiting for worker...", ); React.useEffect(() => { const worker = new Worker(workerUrl, { type: "module" }); - worker.addEventListener("message", (e) => { - setWorkerMessage(e.data); + worker.addEventListener("message", async (e) => { + const root = await ReactClient.createFromReadableStream( + e.data, + ); + setWorkerMessage(root); }); return () => { worker.terminate(); @@ -25,10 +29,7 @@ export function App() {

Worker

-
+
{workerMessage}
); } diff --git a/examples/web-worker-rsc/src/lib/ambient.d.ts b/examples/web-worker-rsc/src/lib/ambient.d.ts index a69b3792..82a838a6 100644 --- a/examples/web-worker-rsc/src/lib/ambient.d.ts +++ b/examples/web-worker-rsc/src/lib/ambient.d.ts @@ -10,3 +10,10 @@ declare module "react-server-dom-webpack/server" { opitons?: unknown, ): ReadableStream; } + +declare module "react-server-dom-webpack/client" { + export function createFromReadableStream( + stream: ReadableStream, + options?: unknown, + ): Promise; +} diff --git a/examples/web-worker-rsc/src/worker/dep.tsx b/examples/web-worker-rsc/src/worker/dep.tsx deleted file mode 100644 index c332443c..00000000 --- a/examples/web-worker-rsc/src/worker/dep.tsx +++ /dev/null @@ -1 +0,0 @@ -export default "dep-ok"; diff --git a/examples/web-worker-rsc/src/worker/root.tsx b/examples/web-worker-rsc/src/worker/root.tsx index 86b9065f..34999bad 100644 --- a/examples/web-worker-rsc/src/worker/root.tsx +++ b/examples/web-worker-rsc/src/worker/root.tsx @@ -5,8 +5,12 @@ export function Root() {
Rendered in web worker
[rendered at {new Date().toISOString()}]
- Sleeping 1 sec...
}> - + [Suspense:fallback] Sleeping 2 sec...} + > +
+ [Suspense:OK] +
); From 752f3ea0a7331f7ab89d9e36e62fc1b4c1bd3e03 Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa Date: Fri, 27 Sep 2024 20:42:03 +0900 Subject: [PATCH 7/8] chore: tweak --- examples/web-worker-rsc/src/worker/root.tsx | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/examples/web-worker-rsc/src/worker/root.tsx b/examples/web-worker-rsc/src/worker/root.tsx index 34999bad..712067e0 100644 --- a/examples/web-worker-rsc/src/worker/root.tsx +++ b/examples/web-worker-rsc/src/worker/root.tsx @@ -6,10 +6,14 @@ export function Root() {
Rendered in web worker
[rendered at {new Date().toISOString()}]
[Suspense:fallback] Sleeping 2 sec...} + fallback={ +
+ [Suspense:fallback]
Sleeping 1 sec...
+
+ } >
- [Suspense:OK] + [Suspense:OK]
From 293ac6f1c3c87fd843d06527b841b23269b86025 Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa Date: Sat, 28 Sep 2024 09:59:29 +0900 Subject: [PATCH 8/8] test: add e2e --- .github/workflows/ci.yml | 1 + examples/web-worker-rsc/e2e/basic.test.ts | 5 ++++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 32a1c6f0..2477517f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -23,6 +23,7 @@ jobs: - run: pnpm -C examples/workerd-cli test - run: pnpm -C examples/browser-cli test - run: pnpm -C examples/web-worker test-e2e + - run: pnpm -C examples/web-worker-rsc test-e2e - run: pnpm -C examples/react-server test-e2e - run: pnpm -C examples/react-server build - run: pnpm -C examples/react-server test-e2e-preview diff --git a/examples/web-worker-rsc/e2e/basic.test.ts b/examples/web-worker-rsc/e2e/basic.test.ts index bc925d45..82de1624 100644 --- a/examples/web-worker-rsc/e2e/basic.test.ts +++ b/examples/web-worker-rsc/e2e/basic.test.ts @@ -3,6 +3,9 @@ import { expect, test } from "@playwright/test"; test("basic", async ({ page }) => { await page.goto("/"); await expect(page.getByTestId("worker-message")).toContainText( - "Rendered in web worker", + "[Suspense:fallback]", + ); + await expect(page.getByTestId("worker-message")).toContainText( + "[Suspense:OK]", ); });