diff --git a/examples/tanstack-start/.gitignore b/examples/tanstack-start/.gitignore new file mode 100644 index 0000000..1ed63c5 --- /dev/null +++ b/examples/tanstack-start/.gitignore @@ -0,0 +1,15 @@ +node_modules +.DS_Store +dist +dist-ssr +*.local +count.txt +.env +.nitro +.tanstack +.wrangler +.output +.vinxi +todos.json +public/uploads +data.db \ No newline at end of file diff --git a/examples/tanstack-start/.vscode/settings.json b/examples/tanstack-start/.vscode/settings.json new file mode 100644 index 0000000..00b5278 --- /dev/null +++ b/examples/tanstack-start/.vscode/settings.json @@ -0,0 +1,11 @@ +{ + "files.watcherExclude": { + "**/routeTree.gen.ts": true + }, + "search.exclude": { + "**/routeTree.gen.ts": true + }, + "files.readonlyInclude": { + "**/routeTree.gen.ts": true + } +} diff --git a/examples/tanstack-start/README.md b/examples/tanstack-start/README.md new file mode 100644 index 0000000..af94249 --- /dev/null +++ b/examples/tanstack-start/README.md @@ -0,0 +1,28 @@ +# bknd + Tanstack Start Example + +This is a minimal example to shows how to integrate bknd with Tanstack Start. + +## Setup + +```bash +bun install +bun run dev +``` + +## How it works + +1. **`bknd.config.ts`** - bknd configuration with database connection, schema, and seed data +2. **`src/routes/api.$.ts`** - Handles `/api/*` requests for bknd +3. **`src/routes/index.tsx`** - Using `getApp()` to fetch data in loader +3. **`src/routes/ssr.tsx`** - Server Side example with `getApp()` to fetch data on server + +## API Endpoints + +- `GET /admin` - for Admin Dashboard +- `GET /api/data/entity/todos` - List todos (requires auth) +- `POST /api/auth/password/login` - Login + +## Test Credentials + +- Email: `test@bknd.io` +- Password: `12345678` diff --git a/examples/tanstack-start/bknd.config.ts b/examples/tanstack-start/bknd.config.ts new file mode 100644 index 0000000..febdcff --- /dev/null +++ b/examples/tanstack-start/bknd.config.ts @@ -0,0 +1,55 @@ +import { em, entity, text, boolean } from "bknd"; +import { registerLocalMediaAdapter } from "bknd/adapter/node"; +import { TanstackStartConfig } from "bknd/adapter/tanstack-start"; + +const local = registerLocalMediaAdapter(); + +const schema = em({ + todos: entity("todos", { + title: text(), + done: boolean(), + }), +}); + +// register your schema to get automatic type completion +type Database = (typeof schema)["DB"]; +declare module "bknd" { + interface DB extends Database {} +} + +export default { + connection: { + url: "file:data.db", + }, + options: { + // the seed option is only executed if the database was empty + seed: async (ctx) => { + // create some entries + await ctx.em.mutator("todos").insertMany([ + { title: "Learn bknd", done: true }, + { title: "Build something cool", done: false }, + ]); + + // and create a user + await ctx.app.module.auth.createUser({ + email: "test@bknd.io", + password: "12345678", + }); + }, + }, + config: { + data: schema.toJSON(), + auth: { + enabled: true, + jwt: { + secret: "random_gibberish_please_change_this", + }, + }, + media: { + enabled: true, + adapter: local({ + path: "./public/uploads", + }), + }, + }, +} satisfies TanstackStartConfig; diff --git a/examples/tanstack-start/package.json b/examples/tanstack-start/package.json new file mode 100644 index 0000000..d26625f --- /dev/null +++ b/examples/tanstack-start/package.json @@ -0,0 +1,42 @@ +{ + "name": "my-bknd-app", + "private": true, + "type": "module", + "scripts": { + "dev": "vite dev --port 3000", + "build": "vite build", + "start": "vite preview", + "test": "vitest run" + }, + "dependencies": { + "@tailwindcss/vite": "^4.1.18", + "@tanstack/react-devtools": "^0.7.0", + "@tanstack/react-router": "^1.132.0", + "@tanstack/react-router-devtools": "^1.132.0", + "@tanstack/react-router-ssr-query": "^1.131.7", + "@tanstack/react-start": "^1.132.0", + "@tanstack/router-plugin": "^1.132.0", + "bknd": "file:../../app", + "lucide-react": "^0.561.0", + "nitro": "^3.0.1-alpha.2", + "react": "^19.2.0", + "react-dom": "^19.2.0", + "tailwindcss": "^4.1.18", + "vite-tsconfig-paths": "^6.0.2" + }, + "devDependencies": { + "@tanstack/devtools-vite": "^0.3.11", + "babel-plugin-react-compiler": "^1.0.0", + "@testing-library/dom": "^10.4.0", + "@testing-library/react": "^16.2.0", + "@types/node": "^22.10.2", + "@types/react": "^19.2.0", + "@types/react-dom": "^19.2.0", + "@vitejs/plugin-react": "^5.0.4", + "jsdom": "^27.0.0", + "typescript": "^5.7.2", + "vite": "^7.1.7", + "vitest": "^3.0.5", + "web-vitals": "^5.1.0" + } +} diff --git a/examples/tanstack-start/public/bknd.ico b/examples/tanstack-start/public/bknd.ico new file mode 100644 index 0000000..c1a946d Binary files /dev/null and b/examples/tanstack-start/public/bknd.ico differ diff --git a/examples/tanstack-start/public/bknd.svg b/examples/tanstack-start/public/bknd.svg new file mode 100644 index 0000000..182ef92 --- /dev/null +++ b/examples/tanstack-start/public/bknd.svg @@ -0,0 +1,14 @@ + + + + \ No newline at end of file diff --git a/examples/tanstack-start/public/favicon.ico b/examples/tanstack-start/public/favicon.ico new file mode 100644 index 0000000..a11777c Binary files /dev/null and b/examples/tanstack-start/public/favicon.ico differ diff --git a/examples/tanstack-start/public/file.svg b/examples/tanstack-start/public/file.svg new file mode 100644 index 0000000..004145c --- /dev/null +++ b/examples/tanstack-start/public/file.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/examples/tanstack-start/public/globe.svg b/examples/tanstack-start/public/globe.svg new file mode 100644 index 0000000..567f17b --- /dev/null +++ b/examples/tanstack-start/public/globe.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/examples/tanstack-start/public/manifest.json b/examples/tanstack-start/public/manifest.json new file mode 100644 index 0000000..078ef50 --- /dev/null +++ b/examples/tanstack-start/public/manifest.json @@ -0,0 +1,25 @@ +{ + "short_name": "TanStack App", + "name": "Create TanStack App Sample", + "icons": [ + { + "src": "favicon.ico", + "sizes": "64x64 32x32 24x24 16x16", + "type": "image/x-icon" + }, + { + "src": "logo192.png", + "type": "image/png", + "sizes": "192x192" + }, + { + "src": "logo512.png", + "type": "image/png", + "sizes": "512x512" + } + ], + "start_url": ".", + "display": "standalone", + "theme_color": "#000000", + "background_color": "#ffffff" +} diff --git a/examples/tanstack-start/public/robots.txt b/examples/tanstack-start/public/robots.txt new file mode 100644 index 0000000..e9e57dc --- /dev/null +++ b/examples/tanstack-start/public/robots.txt @@ -0,0 +1,3 @@ +# https://www.robotstxt.org/robotstxt.html +User-agent: * +Disallow: diff --git a/examples/tanstack-start/public/tanstack-circle-logo.png b/examples/tanstack-start/public/tanstack-circle-logo.png new file mode 100644 index 0000000..9db3e67 Binary files /dev/null and b/examples/tanstack-start/public/tanstack-circle-logo.png differ diff --git a/examples/tanstack-start/public/tanstack-word-logo-white.svg b/examples/tanstack-start/public/tanstack-word-logo-white.svg new file mode 100644 index 0000000..b6ec508 --- /dev/null +++ b/examples/tanstack-start/public/tanstack-word-logo-white.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/examples/tanstack-start/public/window.svg b/examples/tanstack-start/public/window.svg new file mode 100644 index 0000000..b2b2a44 --- /dev/null +++ b/examples/tanstack-start/public/window.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/examples/tanstack-start/src/bknd.ts b/examples/tanstack-start/src/bknd.ts new file mode 100644 index 0000000..3e0b9fb --- /dev/null +++ b/examples/tanstack-start/src/bknd.ts @@ -0,0 +1,20 @@ +import config from "../bknd.config"; +import { getApp } from "bknd/adapter/tanstack-start"; + +export async function getApi({ + headers, + verify, +}: { + verify?: boolean; + headers?: Headers; +}) { + const app = await getApp(config, process.env); + + if (verify) { + const api = app.getApi({ headers }); + await api.verifyAuth(); + return api; + } + + return app.getApi(); +} diff --git a/examples/tanstack-start/src/components/Footer.tsx b/examples/tanstack-start/src/components/Footer.tsx new file mode 100644 index 0000000..e0d4da9 --- /dev/null +++ b/examples/tanstack-start/src/components/Footer.tsx @@ -0,0 +1,52 @@ +import { useRouterState, Link } from "@tanstack/react-router"; + +export function Footer() { + const routerState = useRouterState(); + const pathname = routerState.location.pathname; + + return ( + + ); +} diff --git a/examples/tanstack-start/src/components/List.tsx b/examples/tanstack-start/src/components/List.tsx new file mode 100644 index 0000000..e2a98c0 --- /dev/null +++ b/examples/tanstack-start/src/components/List.tsx @@ -0,0 +1,9 @@ +export const List = ({ items = [] }: { items: React.ReactNode[] }) => ( +
    + {items.map((item, i) => ( +
  1. + {item} +
  2. + ))} +
