diff --git a/examples/hattip-app/.testRun.ts b/examples/hattip-app/.testRun.ts index 131deab..c6a1527 100644 --- a/examples/hattip-app/.testRun.ts +++ b/examples/hattip-app/.testRun.ts @@ -2,13 +2,17 @@ import { expect, fetchHtml, getServerUrl, page, run, test } from "@brillout/test export { testRun }; +let isProd: boolean; + function testRun(cmd: `pnpm run ${"dev" | "preview"}${string}`, options?: Parameters[1]) { run(cmd, options); + isProd = !cmd.startsWith("pnpm run dev"); + testUrl({ url: "/", title: "My Vike App", - text: "Rendered to HTML", + text: isProd ? "SSR running on Cloudflare" : "Rendered to HTML", textHydration: "Rendered to HTML", }); diff --git a/examples/hattip-app/hattip-entry.ts b/examples/hattip-app/hattip-entry.ts index 8f3f9be..aed2071 100644 --- a/examples/hattip-app/hattip-entry.ts +++ b/examples/hattip-app/hattip-entry.ts @@ -1,29 +1,18 @@ +import type { HattipHandler } from "@hattip/core"; +import { createRouter } from "@hattip/router"; +import { createHandler } from "@universal-middleware/hattip"; import { createTodoHandler } from "./server/create-todo-handler"; import { vikeHandler } from "./server/vike-handler"; -import type { HattipHandler } from "@hattip/core"; -import { createRouter, type RouteHandler } from "@hattip/router"; - -type Middleware> = (request: Request, context: Context) => Response | void | Promise | Promise - -function handlerAdapter>( - handler: Middleware, -): RouteHandler { - return (context) => { - const rawContext = context as unknown as Record; - rawContext.context ??= {}; - return handler(context.request, rawContext.context as Context); - }; -} const router = createRouter(); -router.post("/api/todo/create", handlerAdapter(createTodoHandler)); +router.post("/api/todo/create", createHandler(() => createTodoHandler)()); /** * Vike route * * @link {@see https://vike.dev} **/ -router.use(handlerAdapter(vikeHandler)); +router.use(createHandler(() => vikeHandler)()); export default router.buildHandler() as HattipHandler; diff --git a/examples/hattip-app/package.json b/examples/hattip-app/package.json index b686c95..8d99022 100644 --- a/examples/hattip-app/package.json +++ b/examples/hattip-app/package.json @@ -17,6 +17,7 @@ "@hattip/adapter-cloudflare-workers": "^0.0.47", "@hattip/adapter-node": "^0.0.47", "@hattip/vite": "^0.0.47", + "@universal-middleware/core": "^0.2.6", "cross-env": "^7.0.3", "typescript": "^5.5.4", "vike-cloudflare": "^0.1.0", @@ -25,6 +26,7 @@ "dependencies": { "@hattip/core": "^0.0.47", "@hattip/router": "^0.0.47", + "@universal-middleware/hattip": "^0.2.3", "cross-fetch": "^4.0.0", "hattip": "^0.0.33", "lowdb": "^7.0.1", diff --git a/examples/hattip-app/pages/index/+Page.tsx b/examples/hattip-app/pages/index/+Page.tsx index 8159b5b..4fba84e 100644 --- a/examples/hattip-app/pages/index/+Page.tsx +++ b/examples/hattip-app/pages/index/+Page.tsx @@ -1,12 +1,16 @@ +import { usePageContext } from "vike-solid/usePageContext"; import { Counter } from "./Counter.js"; export default function Page() { + const ctx = usePageContext(); + return ( <>

My Vike app

