diff --git a/packages/waku/package.json b/packages/waku/package.json index 68591b582..8f47a3647 100644 --- a/packages/waku/package.json +++ b/packages/waku/package.json @@ -87,6 +87,7 @@ "vite": "5.4.4" }, "devDependencies": { + "@cloudflare/workers-types": "^4.20240909.0", "@netlify/functions": "^2.8.1", "@swc/cli": "^0.4.0", "rollup": "^4.21.3", diff --git a/packages/waku/src/lib/builder/serve-cloudflare.ts b/packages/waku/src/lib/builder/serve-cloudflare.ts index 4555b626a..774da6588 100644 --- a/packages/waku/src/lib/builder/serve-cloudflare.ts +++ b/packages/waku/src/lib/builder/serve-cloudflare.ts @@ -1,41 +1,75 @@ import { Hono } from 'hono'; +import { unstable_getPlatformObject } from 'waku/server'; import { runner } from '../hono/runner.js'; +import type { + ExportedHandler, + fetch, + Response as CloudflareResponse, +} from '@cloudflare/workers-types/experimental'; const loadEntries = () => import(import.meta.env.WAKU_ENTRIES_FILE!); let serveWaku: ReturnType | undefined; export interface CloudflareEnv { ASSETS: { - fetch: (input: RequestInit | URL, init?: RequestInit) => Promise; + fetch: typeof fetch; }; } export const app = new Hono<{ Bindings: CloudflareEnv & { [k: string]: unknown }; }>(); -app.use('*', (c, next) => serveWaku!(c, next)); +app.use('*', (c, next) => { + if (!serveWaku) { + throw new Error('serveWaku is not initialized'); + } + const platform = unstable_getPlatformObject(); + platform.honoContext = c; + platform.env = c.env; + platform.executionContext = c.executionCtx; + return serveWaku(c, next); +}); app.notFound(async (c) => { const assetsFetcher = c.env.ASSETS; const url = new URL(c.req.raw.url); const errorHtmlUrl = `${url.origin}/404.html`; - const notFoundStaticAssetResponse = await assetsFetcher.fetch( + const notFoundStaticAssetResponse = (await assetsFetcher.fetch( new URL(errorHtmlUrl), - ); + )) as unknown as Response; if (notFoundStaticAssetResponse && notFoundStaticAssetResponse.status < 400) { return c.body(notFoundStaticAssetResponse.body, 404); } return c.text('404 Not Found', 404); }); -export default { - async fetch( - request: Request, - env: Record, - ctx: Parameters[2], - ) { +// Waku getEnv only supports strings +// Cloudflare injects bindings to env and JSON +// Use unstable_getPlatformObject() to access cloudflare env and execution context +// https://developers.cloudflare.com/workers/configuration/environment-variables/#add-environment-variables-via-wrangler +// https://developers.cloudflare.com/workers/runtime-apis/bindings/ +const extractWakuEnv = (env: Record): Record => + Object.fromEntries( + Object.entries(env).filter(([, value]) => typeof value === 'string'), + ) as Record; + +const handler: ExportedHandler = { + async fetch(request, env, ctx) { if (!serveWaku) { - serveWaku = runner({ cmd: 'start', loadEntries, env }); + serveWaku = runner({ + cmd: 'start', + loadEntries, + env: extractWakuEnv(env), + }); } - return app.fetch(request, env, ctx); + return app.fetch( + request as unknown as Request, + env, + ctx, + ) as unknown as CloudflareResponse; }, + // It might be useful to have a way to populate other handlers tail, trace, scheduled, email, queue + // But it's not hard to create a separate worker with those handlers and access it via + // service bindings. https://developers.cloudflare.com/pages/functions/bindings/#service-bindings }; + +export default handler; diff --git a/packages/waku/src/server.ts b/packages/waku/src/server.ts index 7e44f4e1a..7f836457c 100644 --- a/packages/waku/src/server.ts +++ b/packages/waku/src/server.ts @@ -147,8 +147,11 @@ export function unstable_getHeaders(): Record { >; } -type PlatformObject = { - buildData?: Record; // must be JSON serializable +type PlatformObject< + T = Record, + BuildData = Record, +> = { + buildData?: BuildData; // must be JSON serializable buildOptions?: { deploy?: | 'vercel-static' @@ -167,11 +170,13 @@ type PlatformObject = { | 'buildClientBundle' | 'buildDeploy'; }; -} & Record; +} & T; (globalThis as any).__WAKU_PLATFORM_OBJECT__ ||= {}; // TODO tentative name -export function unstable_getPlatformObject(): PlatformObject { +export function unstable_getPlatformObject< + T = Record, +>(): PlatformObject { return (globalThis as any).__WAKU_PLATFORM_OBJECT__; } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 029a5d9cc..1de742ba4 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -987,6 +987,9 @@ importers: specifier: 5.4.4 version: 5.4.4(@types/node@22.5.4)(terser@5.32.0) devDependencies: + '@cloudflare/workers-types': + specifier: ^4.20240909.0 + version: 4.20240909.0 '@netlify/functions': specifier: ^2.8.1 version: 2.8.1 @@ -1243,6 +1246,9 @@ packages: resolution: {integrity: sha512-/l42B1qxpG6RdfYf343Uw1vmDjeNhneUXtzhojE7pDgfpEypmRhI6j1kr17XCVv4Cgl9HdAiQY2x0GwKm7rWCw==} engines: {node: '>=6.9.0'} + '@cloudflare/workers-types@4.20240909.0': + resolution: {integrity: sha512-4knwtX6efxIsIxawdmPyynU9+S8A78wntU8eUIEldStWP4gNgxGkeWcfCMXulTx8oxr3DU4aevHyld9HGV8VKQ==} + '@emotion/hash@0.9.2': resolution: {integrity: sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==} @@ -5235,6 +5241,8 @@ snapshots: '@babel/helper-validator-identifier': 7.24.7 to-fast-properties: 2.0.0 + '@cloudflare/workers-types@4.20240909.0': {} + '@emotion/hash@0.9.2': {} '@esbuild/aix-ppc64@0.21.5':