+); diff --git a/examples/tanstack-start/src/logo.svg b/examples/tanstack-start/src/logo.svg new file mode 100644 index 0000000..fe53fe8 --- /dev/null +++ b/examples/tanstack-start/src/logo.svg @@ -0,0 +1,12 @@ + + + logo + + \ No newline at end of file diff --git a/examples/tanstack-start/src/routeTree.gen.ts b/examples/tanstack-start/src/routeTree.gen.ts new file mode 100644 index 0000000..13011d3 --- /dev/null +++ b/examples/tanstack-start/src/routeTree.gen.ts @@ -0,0 +1,122 @@ +/* eslint-disable */ + +// @ts-nocheck + +// noinspection JSUnusedGlobalSymbols + +// This file was automatically generated by TanStack Router. +// You should NOT make any changes in this file as it will be overwritten. +// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified. + +import { Route as rootRouteImport } from './routes/__root' +import { Route as SsrRouteImport } from './routes/ssr' +import { Route as IndexRouteImport } from './routes/index' +import { Route as ApiSplatRouteImport } from './routes/api.$' +import { Route as AdminSplatRouteImport } from './routes/admin.$' + +const SsrRoute = SsrRouteImport.update({ + id: '/ssr', + path: '/ssr', + getParentRoute: () => rootRouteImport, +} as any) +const IndexRoute = IndexRouteImport.update({ + id: '/', + path: '/', + getParentRoute: () => rootRouteImport, +} as any) +const ApiSplatRoute = ApiSplatRouteImport.update({ + id: '/api/$', + path: '/api/$', + getParentRoute: () => rootRouteImport, +} as any) +const AdminSplatRoute = AdminSplatRouteImport.update({ + id: '/admin/$', + path: '/admin/$', + getParentRoute: () => rootRouteImport, +} as any) + +export interface FileRoutesByFullPath { + '/': typeof IndexRoute + '/ssr': typeof SsrRoute + '/admin/$': typeof AdminSplatRoute + '/api/$': typeof ApiSplatRoute +} +export interface FileRoutesByTo { + '/': typeof IndexRoute + '/ssr': typeof SsrRoute + '/admin/$': typeof AdminSplatRoute + '/api/$': typeof ApiSplatRoute +} +export interface FileRoutesById { + __root__: typeof rootRouteImport + '/': typeof IndexRoute + '/ssr': typeof SsrRoute + '/admin/$': typeof AdminSplatRoute + '/api/$': typeof ApiSplatRoute +} +export interface FileRouteTypes { + fileRoutesByFullPath: FileRoutesByFullPath + fullPaths: '/' | '/ssr' | '/admin/$' | '/api/$' + fileRoutesByTo: FileRoutesByTo + to: '/' | '/ssr' | '/admin/$' | '/api/$' + id: '__root__' | '/' | '/ssr' | '/admin/$' | '/api/$' + fileRoutesById: FileRoutesById +} +export interface RootRouteChildren { + IndexRoute: typeof IndexRoute + SsrRoute: typeof SsrRoute + AdminSplatRoute: typeof AdminSplatRoute + ApiSplatRoute: typeof ApiSplatRoute +} + +declare module '@tanstack/react-router' { + interface FileRoutesByPath { + '/ssr': { + id: '/ssr' + path: '/ssr' + fullPath: '/ssr' + preLoaderRoute: typeof SsrRouteImport + parentRoute: typeof rootRouteImport + } + '/': { + id: '/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof IndexRouteImport + parentRoute: typeof rootRouteImport + } + '/api/$': { + id: '/api/$' + path: '/api/$' + fullPath: '/api/$' + preLoaderRoute: typeof ApiSplatRouteImport + parentRoute: typeof rootRouteImport + } + '/admin/$': { + id: '/admin/$' + path: '/admin/$' + fullPath: '/admin/$' + preLoaderRoute: typeof AdminSplatRouteImport + parentRoute: typeof rootRouteImport + } + } +} + +const rootRouteChildren: RootRouteChildren = { + IndexRoute: IndexRoute, + SsrRoute: SsrRoute, + AdminSplatRoute: AdminSplatRoute, + ApiSplatRoute: ApiSplatRoute, +} +export const routeTree = rootRouteImport + ._addFileChildren(rootRouteChildren) + ._addFileTypes() + +import type { getRouter } from './router.tsx' +import type { createStart } from '@tanstack/react-start' +declare module '@tanstack/react-start' { + interface Register { + ssr: true + router: Awaited> + } +} diff --git a/examples/tanstack-start/src/router.tsx b/examples/tanstack-start/src/router.tsx new file mode 100644 index 0000000..5c70836 --- /dev/null +++ b/examples/tanstack-start/src/router.tsx @@ -0,0 +1,17 @@ +import { createRouter } from '@tanstack/react-router' + +// Import the generated route tree +import { routeTree } from './routeTree.gen' + +// Create a new router instance +export const getRouter = () => { + const router = createRouter({ + routeTree, + context: {}, + + scrollRestoration: true, + defaultPreloadStaleTime: 0, + }) + + return router +} diff --git a/examples/tanstack-start/src/routes/__root.tsx b/examples/tanstack-start/src/routes/__root.tsx new file mode 100644 index 0000000..9593904 --- /dev/null +++ b/examples/tanstack-start/src/routes/__root.tsx @@ -0,0 +1,58 @@ +import { HeadContent, Scripts, createRootRoute } from "@tanstack/react-router"; +import { TanStackRouterDevtoolsPanel } from "@tanstack/react-router-devtools"; +import { TanStackDevtools } from "@tanstack/react-devtools"; +import { ClientProvider } from "bknd/client"; + +import appCss from "../styles.css?url"; + +export const Route = createRootRoute({ + head: () => ({ + meta: [ + { + charSet: "utf-8", + }, + { + name: "viewport", + content: "width=device-width, initial-scale=1", + }, + { + title: "TanStack 🤝 Bknd.io", + }, + ], + links: [ + { + rel: "stylesheet", + href: appCss, + }, + ], + }), + + shellComponent: RootDocument, +}); + +function RootDocument({ children }: { children: React.ReactNode }) { + return ( + + + + + + + {children} + + , + }, + ]} + /> + + + + ); +} diff --git a/examples/tanstack-start/src/routes/admin.$.tsx b/examples/tanstack-start/src/routes/admin.$.tsx new file mode 100644 index 0000000..facf57b --- /dev/null +++ b/examples/tanstack-start/src/routes/admin.$.tsx @@ -0,0 +1,23 @@ +import { createFileRoute } from "@tanstack/react-router"; +import { useAuth } from "bknd/client"; +import "bknd/dist/styles.css"; +import { Admin } from "bknd/ui"; + +export const Route = createFileRoute("/admin/$")({ + ssr: false, + component: RouteComponent, +}); + +function RouteComponent() { + const { user } = useAuth(); + return ( + + ); +} diff --git a/examples/tanstack-start/src/routes/api.$.ts b/examples/tanstack-start/src/routes/api.$.ts new file mode 100644 index 0000000..f65526c --- /dev/null +++ b/examples/tanstack-start/src/routes/api.$.ts @@ -0,0 +1,13 @@ +import { createFileRoute } from "@tanstack/react-router"; +import config from "../../bknd.config"; +import { serve } from "bknd/adapter/tanstack-start"; + +const handler = serve(config); + +export const Route = createFileRoute("/api/$")({ + server: { + handlers: { + ANY: async ({ request }) => await handler(request), + }, + }, +}); diff --git a/examples/tanstack-start/src/routes/index.tsx b/examples/tanstack-start/src/routes/index.tsx new file mode 100644 index 0000000..9e37928 --- /dev/null +++ b/examples/tanstack-start/src/routes/index.tsx @@ -0,0 +1,171 @@ +import { + createFileRoute, + useRouter, +} from "@tanstack/react-router"; +import { getApi } from "@/bknd"; +import { createServerFn, useServerFn } from "@tanstack/react-start"; +import { Footer } from "@/components/Footer"; +import { List } from "@/components/List"; + +export const completeTodo = createServerFn({ method: "POST" }) + .inputValidator( + (data) => data as { done: boolean; id: number; title: string }, + ) + .handler(async ({ data: todo }) => { + try { + const api = await getApi({}); + await api.data.updateOne("todos", todo.id, { + done: !todo.done, + }); + console.log("state updated in db"); + } catch (error) { + console.log(error); + } + }); + +export const deleteTodo = createServerFn({ method: "POST" }) + .inputValidator((data) => data as { id: number }) + .handler(async ({ data }) => { + try { + const api = await getApi({}); + await api.data.deleteOne("todos", data.id); + console.log("todo deleted from db"); + } catch (error) { + console.log(error); + } + }); + +export const createTodo = createServerFn({ method: "POST" }) + .inputValidator((data) => data as { title: string }) + .handler(async ({ data }) => { + try { + const api = await getApi({}); + await api.data.createOne("todos", { title: data.title }); + console.log("todo created in db"); + } catch (error) { + console.log(error); + } + }); + +export const getTodo = createServerFn({ method: "POST" }).handler(async () => { + const api = await getApi({}); + const limit = 5; + const todos = await api.data.readMany("todos", { limit, sort: "-id" }); + const total = todos.body.meta.total as number; + return { total, todos, limit }; +}); + +export const Route = createFileRoute("/")({ + ssr:false, + component: App, + loader: async () => { + return await getTodo(); + }, +}); + +function App() { + const { todos, total, limit } = Route.useLoaderData(); + const router = useRouter(); + + const updateTodo = useServerFn(completeTodo); + const removeTodo = useServerFn(deleteTodo); + const addTodo = useServerFn(createTodo); + + return ( +
+
+
+ Next.js logo +
&
+ bknd logo +
+ +
+