This page is:
  • Rendered to HTML.
  • + {typeof ctx?.ctx?.waitUntil === "function" ?
  • SSR running on Cloudflare
  • : null}
  • Interactive.
  • diff --git a/examples/hattip-app/server/create-todo-handler.ts b/examples/hattip-app/server/create-todo-handler.ts index b62fd52..035196e 100644 --- a/examples/hattip-app/server/create-todo-handler.ts +++ b/examples/hattip-app/server/create-todo-handler.ts @@ -1,7 +1,6 @@ -export async function createTodoHandler>( - request: Request, - _context?: Context, -): Promise { +import type { UniversalHandler } from "@universal-middleware/core"; + +export const createTodoHandler = (async (request: Request): Promise => { await request.json(); return new Response(JSON.stringify({ status: "OK" }), { @@ -10,4 +9,4 @@ export async function createTodoHandler>( - request: Request, - context?: Context, -): Promise { - const pageContextInit = { ...context, urlOriginal: request.url }; +export const vikeHandler = (async (request, context, runtime): Promise => { + const pageContextInit = { ...context, urlOriginal: request.url, ...runtime }; const pageContext = await renderPage(pageContextInit); const response = pageContext.httpResponse; @@ -16,4 +14,4 @@ export async function vikeHandler[1]) { run(cmd, options); - isProd = cmd !== "pnpm run dev"; + isProd = !cmd.startsWith("pnpm run dev"); testUrl({ url: "/", title: "My Vike App", - text: "Rendered to HTML", + text: isProd ? "SSR running on Cloudflare" : "Rendered to HTML", textHydration: "Rendered to HTML", }); diff --git a/examples/hono-app/hono-entry.ts b/examples/hono-app/hono-entry.ts index f3e8999..3704a6b 100644 --- a/examples/hono-app/hono-entry.ts +++ b/examples/hono-app/hono-entry.ts @@ -1,40 +1,17 @@ +import { createHandler } from "@universal-middleware/hono"; import { Hono } from "hono"; -import { createMiddleware } from "hono/factory"; import { createTodoHandler } from "./server/create-todo-handler.js"; import { vikeHandler } from "./server/vike-handler"; -type Middleware> = (request: Request, context: Context) => Response | void | Promise | Promise - -export function handlerAdapter>( - handler: Middleware, -) { - return createMiddleware(async (context, next) => { - let ctx = context.get("context"); - if (!ctx) { - ctx = {}; - context.set("context", ctx); - } - - const res = await handler(context.req.raw, ctx as Context); - context.set("context", ctx); - - if (!res) { - await next(); - } - - return res; - }); -} - const app = new Hono(); -app.post("/api/todo/create", handlerAdapter(createTodoHandler)); +app.post("/api/todo/create", createHandler(() => createTodoHandler)()); /** * Vike route * * @link {@see https://vike.dev} **/ -app.all("*", handlerAdapter(vikeHandler)); +app.all("*", createHandler(() => vikeHandler)()); export default app; diff --git a/examples/hono-app/package.json b/examples/hono-app/package.json index f9a30a3..8a298c4 100644 --- a/examples/hono-app/package.json +++ b/examples/hono-app/package.json @@ -18,11 +18,13 @@ "@hono/vite-cloudflare-pages": "^0.4.2", "@hono/vite-dev-server": "^0.14.0", "@types/node": "^18.19.14", + "@universal-middleware/core": "^0.2.6", "typescript": "^5.5.4", "vike-cloudflare": "^0.1.0", "wrangler": "^3.72.2" }, "dependencies": { + "@universal-middleware/hono": "^0.2.4", "cross-fetch": "^4.0.0", "hono": "^4.5.8", "solid-js": "^1.8.21", diff --git a/examples/hono-app/pages/index/+Page.tsx b/examples/hono-app/pages/index/+Page.tsx index 8159b5b..4fba84e 100644 --- a/examples/hono-app/pages/index/+Page.tsx +++ b/examples/hono-app/pages/index/+Page.tsx @@ -1,12 +1,16 @@ +import { usePageContext } from "vike-solid/usePageContext"; import { Counter } from "./Counter.js"; export default function Page() { + const ctx = usePageContext(); + return ( <>

    My Vike app

    This page is:
    • Rendered to HTML.
    • + {typeof ctx?.ctx?.waitUntil === "function" ?
    • SSR running on Cloudflare
    • : null}
    • Interactive.
    • diff --git a/examples/hono-app/server/create-todo-handler.ts b/examples/hono-app/server/create-todo-handler.ts index b62fd52..035196e 100644 --- a/examples/hono-app/server/create-todo-handler.ts +++ b/examples/hono-app/server/create-todo-handler.ts @@ -1,7 +1,6 @@ -export async function createTodoHandler>( - request: Request, - _context?: Context, -): Promise { +import type { UniversalHandler } from "@universal-middleware/core"; + +export const createTodoHandler = (async (request: Request): Promise => { await request.json(); return new Response(JSON.stringify({ status: "OK" }), { @@ -10,4 +9,4 @@ export async function createTodoHandler>( - request: Request, - context?: Context, -): Promise { - const pageContextInit = { ...context, urlOriginal: request.url }; +export const vikeHandler = (async (request, context, runtime): Promise => { + const pageContextInit = { ...context, urlOriginal: request.url, ...runtime }; const pageContext = await renderPage(pageContextInit); const response = pageContext.httpResponse; @@ -16,4 +14,4 @@ export async function vikeHandler[1]) { run(cmd, options); - isProd = cmd !== "pnpm run dev"; + isProd = !cmd.startsWith("pnpm run dev"); testUrl({ url: "/", title: "My Vike App", - text: "Rendered to HTML", + text: isProd ? "SSR running on Cloudflare" : "Rendered to HTML", textHydration: "Rendered to HTML", }); diff --git a/examples/vike-app/pages/index/+Page.tsx b/examples/vike-app/pages/index/+Page.tsx index 8159b5b..4fba84e 100644 --- a/examples/vike-app/pages/index/+Page.tsx +++ b/examples/vike-app/pages/index/+Page.tsx @@ -1,12 +1,16 @@ +import { usePageContext } from "vike-solid/usePageContext"; import { Counter } from "./Counter.js"; export default function Page() { + const ctx = usePageContext(); + return ( <>

      My Vike app

      This page is:
      • Rendered to HTML.
      • + {typeof ctx?.ctx?.waitUntil === "function" ?
      • SSR running on Cloudflare
      • : null}
      • Interactive.
      • diff --git a/examples/vike-app/tsconfig.json b/examples/vike-app/tsconfig.json index 8691f0e..214d0cf 100644 --- a/examples/vike-app/tsconfig.json +++ b/examples/vike-app/tsconfig.json @@ -13,7 +13,7 @@ "moduleResolution": "Bundler", "target": "ES2022", "lib": ["DOM", "DOM.Iterable", "ESNext"], - "types": ["vite/client", "vike-solid/client"], + "types": ["vite/client", "vike-solid/client", "vike-cloudflare/types"], "jsx": "preserve", "jsxImportSource": "solid-js" }, diff --git a/packages/vike-cloudflare/assets/vike.js b/packages/vike-cloudflare/assets/vike.js index f50c69b..fb8d6c4 100644 --- a/packages/vike-cloudflare/assets/vike.js +++ b/packages/vike-cloudflare/assets/vike.js @@ -3,37 +3,40 @@ import { renderPage } from "vike/server"; /** * @param url {string} + * @param ctx {{ env: any, ctx: any }} * @returns {Promise} */ -async function handleSsr(url) { +async function handleSsr(url, ctx) { const pageContextInit = { urlOriginal: url, fetch, + ...ctx, }; const pageContext = await renderPage(pageContextInit); const { httpResponse } = pageContext; if (!httpResponse) { return new Response("Something went wrong", { status: 500 }); } - const { statusCode: status, headers } = httpResponse; + const { statusCode: status, headers } = httpResponse; - const { readable, writable } = new TransformStream(); + const { readable, writable } = new TransformStream(); - httpResponse.pipe(writable); + httpResponse.pipe(writable); - return new Response(readable, { - status, - headers, - }); + return new Response(readable, { + status, + headers, + }); } export default { /** * @param request {Request} - * @param env {{}} + * @param env {any} + * @param ctx {any} * @returns {Promise} */ - async fetch(request, env) { - return handleSsr(request.url); + async fetch(request, env, ctx) { + return handleSsr(request.url, { env, ctx }); }, }; diff --git a/packages/vike-cloudflare/package.json b/packages/vike-cloudflare/package.json index 02207ba..370c510 100644 --- a/packages/vike-cloudflare/package.json +++ b/packages/vike-cloudflare/package.json @@ -33,14 +33,19 @@ "@hattip/adapter-cloudflare-workers": "^0.0.47" }, "files": [ - "dist" + "dist", + "vike.d.ts" ], + "types": "./vike.d.ts", "exports": { ".": { "import": "./dist/index.js", "node": "./dist/index.js", "types": "./dist/index.d.ts", "default": "./dist/index.js" + }, + "./types": { + "types": "./vike.d.ts" } }, "repository": "github:vikejs/vike-cloudflare" diff --git a/packages/vike-cloudflare/vike.d.ts b/packages/vike-cloudflare/vike.d.ts new file mode 100644 index 0000000..749679f --- /dev/null +++ b/packages/vike-cloudflare/vike.d.ts @@ -0,0 +1,14 @@ +declare global { + namespace Vike { + interface PageContext { + ctx?: { + // biome-ignore lint/suspicious/noExplicitAny: + waitUntil(promise: Promise): void; + passThroughOnException(): void; + }; + env?: Record; + } + } +} + +export type {}; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 57eb607..3a37ab7 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -32,6 +32,9 @@ importers: '@hattip/router': specifier: ^0.0.47 version: 0.0.47 + '@universal-middleware/hattip': + specifier: ^0.2.3 + version: 0.2.3 cross-fetch: specifier: ^4.0.0 version: 4.0.0 @@ -66,6 +69,9 @@ importers: '@hattip/vite': specifier: ^0.0.47 version: 0.0.47(vite@5.4.2(@types/node@22.5.0)) + '@universal-middleware/core': + specifier: ^0.2.6 + version: 0.2.6 cross-env: specifier: ^7.0.3 version: 7.0.3 @@ -77,10 +83,13 @@ importers: version: link:../../packages/vike-cloudflare wrangler: specifier: ^3.72.2 - version: 3.72.2 + version: 3.72.2(@cloudflare/workers-types@4.20240821.1) examples/hono-app: dependencies: + '@universal-middleware/hono': + specifier: ^0.2.4 + version: 0.2.4 cross-fetch: specifier: ^4.0.0 version: 4.0.0 @@ -115,6 +124,9 @@ importers: '@types/node': specifier: ^18.19.14 version: 18.19.44 + '@universal-middleware/core': + specifier: ^0.2.6 + version: 0.2.6 typescript: specifier: ^5.5.4 version: 5.5.4 @@ -123,7 +135,7 @@ importers: version: link:../../packages/vike-cloudflare wrangler: specifier: ^3.72.2 - version: 3.72.2 + version: 3.72.2(@cloudflare/workers-types@4.20240821.1) examples/vike-app: dependencies: @@ -160,7 +172,7 @@ importers: version: link:../../packages/vike-cloudflare wrangler: specifier: ^3.72.2 - version: 3.72.2 + version: 3.72.2(@cloudflare/workers-types@4.20240821.1) packages/vike-cloudflare: optionalDependencies: @@ -418,8 +430,8 @@ packages: resolution: {integrity: sha512-cqtLW1QiBC/ABaZIhAdyGCsnHHY6pAb6hsVUZg82Co2gQtf/faxRYV1FgpCwUYroTdk6A66xUMSTmFqreKCJow==} engines: {node: '>=16.7.0'} - '@cloudflare/workers-types@4.20240806.0': - resolution: {integrity: sha512-8lvgrwXGTZEBsUQJ8YUnMk72Anh9omwr6fqWLw/EwVgcw1nQxs/bfdadBEbdP48l9fWXjE4E5XERLUrrFuEpsg==} + '@cloudflare/workers-types@4.20240821.1': + resolution: {integrity: sha512-icAkbnAqgVl6ef9lgLTom8na+kj2RBw2ViPAQ586hbdj0xZcnrjK7P46Eu08OU9D/lNDgN2sKU/sxhe2iK/gIg==} '@cspotcode/source-map-support@0.8.1': resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} @@ -1196,6 +1208,15 @@ packages: '@types/normalize-package-data@2.4.4': resolution: {integrity: sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==} + '@universal-middleware/core@0.2.6': + resolution: {integrity: sha512-MLkmKuf9uGQxQ3PddoXkyozowTRNXkSNYSHHwlRbF6Yu7UnGw+KST5tpOfax8UHzJHjxIuIwRviLtVINHakVOw==} + + '@universal-middleware/hattip@0.2.3': + resolution: {integrity: sha512-vji9laimgfCzjQzBysCEuY2u7c410Fd5zfTm/LkDaZaxQTb7yGKU8IpbwQ/U0ZC4jvPzSE4JmHGFL5N26SyK1g==} + + '@universal-middleware/hono@0.2.4': + resolution: {integrity: sha512-q6C72mw5rQdPGQoDRee9mrMFkF3G1h0AB34LKzkxeBN0e6QYvPvMAbB35WTqNSzx3Sf7wKK1j9Lf3gMaFpSlEQ==} + '@vavite/connect@4.1.2': resolution: {integrity: sha512-BeyDUMHM+OGlqrZkJaPj7JLmDzd00nmqg6baU/jgOmKKfvmtWl/Ybvss8k2G8d93jhcPjg6Bp3sbEXILLIRoSA==} peerDependencies: @@ -2726,7 +2747,7 @@ snapshots: '@cloudflare/workers-shared@0.3.0': {} - '@cloudflare/workers-types@4.20240806.0': {} + '@cloudflare/workers-types@4.20240821.1': {} '@cspotcode/source-map-support@0.8.1': dependencies: @@ -3020,7 +3041,7 @@ snapshots: '@hattip/adapter-cloudflare-workers@0.0.47': dependencies: '@cloudflare/kv-asset-handler': 0.3.4 - '@cloudflare/workers-types': 4.20240806.0 + '@cloudflare/workers-types': 4.20240821.1 '@hattip/core': 0.0.47 '@hattip/adapter-node@0.0.47': @@ -3227,6 +3248,16 @@ snapshots: '@types/normalize-package-data@2.4.4': {} + '@universal-middleware/core@0.2.6': {} + + '@universal-middleware/hattip@0.2.3': + dependencies: + '@universal-middleware/core': 0.2.6 + + '@universal-middleware/hono@0.2.4': + dependencies: + '@universal-middleware/core': 0.2.6 + '@vavite/connect@4.1.2(vite@5.4.2(@types/node@22.5.0))': dependencies: '@types/node': 18.19.44 @@ -4516,7 +4547,7 @@ snapshots: '@cloudflare/workerd-linux-arm64': 1.20240821.1 '@cloudflare/workerd-windows-64': 1.20240821.1 - wrangler@3.72.2: + wrangler@3.72.2(@cloudflare/workers-types@4.20240821.1): dependencies: '@cloudflare/kv-asset-handler': 0.3.4 '@cloudflare/workers-shared': 0.3.0 @@ -4537,6 +4568,7 @@ snapshots: workerd: 1.20240821.1 xxhash-wasm: 1.0.2 optionalDependencies: + '@cloudflare/workers-types': 4.20240821.1 fsevents: 2.3.3 transitivePeerDependencies: - bufferutil