From a5e88fc7ef24e9d483107acaf84fd886f4b5d0fb Mon Sep 17 00:00:00 2001 From: Nik Date: Sat, 23 Nov 2024 21:52:28 +0000 Subject: [PATCH] Switch to React Router (#119) Signed-off-by: Nik Nasr --- apps/web-ui/.gitignore | 1 + apps/web-ui/app/entry.client.tsx | 4 +- apps/web-ui/app/entry.server.tsx | 19 - apps/web-ui/app/root.tsx | 15 +- apps/web-ui/app/routes.ts | 7 + apps/web-ui/app/routes/_index.tsx | 2 +- apps/web-ui/app/tailwind.css | 3 +- apps/web-ui/constants.ts | 1 + apps/web-ui/package.json | 8 +- apps/web-ui/project.json | 14 +- apps/web-ui/react-router.config.ts | 7 + apps/web-ui/remix.env.d.ts | 2 - apps/web-ui/test-setup.ts | 2 - apps/web-ui/tests/routes/_index.spec.tsx | 4 +- apps/web-ui/tsconfig.app.json | 10 +- apps/web-ui/tsconfig.json | 8 +- apps/web-ui/vite.config.ts | 22 +- .../src/lib/DeleteDeployment.tsx | 2 +- .../overview-route/src/lib/Deployment.tsx | 2 +- .../src/lib/Details/Deployment.tsx | 2 +- .../src/lib/Details/Service.tsx | 2 +- .../overview-route/src/lib/Handler.tsx | 6 +- .../src/lib/RegisterDeployment/Context.tsx | 2 +- .../overview-route/src/lib/Service.tsx | 2 +- .../src/lib/ServicePlayground.tsx | 12 +- .../overview-route/src/lib/StopLight.tsx | 18 - libs/ui/api/.babelrc | 12 + libs/ui/api/README.md | 7 + libs/ui/api/eslint.config.js | 12 + libs/ui/api/project.json | 9 + libs/ui/api/src/index.ts | 1 + libs/ui/api/src/lib/API.tsx | 50 + libs/ui/api/tsconfig.json | 20 + libs/ui/api/tsconfig.lib.json | 32 + libs/ui/api/tsconfig.spec.json | 26 + libs/ui/api/vite.config.ts | 25 + libs/ui/button/src/lib/SubmitButton.tsx | 2 +- libs/ui/button/src/test-setup.ts | 2 - libs/ui/dialog/src/lib/Dialog.tsx | 2 +- libs/ui/error/src/lib/CrashError.tsx | 2 +- libs/ui/layout/src/lib/Complementary.tsx | 2 +- libs/ui/nav/src/lib/NavItem.tsx | 2 +- .../feature-flag/src/lib/withFeatureFlag.ts | 4 +- .../react-query/src/lib/QueryProvider.tsx | 7 +- .../remix/src/lib/useFetcherWithErrors.ts | 2 +- package.json | 17 +- pnpm-lock.yaml | 3589 ++--------------- tsconfig.base.json | 1 + 48 files changed, 706 insertions(+), 3297 deletions(-) delete mode 100644 apps/web-ui/app/entry.server.tsx create mode 100644 apps/web-ui/app/routes.ts create mode 100644 apps/web-ui/constants.ts create mode 100644 apps/web-ui/react-router.config.ts delete mode 100644 apps/web-ui/remix.env.d.ts delete mode 100644 libs/features/overview-route/src/lib/StopLight.tsx create mode 100644 libs/ui/api/.babelrc create mode 100644 libs/ui/api/README.md create mode 100644 libs/ui/api/eslint.config.js create mode 100644 libs/ui/api/project.json create mode 100644 libs/ui/api/src/index.ts create mode 100644 libs/ui/api/src/lib/API.tsx create mode 100644 libs/ui/api/tsconfig.json create mode 100644 libs/ui/api/tsconfig.lib.json create mode 100644 libs/ui/api/tsconfig.spec.json create mode 100644 libs/ui/api/vite.config.ts diff --git a/apps/web-ui/.gitignore b/apps/web-ui/.gitignore index 9ca4842f..a70276cd 100644 --- a/apps/web-ui/.gitignore +++ b/apps/web-ui/.gitignore @@ -2,3 +2,4 @@ build public/build .env +.react-router diff --git a/apps/web-ui/app/entry.client.tsx b/apps/web-ui/app/entry.client.tsx index 97512674..f4ddc44d 100644 --- a/apps/web-ui/app/entry.client.tsx +++ b/apps/web-ui/app/entry.client.tsx @@ -1,4 +1,4 @@ -import { RemixBrowser } from '@remix-run/react'; +import { HydratedRouter } from 'react-router/dom'; import { startTransition, StrictMode } from 'react'; import { hydrateRoot } from 'react-dom/client'; import { register } from '@restate/data-access/middleware-service-worker'; @@ -8,7 +8,7 @@ register().then(() => { hydrateRoot( document, - + ); }); diff --git a/apps/web-ui/app/entry.server.tsx b/apps/web-ui/app/entry.server.tsx deleted file mode 100644 index f65553dc..00000000 --- a/apps/web-ui/app/entry.server.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import type { EntryContext } from '@remix-run/node'; -import { RemixServer } from '@remix-run/react'; -import { renderToString } from 'react-dom/server'; - -export default function handleRequest( - request: Request, - responseStatusCode: number, - responseHeaders: Headers, - remixContext: EntryContext -) { - let html = renderToString( - - ); - html = '\n' + html; - return new Response(html, { - headers: { 'Content-Type': 'text/html' }, - status: responseStatusCode, - }); -} diff --git a/apps/web-ui/app/root.tsx b/apps/web-ui/app/root.tsx index 5c99ecc5..c779de35 100644 --- a/apps/web-ui/app/root.tsx +++ b/apps/web-ui/app/root.tsx @@ -6,9 +6,9 @@ import { Scripts, ScrollRestoration, useNavigate, -} from '@remix-run/react'; +} from 'react-router'; import styles from './tailwind.css?url'; -import type { LinksFunction } from '@remix-run/node'; +import type { LinksFunction } from 'react-router'; import { LayoutOutlet, LayoutProvider, LayoutZone } from '@restate/ui/layout'; import { RouterProvider } from 'react-aria-components'; import { Button, Spinner } from '@restate/ui/button'; @@ -34,6 +34,11 @@ export const links: LinksFunction = () => [ rel: 'preconnect', href: 'https://rsms.me/', }, + // TODO: move to the its own lib + { + rel: 'stylesheet', + href: 'https://unpkg.com/@stoplight/elements/styles.min.css', + }, { rel: 'stylesheet', href: styles }, { rel: 'stylesheet', href: 'https://rsms.me/inter/inter.css' }, { @@ -85,6 +90,12 @@ export function Layout({ children }: { children: React.ReactNode }) { + {/* TODO: move to its own lib */} + ); diff --git a/apps/web-ui/app/routes.ts b/apps/web-ui/app/routes.ts new file mode 100644 index 00000000..6e2830af --- /dev/null +++ b/apps/web-ui/app/routes.ts @@ -0,0 +1,7 @@ +import { type RouteConfig, route, index } from '@react-router/dev/routes'; + +export default [ + index('routes/_index.tsx'), + route('overview', 'routes/overview.tsx'), + route('invocations', 'routes/invocations.tsx'), +] satisfies RouteConfig; diff --git a/apps/web-ui/app/routes/_index.tsx b/apps/web-ui/app/routes/_index.tsx index 19fb55a9..e62617a5 100644 --- a/apps/web-ui/app/routes/_index.tsx +++ b/apps/web-ui/app/routes/_index.tsx @@ -1,4 +1,4 @@ -import { redirect } from '@remix-run/react'; +import { redirect } from 'react-router'; export const clientLoader = () => redirect('/overview'); export default () => null; diff --git a/apps/web-ui/app/tailwind.css b/apps/web-ui/app/tailwind.css index 938d9d33..9214d091 100644 --- a/apps/web-ui/app/tailwind.css +++ b/apps/web-ui/app/tailwind.css @@ -1,5 +1,4 @@ -@import '@stoplight/elements/styles.min.css'; - +/* @import 'https://unpkg.com/@stoplight/elements/styles.min.css'; */ @tailwind base; @tailwind components; @tailwind utilities; diff --git a/apps/web-ui/constants.ts b/apps/web-ui/constants.ts new file mode 100644 index 00000000..f84e3fb5 --- /dev/null +++ b/apps/web-ui/constants.ts @@ -0,0 +1 @@ +export const BASE_URL = '/ui/'; diff --git a/apps/web-ui/package.json b/apps/web-ui/package.json index 55b8032b..e7e24ffb 100644 --- a/apps/web-ui/package.json +++ b/apps/web-ui/package.json @@ -6,15 +6,13 @@ "scripts": {}, "type": "module", "dependencies": { - "@remix-run/node": "^2.8.1", - "@remix-run/react": "^2.8.1", - "@remix-run/serve": "^2.8.1", + "@react-router/node": "7.0.0", "isbot": "^4.4.0", "react": "19.0.0-rc-65903583-20240805", - "react-dom": "19.0.0-rc-65903583-20240805" + "react-dom": "19.0.0-rc-65903583-20240805", + "react-router": "7.0.0" }, "devDependencies": { - "@remix-run/dev": "^2.8.1", "@types/react": "^18.2.0", "@types/react-dom": "^18.2.0", "eslint": "^8.56.0", diff --git a/apps/web-ui/project.json b/apps/web-ui/project.json index 2fb3ce21..34afa8e1 100644 --- a/apps/web-ui/project.json +++ b/apps/web-ui/project.json @@ -11,7 +11,7 @@ "options": { "commands": [ { - "command": "cd apps/web-ui && ../../node_modules/.bin/remix vite:dev --port=4300" + "command": "cd apps/web-ui && ../../node_modules/.bin/react-router dev --port=4300" } ] }, @@ -22,7 +22,7 @@ "command": "nx serve mock-admin-api" }, { - "command": "cd apps/web-ui && ADMIN_BASE_URL=http://localhost:4001 ../../node_modules/.bin/remix vite:dev --port=4300" + "command": "cd apps/web-ui && ADMIN_BASE_URL=http://localhost:4001 ../../node_modules/.bin/react-router dev --port=4300" } ], "parallel": true @@ -33,7 +33,7 @@ "command": "nx serve mock-admin-api -c proxy" }, { - "command": "cd apps/web-ui && ADMIN_BASE_URL=http://localhost:4001 ../../node_modules/.bin/remix vite:dev --port=4300" + "command": "cd apps/web-ui && ADMIN_BASE_URL=http://localhost:4001 ../../node_modules/.bin/react-router dev --port=4300" } ], "parallel": true @@ -45,7 +45,7 @@ "options": { "commands": [ "rm -R -f dist/apps/web-ui", - "cd apps/web-ui && ../../node_modules/.bin/remix vite:build", + "cd apps/web-ui && NODE_ENV=production ../../node_modules/.bin/react-router build", "mkdir -p dist/apps/web-ui", "mv apps/web-ui/build/client/* dist/apps/web-ui", "rm -R apps/web-ui/build" @@ -57,7 +57,7 @@ "executor": "nx:run-commands", "options": { "commands": [ - "cd apps/web-ui && ../../node_modules/.bin/remix vite:build && ../../node_modules/.bin/vite preview --port=4300" + "cd apps/web-ui && NODE_ENV=production ../../node_modules/.bin/react-router build && ../../node_modules/.bin/vite preview --port=4300" ] }, "configurations": { @@ -67,7 +67,7 @@ "command": "nx serve mock-admin-api" }, { - "command": "cd apps/web-ui && ../../node_modules/.bin/remix vite:build && ADMIN_BASE_URL=http://localhost:4001 ../../node_modules/.bin/vite preview --port=4300" + "command": "cd apps/web-ui && NODE_ENV=production && ADMIN_BASE_URL=http://localhost:4001 ../../node_modules/.bin/vite preview --port=4300" } ], "parallel": true @@ -78,7 +78,7 @@ "command": "nx serve mock-admin-api -c proxy" }, { - "command": "cd apps/web-ui && ../../node_modules/.bin/remix vite:build && ADMIN_BASE_URL=http://localhost:4001 ../../node_modules/.bin/vite preview --port=4300" + "command": "cd apps/web-ui && NODE_ENV=production ../../node_modules/.bin/react-router build && ADMIN_BASE_URL=http://localhost:4001 ../../node_modules/.bin/vite preview --port=4300" } ], "parallel": true diff --git a/apps/web-ui/react-router.config.ts b/apps/web-ui/react-router.config.ts new file mode 100644 index 00000000..6a664a82 --- /dev/null +++ b/apps/web-ui/react-router.config.ts @@ -0,0 +1,7 @@ +import type { Config } from '@react-router/dev/config'; +import { BASE_URL } from './constants'; + +export default { + ssr: false, + basename: BASE_URL, +} satisfies Config; diff --git a/apps/web-ui/remix.env.d.ts b/apps/web-ui/remix.env.d.ts deleted file mode 100644 index dcf8c45e..00000000 --- a/apps/web-ui/remix.env.d.ts +++ /dev/null @@ -1,2 +0,0 @@ -/// -/// diff --git a/apps/web-ui/test-setup.ts b/apps/web-ui/test-setup.ts index 85205829..07734877 100644 --- a/apps/web-ui/test-setup.ts +++ b/apps/web-ui/test-setup.ts @@ -1,3 +1 @@ -import { installGlobals } from '@remix-run/node'; import '@testing-library/jest-dom/matchers'; -installGlobals(); diff --git a/apps/web-ui/tests/routes/_index.spec.tsx b/apps/web-ui/tests/routes/_index.spec.tsx index c2d95c75..976c0556 100644 --- a/apps/web-ui/tests/routes/_index.spec.tsx +++ b/apps/web-ui/tests/routes/_index.spec.tsx @@ -1,9 +1,9 @@ -import { createRemixStub } from '@remix-run/testing'; +import { createRoutesStub } from 'react-router'; import { render, screen, waitFor } from '@testing-library/react'; import Index, { clientLoader } from '../../app/routes/_index'; test('renders loader data', async () => { - const RemixStub = createRemixStub([ + const RemixStub = createRoutesStub([ { path: '/', Component: Index, diff --git a/apps/web-ui/tsconfig.app.json b/apps/web-ui/tsconfig.app.json index a9c70f51..56e40b13 100644 --- a/apps/web-ui/tsconfig.app.json +++ b/apps/web-ui/tsconfig.app.json @@ -1,10 +1,16 @@ { "extends": "./tsconfig.json", "compilerOptions": { - "types": ["../../@types/vite.d.ts", "../../@types/global-env.d.ts"] + "types": [ + "@react-router/node", + "vite/client", + "../../@types/vite.d.ts", + "../../@types/global-env.d.ts" + ], + "rootDirs": [".", "./.react-router/types"] }, "include": [ - "remix.env.d.ts", + ".react-router/types/**/*", "app/**/*.ts", "app/**/*.tsx", "app/**/*.js", diff --git a/apps/web-ui/tsconfig.json b/apps/web-ui/tsconfig.json index d95d0312..cb9b6f7e 100644 --- a/apps/web-ui/tsconfig.json +++ b/apps/web-ui/tsconfig.json @@ -1,17 +1,19 @@ { "extends": "../../tsconfig.base.json", "compilerOptions": { - "lib": ["DOM", "DOM.Iterable", "ES2019"], + "lib": ["DOM", "DOM.Iterable", "ES2022"], "isolatedModules": true, "esModuleInterop": true, "jsx": "react-jsx", - "moduleResolution": "node", + "moduleResolution": "bundler", "resolveJsonModule": true, "target": "ES2019", "strict": true, "allowJs": true, "forceConsistentCasingInFileNames": true, - "noEmit": true + "noEmit": true, + "skipLibCheck": true, + "module": "ESNext" }, "include": [], "files": [], diff --git a/apps/web-ui/vite.config.ts b/apps/web-ui/vite.config.ts index 2bb6a657..ffa69938 100644 --- a/apps/web-ui/vite.config.ts +++ b/apps/web-ui/vite.config.ts @@ -1,11 +1,9 @@ -import { vitePlugin as remix } from '@remix-run/dev'; -import { defineConfig, loadEnv } from 'vite'; +import { reactRouter } from '@react-router/dev/vite'; +import { defineConfig, loadEnv, Plugin } from 'vite'; import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin'; import license from 'rollup-plugin-license'; import path from 'path'; -import { nodePolyfills } from 'vite-plugin-node-polyfills'; - -const BASE_URL = '/ui/'; +import { BASE_URL } from './constants'; export default defineConfig(({ mode }) => { const env = loadEnv(mode, process.cwd(), ''); @@ -37,19 +35,9 @@ export default defineConfig(({ mode }) => { return !env.isSsrBuild; }, }, - !process.env.VITEST && - remix({ - ssr: false, - basename: BASE_URL, - future: { - v3_fetcherPersist: true, - v3_relativeSplatPath: true, - v3_throwAbortReason: true, - }, - }), + !process.env.VITEST && reactRouter(), nxViteTsPaths(), - nodePolyfills(), - ], + ] as Plugin[], // Uncomment this if you are using workers. worker: { diff --git a/libs/features/overview-route/src/lib/DeleteDeployment.tsx b/libs/features/overview-route/src/lib/DeleteDeployment.tsx index ca646017..800ba8f9 100644 --- a/libs/features/overview-route/src/lib/DeleteDeployment.tsx +++ b/libs/features/overview-route/src/lib/DeleteDeployment.tsx @@ -8,7 +8,7 @@ import { DELETE_DEPLOYMENT_QUERY_PARAM, DEPLOYMENT_QUERY_PARAM, } from './constants'; -import { Form, useSearchParams } from '@remix-run/react'; +import { Form, useSearchParams } from 'react-router'; import { Button, SubmitButton } from '@restate/ui/button'; import { ErrorBanner } from '@restate/ui/error'; import { FormFieldInput } from '@restate/ui/form-field'; diff --git a/libs/features/overview-route/src/lib/Deployment.tsx b/libs/features/overview-route/src/lib/Deployment.tsx index 670a6433..47eefe9b 100644 --- a/libs/features/overview-route/src/lib/Deployment.tsx +++ b/libs/features/overview-route/src/lib/Deployment.tsx @@ -10,7 +10,7 @@ import { import { Revision } from './Revision'; import { DEPLOYMENT_QUERY_PARAM } from './constants'; import { Link } from '@restate/ui/link'; -import { useSearchParams } from '@remix-run/react'; +import { useSearchParams } from 'react-router'; import { useRef } from 'react'; const styles = tv({ diff --git a/libs/features/overview-route/src/lib/Details/Deployment.tsx b/libs/features/overview-route/src/lib/Details/Deployment.tsx index 7b00fde9..2a873ae3 100644 --- a/libs/features/overview-route/src/lib/Details/Deployment.tsx +++ b/libs/features/overview-route/src/lib/Details/Deployment.tsx @@ -7,7 +7,7 @@ import { DELETE_DEPLOYMENT_QUERY_PARAM, DEPLOYMENT_QUERY_PARAM, } from '../constants'; -import { useSearchParams } from '@remix-run/react'; +import { useSearchParams } from 'react-router'; import { Section, SectionContent, SectionTitle } from '@restate/ui/section'; import { Icon, IconName } from '@restate/ui/icons'; import { diff --git a/libs/features/overview-route/src/lib/Details/Service.tsx b/libs/features/overview-route/src/lib/Details/Service.tsx index a9e5d89e..a087a008 100644 --- a/libs/features/overview-route/src/lib/Details/Service.tsx +++ b/libs/features/overview-route/src/lib/Details/Service.tsx @@ -10,7 +10,7 @@ import { useModifyService, useServiceDetails, } from '@restate/data-access/admin-api'; -import { Form, useSearchParams } from '@remix-run/react'; +import { Form, useSearchParams } from 'react-router'; import { Handler } from '../Handler'; import { Icon, IconName } from '@restate/ui/icons'; import { ServiceType } from '../ServiceType'; diff --git a/libs/features/overview-route/src/lib/Handler.tsx b/libs/features/overview-route/src/lib/Handler.tsx index dee0d03d..1fbb4da2 100644 --- a/libs/features/overview-route/src/lib/Handler.tsx +++ b/libs/features/overview-route/src/lib/Handler.tsx @@ -12,7 +12,7 @@ import { } from '@restate/ui/popover'; import { ServicePlaygroundTrigger } from './ServicePlayground'; import { ComponentProps } from 'react'; -import { JsonSchemaViewer } from './StopLight'; +import { JsonSchemaViewer } from '@restate/ui/api'; const styles = tv({ base: 'flex flex-col gap-0.5 relative', @@ -154,9 +154,7 @@ function HandlerInputOutput({ > diff --git a/libs/features/overview-route/src/lib/RegisterDeployment/Context.tsx b/libs/features/overview-route/src/lib/RegisterDeployment/Context.tsx index b9c3e1f5..dc516d3f 100644 --- a/libs/features/overview-route/src/lib/RegisterDeployment/Context.tsx +++ b/libs/features/overview-route/src/lib/RegisterDeployment/Context.tsx @@ -1,4 +1,4 @@ -import { Form } from '@remix-run/react'; +import { Form } from 'react-router'; import { createContext, FormEvent, diff --git a/libs/features/overview-route/src/lib/Service.tsx b/libs/features/overview-route/src/lib/Service.tsx index 0d7d0977..de5beb1e 100644 --- a/libs/features/overview-route/src/lib/Service.tsx +++ b/libs/features/overview-route/src/lib/Service.tsx @@ -5,7 +5,7 @@ import { Deployment } from './Deployment'; import { TruncateWithTooltip } from '@restate/ui/tooltip'; import { Link } from '@restate/ui/link'; import { SERVICE_QUERY_PARAM } from './constants'; -import { useSearchParams } from '@remix-run/react'; +import { useSearchParams } from 'react-router'; import { useRef } from 'react'; const styles = tv({ diff --git a/libs/features/overview-route/src/lib/ServicePlayground.tsx b/libs/features/overview-route/src/lib/ServicePlayground.tsx index c37174b6..3be67393 100644 --- a/libs/features/overview-route/src/lib/ServicePlayground.tsx +++ b/libs/features/overview-route/src/lib/ServicePlayground.tsx @@ -1,4 +1,4 @@ -import { useSearchParams, useNavigate } from '@remix-run/react'; +import { useSearchParams, useNavigate } from 'react-router'; import { useServiceOpenApi } from '@restate/data-access/admin-api'; import { Button } from '@restate/ui/button'; import { QueryDialog, DialogContent, DialogClose } from '@restate/ui/dialog'; @@ -7,7 +7,7 @@ import { SERVICE_PLAYGROUND_QUERY_PARAM } from './constants'; import { ComponentProps, useMemo } from 'react'; import { useRestateContext } from '@restate/features/restate-context'; import { tv } from 'tailwind-variants'; -import { API } from './StopLight'; +import { API } from '@restate/ui/api'; const styles = tv({ base: 'px-1.5 py-0.5 text-xs font-normal font-sans rounded-md flex items-center gap-1', @@ -47,7 +47,6 @@ export function ServicePlaygroundTrigger({ }} variant={variant} className={styles({ className })} - onHover={() => import('@stoplight/elements')} > Playground @@ -79,12 +78,7 @@ export function ServicePlayground() { > {apiSpec ? ( <> - +