diff --git a/example/app/routes/_index.tsx b/example/app/routes/_index.tsx index c287e25..840dafe 100644 --- a/example/app/routes/_index.tsx +++ b/example/app/routes/_index.tsx @@ -1,19 +1,24 @@ import type { LoaderFunctionArgs } from '@remix-run/cloudflare' import { useLoaderData } from '@remix-run/react' -export const loader = ({ context }: LoaderFunctionArgs) => { - const { env } = context.cloudflare as { env: { MY_VAR: string} } - return { - var: env.MY_VAR - } +export const loader = (args: LoaderFunctionArgs) => { + const extra = args.context.extra + const cloudflare = args.context.cloudflare + return { cloudflare, extra } } export default function Index() { - const data = useLoaderData() + const { cloudflare, extra } = useLoaderData() return (

Remix and Hono

-

Var is {data.var}

+

Var is {cloudflare.env.MY_VAR}

+

+ {cloudflare.cf ? 'cf,' : ''} + {cloudflare.ctx ? 'ctx,' : ''} + {cloudflare.caches ? 'caches are available' : ''} +

+

Extra is {extra}

) } diff --git a/example/e2e.test.ts b/example/e2e.test.ts index bff55cc..045b0cd 100644 --- a/example/e2e.test.ts +++ b/example/e2e.test.ts @@ -12,6 +12,12 @@ test('Should return 200 response - /', async ({ page }) => { const contentH2 = await page.textContent('h2') expect(contentH2).toBe('Var is My Value') + + const contentH3 = await page.textContent('h3') + expect(contentH3).toBe('cf,ctx,caches are available') + + const contentH4 = await page.textContent('h4') + expect(contentH4).toBe('Extra is stuff') }) test('Should return 200 response - /api', async ({ page }) => { diff --git a/example/functions/[[path]].ts b/example/functions/[[path]].ts index 538f4d6..e2b321e 100644 --- a/example/functions/[[path]].ts +++ b/example/functions/[[path]].ts @@ -1,6 +1,7 @@ // functions/[[path]].ts import handle from 'hono-remix-adapter/cloudflare-pages' +import { getLoadContext } from 'load-context' import * as build from '../build/server' import server from '../server' -export const onRequest = handle(build, server) +export const onRequest = handle(build, server, { getLoadContext }) diff --git a/example/load-context.ts b/example/load-context.ts new file mode 100644 index 0000000..58c6da1 --- /dev/null +++ b/example/load-context.ts @@ -0,0 +1,28 @@ +import type { AppLoadContext } from '@remix-run/cloudflare' +import type { PlatformProxy } from 'wrangler' + +interface Env { + MY_VAR: string +} + +type Cloudflare = Omit, 'dispose'> + +declare module '@remix-run/cloudflare' { + interface AppLoadContext { + cloudflare: Cloudflare + extra: string + } +} + +type GetLoadContext = (args: { + request: Request + context: { cloudflare: Cloudflare } +}) => AppLoadContext + +// Shared implementation compatible with Vite, Wrangler, and Cloudflare Pages +export const getLoadContext: GetLoadContext = ({ context }) => { + return { + ...context, + extra: 'stuff', + } +} diff --git a/example/vite.config.ts b/example/vite.config.ts index ab93d10..651ecb4 100644 --- a/example/vite.config.ts +++ b/example/vite.config.ts @@ -7,6 +7,7 @@ import { import serverAdapter from 'hono-remix-adapter/vite' import { defineConfig } from 'vite' import tsconfigPaths from 'vite-tsconfig-paths' +import { getLoadContext } from './load-context' export default defineConfig({ plugins: [ @@ -20,6 +21,7 @@ export default defineConfig({ }), serverAdapter({ adapter, + getLoadContext, entry: 'server/index.ts', }), tsconfigPaths(), diff --git a/src/cloudflare-pages.ts b/src/cloudflare-pages.ts index 7395d34..1820658 100644 --- a/src/cloudflare-pages.ts +++ b/src/cloudflare-pages.ts @@ -1,9 +1,38 @@ +import type { ServerBuild } from '@remix-run/cloudflare' +import { createRequestHandler } from '@remix-run/cloudflare' import { Hono } from 'hono' import { handle } from 'hono/cloudflare-pages' +import { createMiddleware } from 'hono/factory' import { staticAssets } from 'remix-hono/cloudflare' -import { remix } from 'remix-hono/handler' +import { createGetLoadContextArgs, defaultGetLoadContext } from './remix' +import type { GetLoadContext } from './remix' -export const handler = (serverBuild: any, userApp?: Hono) => { +interface RemixMiddlewareOptions { + build: ServerBuild + mode?: 'development' | 'production' + getLoadContext: GetLoadContext +} + +function remix({ mode, build, getLoadContext }: RemixMiddlewareOptions) { + return createMiddleware(async (c) => { + const requestHandler = createRequestHandler(build, mode) + const args = createGetLoadContextArgs(c) + + const loadContext = getLoadContext(args) + return await requestHandler( + c.req.raw, + loadContext instanceof Promise ? await loadContext : loadContext + ) + }) +} + +type Options = { + getLoadContext: GetLoadContext +} + +// Relaxing the type definitions +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export const handler = (serverBuild: any, userApp?: Hono, options?: Options) => { const app = new Hono() if (userApp) { @@ -18,13 +47,7 @@ export const handler = (serverBuild: any, userApp?: Hono) => { return remix({ build: serverBuild, mode: 'production', - getLoadContext(c) { - return { - cloudflare: { - env: c.env, - }, - } - }, + getLoadContext: options?.getLoadContext ?? defaultGetLoadContext, })(c, next) } ) diff --git a/src/dev.ts b/src/dev.ts index 6ddc7c0..45173b1 100644 --- a/src/dev.ts +++ b/src/dev.ts @@ -1,7 +1,12 @@ -import type { AppLoadContext } from '@remix-run/cloudflare' import { Hono } from 'hono' +import { createGetLoadContextArgs, defaultGetLoadContext } from './remix' +import type { GetLoadContext } from './remix' -export const handle = (userApp?: Hono) => { +type Options = { + getLoadContext: GetLoadContext +} + +export const handle = (userApp?: Hono, options?: Options) => { const app = new Hono() if (userApp) { @@ -13,13 +18,14 @@ export const handle = (userApp?: Hono) => { const build = await import('virtual:remix/server-build') const { createRequestHandler } = await import('@remix-run/cloudflare') const handler = createRequestHandler(build, 'development') - const remixContext = { - cloudflare: { - env: c.env, - }, - } as unknown as AppLoadContext + + const getLoadContext = options?.getLoadContext ?? defaultGetLoadContext + const args = createGetLoadContextArgs(c) + + const remixContext = getLoadContext(args) return handler(c.req.raw, remixContext) }) + return app } diff --git a/src/remix.ts b/src/remix.ts new file mode 100644 index 0000000..bc2a052 --- /dev/null +++ b/src/remix.ts @@ -0,0 +1,34 @@ +import type { AppLoadContext } from '@remix-run/cloudflare' +import type { Context } from 'hono' + +export type GetLoadContext = (args: { + request: Request + context: { + // Relaxing the type definition + // eslint-disable-next-line @typescript-eslint/no-explicit-any + cloudflare: any + } +}) => AppLoadContext + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export const defaultGetLoadContext = ({ context }: any) => { + return { + ...context, + } +} + +export const createGetLoadContextArgs = (c: Context) => { + return { + context: { + cloudflare: { + env: c.env, + cf: c.req.raw.cf, + ctx: { + ...c.executionCtx, + }, + caches, + }, + }, + request: c.req.raw, + } +} diff --git a/src/vite-plugin.ts b/src/vite-plugin.ts index e0f976a..4382a1b 100644 --- a/src/vite-plugin.ts +++ b/src/vite-plugin.ts @@ -4,6 +4,7 @@ import type { Plugin } from 'vite' import fs from 'node:fs' import path from 'node:path' import { fileURLToPath } from 'node:url' +import type { GetLoadContext } from './remix' interface Adapter { env?: Record | Promise> @@ -17,6 +18,7 @@ interface Adapter { interface Options { entry: string adapter?: () => Adapter | Promise + getLoadContext?: GetLoadContext } export default (options: Options): Plugin => { @@ -51,7 +53,9 @@ export default (options: Options): Plugin => { } const devModule = await server.ssrLoadModule(devPath) - return devModule['default'](app) + return devModule['default'](app, { + getLoadContext: options.getLoadContext, + }) }, }) }