diff --git a/.github/workflows/prerelease.yaml b/.github/workflows/prerelease.yaml index 14847e0..695d4cb 100644 --- a/.github/workflows/prerelease.yaml +++ b/.github/workflows/prerelease.yaml @@ -40,6 +40,12 @@ jobs: env: NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + - name: Publish web-fragments + run: pnpm publish --no-git-checks --tag dev --access public + working-directory: packages/web-fragments + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + - name: Publish vite run: pnpm publish --no-git-checks --tag dev --access public working-directory: packages/vite @@ -69,10 +75,10 @@ jobs: Install the prerelease packages with: \`\`\`bash - npm install @orange-js/orange@0.0.0-${sha} @orange-js/actors@0.0.0-${sha} && npm install -D @orange-js/vite@0.0.0-${sha} @orange-js/cli@0.0.0-${sha} - pnpm add @orange-js/orange@0.0.0-${sha} @orange-js/actors@0.0.0-${sha} && pnpm add -D @orange-js/vite@0.0.0-${sha} @orange-js/cli@0.0.0-${sha} - yarn add @orange-js/orange@0.0.0-${sha} @orange-js/actors@0.0.0-${sha} && yarn add -D @orange-js/vite@0.0.0-${sha} @orange-js/cli@0.0.0-${sha} - bun add @orange-js/orange@0.0.0-${sha} @orange-js/actors@0.0.0-${sha} && bun add -D @orange-js/vite@0.0.0-${sha} @orange-js/cli@0.0.0-${sha} + npm install @orange-js/orange@0.0.0-${sha} @orange-js/actors@0.0.0-${sha} @orange-js/web-fragments@0.0.0-${sha} && npm install -D @orange-js/vite@0.0.0-${sha} @orange-js/cli@0.0.0-${sha} + pnpm add @orange-js/orange@0.0.0-${sha} @orange-js/actors@0.0.0-${sha} @orange-js/web-fragments@0.0.0-${sha} && pnpm add -D @orange-js/vite@0.0.0-${sha} @orange-js/cli@0.0.0-${sha} + yarn add @orange-js/orange@0.0.0-${sha} @orange-js/actors@0.0.0-${sha} @orange-js/web-fragments@0.0.0-${sha} && yarn add -D @orange-js/vite@0.0.0-${sha} @orange-js/cli@0.0.0-${sha} + bun add @orange-js/orange@0.0.0-${sha} @orange-js/actors@0.0.0-${sha} @orange-js/web-fragments@0.0.0-${sha} && bun add -D @orange-js/vite@0.0.0-${sha} @orange-js/cli@0.0.0-${sha} \`\`\``; github.rest.issues.createComment({ diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 2326356..05830d2 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -32,6 +32,12 @@ jobs: env: NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + - name: Publish web-fragments + run: pnpm publish + working-directory: packages/web-fragments + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + - name: Publish vite run: pnpm publish working-directory: packages/vite diff --git a/packages/core/package.json b/packages/core/package.json index 417222c..ff84f3e 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -35,6 +35,10 @@ "./hono": { "import": "./dist/hono.js", "types": "./dist/hono.d.ts" + }, + "./rsc": { + "import": "./dist/rsc.js", + "types": "./dist/rsc.d.ts" } }, "optionalDependencies": { diff --git a/packages/core/src/rsc.ts b/packages/core/src/rsc.ts new file mode 100644 index 0000000..6604680 --- /dev/null +++ b/packages/core/src/rsc.ts @@ -0,0 +1,13 @@ +import { ErrorInfo } from "react"; +import { renderHtml } from "./server.js"; + +export * from "@vitejs/plugin-rsc/rsc"; + +export async function renderServerComponentStreamToHtmlReadableStream( + stream: ReadableStream, + options?: { + onError?: (error: unknown, errorInfo: ErrorInfo) => void; + } +) { + return await renderHtml(stream, options); +} diff --git a/packages/core/src/server.tsx b/packages/core/src/server.tsx index 53c87c6..91e911c 100644 --- a/packages/core/src/server.tsx +++ b/packages/core/src/server.tsx @@ -158,11 +158,7 @@ async function rscResponse({ }); } - const { renderHTML } = await import.meta.viteRsc.loadModule< - typeof import("./ssr.js") - >("ssr", "index"); - - const htmlStream = await renderHTML(rscStream, { + const htmlStream = await renderHtml(rscStream, { formState, // allow quick simulation of javscript disabled browser debugNojs: url.searchParams.has("__nojs"), @@ -186,6 +182,16 @@ const wsPattern = new URLPattern({ pathname: "/:actor/:id", }); +type RenderHtml = typeof import("./ssr.js").renderHTML; + +export const renderHtml: RenderHtml = async (stream, options) => { + const { renderHTML } = await import.meta.viteRsc.loadModule< + typeof import("./ssr.js") + >("ssr", "index"); + + return renderHTML(stream, options); +}; + export function app(Layout: Layout, options?: AppOptions) { return { async fetch(request: Request) { diff --git a/packages/web-fragments/package.json b/packages/web-fragments/package.json new file mode 100644 index 0000000..b9a8a3f --- /dev/null +++ b/packages/web-fragments/package.json @@ -0,0 +1,30 @@ +{ + "name": "@orange-js/web-fragments", + "version": "0.3.0", + "description": "", + "type": "module", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "exports": { + ".": { + "import": "./dist/index.js", + "types": "./dist/index.d.ts" + } + }, + "scripts": { + "build": "tsc", + "build:watch": "tsc -w" + }, + "devDependencies": { + "@types/react": "^19.1.8", + "react": "^19.1.1", + "typescript": "^5.7.2" + }, + "keywords": [], + "author": "", + "license": "ISC", + "packageManager": "pnpm@10.13.1", + "peerDependencies": { + "react": "^19.1.1" + } +} diff --git a/packages/web-fragments/src/index.tsx b/packages/web-fragments/src/index.tsx new file mode 100644 index 0000000..f7706e3 --- /dev/null +++ b/packages/web-fragments/src/index.tsx @@ -0,0 +1,46 @@ +import React from "react"; +import { app as orangeApp } from "@orange-js/orange/server"; +import { routes } from "virtual:orange/routes"; +import { + renderToReadableStream, + renderServerComponentStreamToHtmlReadableStream, +} from "@orange-js/orange/rsc"; + +export const app: typeof orangeApp = (Layout, options) => { + const { fetch } = orangeApp(Layout, options); + return { + fetch: async (request: Request) => { + if (request.url.includes("/__web-fragments")) { + if ("__webFragmentsHandler" in globalThis) { + // @ts-ignore + return await globalThis.__webFragmentsHandler(request); + } + + const fallbacks: Record = {}; + + for (const route of routes) { + if ("FragmentFallback" in route.module) { + const stream = renderToReadableStream({ + root: ( + + {/* @ts-ignore */} + + + ), + }); + + const htmlStream = + await renderServerComponentStreamToHtmlReadableStream(stream); + const html = await new Response(htmlStream).text(); + + fallbacks[route.pattern.pathname] = html; + } + } + + return Response.json(fallbacks); + } + + return await fetch(request); + }, + }; +}; diff --git a/packages/web-fragments/tsconfig.json b/packages/web-fragments/tsconfig.json new file mode 100644 index 0000000..38d58f4 --- /dev/null +++ b/packages/web-fragments/tsconfig.json @@ -0,0 +1,13 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "types": [ + "vite/client", + "@vitejs/plugin-rsc/types", + "@cloudflare/workers-types", + "@types/node", + "@orange-js/orange/modules" + ], + "outDir": "dist" + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f8ef43b..ab1ad79 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -301,6 +301,18 @@ importers: specifier: ^7.1.5 version: 7.1.5(@types/node@22.13.0)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.43.1)(tsx@4.19.2) + packages/web-fragments: + devDependencies: + '@types/react': + specifier: ^19.1.8 + version: 19.1.8 + react: + specifier: ^19.1.1 + version: 19.1.1 + typescript: + specifier: ^5.7.2 + version: 5.8.3 + packages: '@ampproject/remapping@2.3.0': diff --git a/scripts/version-for-prerelease.ts b/scripts/version-for-prerelease.ts index fe5d369..9cb7802 100644 --- a/scripts/version-for-prerelease.ts +++ b/scripts/version-for-prerelease.ts @@ -12,4 +12,4 @@ for (const pkg of pkgs) { pkgJson.version = `0.0.0-${commitHash}`; fs.writeFileSync(pkgJsonPath, JSON.stringify(pkgJson, null, 2)); -} \ No newline at end of file +}