Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add support for react router 7 #39

Merged
merged 2 commits into from
Jan 4, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@ node_modules
.wrangler
.mf
*.tgz
dist
dist
.DS_Store
23 changes: 11 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# hono-remix-adapter

`hono-remix-adapter` is a set of tools for adapting between Hono and Remix. It is composed of a Vite plugin and handlers that enable it to support platforms like Cloudflare Workers and Node.js. You just create Hono app, and it will be applied to your Remix app.
`hono-remix-adapter` is a set of tools for adapting between Hono and React Router. It is composed of a Vite plugin and handlers that enable it to support platforms like Cloudflare Workers and Node.js. You just create Hono app, and it will be applied to your React Router app.

```ts
// server/index.ts
Expand Down Expand Up @@ -179,12 +179,12 @@ First, create the `getLoadContext` function and export it:

```ts
// load-context.ts
import type { AppLoadContext } from '@remix-run/cloudflare'
import type { AppLoadContext } from 'react-router'
import type { PlatformProxy } from 'wrangler'

type Cloudflare = Omit<PlatformProxy, 'dispose'>

declare module '@remix-run/cloudflare' {
declare module 'react-router' {
interface AppLoadContext {
cloudflare: Cloudflare
extra: string
Expand All @@ -209,15 +209,15 @@ Then import the `getLoadContext` and add it to the `serverAdapter` as an argumen
```ts
// vite.config.ts
import adapter from '@hono/vite-dev-server/cloudflare'
import { vitePlugin as remix } from '@remix-run/dev'
import { reactRouter } from '@react-router/dev'
import serverAdapter from 'hono-remix-adapter/vite'
import { defineConfig } from 'vite'
import { getLoadContext } from './load-context'

export default defineConfig({
plugins: [
// ...
remix(),
reactRouter(),
serverAdapter({
adapter,
getLoadContext,
Expand Down Expand Up @@ -275,20 +275,19 @@ app.use(async (c, next) => {
export default app
```

In the Remix route, you can get the context from `args.context.hono.context`:
In the React Router route, you can get the context from `args.context.hono.context`:

```ts
// app/routes/_index.tsx
import type { LoaderFunctionArgs } from '@remix-run/cloudflare'
import { useLoaderData } from '@remix-run/react'
import { Router } from "./types/_index"

export const loader = ({ context }) => {
const message = args.context.hono.context.get('message')
return { message }
}

export default function Index() {
const { message } = useLoaderData<typeof loader>()
export default function Index({ loaderData }:Route.ComponentProps) {
const { message } = loaderData
return <h1>Message is {message}</h1>
}
```
Expand All @@ -297,7 +296,7 @@ To enable type inference, config the `load-context.ts` like follows:

```ts
// load-context.ts
import type { AppLoadContext } from '@remix-run/cloudflare'
import type { AppLoadContext } from 'react-router'
import type { Context } from 'hono'
import type { PlatformProxy } from 'wrangler'

Expand All @@ -309,7 +308,7 @@ type Env = {

type Cloudflare = Omit<PlatformProxy, 'dispose'>

declare module '@remix-run/cloudflare' {
declare module 'react-router' {
interface AppLoadContext {
cloudflare: Cloudflare
hono: {
Expand Down
1 change: 1 addition & 0 deletions examples/cloudflare-pages/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,6 @@ test-results
/build
.env
.dev.vars
.react-router

.wrangler
4 changes: 2 additions & 2 deletions examples/cloudflare-pages/app/entry.client.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,15 @@
* For more information, see https://remix.run/file-conventions/entry.client
*/

import { RemixBrowser } from '@remix-run/react'
import { StrictMode, startTransition } from 'react'
import { hydrateRoot } from 'react-dom/client'
import { HydratedRouter } from 'react-router/dom'

startTransition(() => {
hydrateRoot(
document,
<StrictMode>
<RemixBrowser />
<HydratedRouter />
</StrictMode>
)
})
8 changes: 4 additions & 4 deletions examples/cloudflare-pages/app/entry.server.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,23 @@
* For more information, see https://remix.run/file-conventions/entry.server
*/

import type { AppLoadContext, EntryContext } from '@remix-run/cloudflare'
import { RemixServer } from '@remix-run/react'
import { isbot } from 'isbot'
import { renderToReadableStream } from 'react-dom/server'
import type { AppLoadContext, EntryContext } from 'react-router'
import { ServerRouter } from 'react-router'

export default async function handleRequest(
request: Request,
responseStatusCode: number,
responseHeaders: Headers,
remixContext: EntryContext,
reactRouterContext: EntryContext,
// This is ignored so we can keep it in the template for visibility. Feel
// free to delete this parameter in your app if you're not using it!
// eslint-disable-next-line @typescript-eslint/no-unused-vars
loadContext: AppLoadContext
) {
const body = await renderToReadableStream(
<RemixServer context={remixContext} url={request.url} />,
<ServerRouter context={reactRouterContext} url={request.url} />,
{
signal: request.signal,
onError(error: unknown) {
Expand Down
2 changes: 1 addition & 1 deletion examples/cloudflare-pages/app/root.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Outlet, Scripts } from '@remix-run/react'
import { Outlet, Scripts } from 'react-router'

export function Layout({ children }: { children: React.ReactNode }) {
return (
Expand Down
4 changes: 4 additions & 0 deletions examples/cloudflare-pages/app/routes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import type { RouteConfig } from '@react-router/dev/routes'
import { flatRoutes } from '@react-router/fs-routes'

export default flatRoutes() satisfies RouteConfig
9 changes: 4 additions & 5 deletions examples/cloudflare-pages/app/routes/_index.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,16 @@
import type { LoaderFunctionArgs } from '@remix-run/cloudflare'
import { useLoaderData } from '@remix-run/react'
import type { Route } from './+types/_index'
import logoDark from '/logo-dark.png?inline'

export const loader = (args: LoaderFunctionArgs) => {
export const loader = (args: Route.LoaderArgs) => {
const extra = args.context.extra
const cloudflare = args.context.cloudflare
const myVarInVariables = args.context.hono.context.get('MY_VAR_IN_VARIABLES')
const isWaitUntilDefined = !!cloudflare.ctx.waitUntil
return { cloudflare, extra, myVarInVariables, isWaitUntilDefined }
}

export default function Index() {
const { cloudflare, extra, myVarInVariables, isWaitUntilDefined } = useLoaderData<typeof loader>()
export default function Index({ loaderData }: Route.ComponentProps) {
const { cloudflare, extra, myVarInVariables, isWaitUntilDefined } = loaderData
return (
<div>
<h1>Remix and Hono</h1>
Expand Down
4 changes: 2 additions & 2 deletions examples/cloudflare-pages/load-context.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { AppLoadContext } from '@remix-run/cloudflare'
import type { Context } from 'hono'
import type { AppLoadContext } from 'react-router'
import type { PlatformProxy } from 'wrangler'

type Env = {
Expand All @@ -13,7 +13,7 @@ type Env = {

type Cloudflare = Omit<PlatformProxy<Env['Bindings']>, 'dispose'>

declare module '@remix-run/cloudflare' {
declare module 'react-router' {
interface AppLoadContext {
cloudflare: Cloudflare
extra: string
Expand Down
18 changes: 10 additions & 8 deletions examples/cloudflare-pages/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,27 +4,29 @@
"sideEffects": false,
"type": "module",
"scripts": {
"build": "remix vite:build",
"build": "react-router build",
"deploy": "npm run build && wrangler pages deploy",
"dev": "remix vite:dev",
"dev": "react-router dev",
"start": "wrangler pages dev ./build/client",
"typecheck": "tsc",
"typecheck": "react-router typegen && tsc",
"preview": "npm run build && wrangler pages dev",
"test:e2e:vite": "playwright test -c playwright-vite.config.ts e2e.test.ts",
"test:e2e:pages": "npm run build && playwright test -c playwright-pages.config.ts e2e.test.ts"
"test:e2e:pages": "npm run build && playwright test -c playwright-pages.config.ts e2e.test.ts",
"typegen": "react-router typegen"
},
"dependencies": {
"@remix-run/cloudflare": "^2.14.0",
"@remix-run/react": "^1.19.3",
"@react-router/cloudflare": "^7.0.1",
"@react-router/fs-routes": "^7.0.1",
"hono": "^4.5.11",
"isbot": "^4.1.0",
"react": "^18.3.1",
"react-dom": "^18.3.1"
"react-dom": "^18.3.1",
"react-router": "^7.0.1"
},
"devDependencies": {
"@hono/vite-dev-server": "^0.16.0",
"@playwright/test": "^1.48.2",
"@remix-run/dev": "^2.14.0",
"@react-router/dev": "^7.0.1",
"@types/react": "^18.2.20",
"@types/react-dom": "^18.2.7",
"playwright": "^1.47.0",
Expand Down
5 changes: 5 additions & 0 deletions examples/cloudflare-pages/react-router.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import type { Config } from '@react-router/dev/config'

export default {
ssr: true,
} satisfies Config
4 changes: 2 additions & 2 deletions examples/cloudflare-pages/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
"ES2022"
],
"types": [
"@remix-run/cloudflare",
"vite/client",
"@cloudflare/workers-types/2023-07-01"
],
Expand All @@ -30,6 +29,7 @@
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"baseUrl": ".",
"rootDirs": [".", "./.react-router/types"],
"paths": {
"~/*": [
"./app/*"
Expand All @@ -38,4 +38,4 @@
// Vite takes care of building everything, not tsc.
"noEmit": true
}
}
}
14 changes: 3 additions & 11 deletions examples/cloudflare-pages/vite.config.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
// vite.config.ts
import adapter from '@hono/vite-dev-server/cloudflare'
import {
vitePlugin as remix,
cloudflareDevProxyVitePlugin as remixCloudflareDevProxy,
} from '@remix-run/dev'
import { reactRouter } from '@react-router/dev/vite'
import { cloudflareDevProxy as remixCloudflareDevProxy } from '@react-router/dev/vite/cloudflare'
import serverAdapter from 'hono-remix-adapter/vite'
import { defineConfig } from 'vite'
import tsconfigPaths from 'vite-tsconfig-paths'
Expand All @@ -12,13 +10,7 @@ import { getLoadContext } from './load-context'
export default defineConfig({
plugins: [
remixCloudflareDevProxy(),
remix({
future: {
v3_fetcherPersist: true,
v3_relativeSplatPath: true,
v3_throwAbortReason: true,
},
}),
reactRouter(),
serverAdapter({
adapter,
getLoadContext,
Expand Down
1 change: 1 addition & 0 deletions examples/cloudflare-workers/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,6 @@ test-results
/build
.env
.dev.vars
.react-router

.wrangler
4 changes: 2 additions & 2 deletions examples/cloudflare-workers/app/entry.client.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,15 @@
* For more information, see https://remix.run/file-conventions/entry.client
*/

import { RemixBrowser } from '@remix-run/react'
import { startTransition, StrictMode } from 'react'
import { hydrateRoot } from 'react-dom/client'
import { HydratedRouter } from 'react-router/dom'

startTransition(() => {
hydrateRoot(
document,
<StrictMode>
<RemixBrowser />
<HydratedRouter />
</StrictMode>
)
})
8 changes: 4 additions & 4 deletions examples/cloudflare-workers/app/entry.server.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,18 @@
* For more information, see https://remix.run/file-conventions/entry.server
*/

import type { AppLoadContext, EntryContext } from '@remix-run/cloudflare'
import { RemixServer } from '@remix-run/react'
import { isbot } from 'isbot'
import { renderToReadableStream } from 'react-dom/server'
import type { AppLoadContext, EntryContext } from 'react-router'
import { ServerRouter } from 'react-router'

const ABORT_DELAY = 5000

export default async function handleRequest(
request: Request,
responseStatusCode: number,
responseHeaders: Headers,
remixContext: EntryContext,
reactRouterContext: EntryContext,
// This is ignored so we can keep it in the template for visibility. Feel
// free to delete this parameter in your app if you're not using it!
// eslint-disable-next-line @typescript-eslint/no-unused-vars
Expand All @@ -25,7 +25,7 @@ export default async function handleRequest(
const timeoutId = setTimeout(() => controller.abort(), ABORT_DELAY)

const body = await renderToReadableStream(
<RemixServer context={remixContext} url={request.url} abortDelay={ABORT_DELAY} />,
<ServerRouter context={reactRouterContext} url={request.url} abortDelay={ABORT_DELAY} />,
{
signal: controller.signal,
onError(error: unknown) {
Expand Down
2 changes: 1 addition & 1 deletion examples/cloudflare-workers/app/root.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Outlet, Scripts } from '@remix-run/react'
import { Outlet, Scripts } from 'react-router'

export function Layout({ children }: { children: React.ReactNode }) {
return (
Expand Down
4 changes: 4 additions & 0 deletions examples/cloudflare-workers/app/routes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import type { RouteConfig } from '@react-router/dev/routes'
import { flatRoutes } from '@react-router/fs-routes'

export default flatRoutes() satisfies RouteConfig
9 changes: 4 additions & 5 deletions examples/cloudflare-workers/app/routes/_index.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,15 @@
import type { LoaderFunctionArgs } from '@remix-run/cloudflare'
import { useLoaderData } from '@remix-run/react'
import type { Route } from './+types/_index'

export const loader = (args: LoaderFunctionArgs) => {
export const loader = (args: Route.LoaderArgs) => {
const extra = args.context.extra
const cloudflare = args.context.cloudflare
const myVarInVariables = args.context.hono.context.get('MY_VAR_IN_VARIABLES')
const isWaitUntilDefined = !!cloudflare.ctx.waitUntil
return { cloudflare, extra, myVarInVariables, isWaitUntilDefined }
}

export default function Index() {
const { cloudflare, extra, myVarInVariables, isWaitUntilDefined } = useLoaderData<typeof loader>()
export default function Index({ loaderData }: Route.ComponentProps) {
const { cloudflare, extra, myVarInVariables, isWaitUntilDefined } = loaderData
return (
<div>
<h1>Remix and Hono</h1>
Expand Down
2 changes: 1 addition & 1 deletion examples/cloudflare-workers/load-context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ type GetLoadContextArgs = {
}
}

declare module '@remix-run/cloudflare' {
declare module 'react-router' {
// eslint-disable-next-line @typescript-eslint/no-empty-interface
interface AppLoadContext extends ReturnType<typeof getLoadContext> {
// This will merge the result of `getLoadContext` into the `AppLoadContext`
Expand Down
Loading
Loading