├── example ├── .dev.vars ├── src │ ├── global.css │ ├── env.ts │ ├── durable-objects │ │ ├── server-components.tsx │ │ └── counter.ts │ ├── session.ts │ ├── components │ │ └── counter │ │ │ └── client.tsx │ ├── durable-objects.ts │ ├── browser.tsx │ ├── worker.tsx │ └── app.tsx ├── postcss.config.js ├── env.d.ts ├── tailwind.config.ts ├── wrangler.js ├── wrangler.dev.toml ├── wrangler.toml ├── tsconfig.json ├── package.json └── vite.config.ts ├── README.md ├── pnpm-workspace.yaml ├── .gitignore ├── hono-rsc ├── src │ ├── browser.ts │ ├── runtime.client.ts │ ├── runtime.server.ts │ ├── vite.ts │ ├── index.ts │ ├── browser.vite.tsx │ └── runtime.ts ├── modules.d.ts ├── tsconfig.json ├── tsup.config.ts ├── package.json └── test.tsx ├── plugin ├── tsconfig.json ├── tsup.config.ts ├── package.json └── src │ ├── shared.ts │ ├── runner.ts │ ├── durable-object-runner.ts │ └── index.ts ├── package.json └── pnpm-lock.yaml /example/.dev.vars: -------------------------------------------------------------------------------- 1 | COOKIE_SECRET="$uper_$ecret" 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ```sh 2 | pnpm i && pnpm build 3 | cd example 4 | pnpm dev 5 | ``` 6 | -------------------------------------------------------------------------------- /pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | packages: 2 | - example 3 | - hono-rsc 4 | - plugin 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .mf 2 | .wrangler 3 | dist 4 | node_modules 5 | vite.config.ts.timestamp-* 6 | -------------------------------------------------------------------------------- /example/src/global.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | -------------------------------------------------------------------------------- /hono-rsc/src/browser.ts: -------------------------------------------------------------------------------- 1 | import { rscStream } from "rsc-html-stream/client"; 2 | 3 | export { rscStream }; 4 | -------------------------------------------------------------------------------- /example/postcss.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | }; 7 | -------------------------------------------------------------------------------- /hono-rsc/src/runtime.client.ts: -------------------------------------------------------------------------------- 1 | export function registerServerReference(...args: any[]) { 2 | throw new Error("Server references not implemented yet"); 3 | } 4 | -------------------------------------------------------------------------------- /example/env.d.ts: -------------------------------------------------------------------------------- 1 | declare module "__STATIC_CONTENT_MANIFEST" { 2 | const manifest: string; 3 | export default manifest; 4 | } 5 | 6 | declare module "bridge:*" { 7 | const url: string; 8 | export default url; 9 | } 10 | -------------------------------------------------------------------------------- /example/tailwind.config.ts: -------------------------------------------------------------------------------- 1 | import type { Config } from "tailwindcss"; 2 | 3 | export default { 4 | content: ["./src/**/*.{js,ts,jsx,tsx}"], 5 | theme: { 6 | extend: {}, 7 | }, 8 | plugins: [], 9 | } satisfies Config; 10 | -------------------------------------------------------------------------------- /example/wrangler.js: -------------------------------------------------------------------------------- 1 | import prerender from "./dist/prerender/worker.js"; 2 | 3 | export { Counter } from "./dist/server/durables/counter.js"; 4 | export { ServerComponents } from "./dist/server/durables/server-components.js"; 5 | 6 | export default prerender; 7 | -------------------------------------------------------------------------------- /example/src/env.ts: -------------------------------------------------------------------------------- 1 | import type { Counter } from "./durable-objects/counter.js"; 2 | 3 | export type Env = { 4 | COOKIE_SECRET: string; 5 | COUNTER: DurableObjectNamespace; 6 | COUNTER_KV: KVNamespace; 7 | SERVER_COMPONENTS: DurableObjectNamespace; 8 | __STATIC_CONTENT?: KVNamespace; 9 | }; 10 | -------------------------------------------------------------------------------- /plugin/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/tsconfig", 3 | "compilerOptions": { 4 | "target": "ES2022", 5 | "module": "Node16", 6 | "moduleResolution": "Node16", 7 | "strict": true, 8 | "noEmit": true, 9 | "skipLibCheck": true, 10 | "types": ["@cloudflare/workers-types"] 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /hono-rsc/modules.d.ts: -------------------------------------------------------------------------------- 1 | declare module "virtual:client-modules" { 2 | const clientModules: Record Promise>>; 3 | 4 | export default clientModules; 5 | } 6 | 7 | declare module "virtual:server-modules" { 8 | const serverModules: Record Promise>>; 9 | 10 | export default serverModules; 11 | } 12 | -------------------------------------------------------------------------------- /hono-rsc/src/runtime.server.ts: -------------------------------------------------------------------------------- 1 | export function registerClientReference(proxy: any, id: string, name: string) { 2 | return Object.defineProperties(proxy, { 3 | $$typeof: { value: Symbol.for("hono.client.reference") }, 4 | $$id: { value: id }, 5 | $$name: { value: name }, 6 | }); 7 | } 8 | 9 | export function registerServerReference(...args: any[]) { 10 | throw new Error("Server references not implemented yet"); 11 | } 12 | -------------------------------------------------------------------------------- /example/wrangler.dev.toml: -------------------------------------------------------------------------------- 1 | main = "src/worker.tsx" 2 | compatibility_date = "2024-05-28" 3 | 4 | [[kv_namespaces]] 5 | binding = "COUNTER_KV" 6 | id = "fdsa" 7 | 8 | [[durable_objects.bindings]] 9 | name = "COUNTER" 10 | class_name = "Counter" 11 | 12 | [[durable_objects.bindings]] 13 | name = "SERVER_COMPONENTS" 14 | class_name = "ServerComponents" 15 | 16 | [[migrations]] 17 | tag = "v1" 18 | new_classes = ["Counter", "ServerComponents"] 19 | -------------------------------------------------------------------------------- /plugin/tsup.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "tsup"; 2 | 3 | export default [ 4 | defineConfig({ 5 | entry: ["src/durable-object-runner.ts"], 6 | format: ["esm"], 7 | platform: "browser", 8 | external: ["cloudflare:workers"], 9 | }), 10 | defineConfig({ 11 | entry: ["src/runner.ts"], 12 | format: ["esm"], 13 | platform: "browser", 14 | external: ["cloudflare:workers"], 15 | }), 16 | defineConfig({ 17 | entry: ["src/index.ts"], 18 | format: ["esm"], 19 | platform: "node", 20 | dts: true, 21 | external: ["vite", "miniflare", "wrangler"], 22 | }), 23 | ]; 24 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "name": "cf-vite-plugin", 4 | "pnpm": { 5 | "overrides": { 6 | "@cloudflare/kv-asset-handler": "0.3.2", 7 | "@cloudflare/workers-types": "4.20240529.0", 8 | "@types/node": "20.13.0", 9 | "hono": "4.4.2", 10 | "miniflare": "3.20240524.1", 11 | "ts-node": "10.9.2", 12 | "tsup": "8.0.2", 13 | "typescript": "5.4.5", 14 | "wrangler": "3.58.0", 15 | "unplugin-rsc": "0.0.10", 16 | "vite": "6.0.0-alpha.17" 17 | } 18 | }, 19 | "scripts": { 20 | "build": "pnpm --recursive build", 21 | "dev": "pnpm --parallel dev" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /example/wrangler.toml: -------------------------------------------------------------------------------- 1 | name = "cf-vite-plugin-example" 2 | workers_dev = true 3 | main = "wrangler.js" 4 | compatibility_date = "2024-05-28" 5 | 6 | [build] 7 | command = "pnpm build" 8 | 9 | [site] 10 | bucket = "./dist/browser" 11 | 12 | [[kv_namespaces]] 13 | binding = "COUNTER_KV" 14 | id = "2d5935f2fa4548e9ae24fb06926105a2" 15 | 16 | [[durable_objects.bindings]] 17 | name = "COUNTER" 18 | class_name = "Counter" 19 | 20 | [[durable_objects.bindings]] 21 | name = "SERVER_COMPONENTS" 22 | class_name = "ServerComponents" 23 | 24 | [[migrations]] 25 | tag = "v1" 26 | new_classes = ["Counter"] 27 | 28 | [[migrations]] 29 | tag = "v2" 30 | new_classes = ["ServerComponents"] 31 | -------------------------------------------------------------------------------- /example/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/tsconfig", 3 | "include": [ 4 | "env.d.ts", 5 | "tailwind.config.ts", 6 | "vite.config.ts", 7 | "wrangler.ts", 8 | "src/**/*" 9 | ], 10 | "compilerOptions": { 11 | "target": "ES2022", 12 | "module": "Node16", 13 | "moduleResolution": "Node16", 14 | "strict": true, 15 | "noEmit": true, 16 | "skipLibCheck": true, 17 | "lib": ["DOM", "DOM.Iterable", "ESNext"], 18 | "types": [ 19 | "@cloudflare/workers-types", 20 | "@jacob-ebey/hono-server-components/modules", 21 | "dom-navigation", 22 | "vite/client" 23 | ], 24 | "jsx": "react-jsx", 25 | "jsxImportSource": "hono/jsx" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /hono-rsc/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/tsconfig", 3 | "include": ["src/**/*"], 4 | "compilerOptions": { 5 | "outDir": "dist", 6 | "emitDeclarationOnly": true, 7 | "declaration": true, 8 | "target": "ES2022", 9 | "module": "Node16", 10 | "moduleResolution": "Node16", 11 | "strict": true, 12 | "skipLibCheck": true, 13 | "types": ["./modules.d.ts", "node", "vite/client"], 14 | "jsx": "react-jsx", 15 | "jsxImportSource": "hono/jsx", 16 | "sourceMap": true, 17 | "baseUrl": ".", 18 | "rootDir": "./src", 19 | "paths": { 20 | "@jacob-ebey/hono-server-components/browser": ["./src/browser.ts"], 21 | "@jacob-ebey/hono-server-components/runtime": ["./src/runtime.ts"] 22 | } 23 | }, 24 | "ts-node": { 25 | "transpileOnly": true 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /example/src/durable-objects/server-components.tsx: -------------------------------------------------------------------------------- 1 | import type { Env } from "../env.js"; 2 | import { app } from "../app.js"; 3 | 4 | export class ServerComponents implements DurableObject { 5 | #env: Env; 6 | #state: DurableObjectState; 7 | 8 | constructor(state: DurableObjectState, env: Env) { 9 | this.#state = state; 10 | this.#env = env; 11 | } 12 | 13 | async fetch(request: Request) { 14 | const executionCtx = { 15 | passThroughOnException() { 16 | throw new Error("passThroughOnException not implemented"); 17 | }, 18 | waitUntil: (promise: Promise) => { 19 | this.#state.waitUntil(promise); 20 | }, 21 | }; 22 | const res = await app.fetch( 23 | request, 24 | { 25 | ...this.#env, 26 | state: this.#state, 27 | }, 28 | executionCtx 29 | ); 30 | return res; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /plugin/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@jacob-ebey/cf-vite-plugin", 3 | "version": "0.0.0-pre.6", 4 | "type": "module", 5 | "files": [ 6 | "dist" 7 | ], 8 | "exports": { 9 | ".": { 10 | "types": "./dist/index.d.ts", 11 | "default": "./dist/index.js" 12 | }, 13 | "./dist/durable-object-runner.js": "./dist/durable-object-runner.js", 14 | "./dist/runner.js": "./dist/runner.js", 15 | "./package.json": "./package.json" 16 | }, 17 | "scripts": { 18 | "dev": "tsup --watch", 19 | "build": "tsup --clean" 20 | }, 21 | "dependencies": { 22 | "miniflare": "3.20240524.1", 23 | "wrangler": "3.58.0", 24 | "vite": "6.0.0-alpha.18" 25 | }, 26 | "devDependencies": { 27 | "@cloudflare/workers-types": "$cloudflare__workers-types", 28 | "@hattip/adapter-node": "0.0.45", 29 | "@hiogawa/utils": "1.6.4-pre.2", 30 | "@types/node": "$types__node", 31 | "tsup": "$tsup", 32 | "typescript": "$typescript", 33 | "wrangler": "$wrangler" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /example/src/durable-objects/counter.ts: -------------------------------------------------------------------------------- 1 | import { Hono } from "hono"; 2 | 3 | import { Env } from "../env.js"; 4 | 5 | const app = new Hono<{ Bindings: Env & { state: DurableObjectState } }>() 6 | .get("/value", async ({ env: { state }, json }) => { 7 | const value = (await state.storage.get("value")) || 0; 8 | return json(value); 9 | }) 10 | .post("/increment", async ({ env: { state }, json }) => { 11 | const value = (await state.storage.get("value")) || 0; 12 | const newValue = value + 1; 13 | await state.storage.put("value", newValue); 14 | return json(newValue); 15 | }); 16 | 17 | export class Counter implements DurableObject { 18 | #env: Env; 19 | #state: DurableObjectState; 20 | 21 | constructor(state: DurableObjectState, env: Env) { 22 | this.#state = state; 23 | this.#env = env; 24 | } 25 | 26 | async fetch(request: Request) { 27 | return app.fetch(request, { 28 | ...this.#env, 29 | state: this.#state, 30 | }); 31 | } 32 | } 33 | 34 | export type CounterAPI = typeof app; 35 | -------------------------------------------------------------------------------- /plugin/src/shared.ts: -------------------------------------------------------------------------------- 1 | import type { ModuleRunner } from "vite/module-runner"; 2 | 3 | export const RUNNER_INIT_PATH = "/__viteInit"; 4 | export const RUNNER_EVAL_PATH = "/__viteEval"; 5 | export const ANY_URL = "https://any.local"; 6 | 7 | export type RunnerEnv = { 8 | __viteRoot: string; 9 | __viteUnsafeEval: { 10 | eval: (code: string, filename?: string) => any; 11 | }; 12 | __viteFetchModule: { 13 | fetch: (request: Request) => Promise; 14 | }; 15 | __viteRunner: DurableObject; 16 | }; 17 | 18 | export type RunnerFetchMetadata = { 19 | entry: string; 20 | }; 21 | 22 | export type DurableObjectRunnerFetchMetadata = { 23 | entry: string; 24 | }; 25 | 26 | export type EvalFn = (ctx: { 27 | mod: any; 28 | data?: In; 29 | env: any; 30 | runner: ModuleRunner; 31 | }) => Promise | Out; 32 | 33 | export type EvalApi = (request: { 34 | entry: string; 35 | data?: In; 36 | fn: EvalFn; 37 | }) => Promise>; 38 | 39 | export type EvalMetadata = { 40 | entry: string; 41 | fnString: string; 42 | }; 43 | -------------------------------------------------------------------------------- /example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example", 3 | "private": true, 4 | "version": "1.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite dev", 8 | "build": "vite build --app && tsc", 9 | "start": "wrangler dev --local wrangler.js", 10 | "deploy": "wrangler publish" 11 | }, 12 | "dependencies": { 13 | "@cloudflare/kv-asset-handler": "$cloudflare__kv-asset-handler", 14 | "@jacob-ebey/hono-server-components": "workspace:*", 15 | "hono": "$hono", 16 | "workers-swr": "0.0.8" 17 | }, 18 | "devDependencies": { 19 | "@cloudflare/workers-types": "$cloudflare__workers-types", 20 | "@hattip/adapter-node": "0.0.45", 21 | "@jacob-ebey/cf-vite-plugin": "workspace:*", 22 | "@preact/preset-vite": "2.8.2", 23 | "@types/dom-navigation": "1.0.3", 24 | "@types/node": "$types__node", 25 | "@vitejs/plugin-react": "4.3.0", 26 | "autoprefixer": "10.4.19", 27 | "postcss": "8.4.38", 28 | "tailwindcss": "3.4.3", 29 | "typescript": "$typescript", 30 | "unplugin-rsc": "$unplugin-rsc", 31 | "vite": "$vite", 32 | "wrangler": "$wrangler" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /example/src/session.ts: -------------------------------------------------------------------------------- 1 | import type { Context } from "hono"; 2 | import { getSignedCookie, setSignedCookie } from "hono/cookie"; 3 | import { createMiddleware } from "hono/factory"; 4 | 5 | import { durableObjectsMiddleware } from "./durable-objects.js"; 6 | 7 | export type SessionVariables = { 8 | sessionId: string; 9 | }; 10 | 11 | type HonoEnv = { 12 | Bindings: { 13 | COOKIE_SECRET: string; 14 | }; 15 | Variables: SessionVariables; 16 | }; 17 | 18 | export const sessionMiddleware = createMiddleware(async (c, next) => { 19 | const session = await getSignedCookie(c, c.env.COOKIE_SECRET, "_session"); 20 | c.set("sessionId", session || "global"); 21 | 22 | await next(); 23 | }); 24 | 25 | export async function setSessionId(c: Context, sessionId: string) { 26 | c.set("sessionId", sessionId); 27 | await setSignedCookie(c, "_session", sessionId, c.env.COOKIE_SECRET, { 28 | httpOnly: true, 29 | path: "/", 30 | sameSite: "lax", 31 | secure: c.req.url.startsWith("https://") || c.req.url.startsWith("wss://"), 32 | }); 33 | await durableObjectsMiddleware(c, () => Promise.resolve()); 34 | } 35 | -------------------------------------------------------------------------------- /example/src/components/counter/client.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { useState } from "hono/jsx"; 4 | 5 | export function Counter({ initialCount }: { initialCount: number }) { 6 | const [count, setCount] = useState(initialCount); 7 | 8 | return ( 9 |
10 |
11 |
12 |

13 | Yo yo yo! 14 |

15 |

16 | Count: {count} 17 |

18 |

19 | This shit actually works! 20 |

21 |
22 |
23 |
24 | 31 |
32 |
33 | ); 34 | } 35 | -------------------------------------------------------------------------------- /hono-rsc/tsup.config.ts: -------------------------------------------------------------------------------- 1 | import * as fsp from "fs/promises"; 2 | 3 | import { defineConfig } from "tsup"; 4 | 5 | export default [ 6 | defineConfig({ 7 | entry: ["src/runtime.ts"], 8 | format: ["esm"], 9 | platform: "neutral", 10 | dts: false, 11 | external: ["hono/jsx", "hono/utils/html"], 12 | }), 13 | defineConfig({ 14 | entry: ["src/runtime.client.ts"], 15 | format: ["esm"], 16 | platform: "neutral", 17 | dts: false, 18 | }), 19 | defineConfig({ 20 | entry: ["src/runtime.server.ts"], 21 | format: ["esm"], 22 | platform: "neutral", 23 | dts: false, 24 | }), 25 | defineConfig({ 26 | entry: ["src/browser.ts"], 27 | format: ["esm"], 28 | platform: "browser", 29 | dts: false, 30 | }), 31 | defineConfig({ 32 | entry: ["src/browser.vite.tsx"], 33 | format: ["esm"], 34 | platform: "browser", 35 | dts: false, 36 | external: [ 37 | "@jacob-ebey/hono-server-components/browser", 38 | "@jacob-ebey/hono-server-components/runtime", 39 | "virtual:client-modules", 40 | ], 41 | }), 42 | defineConfig({ 43 | entry: ["src/vite.ts"], 44 | format: ["esm"], 45 | platform: "node", 46 | dts: false, 47 | external: ["@jacob-ebey/hono-server-components/runtime", "unplugin-rsc"], 48 | }), 49 | defineConfig({ 50 | entry: ["src/index.ts"], 51 | format: ["esm"], 52 | platform: "neutral", 53 | dts: false, 54 | external: [ 55 | "@jacob-ebey/hono-server-components/runtime", 56 | "hono", 57 | "hono/factory", 58 | "hono/html", 59 | "hono/jsx", 60 | "hono/jsx-renderer", 61 | "hono/jsx/streaming", 62 | "hono/utils/html", 63 | ], 64 | }), 65 | ]; 66 | -------------------------------------------------------------------------------- /hono-rsc/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@jacob-ebey/hono-server-components", 3 | "version": "0.0.0-pre.1", 4 | "type": "module", 5 | "exports": { 6 | ".": { 7 | "types": "./dist/index.d.ts", 8 | "default": "./dist/index.js" 9 | }, 10 | "./browser": { 11 | "types": "./dist/browser.d.ts", 12 | "default": "./dist/browser.js" 13 | }, 14 | "./browser.vite": { 15 | "types": "./dist/browser.vite.d.ts", 16 | "default": "./dist/browser.vite.js" 17 | }, 18 | "./modules": "./modules.d.ts", 19 | "./runtime": { 20 | "types": "./dist/runtime.d.ts", 21 | "default": "./dist/runtime.js" 22 | }, 23 | "./runtime.client": { 24 | "types": "./dist/runtime.client.d.ts", 25 | "default": "./dist/runtime.client.js" 26 | }, 27 | "./runtime.server": { 28 | "types": "./dist/runtime.server.d.ts", 29 | "default": "./dist/runtime.server.js" 30 | }, 31 | "./vite": { 32 | "types": "./dist/vite.d.ts", 33 | "default": "./dist/vite.js" 34 | }, 35 | "./package.json": "./package.json" 36 | }, 37 | "scripts": { 38 | "dev": "tsup --watch", 39 | "build": "tsup --clean && tsc", 40 | "test": "node --no-warnings --loader ts-node/esm ./test.tsx" 41 | }, 42 | "peerDependencies": { 43 | "unplugin-rsc": "$unplugin-rsc", 44 | "vite": "$vite" 45 | }, 46 | "peerDependenciesMeta": { 47 | "unplugin-rsc": { 48 | "optional": true 49 | }, 50 | "vite": { 51 | "optional": true 52 | } 53 | }, 54 | "dependencies": { 55 | "hono": "$hono" 56 | }, 57 | "devDependencies": { 58 | "@hono/node-server": "1.11.2", 59 | "@types/node": "$types__node", 60 | "rsc-html-stream": "0.0.3", 61 | "ts-node": "$ts-node", 62 | "tsup": "$tsup", 63 | "turbo-stream": "2.1.0", 64 | "typescript": "$typescript" 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /example/src/durable-objects.ts: -------------------------------------------------------------------------------- 1 | import type { Hono, Schema } from "hono"; 2 | import type { ClientRequest } from "hono/client"; 3 | import { hc } from "hono/client"; 4 | import { createMiddleware } from "hono/factory"; 5 | import { UnionToIntersection } from "hono/utils/types"; 6 | 7 | import type { CounterAPI } from "./durable-objects/counter.js"; 8 | import type { Env } from "./env.js"; 9 | import type { SessionVariables } from "./session.js"; 10 | 11 | export const durableObjectsMiddleware = createMiddleware<{ 12 | Bindings: Env; 13 | Variables: SessionVariables & { 14 | counter: DurableClient; 15 | }; 16 | }>(async ({ env: { COUNTER }, get, set }, next) => { 17 | set( 18 | "counter", 19 | createDurableClient( 20 | COUNTER.get(COUNTER.idFromName(get("sessionId"))) 21 | ) 22 | ); 23 | return next(); 24 | }); 25 | 26 | type PathToChain< 27 | Path extends string, 28 | E extends Schema, 29 | Original extends string = "" 30 | > = Path extends `/${infer P}` 31 | ? PathToChain 32 | : Path extends `${infer P}/${infer R}` 33 | ? { 34 | [K in P]: PathToChain; 35 | } 36 | : { 37 | [K in Path extends "" ? "index" : Path]: ClientRequest< 38 | E extends Record ? E[Original] : never 39 | >; 40 | }; 41 | 42 | type DurableClient = UnionToIntersection< 43 | T extends Hono 44 | ? S extends Record 45 | ? K extends string 46 | ? PathToChain 47 | : never 48 | : never 49 | : never 50 | >; 51 | 52 | function createDurableClient>( 53 | durable?: DurableObjectStub 54 | ) { 55 | return hc("https://durable-object/", { 56 | fetch: (info: RequestInfo | URL, init?: RequestInit) => { 57 | if (!durable) { 58 | throw new Error("Durable Object not available"); 59 | } 60 | if (info instanceof URL) { 61 | info = info.href; 62 | } 63 | return durable.fetch(info, init); 64 | }, 65 | }); 66 | } 67 | -------------------------------------------------------------------------------- /example/src/browser.tsx: -------------------------------------------------------------------------------- 1 | import type { Child } from "hono/jsx"; 2 | import { 3 | jsx, 4 | render, 5 | useState, 6 | startTransition, 7 | useEffect, 8 | use, 9 | Suspense 10 | } from "hono/jsx/dom"; 11 | 12 | import { decode } from "@jacob-ebey/hono-server-components/runtime"; 13 | import { rscStream } from "@jacob-ebey/hono-server-components/browser"; 14 | 15 | import clientModules from "virtual:client-modules"; 16 | 17 | let updateRoot: (root: Promise) => void; 18 | function DocumentHydrator({ initialRoot }: { initialRoot: Promise }) { 19 | let [root, setRoot] = useState(initialRoot); 20 | // useEffect(() => { 21 | updateRoot = (newRoot) => { 22 | startTransition(() => { 23 | try { 24 | setRoot(newRoot); 25 | } catch (reason) { 26 | console.log({ reason }); 27 | if ( 28 | typeof reason === "object" && 29 | reason && 30 | "then" in reason && 31 | typeof reason.then === "function" 32 | ) { 33 | reason.then( 34 | () => updateRoot(newRoot), 35 | () => {} 36 | ); 37 | 38 | return; 39 | } 40 | throw reason; 41 | } 42 | }); 43 | }; 44 | // }, [setRoot]); 45 | // console.log({ root }); 46 | return {use(root)}; 47 | } 48 | 49 | const decodePromise = decode(rscStream, { 50 | loadClientModule(id) { 51 | if (import.meta.env.PROD) { 52 | return clientModules[id](); 53 | } 54 | return import(/* @vite-ignore */ id); 55 | }, 56 | }).then((decoded) => decoded.value as Child); 57 | 58 | render( 59 | , 60 | document.getElementById("app")! 61 | ); 62 | 63 | let abortController = new AbortController(); 64 | // hydrateDocument().then(({ render }) => { 65 | window.navigation.addEventListener("navigate", (event) => { 66 | if (!event.canIntercept || event.hashChange || event.downloadRequest) { 67 | return; 68 | } 69 | 70 | // Check if the URL is on the same origin. 71 | const url = new URL(event.destination.url); 72 | if (url.origin !== location.origin) { 73 | return; 74 | } 75 | 76 | console.log("INERCEPTED", event) 77 | 78 | event.intercept({ 79 | focusReset: "after-transition", 80 | async handler() { 81 | const toAbort = abortController; 82 | abortController = new AbortController(); 83 | const signal = abortController.signal; 84 | 85 | const response = await fetch(url, { 86 | headers: { 87 | RSC: "1", 88 | }, 89 | credentials: "same-origin", 90 | signal, 91 | method: event.formData ? "POST" : "GET", 92 | body: event.formData, 93 | }); 94 | if (!response.body) { 95 | throw new Error("No RSC body"); 96 | } 97 | 98 | const decoded = await decode(response.body, { 99 | loadClientModule(id) { 100 | if (import.meta.env.PROD) { 101 | return clientModules[id](); 102 | } 103 | return import(/* @vite-ignore */ id); 104 | }, 105 | }); 106 | decoded.done.catch(() => {}); 107 | updateRoot(Promise.resolve(decoded.value as Child)); 108 | 109 | toAbort.abort(); 110 | await decoded.done; 111 | }, 112 | }); 113 | }); 114 | // }); 115 | -------------------------------------------------------------------------------- /example/src/worker.tsx: -------------------------------------------------------------------------------- 1 | import { rscConsumer } from "@jacob-ebey/hono-server-components"; 2 | import { Hono } from "hono"; 3 | 4 | import clientModules from "virtual:client-modules"; 5 | 6 | import { durableObjectsMiddleware } from "./durable-objects.js"; 7 | import type { Env } from "./env.js"; 8 | import type { SessionVariables } from "./session.js"; 9 | import { sessionMiddleware } from "./session.js"; 10 | 11 | import browserEntry from "bridge:./browser.js"; 12 | import stylesEntry from "bridge:./global.css"; 13 | 14 | type HonoEnv = { Bindings: Env; Variables: SessionVariables }; 15 | 16 | function Entry({ entry }: { entry: string }) { 17 | const baseId = entry.replace(/\?.*$/, ""); 18 | if (import.meta.env.PROD && baseId.endsWith(".css")) { 19 | return ; 20 | } 21 | return