From d5684da0a41d545703fa2e287571c0467fb93304 Mon Sep 17 00:00:00 2001 From: Alem Tuzlak Date: Tue, 29 Apr 2025 19:51:49 +0200 Subject: [PATCH 01/15] Make routes tab great again --- package-lock.json | 46 +++++++++++++++--- package.json | 1 + src/client/components/RouteInfo.tsx | 28 +++++++---- src/client/tabs/RoutesTab.tsx | 30 ++++++++++-- src/client/utils/routing.ts | 3 +- src/client/utils/sanitize.ts | 6 ++- src/vite/node-server.ts | 49 ++++++++++++++++++++ src/vite/plugin.tsx | 54 +++++++++++++++++++++- test-apps/react-router-vite/vite.config.ts | 6 +-- 9 files changed, 193 insertions(+), 30 deletions(-) create mode 100644 src/vite/node-server.ts diff --git a/package-lock.json b/package-lock.json index 9663b765..67a3eee4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "react-router-devtools", - "version": "1.1.8", + "version": "1.1.10", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "react-router-devtools", - "version": "1.1.8", + "version": "1.1.10", "license": "MIT", "workspaces": [ ".", @@ -62,6 +62,7 @@ "tsx": "^4.19.2", "typescript": "^5.7.3", "vite": "^6.0.11", + "vite-node": "^3.1.2", "vitest": "^3.0.4" }, "optionalDependencies": { @@ -2354,6 +2355,28 @@ "node": ">=10" } }, + "node_modules/@react-router/dev/node_modules/vite-node": { + "version": "3.0.0-beta.2", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.0.0-beta.2.tgz", + "integrity": "sha512-ofTf6cfRdL30Wbl9n/BX81EyIR5s4PReLmSurrxQ+koLaWUNOEo8E0lCM53OJkb8vpa2URM2nSrxZsIFyvY1rg==", + "license": "MIT", + "dependencies": { + "cac": "^6.7.14", + "debug": "^4.4.0", + "es-module-lexer": "^1.5.4", + "pathe": "^1.1.2", + "vite": "^5.0.0 || ^6.0.0" + }, + "bin": { + "vite-node": "vite-node.mjs" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, "node_modules/@react-router/express": { "version": "7.1.4", "resolved": "https://registry.npmjs.org/@react-router/express/-/express-7.1.4.tgz", @@ -12438,14 +12461,16 @@ } }, "node_modules/vite-node": { - "version": "3.0.0-beta.2", - "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.0.0-beta.2.tgz", - "integrity": "sha512-ofTf6cfRdL30Wbl9n/BX81EyIR5s4PReLmSurrxQ+koLaWUNOEo8E0lCM53OJkb8vpa2URM2nSrxZsIFyvY1rg==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.1.2.tgz", + "integrity": "sha512-/8iMryv46J3aK13iUXsei5G/A3CUlW4665THCPS+K8xAaqrVWiGB4RfXMQXCLjpK9P2eK//BczrVkn5JLAk6DA==", + "dev": true, + "license": "MIT", "dependencies": { "cac": "^6.7.14", "debug": "^4.4.0", - "es-module-lexer": "^1.5.4", - "pathe": "^1.1.2", + "es-module-lexer": "^1.6.0", + "pathe": "^2.0.3", "vite": "^5.0.0 || ^6.0.0" }, "bin": { @@ -12458,6 +12483,13 @@ "url": "https://opencollective.com/vitest" } }, + "node_modules/vite-node/node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, "node_modules/vite-plugin-inspect": { "version": "10.1.0", "resolved": "https://registry.npmjs.org/vite-plugin-inspect/-/vite-plugin-inspect-10.1.0.tgz", diff --git a/package.json b/package.json index 9a9ea8c5..6f0bf3d4 100644 --- a/package.json +++ b/package.json @@ -139,6 +139,7 @@ "tsx": "^4.19.2", "typescript": "^5.7.3", "vite": "^6.0.11", + "vite-node": "^3.1.2", "vitest": "^3.0.4" }, "dependencies": { diff --git a/src/client/components/RouteInfo.tsx b/src/client/components/RouteInfo.tsx index cb42eb6d..26006aab 100644 --- a/src/client/components/RouteInfo.tsx +++ b/src/client/components/RouteInfo.tsx @@ -3,6 +3,7 @@ import type { MouseEvent } from "react" import { Link } from "react-router" import { useSettingsContext } from "../context/useRDTContext.js" import { type ExtendedRoute, constructRoutePath } from "../utils/routing.js" +import { findParentErrorBoundary } from "../utils/sanitize.js" import { Input } from "./Input.js" import { Tag } from "./Tag.js" import { Icon } from "./icon/Icon.js" @@ -14,20 +15,22 @@ interface RouteInfoProps { onClose?: () => void } -export const RouteInfo = ({ route, className, openNewRoute, onClose }: RouteInfoProps) => { +export const RouteInfo = ({ route: routeToUse, className, openNewRoute, onClose }: RouteInfoProps) => { + const route = window.__reactRouterManifest?.routes[routeToUse.id] || routeToUse const { settings, setSettings } = useSettingsContext() const { routeWildcards, routeViewMode } = settings - const { hasWildcard, path, pathToOpen } = constructRoutePath(route, routeWildcards) + const { hasWildcard, path, pathToOpen } = constructRoutePath(routeToUse, routeWildcards) const isTreeView = routeViewMode === "tree" - const hasParentErrorBoundary = route.errorBoundary.errorBoundaryId && route.errorBoundary.errorBoundaryId !== route.id - const hasErrorBoundary = route.errorBoundary.hasErrorBoundary + const { hasErrorBoundary, errorBoundaryId } = findParentErrorBoundary(route) + const hasParentErrorBoundary = errorBoundaryId && errorBoundaryId !== route.id + return (
{isTreeView && ( <> -

{route.url}

+

{routeToUse.url}


Path: {path} @@ -41,12 +44,19 @@ export const RouteInfo = ({ route, className, openNewRoute, onClose }: RouteInfo Route file: {route.id}

+
Components contained in the route: -
+
Loader + + Client Loader + + + Client Action + Action @@ -60,9 +70,7 @@ export const RouteInfo = ({ route, className, openNewRoute, onClose }: RouteInfo
{hasErrorBoundary ? (
- {hasParentErrorBoundary - ? `Covered by parent ErrorBoundary located in: ${route.errorBoundary.errorBoundaryId}` - : ""} + {hasParentErrorBoundary ? `Covered by parent ErrorBoundary located in: ${errorBoundaryId}` : ""}
) : null}
@@ -70,7 +78,7 @@ export const RouteInfo = ({ route, className, openNewRoute, onClose }: RouteInfo <>

Wildcard parameters:

- {route.url + {routeToUse.url .split("/") .filter((p) => p.startsWith(":")) .map((param) => ( diff --git a/src/client/tabs/RoutesTab.tsx b/src/client/tabs/RoutesTab.tsx index 5b96daaf..b5831495 100644 --- a/src/client/tabs/RoutesTab.tsx +++ b/src/client/tabs/RoutesTab.tsx @@ -1,4 +1,4 @@ -import { type MouseEvent, useState } from "react" +import { type MouseEvent, useEffect, useState } from "react" import { useMatches, useNavigate } from "react-router" import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from "../components/Accordion.js" import { NewRouteForm } from "../components/NewRouteForm.js" @@ -22,7 +22,7 @@ const RoutesTab = () => { const { detachedWindow } = useDetachedWindowControls() const [activeRoute, setActiveRoute] = useState(null) const [routes] = useState(createExtendedRoutes() as ExtendedRoute[]) - const [treeRoutes] = useState(createRouteTree(window.__reactRouterManifest?.routes)) + const [treeRoutes, setTreeRoutes] = useState(createRouteTree(window.__reactRouterManifest?.routes)) const isTreeView = routeViewMode === "tree" const openNewRoute = (path: string) => (e?: MouseEvent) => { e?.preventDefault() @@ -32,6 +32,26 @@ const RoutesTab = () => { } } + useEffect(function fetchAllRoutesOnMount() { + import.meta.hot?.send("routes-info") + const cb = (event: any) => { + const parsed = JSON.parse(event) + const data = parsed.data as Record[] + + const routeObject: Record = {} + for (const route of data) { + routeObject[route.id] = route + } + + routeObject.root = window.__reactRouterManifest?.routes?.root + + setTreeRoutes(createRouteTree(routeObject)) + } + import.meta.hot?.on("routes-info", cb) + return () => { + import.meta.hot?.off("routes-info", cb) + } + }, []) return (
@@ -40,7 +60,11 @@ const RoutesTab = () => { - activeRoutes.includes((link.target.data.attributes as any).id) ? "stroke-yellow-500" : "stroke-gray-400" + activeRoutes.includes((link.target.data.attributes as any).id) + ? "stroke-yellow-500" + : window.__reactRouterManifest?.routes?.[link.target.data.attributes.id] + ? "stroke-gray-400" + : "stroke-gray-400/20" } renderCustomNodeElement={(props) => RouteNode({ diff --git a/src/client/utils/routing.ts b/src/client/utils/routing.ts index 573d149d..4b3af203 100644 --- a/src/client/utils/routing.ts +++ b/src/client/utils/routing.ts @@ -89,8 +89,7 @@ export const createExtendedRoutes = () => { ...route, // biome-ignore lint/style/noNonNullAssertion: url: convertReactRouterPathToUrl(window.__reactRouterManifest!.routes, route), - // biome-ignore lint/style/noNonNullAssertion: - errorBoundary: findParentErrorBoundary(window.__reactRouterManifest!.routes, route), + errorBoundary: findParentErrorBoundary(route), } }) .filter((route) => isLeafRoute(route as any)) diff --git a/src/client/utils/sanitize.ts b/src/client/utils/sanitize.ts index 6a9309b1..c185c20f 100644 --- a/src/client/utils/sanitize.ts +++ b/src/client/utils/sanitize.ts @@ -21,7 +21,9 @@ export const convertReactRouterPathToUrl = (routes: any, route: Route) => { return output === "" ? "/" : output } -export const findParentErrorBoundary = (routes: RouteManifest, route: Route) => { +export const findParentErrorBoundary = (route: Route) => { + // biome-ignore lint/style/noNonNullAssertion: + const routes = window.__reactRouterManifest?.routes! let currentRoute: Route | null = route while (currentRoute) { @@ -64,7 +66,7 @@ const constructTree = (routes: any, parentId?: string): RawNodeDatum[] => { ...route, url, }, - errorBoundary: findParentErrorBoundary(routes, route), + errorBoundary: findParentErrorBoundary(route), children: constructTree(routes, route.id), } nodes.push(node) diff --git a/src/vite/node-server.ts b/src/vite/node-server.ts new file mode 100644 index 00000000..a1fd3ad6 --- /dev/null +++ b/src/vite/node-server.ts @@ -0,0 +1,49 @@ +import { createServer, version as viteVersion } from "vite" +import { ViteNodeRunner } from "vite-node/client" +import { ViteNodeServer } from "vite-node/server" +import { installSourcemapsSupport } from "vite-node/source-map" + +// create vite server +const server = await createServer({ + server: { + preTransformRequests: false, + hmr: false, + watch: null, + }, + + optimizeDeps: { + noDiscovery: true, + }, + configFile: false, + envFile: false, + plugins: [], +}) +// For old Vite, this is need to initialize the plugins. +if (Number(viteVersion.split(".")[0]) < 6) { + await server.pluginContainer.buildStart({}) +} + +// create vite-node server +const node = new ViteNodeServer(server) + +// fixes stacktraces in Errors +installSourcemapsSupport({ + getSourceMap: (source) => node.getSourceMap(source), +}) + +// create vite-node runner +const runner = new ViteNodeRunner({ + root: server.config.root, + base: server.config.base, + // when having the server and runner in a different context, + // you will need to handle the communication between them + // and pass to this function + fetchModule(id) { + return node.fetchModule(id) + }, + resolveId(id, importer) { + return node.resolveId(id, importer) + }, +}) + +export { runner } diff --git a/src/vite/plugin.tsx b/src/vite/plugin.tsx index 666e0e67..cddc2bba 100644 --- a/src/vite/plugin.tsx +++ b/src/vite/plugin.tsx @@ -1,3 +1,4 @@ +import path from "node:path" import chalk from "chalk" import { type Plugin, normalizePath } from "vite" import type { RdtClientConfig } from "../client/context/RDTContext.js" @@ -7,6 +8,7 @@ import type { ActionEvent, LoaderEvent } from "../server/event-queue.js" import type { RequestEvent } from "../shared/request-event.js" import { DEFAULT_EDITOR_CONFIG, type EditorConfig, type OpenSourceData, handleOpenSource } from "./editor.js" import { type WriteFileData, handleWriteFile } from "./file.js" +import { runner } from "./node-server.js" import { handleDevToolsViteRequest, processPlugins } from "./utils.js" import { augmentDataFetchingFunctions } from "./utils/data-functions-augment.js" import { injectRdtClient } from "./utils/inject-client.js" @@ -41,6 +43,15 @@ type ReactRouterViteConfig = { editor?: EditorConfig } +type Route = { + id: string + file: string + path?: string + index?: boolean + caseSensitive?: boolean + children?: Route[] +} + export const defineRdtConfig = (config: ReactRouterViteConfig) => config export const reactRouterDevTools: (args?: ReactRouterViteConfig) => Plugin[] = (args) => { @@ -53,6 +64,8 @@ export const reactRouterDevTools: (args?: ReactRouterViteConfig) => Plugin[] = ( const includeServer = args?.includeInProd?.server ?? false const includeDevtools = args?.includeInProd?.devTools ?? false + let routes: Route[] = [] + let flatRoutes: Route[] = [] const appDir = args?.appDir || "./app" const appDirName = appDir.replace("./", "") const shouldInject = (mode: string | undefined, include: boolean) => mode === "development" || include @@ -61,8 +74,12 @@ export const reactRouterDevTools: (args?: ReactRouterViteConfig) => Plugin[] = ( if (!extensions.some((ext) => id.endsWith(ext))) { return } - const isRoute = id.includes(`${appDirName}/root`) || id.includes(`${appDirName}/routes`) - if (id.includes("node_modules") || id.includes("dist") || id.includes("build") || id.includes("?") || !isRoute) { + if (id.includes("node_modules") || id.includes("dist") || id.includes("build") || id.includes("?")) { + return + } + + const isRoute = id.includes(`${appDirName}/root`) || flatRoutes.some((route) => id.endsWith(route.file)) + if (!isRoute) { return } @@ -80,6 +97,30 @@ export const reactRouterDevTools: (args?: ReactRouterViteConfig) => Plugin[] = ( return shouldInject(config.mode, includeClient) }, async configResolved(resolvedViteConfig) { + try { + // Set the route config + const routeConfigExport = (await runner.executeFile(path.join(process.cwd(), "./app/routes.ts"))).default + const routeConfig = await routeConfigExport + routes = routeConfig + const recursiveFlatten = (routeOrRoutes: Route | Route[]): Route[] => { + if (Array.isArray(routeOrRoutes)) { + return routeOrRoutes.flatMap((route) => recursiveFlatten(route)) + } + if (routeOrRoutes.children) { + return [ + routeOrRoutes, + ...recursiveFlatten( + routeOrRoutes.children.map((child) => ({ + ...child, + parentId: routeOrRoutes.id, + })) + ), + ] + } + return [routeOrRoutes] + } + flatRoutes = routes.map((route) => ({ ...route, parentId: "root" })).flatMap(recursiveFlatten) + } catch (e) {} const reactRouterIndex = resolvedViteConfig.plugins.findIndex((p) => p.name === "react-router") const devToolsIndex = resolvedViteConfig.plugins.findIndex((p) => p.name === "react-router-devtools") if (reactRouterIndex >= 0 && devToolsIndex > reactRouterIndex) { @@ -203,6 +244,15 @@ export const reactRouterDevTools: (args?: ReactRouterViteConfig) => Plugin[] = ( }) ) }) + server.hot.on("routes-info", (data, client) => { + client.send( + "routes-info", + JSON.stringify({ + type: "routes-info", + data: flatRoutes, + }) + ) + }) if (!server.config.isProduction) { channel?.on("remove-event", (data, client) => { diff --git a/test-apps/react-router-vite/vite.config.ts b/test-apps/react-router-vite/vite.config.ts index e0905b6c..0507df38 100644 --- a/test-apps/react-router-vite/vite.config.ts +++ b/test-apps/react-router-vite/vite.config.ts @@ -36,11 +36,9 @@ export default defineConfig({ reactRouter(), tsconfigPaths() ], - optimizeDeps: { - exclude: ["react-router-devtools"] - }, + server: { open: true, - port: 3005, + port: 3000, }, }); From 783d160c3f6cb11ffccc51221f53950551f2db7a Mon Sep 17 00:00:00 2001 From: Alem Tuzlak Date: Tue, 29 Apr 2025 19:58:49 +0200 Subject: [PATCH 02/15] small bug fix --- src/vite/plugin.tsx | 2 +- tsup.config.ts | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/vite/plugin.tsx b/src/vite/plugin.tsx index cddc2bba..cd0ee125 100644 --- a/src/vite/plugin.tsx +++ b/src/vite/plugin.tsx @@ -1,4 +1,3 @@ -import path from "node:path" import chalk from "chalk" import { type Plugin, normalizePath } from "vite" import type { RdtClientConfig } from "../client/context/RDTContext.js" @@ -98,6 +97,7 @@ export const reactRouterDevTools: (args?: ReactRouterViteConfig) => Plugin[] = ( }, async configResolved(resolvedViteConfig) { try { + const path = await import("node:path") // Set the route config const routeConfigExport = (await runner.executeFile(path.join(process.cwd(), "./app/routes.ts"))).default const routeConfig = await routeConfigExport diff --git a/tsup.config.ts b/tsup.config.ts index 93f53d0f..87abce49 100644 --- a/tsup.config.ts +++ b/tsup.config.ts @@ -7,4 +7,5 @@ export default defineConfig({ clean: false, dts: true, format: ["esm"], + external: ["vite-node"], }) From ce1c7a089322e05ae13d55b3620a5aa803d93ba8 Mon Sep 17 00:00:00 2001 From: Alem Tuzlak Date: Wed, 30 Apr 2025 12:09:18 +0200 Subject: [PATCH 03/15] fix for custom ids --- package.json | 4 ++-- src/client/components/RouteInfo.tsx | 2 +- src/client/utils/routing.ts | 19 +++++++++++++++---- src/vite/node-server.ts | 2 ++ src/vite/plugin.tsx | 2 +- test-apps/react-router-vite/app/root.tsx | 2 +- test-apps/react-router-vite/app/routes.ts | 7 ++++++- test-apps/react-router-vite/app/subroutes.ts | 7 +++++++ test-apps/react-router-vite/vite.config.ts | 4 +++- 9 files changed, 38 insertions(+), 11 deletions(-) create mode 100644 test-apps/react-router-vite/app/subroutes.ts diff --git a/package.json b/package.json index 6f0bf3d4..602d10ac 100644 --- a/package.json +++ b/package.json @@ -132,7 +132,6 @@ "npm-run-all": "^4.1.5", "postcss": "^8.5.1", "prompt": "^1.3.0", - "tailwind-merge": "^3.0.1", "tailwindcss": "^3.4.0", "tailwindcss-animate": "^1.0.7", "tsup": "^8.3.6", @@ -159,7 +158,8 @@ "react-d3-tree": "^3.6.4", "react-diff-viewer-continued": "^4.0.5", "react-hotkeys-hook": "^4.6.1", - "react-tooltip": "^5.28.0" + "react-tooltip": "^5.28.0", + "tailwind-merge": "^3.0.1" }, "optionalDependencies": { "@biomejs/cli-darwin-arm64": "^1.9.4", diff --git a/src/client/components/RouteInfo.tsx b/src/client/components/RouteInfo.tsx index 26006aab..4d5a2cdd 100644 --- a/src/client/components/RouteInfo.tsx +++ b/src/client/components/RouteInfo.tsx @@ -42,7 +42,7 @@ export const RouteInfo = ({ route: routeToUse, className, openNewRoute, onClose )}
Route file: - {route.id} + {route.module ?? routeToUse.file}
diff --git a/src/client/utils/routing.ts b/src/client/utils/routing.ts index 4b3af203..5792280f 100644 --- a/src/client/utils/routing.ts +++ b/src/client/utils/routing.ts @@ -48,18 +48,29 @@ const ROUTE_FILLS = { PURPLE: "fill-purple-500 text-white", } as const +const UNDISCOVERED_ROUTE_FILLS = { + GREEN: "fill-green-500/20 text-white", + BLUE: "fill-blue-500/20 text-white", + PURPLE: "fill-purple-500/20 text-white", +} + export function getRouteColor(route: Route) { + const isDiscovered = !!window.__reactRouterManifest?.routes[route.id] + const FILL = isDiscovered ? ROUTE_FILLS : UNDISCOVERED_ROUTE_FILLS switch (getRouteType(route)) { case "ROOT": - return ROUTE_FILLS.PURPLE - case "LAYOUT": - return ROUTE_FILLS.BLUE + return FILL.PURPLE + case "ROUTE": - return ROUTE_FILLS.GREEN + return FILL.GREEN + + case "LAYOUT": + return FILL.BLUE } } export type ExtendedRoute = EntryRoute & { url: string + file?: string errorBoundary: { hasErrorBoundary: boolean; errorBoundaryId: string | null } } diff --git a/src/vite/node-server.ts b/src/vite/node-server.ts index a1fd3ad6..7d995574 100644 --- a/src/vite/node-server.ts +++ b/src/vite/node-server.ts @@ -5,6 +5,8 @@ import { installSourcemapsSupport } from "vite-node/source-map" // create vite server const server = await createServer({ + mode: "development", + root: process.cwd(), server: { preTransformRequests: false, hmr: false, diff --git a/src/vite/plugin.tsx b/src/vite/plugin.tsx index cd0ee125..07173ddb 100644 --- a/src/vite/plugin.tsx +++ b/src/vite/plugin.tsx @@ -112,7 +112,7 @@ export const reactRouterDevTools: (args?: ReactRouterViteConfig) => Plugin[] = ( ...recursiveFlatten( routeOrRoutes.children.map((child) => ({ ...child, - parentId: routeOrRoutes.id, + parentId: routeOrRoutes.file.split(".").slice(0, -1).join("."), })) ), ] diff --git a/test-apps/react-router-vite/app/root.tsx b/test-apps/react-router-vite/app/root.tsx index 956163c4..2f650825 100644 --- a/test-apps/react-router-vite/app/root.tsx +++ b/test-apps/react-router-vite/app/root.tsx @@ -28,7 +28,7 @@ export const loader = ({context, devTools }: LoaderFunctionArgs) => { }); const start =devTools?.tracing.start("test")!; devTools?.tracing.end("test", start); - return data({ message: "Hello World", mainPromise, bigInt: BigInt(10) }, { headers: { "Cache-Control": "max-age=3600, private" } }); + return data({ message: "Hello World", mainPromise, bigInt: BigInt(10) }, ); } export const action =async ({devTools}: ActionFunctionArgs) => { diff --git a/test-apps/react-router-vite/app/routes.ts b/test-apps/react-router-vite/app/routes.ts index b8a61f25..7efea74a 100644 --- a/test-apps/react-router-vite/app/routes.ts +++ b/test-apps/react-router-vite/app/routes.ts @@ -1,3 +1,8 @@ import { flatRoutes } from "@react-router/fs-routes"; -export default flatRoutes() \ No newline at end of file +import { type RouteConfig, index, layout, prefix, route } from "@react-router/dev/routes" +import subroutes from "./subroutes.js" + + + +export default [...await flatRoutes(), ...subroutes ] \ No newline at end of file diff --git a/test-apps/react-router-vite/app/subroutes.ts b/test-apps/react-router-vite/app/subroutes.ts new file mode 100644 index 00000000..8f2b3be7 --- /dev/null +++ b/test-apps/react-router-vite/app/subroutes.ts @@ -0,0 +1,7 @@ +import { type RouteConfig, index, layout, prefix, route } from "@react-router/dev/routes" + + +export default [ + route("outside", "./routes/_index.tsx", { id: "something" }), + +] satisfies RouteConfig diff --git a/test-apps/react-router-vite/vite.config.ts b/test-apps/react-router-vite/vite.config.ts index 0507df38..03409038 100644 --- a/test-apps/react-router-vite/vite.config.ts +++ b/test-apps/react-router-vite/vite.config.ts @@ -36,7 +36,9 @@ export default defineConfig({ reactRouter(), tsconfigPaths() ], - + optimizeDeps: { + noDiscovery: true + }, server: { open: true, port: 3000, From 56347b43b87c259ebde3cd53e01faf2276c6ed0e Mon Sep 17 00:00:00 2001 From: Alem Tuzlak Date: Wed, 30 Apr 2025 12:55:19 +0200 Subject: [PATCH 04/15] optimize the recursive routes call --- src/client/utils/sanitize.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/client/utils/sanitize.ts b/src/client/utils/sanitize.ts index c185c20f..0a96ef4e 100644 --- a/src/client/utils/sanitize.ts +++ b/src/client/utils/sanitize.ts @@ -52,13 +52,17 @@ interface RawNodeDatum { children?: RawNodeDatum[] errorBoundary: { hasErrorBoundary: boolean; errorBoundaryId: string | null } } - +const routeMap = new Map() const constructTree = (routes: any, parentId?: string): RawNodeDatum[] => { const nodes: RawNodeDatum[] = [] const routeKeys = Object.keys(routes) for (const key of routeKeys) { const route = routes[key] if (route.parentId === parentId) { + if (routeMap.get(key)) { + nodes.push(routeMap.get(key)) + return nodes + } const url = convertReactRouterPathToUrl(routes, route) const node: RawNodeDatum = { name: url, @@ -69,6 +73,7 @@ const constructTree = (routes: any, parentId?: string): RawNodeDatum[] => { errorBoundary: findParentErrorBoundary(route), children: constructTree(routes, route.id), } + routeMap.set(key, node) nodes.push(node) } } From bc0a9f0a12c308163ecb1a1eece437e3e78e0217 Mon Sep 17 00:00:00 2001 From: Alem Tuzlak Date: Wed, 30 Apr 2025 13:54:16 +0200 Subject: [PATCH 05/15] fix for custom ids --- src/client/components/RouteInfo.tsx | 7 ++++--- src/vite/plugin.tsx | 14 ++++++++++---- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/src/client/components/RouteInfo.tsx b/src/client/components/RouteInfo.tsx index 4d5a2cdd..d5847d93 100644 --- a/src/client/components/RouteInfo.tsx +++ b/src/client/components/RouteInfo.tsx @@ -30,13 +30,14 @@ export const RouteInfo = ({ route: routeToUse, className, openNewRoute, onClose <> -

{routeToUse.url}

+

{routeToUse.url}


- Path: {path} + Path: + {path}

- Url: {pathToOpen} + Url: {pathToOpen}

)} diff --git a/src/vite/plugin.tsx b/src/vite/plugin.tsx index 07173ddb..a7a42259 100644 --- a/src/vite/plugin.tsx +++ b/src/vite/plugin.tsx @@ -110,10 +110,16 @@ export const reactRouterDevTools: (args?: ReactRouterViteConfig) => Plugin[] = ( return [ routeOrRoutes, ...recursiveFlatten( - routeOrRoutes.children.map((child) => ({ - ...child, - parentId: routeOrRoutes.file.split(".").slice(0, -1).join("."), - })) + routeOrRoutes.children.map((child) => { + const withoutExtension = child.file.split(".").slice(0, -1).join(".") + // remove the trailing ./ and ../ + const withoutRelative = withoutExtension.replace(/^\.\//, "").replace(/^\.\.\//, "") + return { + ...child, + id: child.id ?? withoutRelative, + parentId: withoutExtension, + } + }) ), ] } From e55836d671a6a8c2ed322d7b3d4cc8e17626eee7 Mon Sep 17 00:00:00 2001 From: Alem Tuzlak Date: Wed, 30 Apr 2025 14:04:11 +0200 Subject: [PATCH 06/15] fix --- src/vite/plugin.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/vite/plugin.tsx b/src/vite/plugin.tsx index a7a42259..2c1e70c6 100644 --- a/src/vite/plugin.tsx +++ b/src/vite/plugin.tsx @@ -112,12 +112,13 @@ export const reactRouterDevTools: (args?: ReactRouterViteConfig) => Plugin[] = ( ...recursiveFlatten( routeOrRoutes.children.map((child) => { const withoutExtension = child.file.split(".").slice(0, -1).join(".") + const withoutExtensionParent = routeOrRoutes.file.split(".").slice(0, -1).join(".") // remove the trailing ./ and ../ const withoutRelative = withoutExtension.replace(/^\.\//, "").replace(/^\.\.\//, "") return { ...child, id: child.id ?? withoutRelative, - parentId: withoutExtension, + parentId: withoutExtensionParent, } }) ), From d83fb115a7a659fea9ef19e0b791026020c1d748 Mon Sep 17 00:00:00 2001 From: Alem Tuzlak Date: Wed, 30 Apr 2025 14:14:58 +0200 Subject: [PATCH 07/15] fix --- src/vite/plugin.tsx | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/vite/plugin.tsx b/src/vite/plugin.tsx index 2c1e70c6..1a29344a 100644 --- a/src/vite/plugin.tsx +++ b/src/vite/plugin.tsx @@ -126,7 +126,13 @@ export const reactRouterDevTools: (args?: ReactRouterViteConfig) => Plugin[] = ( } return [routeOrRoutes] } - flatRoutes = routes.map((route) => ({ ...route, parentId: "root" })).flatMap(recursiveFlatten) + flatRoutes = routes + .map((route) => { + const withoutExtension = route.file.split(".").slice(0, -1).join(".") + const withoutRelative = withoutExtension.replace(/^\.\//, "").replace(/^\.\.\//, "") + return { ...route, parentId: "root", id: route.id ?? withoutRelative } + }) + .flatMap(recursiveFlatten) } catch (e) {} const reactRouterIndex = resolvedViteConfig.plugins.findIndex((p) => p.name === "react-router") const devToolsIndex = resolvedViteConfig.plugins.findIndex((p) => p.name === "react-router-devtools") From ea83c3da8732a2653daeb804dd07347edcc689f6 Mon Sep 17 00:00:00 2001 From: Alem Tuzlak Date: Wed, 30 Apr 2025 14:29:54 +0200 Subject: [PATCH 08/15] please God --- src/vite/plugin.tsx | 36 ++++++++++++++++++++++++++++-------- 1 file changed, 28 insertions(+), 8 deletions(-) diff --git a/src/vite/plugin.tsx b/src/vite/plugin.tsx index 1a29344a..ce087613 100644 --- a/src/vite/plugin.tsx +++ b/src/vite/plugin.tsx @@ -102,6 +102,7 @@ export const reactRouterDevTools: (args?: ReactRouterViteConfig) => Plugin[] = ( const routeConfigExport = (await runner.executeFile(path.join(process.cwd(), "./app/routes.ts"))).default const routeConfig = await routeConfigExport routes = routeConfig + const recursiveFlatten = (routeOrRoutes: Route | Route[]): Route[] => { if (Array.isArray(routeOrRoutes)) { return routeOrRoutes.flatMap((route) => recursiveFlatten(route)) @@ -111,13 +112,26 @@ export const reactRouterDevTools: (args?: ReactRouterViteConfig) => Plugin[] = ( routeOrRoutes, ...recursiveFlatten( routeOrRoutes.children.map((child) => { - const withoutExtension = child.file.split(".").slice(0, -1).join(".") - const withoutExtensionParent = routeOrRoutes.file.split(".").slice(0, -1).join(".") - // remove the trailing ./ and ../ - const withoutRelative = withoutExtension.replace(/^\.\//, "").replace(/^\.\.\//, "") + // ./path.tsx => path + // ../path.tsx => path + const withoutExtension = child.file + .split(".") + .slice(0, -1) + .join(".") + .replace(/^\.\//, "") + .replace(/^\.\.\//, "") + // ./path.tsx => path + // ../path.tsx => path + const withoutExtensionParent = routeOrRoutes.file + .split(".") + .slice(0, -1) + .join(".") + .replace(/^\.\//, "") + .replace(/^\.\.\//, "") + return { ...child, - id: child.id ?? withoutRelative, + id: child.id ?? withoutExtension, parentId: withoutExtensionParent, } }) @@ -128,9 +142,15 @@ export const reactRouterDevTools: (args?: ReactRouterViteConfig) => Plugin[] = ( } flatRoutes = routes .map((route) => { - const withoutExtension = route.file.split(".").slice(0, -1).join(".") - const withoutRelative = withoutExtension.replace(/^\.\//, "").replace(/^\.\.\//, "") - return { ...route, parentId: "root", id: route.id ?? withoutRelative } + // ./path.tsx => path + // ../path.tsx => path + const withoutExtension = route.file + .split(".") + .slice(0, -1) + .join(".") + .replace(/^\.\//, "") + .replace(/^\.\.\//, "") + return { ...route, parentId: "root", id: route.id ?? withoutExtension } }) .flatMap(recursiveFlatten) } catch (e) {} From 307cf7200f0bfa8861b7f9032c14e218e4c09d41 Mon Sep 17 00:00:00 2001 From: Alem Tuzlak Date: Wed, 30 Apr 2025 14:38:31 +0200 Subject: [PATCH 09/15] fix --- src/client/utils/sanitize.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/client/utils/sanitize.ts b/src/client/utils/sanitize.ts index 0a96ef4e..bb1b1ae3 100644 --- a/src/client/utils/sanitize.ts +++ b/src/client/utils/sanitize.ts @@ -61,7 +61,7 @@ const constructTree = (routes: any, parentId?: string): RawNodeDatum[] => { if (route.parentId === parentId) { if (routeMap.get(key)) { nodes.push(routeMap.get(key)) - return nodes + continue } const url = convertReactRouterPathToUrl(routes, route) const node: RawNodeDatum = { From 0652151349a4a7ba0b02bea01f413409b7dbf4fb Mon Sep 17 00:00:00 2001 From: Alem Tuzlak Date: Wed, 30 Apr 2025 14:41:08 +0200 Subject: [PATCH 10/15] fix --- src/client/utils/sanitize.ts | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/client/utils/sanitize.ts b/src/client/utils/sanitize.ts index bb1b1ae3..646fbb65 100644 --- a/src/client/utils/sanitize.ts +++ b/src/client/utils/sanitize.ts @@ -52,17 +52,13 @@ interface RawNodeDatum { children?: RawNodeDatum[] errorBoundary: { hasErrorBoundary: boolean; errorBoundaryId: string | null } } -const routeMap = new Map() + const constructTree = (routes: any, parentId?: string): RawNodeDatum[] => { const nodes: RawNodeDatum[] = [] const routeKeys = Object.keys(routes) for (const key of routeKeys) { const route = routes[key] if (route.parentId === parentId) { - if (routeMap.get(key)) { - nodes.push(routeMap.get(key)) - continue - } const url = convertReactRouterPathToUrl(routes, route) const node: RawNodeDatum = { name: url, @@ -73,7 +69,7 @@ const constructTree = (routes: any, parentId?: string): RawNodeDatum[] => { errorBoundary: findParentErrorBoundary(route), children: constructTree(routes, route.id), } - routeMap.set(key, node) + nodes.push(node) } } From 1d41d26563becc346e314737eea716ae9d091279 Mon Sep 17 00:00:00 2001 From: Alem Tuzlak Date: Wed, 30 Apr 2025 15:01:54 +0200 Subject: [PATCH 11/15] logs --- src/client/utils/sanitize.ts | 8 ++++++++ src/vite/plugin.tsx | 8 +++++++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/src/client/utils/sanitize.ts b/src/client/utils/sanitize.ts index 646fbb65..e96ed599 100644 --- a/src/client/utils/sanitize.ts +++ b/src/client/utils/sanitize.ts @@ -58,6 +58,12 @@ const constructTree = (routes: any, parentId?: string): RawNodeDatum[] => { const routeKeys = Object.keys(routes) for (const key of routeKeys) { const route = routes[key] + // biome-ignore lint/suspicious/noConsole: + console.log({ + key, + route, + parentId, + }) if (route.parentId === parentId) { const url = convertReactRouterPathToUrl(routes, route) const node: RawNodeDatum = { @@ -78,6 +84,8 @@ const constructTree = (routes: any, parentId?: string): RawNodeDatum[] => { } export const createRouteTree = (routes: RouteManifest | undefined) => { + // biome-ignore lint/suspicious/noConsole: + console.log("createRouteTree", routes) return constructTree(routes) } diff --git a/src/vite/plugin.tsx b/src/vite/plugin.tsx index ce087613..1a24e030 100644 --- a/src/vite/plugin.tsx +++ b/src/vite/plugin.tsx @@ -78,11 +78,17 @@ export const reactRouterDevTools: (args?: ReactRouterViteConfig) => Plugin[] = ( } const isRoute = id.includes(`${appDirName}/root`) || flatRoutes.some((route) => id.endsWith(route.file)) + // biome-ignore lint/suspicious/noConsole: + console.log("isRoute", isRoute, id) if (!isRoute) { return } - const routeId = id.replace(normalizePath(process.cwd()), "").replace(`/${appDirName}/`, "").replace(".tsx", "") + const routeId = id + .replace(normalizePath(process.cwd()), "") + .replace(`/${appDirName}/`, "") + .replace(".tsx", "") + .replace(".ts", "") return routeId } // Set the server config on the process object so that it can be accessed by the plugin From fa8b264ee42c381ae2eea8dad6253873fee5c1a2 Mon Sep 17 00:00:00 2001 From: Alem Tuzlak Date: Wed, 30 Apr 2025 15:13:41 +0200 Subject: [PATCH 12/15] test --- src/client/utils/sanitize.ts | 9 +-------- src/vite/plugin.tsx | 2 +- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/src/client/utils/sanitize.ts b/src/client/utils/sanitize.ts index e96ed599..3b67eab7 100644 --- a/src/client/utils/sanitize.ts +++ b/src/client/utils/sanitize.ts @@ -58,12 +58,7 @@ const constructTree = (routes: any, parentId?: string): RawNodeDatum[] => { const routeKeys = Object.keys(routes) for (const key of routeKeys) { const route = routes[key] - // biome-ignore lint/suspicious/noConsole: - console.log({ - key, - route, - parentId, - }) + if (route.parentId === parentId) { const url = convertReactRouterPathToUrl(routes, route) const node: RawNodeDatum = { @@ -84,8 +79,6 @@ const constructTree = (routes: any, parentId?: string): RawNodeDatum[] => { } export const createRouteTree = (routes: RouteManifest | undefined) => { - // biome-ignore lint/suspicious/noConsole: - console.log("createRouteTree", routes) return constructTree(routes) } diff --git a/src/vite/plugin.tsx b/src/vite/plugin.tsx index 1a24e030..66b933a8 100644 --- a/src/vite/plugin.tsx +++ b/src/vite/plugin.tsx @@ -79,7 +79,7 @@ export const reactRouterDevTools: (args?: ReactRouterViteConfig) => Plugin[] = ( const isRoute = id.includes(`${appDirName}/root`) || flatRoutes.some((route) => id.endsWith(route.file)) // biome-ignore lint/suspicious/noConsole: - console.log("isRoute", isRoute, id) + console.log("isRoute", isRoute, id, flatRoutes.length, flatRoutes[0]?.file) if (!isRoute) { return } From aed7908a6c671d08f161ce6f0464e45ca9123ba1 Mon Sep 17 00:00:00 2001 From: Alem Tuzlak Date: Wed, 30 Apr 2025 15:16:57 +0200 Subject: [PATCH 13/15] test --- src/vite/plugin.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/vite/plugin.tsx b/src/vite/plugin.tsx index 66b933a8..62769b1e 100644 --- a/src/vite/plugin.tsx +++ b/src/vite/plugin.tsx @@ -77,7 +77,9 @@ export const reactRouterDevTools: (args?: ReactRouterViteConfig) => Plugin[] = ( return } - const isRoute = id.includes(`${appDirName}/root`) || flatRoutes.some((route) => id.endsWith(route.file)) + const isRoute = + id.includes(`${appDirName}/root`) || + flatRoutes.some((route) => id.endsWith(route.file.replace(/^\.\//, "").replace(/^\.\.\//, ""))) // biome-ignore lint/suspicious/noConsole: console.log("isRoute", isRoute, id, flatRoutes.length, flatRoutes[0]?.file) if (!isRoute) { From a7d7c53b7d8d0b2bf21a6afaecaf2a2967a78e65 Mon Sep 17 00:00:00 2001 From: Alem Tuzlak Date: Wed, 30 Apr 2025 15:26:42 +0200 Subject: [PATCH 14/15] remove logs --- docs/app/components/ui/Sparkles.tsx | 1 - src/vite/plugin.tsx | 3 +-- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/docs/app/components/ui/Sparkles.tsx b/docs/app/components/ui/Sparkles.tsx index faf6ccc6..73060dac 100644 --- a/docs/app/components/ui/Sparkles.tsx +++ b/docs/app/components/ui/Sparkles.tsx @@ -39,7 +39,6 @@ export const SparklesCore = (props: ParticlesProps) => { const particlesLoaded = async (container?: Container) => { if (container) { - console.log(container) controls.start({ opacity: 1, transition: { diff --git a/src/vite/plugin.tsx b/src/vite/plugin.tsx index 62769b1e..f5509189 100644 --- a/src/vite/plugin.tsx +++ b/src/vite/plugin.tsx @@ -80,8 +80,7 @@ export const reactRouterDevTools: (args?: ReactRouterViteConfig) => Plugin[] = ( const isRoute = id.includes(`${appDirName}/root`) || flatRoutes.some((route) => id.endsWith(route.file.replace(/^\.\//, "").replace(/^\.\.\//, ""))) - // biome-ignore lint/suspicious/noConsole: - console.log("isRoute", isRoute, id, flatRoutes.length, flatRoutes[0]?.file) + if (!isRoute) { return } From 674871e704cf48bf7e3d41e30dc62fce0795f783 Mon Sep 17 00:00:00 2001 From: Alem Tuzlak Date: Wed, 30 Apr 2025 16:46:21 +0200 Subject: [PATCH 15/15] version bump --- package-lock.json | 14 ++++++-------- package.json | 7 +++---- 2 files changed, 9 insertions(+), 12 deletions(-) diff --git a/package-lock.json b/package-lock.json index 67a3eee4..46afb9dd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "react-router-devtools", - "version": "1.1.10", + "version": "5.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "react-router-devtools", - "version": "1.1.10", + "version": "5.0.0", "license": "MIT", "workspaces": [ ".", @@ -29,7 +29,8 @@ "react-d3-tree": "^3.6.4", "react-diff-viewer-continued": "^4.0.5", "react-hotkeys-hook": "^4.6.1", - "react-tooltip": "^5.28.0" + "react-tooltip": "^5.28.0", + "tailwind-merge": "^3.0.1" }, "devDependencies": { "@biomejs/biome": "1.9.4", @@ -41,8 +42,6 @@ "@types/babel__core": "^7.20.5", "@types/beautify": "^0.0.3", "@types/node": "^22.12.0", - "@types/react": "^19.0.8", - "@types/react-dom": "^19.0.3", "@vitest/coverage-v8": "^3.0.4", "@vitest/ui": "^3.0.4", "autoprefixer": "^10.4.20", @@ -55,7 +54,6 @@ "npm-run-all": "^4.1.5", "postcss": "^8.5.1", "prompt": "^1.3.0", - "tailwind-merge": "^3.0.1", "tailwindcss": "^3.4.0", "tailwindcss-animate": "^1.0.7", "tsup": "^8.3.6", @@ -71,6 +69,8 @@ "@rollup/rollup-linux-x64-gnu": "^4.32.1" }, "peerDependencies": { + "@types/react": ">=17", + "@types/react-dom": ">=17", "react": ">=17", "react-dom": ">=17", "react-router": ">=7.0.0", @@ -3158,7 +3158,6 @@ "version": "19.0.3", "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.0.3.tgz", "integrity": "sha512-0Knk+HJiMP/qOZgMyNFamlIjw9OFCsyC2ZbigmEEyXXixgre6IQpm/4V+r3qH4GC1JPvRJKInw+on2rV6YZLeA==", - "devOptional": true, "peerDependencies": { "@types/react": "^19.0.0" } @@ -10989,7 +10988,6 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.0.1.tgz", "integrity": "sha512-AvzE8FmSoXC7nC+oU5GlQJbip2UO7tmOhOfQyOmPhrStOGXHU08j8mZEHZ4BmCqY5dWTCo4ClWkNyRNx1wpT0g==", - "dev": true, "funding": { "type": "github", "url": "https://github.com/sponsors/dcastil" diff --git a/package.json b/package.json index 602d10ac..a7cd46de 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "react-router-devtools", "description": "Devtools for React Router - debug, trace, find hydration errors, catch bugs and inspect server/client data with react-router-devtools", "author": "Alem Tuzlak", - "version": "1.1.10", + "version": "5.0.0", "license": "MIT", "keywords": [ "react-router", @@ -19,7 +19,6 @@ ], "private": false, "type": "module", - "main": "./dist/index.cjs", "module": "./dist/index.js", "types": "./dist/index.d.ts", "exports": { @@ -105,6 +104,8 @@ "peerDependencies": { "react": ">=17", "react-dom": ">=17", + "@types/react": ">=17", + "@types/react-dom": ">=17", "react-router": ">=7.0.0", "vite": ">=5.0.0 || >=6.0.0" }, @@ -118,8 +119,6 @@ "@types/babel__core": "^7.20.5", "@types/beautify": "^0.0.3", "@types/node": "^22.12.0", - "@types/react": "^19.0.8", - "@types/react-dom": "^19.0.3", "@vitest/coverage-v8": "^3.0.4", "@vitest/ui": "^3.0.4", "autoprefixer": "^10.4.20",