+ What's next? +

+
+ {total > limit && ( +
+ {total - limit} more todo(s) hidden +
+ )} +
+ {todos.reverse().map((todo) => ( +
+
+ { + await updateTodo({ data: todo }); + router.invalidate(); + }} + /> +
+ {todo.title} +
+
+ +
+ ))} +
+
t.id).join()} + onSubmit={async (e) => { + e.preventDefault(); + const formData = new FormData(e.currentTarget); + const title = formData.get("title") as string; + await addTodo({ data: { title } }); + router.invalidate(); + e.currentTarget.reset(); + }} + > + + +
+
+
+
+
+
+ ); +} + +const Description = () => ( + +); + diff --git a/examples/tanstack-start/src/routes/ssr.tsx b/examples/tanstack-start/src/routes/ssr.tsx new file mode 100644 index 0000000..eb14b1b --- /dev/null +++ b/examples/tanstack-start/src/routes/ssr.tsx @@ -0,0 +1,124 @@ +import { getApi } from "@/bknd"; +import { createServerFn } from "@tanstack/react-start"; +import { Link } from "@tanstack/react-router"; +import { createFileRoute } from "@tanstack/react-router"; +import { getRequest } from "@tanstack/react-start/server"; +import { Footer } from "@/components/Footer"; +import { List } from "@/components/List"; + +export const getTodo = createServerFn({ method: "POST" }).handler(async () => { + const api = await getApi({}); + const limit = 5; + const todos = await api.data.readMany("todos"); + const total = todos.body.meta.total as number; + return { total, todos, limit }; +}); + +export const getUser = createServerFn({ method: "POST" }).handler(async () => { + const request = getRequest(); + const api = await getApi({ verify: true, headers: request.headers }); + const user = api.getUser(); + return { user }; +}); + +export const Route = createFileRoute("/ssr")({ + component: RouteComponent, + loader: async () => { + return { ...(await getTodo()), ...(await getUser()) }; + }, +}); + +function RouteComponent() { + const { todos, user } = Route.useLoaderData(); + + return ( +
+
+
+ Next.js logo +
&
+ bknd logo +
+ todo.title)} /> + + +
+ {user ? ( + <> + Logged in as {user.email}.{" "} + + Logout + + + ) : ( +
+

+ Not logged in.{" "} + + Login + +

+

+ Sign in with:{" "} + + test@bknd.io + {" "} + /{" "} + + 12345678 + +

+
+ )} +
+
+
+
+ ); +} + +function Buttons() { + return ( + + ); +} diff --git a/examples/tanstack-start/src/styles.css b/examples/tanstack-start/src/styles.css new file mode 100644 index 0000000..bafe49a --- /dev/null +++ b/examples/tanstack-start/src/styles.css @@ -0,0 +1,25 @@ +@import "tailwindcss"; + +:root { + --background: #ffffff; + --foreground: #171717; +} + +@media (prefers-color-scheme: dark) { + :root { + --background: #0a0a0a; + --foreground: #ededed; + } +} + +@theme { + --font-sans: var(--font-geist-sans); + --font-mono: var(--font-geist-mono); + --color-background: var(--background); + --color-foreground: var(--foreground); +} + +body { + @apply bg-background text-foreground; + font-family: Arial, Helvetica, sans-serif; +} diff --git a/examples/tanstack-start/tsconfig.json b/examples/tanstack-start/tsconfig.json new file mode 100644 index 0000000..477479f --- /dev/null +++ b/examples/tanstack-start/tsconfig.json @@ -0,0 +1,28 @@ +{ + "include": ["**/*.ts", "**/*.tsx"], + "compilerOptions": { + "target": "ES2022", + "jsx": "react-jsx", + "module": "ESNext", + "lib": ["ES2022", "DOM", "DOM.Iterable"], + "types": ["vite/client"], + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": false, + "noEmit": true, + + /* Linting */ + "skipLibCheck": true, + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedSideEffectImports": true, + "baseUrl": ".", + "paths": { + "@/*": ["./src/*"] + } + } +} diff --git a/examples/tanstack-start/vite.config.ts b/examples/tanstack-start/vite.config.ts new file mode 100644 index 0000000..a0b568e --- /dev/null +++ b/examples/tanstack-start/vite.config.ts @@ -0,0 +1,34 @@ +import { defineConfig } from "vite"; +import { devtools } from "@tanstack/devtools-vite"; +import { tanstackStart } from "@tanstack/react-start/plugin/vite"; +import viteReact from "@vitejs/plugin-react"; +import viteTsConfigPaths from "vite-tsconfig-paths"; +import { fileURLToPath, URL } from "url"; +import tailwindcss from "@tailwindcss/vite"; +import { nitro } from "nitro/vite"; + +const config = defineConfig({ + resolve: { + alias: { + "@": fileURLToPath(new URL("./src", import.meta.url)), + }, + }, + plugins: [ + nitro({ preset: "node-server" }), + tailwindcss(), + devtools(), + // this is the plugin that enables path aliases + viteTsConfigPaths({ + projects: ["./tsconfig.json"], + }), + + tanstackStart(), + viteReact({ + babel: { + plugins: ["babel-plugin-react-compiler"], + }, + }), + ], +}); + +export default config;