diff --git a/.github/dependabot.yml b/.github/dependabot.yml index beba5fd..9026e5e 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,16 +1,16 @@ version: 2 updates: - - package-ecosystem: 'github-actions' - directory: '/' + - package-ecosystem: "github-actions" + directory: "/" schedule: - interval: 'weekly' - - package-ecosystem: 'npm' - directory: '/' + interval: "weekly" + - package-ecosystem: "npm" + directory: "/" open-pull-requests-limit: 10 schedule: - interval: 'weekly' + interval: "weekly" groups: minor-upgrades: update-types: - - 'minor' - - 'patch' \ No newline at end of file + - "minor" + - "patch" diff --git a/.github/lock.yml b/.github/lock.yml index 74eee94..24b7466 100644 --- a/.github/lock.yml +++ b/.github/lock.yml @@ -11,7 +11,7 @@ daysUntilLock: 60 skipCreatedBefore: false # Issues and pull requests with these labels will be ignored. Set to `[]` to disable -exemptLabels: ['Type: Security'] +exemptLabels: ["Type: Security"] # Label to add before locking, such as `outdated`. Set to `false` to disable lockLabel: false diff --git a/.github/stale.yml b/.github/stale.yml index 8b47808..4b95d78 100644 --- a/.github/stale.yml +++ b/.github/stale.yml @@ -9,10 +9,10 @@ daysUntilClose: 7 # Issues with these labels will never be considered stale exemptLabels: - - 'Type: Security' + - "Type: Security" # Label to use when marking an issue as stale -staleLabel: 'Status: Abandoned' +staleLabel: "Status: Abandoned" # Comment to post when marking an issue as stale. Set to `false` to disable markComment: > diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 2634144..a0143ac 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -4,7 +4,7 @@ on: types: [opened, synchronize, reopened] push: schedule: - - cron: '0 16 * * *' + - cron: "0 16 * * *" jobs: linux: @@ -44,4 +44,4 @@ jobs: - name: Build run: npm run build - name: Run tests - run: npm test \ No newline at end of file + run: npm test diff --git a/.gitignore b/.gitignore index baf462d..800c854 100644 --- a/.gitignore +++ b/.gitignore @@ -31,4 +31,6 @@ public/assets/ */.vitepress/cache package-lock.json -yarn.lock \ No newline at end of file +yarn.lock + +.react-router/ \ No newline at end of file diff --git a/examples/with-tailwind-and-shadcn/package.json b/examples/with-tailwind-and-shadcn/package.json index ef78ca3..d8d946d 100644 --- a/examples/with-tailwind-and-shadcn/package.json +++ b/examples/with-tailwind-and-shadcn/package.json @@ -43,7 +43,7 @@ "@japa/expect": "^3.0.2", "@japa/plugin-adonisjs": "^3.0.1", "@japa/runner": "^3.1.4", - "@remix-run/dev": "^2.8.1", + "@react-router/dev": "^7.0.0", "@swc/core": "^1.4.17", "@types/luxon": "^3.4.2", "@types/node": "^20.12.7", @@ -70,10 +70,8 @@ "@adonisjs/vite": "^3.0.0-11", "@matstack/remix-adonisjs": "^0.0.32", "@radix-ui/react-popover": "^1.0.7", - "@remix-run/css-bundle": "^2.8.1", - "@remix-run/node": "^2.8.1", - "@remix-run/react": "^2.8.1", - "@remix-run/serve": "^2.8.1", + "@react-router/node": "^7.0.0", + "@react-router/serve": "^7.0.0", "@vinejs/vine": "^2.0.0", "class-variance-authority": "^0.7.0", "clsx": "^2.1.1", @@ -83,6 +81,7 @@ "luxon": "^3.4.4", "react": "^18.2.0", "react-dom": "^18.2.0", + "react-router": "^7.0.0", "reflect-metadata": "^0.2.2", "sqlite3": "^5.1.7", "tailwind-merge": "^2.3.0", @@ -104,4 +103,4 @@ "vite.config.ts.timestamp*" ] } -} +} \ No newline at end of file diff --git a/examples/with-tailwind-and-shadcn/resources/remix_app/root.tsx b/examples/with-tailwind-and-shadcn/resources/remix_app/root.tsx index 746e03d..b508847 100644 --- a/examples/with-tailwind-and-shadcn/resources/remix_app/root.tsx +++ b/examples/with-tailwind-and-shadcn/resources/remix_app/root.tsx @@ -1,13 +1,5 @@ -import { ActionFunctionArgs, LoaderFunctionArgs, json } from '@remix-run/node' -import { - Form, - Links, - Meta, - Outlet, - Scripts, - ScrollRestoration, - useLoaderData -} from '@remix-run/react' +import { ActionFunctionArgs, LoaderFunctionArgs, json } from 'react-router' +import { Form, Links, Meta, Outlet, Scripts, ScrollRestoration, useLoaderData } from 'react-router' import { cn } from './lib/utils' import vine from '@vinejs/vine' @@ -21,9 +13,11 @@ export async function loader({ context }: LoaderFunctionArgs) { // See the docs for more complex intent validation: // https://matstack.dev/remix-adonisjs/recipes/validate-action-intent -const actionValidator = vine.compile(vine.object({ - intent: vine.enum(['toggleColorScheme']) -})) +const actionValidator = vine.compile( + vine.object({ + intent: vine.enum(['toggleColorScheme']), + }) +) export async function action({ context }: ActionFunctionArgs) { const { http } = context @@ -49,7 +43,9 @@ export default function Page({ children }: { children: React.ReactNode }) { {children}
- +
diff --git a/examples/with-tailwind-and-shadcn/resources/remix_app/routes/_index.tsx b/examples/with-tailwind-and-shadcn/resources/remix_app/routes/_index.tsx index a35ac6c..df9aeed 100644 --- a/examples/with-tailwind-and-shadcn/resources/remix_app/routes/_index.tsx +++ b/examples/with-tailwind-and-shadcn/resources/remix_app/routes/_index.tsx @@ -1,5 +1,5 @@ -import { json, type LoaderFunctionArgs, type MetaFunction } from '@remix-run/node' -import { useLoaderData } from '@remix-run/react' +import { json, type LoaderFunctionArgs, type MetaFunction } from 'react-router' +import { useLoaderData } from 'react-router' import { Popover, PopoverContent, PopoverTrigger } from '~/components/ui/popover' export const meta: MetaFunction = () => { @@ -15,33 +15,39 @@ export default function Index() { const { message } = useLoaderData() return ( -
-
-

Welcome to Remix

-

{message}

+
+
+

Welcome to Remix

+

{message}

-
    +
    • - + rel="noreferrer" + > 15m Quickstart Blog Tutorial
    • - + rel="noreferrer" + > Deep Dive Jokes App Tutorial
    • - + rel="noreferrer" + > Remix Docs
    • diff --git a/examples/with-tailwind-and-shadcn/vite.config.ts b/examples/with-tailwind-and-shadcn/vite.config.ts index e49eca1..9f5122b 100644 --- a/examples/with-tailwind-and-shadcn/vite.config.ts +++ b/examples/with-tailwind-and-shadcn/vite.config.ts @@ -1,4 +1,4 @@ -import { vitePlugin as remix } from '@remix-run/dev' +import { reactRouter } from '@react-router/dev/vite' import { defineConfig } from 'vite' import tailwindcss from 'tailwindcss' import autoprefixer from 'autoprefixer' @@ -7,7 +7,7 @@ import tsconfigPaths from 'vite-tsconfig-paths' export default defineConfig(({ isSsrBuild }) => ({ base: '/assets/', plugins: [ - remix({ + reactRouter({ appDirectory: 'resources/remix_app', buildDirectory: 'build/remix', serverBuildFile: 'server.js', diff --git a/packages/adapter/commands/remix_route.ts b/packages/adapter/commands/remix_route.ts index 10d3e26..9099a03 100644 --- a/packages/adapter/commands/remix_route.ts +++ b/packages/adapter/commands/remix_route.ts @@ -68,50 +68,48 @@ export default class MakeRemixRoute extends BaseCommand { protected stubPath: string = 'make/route.tsx.stub' private getImports() { - const node = new Set() - const react = new Set() + const localType = new Set() + const router = new Set() if (this.action) { - node.add('ActionFunctionArgs') - react.add('useActionData') + localType.add('Route') + router.add('useActionData') } if (this.clientAction) { - node.add('ClientActionFunctionArgs') + localType.add('Route') } if (this.loader) { - node.add('LoaderFunctionArgs') - node.add('json') - react.add('useLoaderData') + localType.add('Route') + router.add('useLoaderData') } if (this.clientLoader) { - node.add('ClientLoaderFunctionArgs') + localType.add('Route') } if (this.meta) { - node.add('MetaFunction') + router.add('MetaFunction') } if (this.errorBoundary) { - react.add('isRouteErrorResponse') - react.add('useRouteError') + router.add('isRouteErrorResponse') + router.add('useRouteError') } if (this.headers) { - node.add('HeadersFunction') + localType.add('Route') } return { - node, - react, + router, + localType, } } async run() { const imports = this.getImports() const codemods = await this.createCodemods() - console.log(this.parsed.flags) await codemods.makeUsingStub(stubsRoot, this.stubPath, { flags: this.parsed.flags, name: this.name, imports: { - node: [...imports.node].join(', '), - react: [...imports.react].join(', '), + localType: [...imports.localType].join(', '), + router: [...imports.router].join(', '), }, }) } diff --git a/packages/adapter/configure.ts b/packages/adapter/configure.ts index 0373d4d..6dac4b7 100644 --- a/packages/adapter/configure.ts +++ b/packages/adapter/configure.ts @@ -7,15 +7,19 @@ export async function configure(command: Configure) { const codemods = await command.createCodemods() const dependencies = [ - '@remix-run/css-bundle', - '@remix-run/node', - '@remix-run/react', - '@remix-run/serve', + '@react-router/node', + '@react-router/serve', + 'react-router', 'react', 'react-dom', ] - const devDependencies = ['@remix-run/dev', '@types/react', '@types/react-dom'] + const devDependencies = [ + '@react-router/dev', + '@types/react', + '@types/react-dom', + '@react-router/fs-routes', + ] await codemods.installPackages( dependencies.map((name) => ({ diff --git a/packages/adapter/env.d.ts b/packages/adapter/env.d.ts new file mode 100644 index 0000000..4ba8308 --- /dev/null +++ b/packages/adapter/env.d.ts @@ -0,0 +1,5 @@ +import type { AdonisApplicationContext } from './src/types/main.js' + +declare module 'react-router' { + interface AppLoadContext extends AdonisApplicationContext {} +} diff --git a/packages/adapter/package.json b/packages/adapter/package.json index 4c35380..bd066f1 100644 --- a/packages/adapter/package.json +++ b/packages/adapter/package.json @@ -1,7 +1,7 @@ { "name": "@matstack/remix-adonisjs", "description": "An adapter for using Remix with AdonisJS", - "version": "0.0.35", + "version": "1.0.0-rr7.6", "engines": { "node": ">=20.10.0" }, @@ -81,12 +81,9 @@ "vite": "^5.4.11" }, "peerDependencies": { - "@adonisjs/core": "^6.7.0", - "@remix-run/css-bundle": "^2.2.0", - "@remix-run/dev": "^2.2.0", - "@remix-run/node": "^2.2.0", - "@remix-run/react": "^2.2.0", + "@react-router/node": "^7.0.0", "react": "^18.2.0", + "react-router": "^7.0.0", "react-dom": "^18.2.0" }, "peerDependenciesMeta": {}, diff --git a/packages/adapter/providers/remix_provider.ts b/packages/adapter/providers/remix_provider.ts index cc03409..cd66cfa 100644 --- a/packages/adapter/providers/remix_provider.ts +++ b/packages/adapter/providers/remix_provider.ts @@ -37,7 +37,7 @@ export default class RemixProvider { const devServer = vite.getDevServer() const build = (this.app.inDev || this.app.inTest) && devServer - ? () => devServer.ssrLoadModule('virtual:remix/server-build') + ? () => devServer.ssrLoadModule('virtual:react-router/server-build') : await import(this.remixBundle) const requestHandler = createRequestHandler({ diff --git a/packages/adapter/src/hooks/build_hook.ts b/packages/adapter/src/hooks/build_hook.ts index dc6e119..fba2538 100644 --- a/packages/adapter/src/hooks/build_hook.ts +++ b/packages/adapter/src/hooks/build_hook.ts @@ -6,11 +6,11 @@ import path from 'node:path' /** * - * The hook is responsible for launching the remix vite:build command when the application is built + * The hook is responsible for launching the react-router build command when the application is built */ export default async function remixBuildHook({ logger }: Parameters[0]) { - logger.info('building remix app with vite') - await runCommand('npx remix vite:build') + logger.info('building React Router app with vite') + await runCommand('npx react-router build') const config = await resolveViteConfig() // const cli = await import('@remix-run/dev') // await cli.run(['vite:build']) diff --git a/packages/adapter/src/remix_adapter.ts b/packages/adapter/src/remix_adapter.ts index 6f3b5cb..dbdf54f 100644 --- a/packages/adapter/src/remix_adapter.ts +++ b/packages/adapter/src/remix_adapter.ts @@ -4,12 +4,14 @@ import type { Container } from '@adonisjs/core/container' import type { HttpContext } from '@adonisjs/core/http' import type { ContainerBindings } from '@adonisjs/core/types' import type { Request as AdonisRequest, Response as AdonisResponse } from '@adonisjs/http-server' + import { AppLoadContext, ServerBuild, - createReadableStreamFromReadable, createRequestHandler as createRemixRequestHandler, -} from '@remix-run/node' +} from 'react-router' + +import { createReadableStreamFromReadable } from '@react-router/node' import debug from './debug.js' import { ReadableWebToNodeStream } from './stream_conversion.js' @@ -18,7 +20,7 @@ export type HandlerContext = { container: Container } -export type LoaderContext = { +export type AdonisApplicationContext = { http: HttpContext make: Container['make'] } diff --git a/packages/adapter/src/types/main.ts b/packages/adapter/src/types/main.ts index 2c91399..33c962f 100644 --- a/packages/adapter/src/types/main.ts +++ b/packages/adapter/src/types/main.ts @@ -1,5 +1 @@ -import type { LoaderContext } from '../remix_adapter.js' - -declare module '@remix-run/node' { - export interface AppLoadContext extends LoaderContext {} -} +export type { AdonisApplicationContext } from '../remix_adapter.js' diff --git a/packages/adapter/stubs/_index.tsx.stub b/packages/adapter/stubs/_index.tsx.stub index 1450f00..b844198 100644 --- a/packages/adapter/stubs/_index.tsx.stub +++ b/packages/adapter/stubs/_index.tsx.stub @@ -1,45 +1,26 @@ {{{ exports({ to: app.makePath('resources/remix_app/routes/_index.tsx') }) }}} -import type { MetaFunction } from "@remix-run/node"; +import { useLoaderData } from "react-router"; +import type { Route } from "./+types/_index"; -export const meta: MetaFunction = () => { +export function meta({ }: Route.MetaArgs) { return [ - { title: "New Remix App" }, - { name: "description", content: "Welcome to Remix!" }, + { title: "New React Router App" }, + { name: "description", content: "Welcome to React Router!" }, ]; -}; +} + + +export async function loader({ context }: Route.LoaderArgs) { + const service = await context.make('hello_service') + + return { + message: service.getMessage(), + } +} -export default function Index() { - return ( -
      -

      Welcome to Remix

      -

      ...powered by AdonisJS 😎

      - -
      - ); +export default function Home() { + const { message } = useLoaderData() + return
      {message}
      ; } diff --git a/packages/adapter/stubs/app.css.stub b/packages/adapter/stubs/app.css.stub new file mode 100644 index 0000000..7647a5c --- /dev/null +++ b/packages/adapter/stubs/app.css.stub @@ -0,0 +1,15 @@ +{{{ + exports({ to: app.makePath('resources/remix_app/app.css') }) +}}} +@tailwind base; +@tailwind components; +@tailwind utilities; + +html, +body { + @apply bg-white dark:bg-gray-950; + + @media (prefers-color-scheme: dark) { + color-scheme: dark; + } +} diff --git a/packages/adapter/stubs/env.d.ts.stub b/packages/adapter/stubs/env.d.ts.stub index e6f0938..02c2ef9 100644 --- a/packages/adapter/stubs/env.d.ts.stub +++ b/packages/adapter/stubs/env.d.ts.stub @@ -1,5 +1,8 @@ {{{ exports({ to: app.makePath("env.d.ts") }) }}} -/// -/// \ No newline at end of file +import type { AdonisApplicationContext } from '@matstack/remix-adonisjs/types'; + +declare module 'react-router' { + interface AppLoadContext extends AdonisApplicationContext { } +} diff --git a/packages/adapter/stubs/make/route.tsx.stub b/packages/adapter/stubs/make/route.tsx.stub index 7c20fd3..0d667d3 100644 --- a/packages/adapter/stubs/make/route.tsx.stub +++ b/packages/adapter/stubs/make/route.tsx.stub @@ -6,25 +6,24 @@ to: app.makePath('resources/remix_app/routes', routeFileName) }) }}} -{{#if imports.node}} -import { {{imports.node}} } from '@remix-run/node' +{{#if imports.router}} +import { {{imports.router}} } from 'react-router' {{/if}} -{{#if imports.react}} -import { {{imports.react}} } from '@remix-run/react' +{{#if imports.localType}} +import { {{imports.localType}} } from './+types/{{entity.name}}' {{/if}} {{{#if flags.loader}}} -export const loader = ({ context }: LoaderFunctionArgs) => { +export async function loader({ context }: Route.LoaderArgs) { const { http, make } = context - return json({ + return { message: 'Hello from ' + http.request.completeUrl(), - }) + } } {{{/if}}} {{#if flags["client-loader"]}} -// https://remix.run/docs/en/main/route/client-loader -export const clientLoader = async ({ request, params, serverLoader }: ClientLoaderFunctionArgs) => { +export async function clientLoader({ serverLoader }: Route.ClientLoaderArgs) { // call the server loader const serverData = await serverLoader(); // And/or fetch data on the client @@ -34,16 +33,15 @@ export const clientLoader = async ({ request, params, serverLoader }: ClientLoad } {{/if}} {{#if flags.action}} - -export const action = ({ context }: ActionFunctionArgs) => { +export async function action({ context }: Route.ActionArgs) { const { http, make } = context return null } + {{/if}} {{#if flags["client-action"]}} -// https://remix.run/docs/en/main/route/client-action -export const clientAction = async ({ request, params, serverAction }: ClientActionFunctionArgs) => { +export async function clientAction({ serverAction }: Route.ClientActionArgs) { // invalidateClientSideCache() const data = await serverAction() return data @@ -51,23 +49,18 @@ export const clientAction = async ({ request, params, serverAction }: ClientActi {{/if}} {{#if flags.meta}} -// https://remix.run/docs/en/main/route/meta -export const meta: MetaFunction = () => { +export function meta({ }: Route.MetaArgs) { return [{ title: '{{{name}}}' }, { name: 'description', content: 'Route {{{name}}}' }] } {{/if}} {{#if flags.headers}} -// https://remix.run/docs/en/main/route/headers -export const headers: HeadersFunction = ({ - actionHeaders, - errorHeaders, - loaderHeaders, - parentHeaders, -}) => ({ - 'X-Stretchy-Pants': 'its for fun', - 'Cache-Control': 'max-age=300, s-maxage=3600', -}) +export function headers({ }: Route.HeadersArgs) { + return { + "X-Stretchy-Pants": "its for fun", + "Cache-Control": "max-age=300, s-maxage=3600", + }; +} {{/if}} export default function Page() { @@ -81,7 +74,6 @@ export default function Page() { } {{#if flags['error-boundary']}} -// https://remix.run/docs/en/main/route/error-boundary export function ErrorBoundary() { const error = useRouteError(); diff --git a/packages/adapter/stubs/react-router.config.ts.stub b/packages/adapter/stubs/react-router.config.ts.stub new file mode 100644 index 0000000..c73a714 --- /dev/null +++ b/packages/adapter/stubs/react-router.config.ts.stub @@ -0,0 +1,10 @@ +{{{ + exports({ to: app.makePath('resources/remix_app/react-router.config.ts') }) +}}} +import type { Config } from '@react-router/dev/config' +export default { + ssr: true, + appDirectory: 'resources/remix_app', + buildDirectory: 'build/remix', + serverBuildFile: 'server.js', +} satisfies Config diff --git a/packages/adapter/stubs/root.tsx.stub b/packages/adapter/stubs/root.tsx.stub index 9cee13e..d3c3cac 100644 --- a/packages/adapter/stubs/root.tsx.stub +++ b/packages/adapter/stubs/root.tsx.stub @@ -1,21 +1,33 @@ {{{ exports({ to: app.makePath('resources/remix_app/root.tsx') }) }}} -import { cssBundleHref } from "@remix-run/css-bundle"; -import type { LinksFunction } from "@remix-run/node"; import { + isRouteErrorResponse, Links, Meta, Outlet, Scripts, ScrollRestoration, -} from "@remix-run/react"; +} from "react-router"; -export const links: LinksFunction = () => [ - ...(cssBundleHref ? [{ rel: "stylesheet", href: cssBundleHref }] : []), +import type { Route } from "./+types/root"; +import stylesheet from "./app.css?url"; + +export const links: Route.LinksFunction = () => [ + { rel: "preconnect", href: "https://fonts.googleapis.com" }, + { + rel: "preconnect", + href: "https://fonts.gstatic.com", + crossOrigin: "anonymous", + }, + { + rel: "stylesheet", + href: "https://fonts.googleapis.com/css2?family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&display=swap", + }, + { rel: "stylesheet", href: stylesheet }, ]; -export default function App() { +export function Layout({ children }: { children: React.ReactNode }) { return ( @@ -25,10 +37,43 @@ export default function App() { - + {children} ); -} \ No newline at end of file +} + +export default function App() { + return ; +} + +export function ErrorBoundary({ error }: Route.ErrorBoundaryProps) { + let message = "Oops!"; + let details = "An unexpected error occurred."; + let stack: string | undefined; + + if (isRouteErrorResponse(error)) { + message = error.status === 404 ? "404" : "Error"; + details = + error.status === 404 + ? "The requested page could not be found." + : error.statusText || details; + } else if (import.meta.env.DEV && error && error instanceof Error) { + details = error.message; + stack = error.stack; + } + + return ( +
      +

      {message}

      +

      {details}

      + {stack && ( +
      +          {stack}
      +        
      + )} +
      + ); +} diff --git a/packages/adapter/stubs/routes.ts.stub b/packages/adapter/stubs/routes.ts.stub new file mode 100644 index 0000000..5727c20 --- /dev/null +++ b/packages/adapter/stubs/routes.ts.stub @@ -0,0 +1,7 @@ +{{{ + exports({ to: app.makePath("resources/remix_app/routes.ts") }) +}}} +import { type RouteConfig } from '@react-router/dev/routes' +import { flatRoutes } from '@react-router/fs-routes' + +export default flatRoutes() satisfies RouteConfig diff --git a/packages/adapter/stubs/vite.config.ts.stub b/packages/adapter/stubs/vite.config.ts.stub index 643dd3c..ee7b1f2 100644 --- a/packages/adapter/stubs/vite.config.ts.stub +++ b/packages/adapter/stubs/vite.config.ts.stub @@ -1,25 +1,18 @@ {{{ exports({ to: app.makePath("vite.config.ts") }) }}} -import adonisjs from '@adonisjs/vite/client' -import { vitePlugin as remix } from '@remix-run/dev' -import { defineConfig } from 'vite' +import { reactRouter } from '@react-router/dev/vite'; +import { defineConfig } from 'vite'; -export default defineConfig({ - base: '/assets/', +export default defineConfig(({ isSsrBuild }) => ({ plugins: [ - remix({ - appDirectory: 'resources/remix_app', - buildDirectory: 'build/remix', - serverBuildFile: 'server.js', - }), - adonisjs({ - entrypoints: [], - }), + reactRouter(), ], - esbuildOptions: isSsrBuild - ? { - target: 'ES2022', - } - : {}, -}) + optimizeDeps: { + esbuildOptions: isSsrBuild + ? { + target: 'ES2022', + } + : {}, + }, +})) diff --git a/packages/adapter/tests/adapter/http_server.ts b/packages/adapter/tests/adapter/http_server.ts index 30aa7a8..54a86fb 100644 --- a/packages/adapter/tests/adapter/http_server.ts +++ b/packages/adapter/tests/adapter/http_server.ts @@ -8,9 +8,9 @@ import { import { SessionMiddlewareFactory } from '@adonisjs/session/factories' import { SessionConfig } from '@adonisjs/session/types' import { getActiveTest } from '@japa/runner' -import { RequestHandler } from '@remix-run/node' import getPort from 'get-port' import { IncomingMessage, Server, ServerResponse, createServer } from 'node:http' +import { RequestHandler } from 'react-router' import debug from '../../src/debug.js' import { createRemixRequest, sendRemixResponse } from '../../src/remix_adapter.js' import { CookieStore } from './cookie.js' diff --git a/packages/adapter/tests/adapter/session.spec.ts b/packages/adapter/tests/adapter/session.spec.ts index 8812430..fc6b848 100644 --- a/packages/adapter/tests/adapter/session.spec.ts +++ b/packages/adapter/tests/adapter/session.spec.ts @@ -1,5 +1,5 @@ import { test } from '@japa/runner' -import { redirect } from '@remix-run/node' +import { redirect } from 'react-router' import setCookieParser from 'set-cookie-parser' import supertest from 'supertest' import { cookieClient, remixHandler } from './http_server.js' diff --git a/packages/adapter/tests/commands/remix_route.spec.ts b/packages/adapter/tests/commands/remix_route.spec.ts index f519463..680e690 100644 --- a/packages/adapter/tests/commands/remix_route.spec.ts +++ b/packages/adapter/tests/commands/remix_route.spec.ts @@ -36,29 +36,27 @@ test.group('Create remix route from stub', (group) => { let flagRoutes: FlagRoute[] = [ { flag: 'loader', - codeMatch: 'export const loader = ({ context }: LoaderFunctionArgs) => {', + codeMatch: 'export async function loader({ context }: Route.LoaderArgs) {', }, { flag: 'client-loader', - codeMatch: - 'export const clientLoader = async ({ request, params, serverLoader }: ClientLoaderFunctionArgs) => {', + codeMatch: 'export async function clientLoader({ serverLoader }: Route.ClientLoaderArgs) {', }, { flag: 'action', - codeMatch: 'export const action = ({ context }: ActionFunctionArgs) => {', + codeMatch: 'export async function action({ context }: Route.ActionArgs) {', }, { flag: 'client-action', - codeMatch: - 'export const clientAction = async ({ request, params, serverAction }: ClientActionFunctionArgs) => {', + codeMatch: 'export async function clientAction({ serverAction }: Route.ClientActionArgs) {', }, { flag: 'meta', - codeMatch: 'export const meta: MetaFunction = () => {', + codeMatch: 'export function meta({ }: Route.MetaArgs) {', }, { flag: 'headers', - codeMatch: 'export const headers: HeadersFunction = ({', + codeMatch: 'export function headers({ }: Route.HeadersArgs) {', }, { diff --git a/packages/reference-app/README.md b/packages/reference-app/README.md index a48ea6f..5d5fcfe 100644 --- a/packages/reference-app/README.md +++ b/packages/reference-app/README.md @@ -1,4 +1,4 @@ # Reference app This is a sample app built using remix-adonisjs. -It is used as an integration test for making sure that everything works as expected. \ No newline at end of file +It is used as an integration test for making sure that everything works as expected. diff --git a/packages/reference-app/adonisrc.ts b/packages/reference-app/adonisrc.ts index 34f0e56..7a13fe5 100644 --- a/packages/reference-app/adonisrc.ts +++ b/packages/reference-app/adonisrc.ts @@ -10,7 +10,11 @@ export default defineConfig({ | will be scanned automatically from the "./commands" directory. | */ - commands: [() => import('@adonisjs/core/commands'), () => import('@adonisjs/lucid/commands')], + commands: [ + () => import('@adonisjs/core/commands'), + () => import('@adonisjs/lucid/commands'), + () => import('@matstack/remix-adonisjs/commands') + ], /* |-------------------------------------------------------------------------- diff --git a/packages/reference-app/env.d.ts b/packages/reference-app/env.d.ts index ba4873c..db4f019 100644 --- a/packages/reference-app/env.d.ts +++ b/packages/reference-app/env.d.ts @@ -1,2 +1,5 @@ -/// -/// \ No newline at end of file +import type { AdonisApplicationContext } from '@matstack/remix-adonisjs/types'; + +declare module 'react-router' { + interface AppLoadContext extends AdonisApplicationContext { } +} \ No newline at end of file diff --git a/packages/reference-app/package.json b/packages/reference-app/package.json index ea2eb7a..3fc4a35 100644 --- a/packages/reference-app/package.json +++ b/packages/reference-app/package.json @@ -6,6 +6,7 @@ "license": "MIT", "scripts": { "start": "node bin/server.js", + "prebuild": "react-router typegen", "build": "node ace build", "dev": "node ace serve", "test": "node ace test", @@ -34,41 +35,42 @@ "@japa/file-system": "^2.3.0", "@japa/plugin-adonisjs": "^3.0.1", "@japa/runner": "^3.1.4", - "@remix-run/dev": "^2.14.0", - "@swc/core": "^1.9.2", + "@react-router/dev": "^7.0.2", + "@react-router/fs-routes": "^7.0.2", + "@swc/core": "^1.10.1", "@types/luxon": "^3.4.2", - "@types/node": "^22.9.0", - "@types/react": "^18.3.12", - "@types/react-dom": "^18.3.1", + "@types/node": "^22.10.2", + "@types/react": "^19.0.1", + "@types/react-dom": "^19.0.2", "eslint": "^8.57.0", "hot-hook": "^0.3.1", - "prettier": "^3.3.3", + "prettier": "^3.4.2", "ts-node": "^10.9.2", "typescript": "5.6.3", "vite": "^5.4.11" }, "dependencies": { "@adonisjs/auth": "^9.2.4", - "@adonisjs/core": "^6.14.1", - "@adonisjs/lucid": "^21.4.0", + "@adonisjs/core": "^6.16.0", + "@adonisjs/lucid": "^21.5.1", "@adonisjs/session": "^7.5.0", "@adonisjs/shield": "^8.1.1", "@adonisjs/static": "^1.1.1", "@adonisjs/vite": "^3.0.0", - "@japa/api-client": "^2.0.3", + "@japa/api-client": "^2.0.4", "@japa/browser-client": "^2.0.3", "@matstack/remix-adonisjs": "*", - "@remix-run/css-bundle": "^2.14.0", - "@remix-run/node": "^2.14.0", - "@remix-run/react": "^2.14.0", - "@remix-run/serve": "^2.14.0", - "@vinejs/vine": "^2.1.0", + "@react-router/node": "^7.0.2", + "@react-router/serve": "^7.0.2", + "@vinejs/vine": "^3.0.0", "edge.js": "^6.2.0", "isbot": "^5", "luxon": "^3.5.0", "pino-pretty": "^13.0.0", "react": "^18.3.1", "react-dom": "^18.3.1", + "react-router": "^7.0.2", + "react-router-dom": "^7.0.2", "reflect-metadata": "^0.2.2", "sqlite3": "^5.1.7" }, diff --git a/packages/reference-app/react-router.config.ts b/packages/reference-app/react-router.config.ts new file mode 100644 index 0000000..ae3698d --- /dev/null +++ b/packages/reference-app/react-router.config.ts @@ -0,0 +1,7 @@ +import type { Config } from '@react-router/dev/config' +export default { + ssr: true, + appDirectory: 'resources/remix_app', + buildDirectory: 'build/remix', + serverBuildFile: 'server.js', +} satisfies Config diff --git a/packages/reference-app/resources/remix_app/components/error_boundary.tsx b/packages/reference-app/resources/remix_app/components/error_boundary.tsx index 8c105d9..ad40329 100644 --- a/packages/reference-app/resources/remix_app/components/error_boundary.tsx +++ b/packages/reference-app/resources/remix_app/components/error_boundary.tsx @@ -1,4 +1,4 @@ -import { isRouteErrorResponse, useRouteError } from '@remix-run/react' +import { isRouteErrorResponse, useRouteError } from 'react-router' export function ErrorBoundaryComponent() { const error = useRouteError() diff --git a/packages/reference-app/resources/remix_app/root.tsx b/packages/reference-app/resources/remix_app/root.tsx index a303782..62da400 100644 --- a/packages/reference-app/resources/remix_app/root.tsx +++ b/packages/reference-app/resources/remix_app/root.tsx @@ -1,10 +1,4 @@ -import { cssBundleHref } from '@remix-run/css-bundle' -import type { LinksFunction } from '@remix-run/node' -import { Links, Meta, Outlet, Scripts, ScrollRestoration } from '@remix-run/react' - -export const links: LinksFunction = () => [ - ...(cssBundleHref ? [{ rel: 'stylesheet', href: cssBundleHref }] : []), -] +import { Links, Meta, Outlet, Scripts, ScrollRestoration } from 'react-router' export default function App() { return ( @@ -17,11 +11,7 @@ export default function App() { rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@picocss/pico@1/css/pico.min.css" /> - + diff --git a/packages/reference-app/resources/remix_app/routes.ts b/packages/reference-app/resources/remix_app/routes.ts new file mode 100644 index 0000000..83c333f --- /dev/null +++ b/packages/reference-app/resources/remix_app/routes.ts @@ -0,0 +1,4 @@ +import { type RouteConfig } from '@react-router/dev/routes' +import { flatRoutes } from '@react-router/fs-routes' + +export default flatRoutes() satisfies RouteConfig diff --git a/packages/reference-app/resources/remix_app/routes/_index.tsx b/packages/reference-app/resources/remix_app/routes/_index.tsx index fa833c1..7fa3e79 100644 --- a/packages/reference-app/resources/remix_app/routes/_index.tsx +++ b/packages/reference-app/resources/remix_app/routes/_index.tsx @@ -1,4 +1,4 @@ -import type { MetaFunction } from '@remix-run/node' +import type { MetaFunction } from 'react-router' export const meta: MetaFunction = () => { return [{ title: 'New Remix App' }, { name: 'description', content: 'Welcome to Remix!' }] diff --git a/packages/reference-app/resources/remix_app/routes/adonis_redirect.tsx b/packages/reference-app/resources/remix_app/routes/adonis_redirect.tsx index 24b17c8..590c3fe 100644 --- a/packages/reference-app/resources/remix_app/routes/adonis_redirect.tsx +++ b/packages/reference-app/resources/remix_app/routes/adonis_redirect.tsx @@ -1,19 +1,15 @@ -import type { LoaderFunctionArgs } from '@remix-run/node' +import { Route } from "./+types/adonis_redirect.js" -export const loader = async ({ context }: LoaderFunctionArgs) => { +export const loader = async ({ context }: Route.LoaderArgs) => { const { http } = context http.response.redirect('/login') return { - message: 'Hello, world!' + message: 'Hello, world!', } } export default function Page() { - return ( -
      - This should not be visible -
      - ) -} \ No newline at end of file + return
      This should not be visible
      +} diff --git a/packages/reference-app/resources/remix_app/routes/dashboard.tsx b/packages/reference-app/resources/remix_app/routes/dashboard.tsx index d2127fb..fee6853 100644 --- a/packages/reference-app/resources/remix_app/routes/dashboard.tsx +++ b/packages/reference-app/resources/remix_app/routes/dashboard.tsx @@ -1,10 +1,10 @@ -import { LoaderFunctionArgs, json } from '@remix-run/node' -import { useLoaderData } from '@remix-run/react' +import { useLoaderData } from 'react-router' +import { Route } from './+types/dashboard.js' -export const loader = ({ context }: LoaderFunctionArgs) => { +export const loader = ({ context }: Route.LoaderArgs) => { const { http } = context - return json(http.session.all()) + return http.session.all() } export default function Page() { const data = useLoaderData() diff --git a/packages/reference-app/resources/remix_app/routes/echo.tsx b/packages/reference-app/resources/remix_app/routes/echo.tsx index 391ba84..524e1ae 100644 --- a/packages/reference-app/resources/remix_app/routes/echo.tsx +++ b/packages/reference-app/resources/remix_app/routes/echo.tsx @@ -1,12 +1,12 @@ -import { LoaderFunctionArgs, json } from '@remix-run/node' -import { useLoaderData } from '@remix-run/react' +import { useLoaderData } from 'react-router' +import { Route } from './+types/echo.js' -export const loader = ({ request }: LoaderFunctionArgs) => { +export const loader = ({ request }: Route.LoaderArgs) => { const params = new URL(request.url).searchParams - return json({ + return { message: params.get('message'), - }) + } } export default function Page() { diff --git a/packages/reference-app/resources/remix_app/routes/feedback.tsx b/packages/reference-app/resources/remix_app/routes/feedback.tsx index 00c8f25..fd9560d 100644 --- a/packages/reference-app/resources/remix_app/routes/feedback.tsx +++ b/packages/reference-app/resources/remix_app/routes/feedback.tsx @@ -1,10 +1,9 @@ -import { json } from '@remix-run/node' -import { Form, useActionData } from '@remix-run/react' +import { Form, useActionData } from 'react-router' export const action = () => { - return json({ + return { message: 'Thank you for your feedback!', - }) + } } export default function Page() { diff --git a/packages/reference-app/resources/remix_app/routes/login.tsx b/packages/reference-app/resources/remix_app/routes/login.tsx index 792675a..f102554 100644 --- a/packages/reference-app/resources/remix_app/routes/login.tsx +++ b/packages/reference-app/resources/remix_app/routes/login.tsx @@ -1,7 +1,7 @@ -import { ActionFunctionArgs, redirect } from '@remix-run/node' -import { Form } from '@remix-run/react' +import { Form, redirect } from 'react-router' +import { Route } from './+types/login.js' -export const action = async ({ context }: ActionFunctionArgs) => { +export const action = async ({ context }: Route.ActionArgs) => { const { http } = context const { password } = http.request.only(['password']) diff --git a/packages/reference-app/resources/remix_app/routes/posts.tsx b/packages/reference-app/resources/remix_app/routes/posts.tsx index c19345b..0f65b4f 100644 --- a/packages/reference-app/resources/remix_app/routes/posts.tsx +++ b/packages/reference-app/resources/remix_app/routes/posts.tsx @@ -1,15 +1,15 @@ -import { Await, defer, useLoaderData } from '@remix-run/react' +import { Await, useLoaderData } from 'react-router' import { Suspense } from 'react' export const loader = async () => { // simulate a slow loader - return defer({ + return { lazyPosts: new Promise((resolve) => { setTimeout(() => { resolve(['Post 1', 'Post 2']) }, 100) }), - }) + } } export default function Page() { diff --git a/packages/reference-app/resources/remix_app/routes/profile.tsx b/packages/reference-app/resources/remix_app/routes/profile.tsx index ffc965d..e52f66f 100644 --- a/packages/reference-app/resources/remix_app/routes/profile.tsx +++ b/packages/reference-app/resources/remix_app/routes/profile.tsx @@ -1,11 +1,10 @@ -import { json } from '@remix-run/node' -import { useLoaderData } from '@remix-run/react' +import { useLoaderData } from 'react-router' export const loader = () => { - return json({ + return { userName: 'John Doe', email: 'john.doe@example.com', - }) + } } export default function Page() { diff --git a/packages/reference-app/resources/remix_app/routes/resolve_container.tsx b/packages/reference-app/resources/remix_app/routes/resolve_container.tsx index 268e78f..9e2bdfc 100644 --- a/packages/reference-app/resources/remix_app/routes/resolve_container.tsx +++ b/packages/reference-app/resources/remix_app/routes/resolve_container.tsx @@ -1,11 +1,11 @@ -import { LoaderFunctionArgs, json } from '@remix-run/node' -import { useLoaderData } from '@remix-run/react' +import { useLoaderData } from 'react-router' +import { Route } from './+types/resolve_container.js' -export const loader = async ({ context }: LoaderFunctionArgs) => { +export const loader = async ({ context }: Route.LoaderArgs) => { const app = await context.make('app') - return json({ + return { env: app.getEnvironment(), - }) + } } export default function Page() { diff --git a/packages/reference-app/resources/remix_app/routes/webhooks.payment.tsx b/packages/reference-app/resources/remix_app/routes/webhooks.payment.tsx index a100666..b46e67e 100644 --- a/packages/reference-app/resources/remix_app/routes/webhooks.payment.tsx +++ b/packages/reference-app/resources/remix_app/routes/webhooks.payment.tsx @@ -1,10 +1,18 @@ -import { ActionFunctionArgs, json } from '@remix-run/node' +import { Route } from './+types/webhooks.payment.js' -export const action = async ({ request, context }: ActionFunctionArgs) => { +export const action = async ({ request, context }: Route.ActionArgs) => { const body = await request.text() const { http } = context http.logger.info('webhook body', body) - return json({ + return Response.json({ status: 'ok', }) } + + +export function headers({ }: Route.HeadersArgs) { + return { + "X-Stretchy-Pants": "its for fun", + "Cache-Control": "max-age=300, s-maxage=3600", + }; +} \ No newline at end of file diff --git a/packages/reference-app/tsconfig.json b/packages/reference-app/tsconfig.json index 8f2c9d3..3e758ce 100644 --- a/packages/reference-app/tsconfig.json +++ b/packages/reference-app/tsconfig.json @@ -1,10 +1,17 @@ { "extends": "@adonisjs/tsconfig/tsconfig.app.json", + "include": [ + "**/*", + "**/.server/**/*", + "**/.client/**/*", + ".react-router/types/**/*" + ], "compilerOptions": { - "rootDir": "./", + "rootDirs": ["./", "./.react-router/types"], "outDir": "./build", "moduleResolution": "bundler", "lib": ["ES2022", "DOM", "DOM.Iterable"], + "types": ["@react-router/node", "vite/client"], "module": "ESNext", "jsx": "preserve", "skipLibCheck": true, diff --git a/packages/reference-app/vite.config.ts b/packages/reference-app/vite.config.ts index 563ba6f..6698aea 100644 --- a/packages/reference-app/vite.config.ts +++ b/packages/reference-app/vite.config.ts @@ -1,14 +1,8 @@ -import { vitePlugin as remix } from '@remix-run/dev' +import { reactRouter } from '@react-router/dev/vite' import { defineConfig } from 'vite' export default defineConfig(({ isSsrBuild }) => ({ - plugins: [ - remix({ - appDirectory: 'resources/remix_app', - buildDirectory: 'build/remix', - serverBuildFile: 'server.js', - }), - ], + plugins: [reactRouter()], optimizeDeps: { esbuildOptions: isSsrBuild ? { diff --git a/tsconfig.build.json b/tsconfig.build.json index b420878..2bc63c7 100644 --- a/tsconfig.build.json +++ b/tsconfig.build.json @@ -3,5 +3,8 @@ // list, TS won't automatically include all sources below root (the default). "files": [], // Building this project will build all of the following: - "references": [{ "path": "packages/adapter" }, { "path": "packages/reference-app" }] -} \ No newline at end of file + "references": [ + { "path": "packages/adapter" }, + { "path": "packages/reference-app" } + ] +} diff --git a/tsconfig.json b/tsconfig.json index 60df488..955248b 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -2,6 +2,6 @@ "extends": "@adonisjs/tsconfig/tsconfig.package.json", "compilerOptions": { "incremental": true, - "composite": true, + "composite": true } -} \ No newline at end of file +}