diff --git a/README.md b/README.md index 34a4e3f..f67b015 100644 --- a/README.md +++ b/README.md @@ -100,10 +100,101 @@ The `RemixDevTools` component accepts the following props: - `defaultOpen`: Whether to open the Remix Development Tools by default. Defaults to `false`. - `position`: The position of the Remix Development Tools trigger. Defaults to `bottom-right`. - `requireUrlFlag`: Requires rdt=true to be present in the URL search to open the Remix Development Tools. Defaults to `false`. -- `showRouteBoundaries`: Allows you to see each Outlet and route boundaries by coloring the background. Defaults to `false`. +- [**DEPRECATED**] `showRouteBoundaries`:This flag has been deprecated in favor of adding route boundaries. Please see the section below for more information. - `hideUntilHover`: Allows you to hide the trigger until you hover over it. Defaults to `false`. - `additionalTabs`: Allows you to provide additional tabs to the Remix Development Tools. Defaults to `[]`. +## Adding route boundaries + +The `showErrorBoundaries` flag has been deprecated in favor of this method. Please use it instead. + +In order to add Route boundaries to your project you need to do the following two things: + +1. Modify your `entry.server.ts` to add the following code: + +```diff +function handleBrowserRequest( + request: Request, + responseStatusCode: number, + responseHeaders: Headers, + remixContext: EntryContext +) { +- return new Promise((resolve, reject) => { ++ return new Promise(async (resolve, reject) => { + let shellRendered = false; ++ const context = process.env.NODE_ENV === "development" ? await import("remix-development-tools").then(({ initRouteBoundariesServer }) => initRouteBoundariesServer(remixContext)) : remixContext; + const { pipe, abort } = renderToPipeableStream( + , + { + onShellReady() { + shellRendered = true; + const body = new PassThrough(); + + responseHeaders.set("Content-Type", "text/html"); + + resolve( + new Response(body, { + headers: responseHeaders, + status: responseStatusCode, + }) + ); + + pipe(body); + }, + onShellError(error: unknown) { + reject(error); + }, + onError(error: unknown) { + responseStatusCode = 500; + // Log streaming rendering errors from inside the shell. Don't log + // errors encountered during initial shell rendering since they'll + // reject and get logged in handleDocumentRequest. + if (shellRendered) { + console.error(error); + } + }, + } + ); + + setTimeout(abort, ABORT_DELAY); + }); +} +``` + +2. Modify your `entry.client.tsx` to add the following code: + +```diff ++ if(process.env.NODE_ENV === "development") { ++ import("remix-development-tools").then(({ initRouteBoundariesClient }) => { ++ initRouteBoundariesClient(); ++ startTransition(() => { ++ hydrateRoot( ++ document, ++ ++ ++ ++ ); ++ }); ++ ++ }); ++ } else { + startTransition(() => { + hydrateRoot( + document, + + + + ); + }); ++ } +``` +3. You are good to go. Now you can see the route boundaries in your project when you hover each route. + ## Plugins Writing plugins for Remix Development Tools is easy. You can write a plugin that adds a new tab to the Remix Development Tools in the following way: diff --git a/package-lock.json b/package-lock.json index 0abff9c..e6292ad 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "remix-development-tools", - "version": "1.2.2", + "version": "1.4.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "remix-development-tools", - "version": "1.2.2", + "version": "1.4.0", "license": "MIT", "workspaces": [ ".", diff --git a/package.json b/package.json index 39f9596..485babb 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "remix-development-tools", "description": "Remix development tools.", "author": "Alem Tuzlak", - "version": "1.3.0", + "version": "1.4.0", "license": "MIT", "keywords": [ "remix", diff --git a/src/RemixDevTools/RemixDevTools.tsx b/src/RemixDevTools/RemixDevTools.tsx index 46ca410..07eb757 100644 --- a/src/RemixDevTools/RemixDevTools.tsx +++ b/src/RemixDevTools/RemixDevTools.tsx @@ -5,7 +5,6 @@ import { useTimelineHandler } from "./hooks/useTimelineHandler"; import { useRDTContext } from "./context/useRDTContext"; import { isDev } from "./utils/isDev"; import { useLocation } from "@remix-run/react"; -import { useOutletAugment } from "./hooks/useOutletAugment"; import { Trigger } from "./components/Trigger"; import { MainPanel } from "./layout/MainPanel"; import { Tabs } from "./layout/Tabs"; @@ -15,7 +14,6 @@ interface Props extends RemixDevToolsProps { defaultOpen: boolean; position: Exclude; hideUntilHover: boolean; - showRouteBoundaries: boolean; } const RemixDevTools = ({ @@ -23,9 +21,7 @@ const RemixDevTools = ({ position, additionalTabs, hideUntilHover, - showRouteBoundaries, }: Props) => { - useOutletAugment(showRouteBoundaries); useTimelineHandler(); const { persistOpen } = useRDTContext(); const [isOpen, setIsOpen] = useState(defaultOpen || persistOpen); @@ -49,7 +45,6 @@ const RemixDevTools = ({ ); }; - let hydrating = true; function useHydrated() { @@ -78,8 +73,6 @@ export interface RemixDevToolsProps { | "top-left" | "middle-right" | "middle-left"; - // Show route boundaries when you hover over a route in active page tab - showRouteBoundaries?: boolean; // Additional tabs to add to the dev tools additionalTabs?: Tab[]; // Whether the dev tools trigger should hide until hovered @@ -90,7 +83,6 @@ const RDTWithContext = ({ port = 3003, defaultOpen = false, requireUrlFlag, - showRouteBoundaries = false, position = "bottom-right", hideUntilHover = false, additionalTabs, @@ -101,14 +93,14 @@ const RDTWithContext = ({ if (!hydrated || !isDevelopment) return null; if (requireUrlFlag && !url.includes("rdt=true")) return null; + return ( - + ); diff --git a/src/RemixDevTools/context/RDTContext.tsx b/src/RemixDevTools/context/RDTContext.tsx index 94d7f9f..f7e5ac6 100644 --- a/src/RemixDevTools/context/RDTContext.tsx +++ b/src/RemixDevTools/context/RDTContext.tsx @@ -22,18 +22,13 @@ interface ContextProps { export const REMIX_DEV_TOOLS = "remixDevTools"; -export const RDTContextProvider = ({ - children, - port, - showRouteBoundaries, -}: ContextProps) => { +export const RDTContextProvider = ({ children, port }: ContextProps) => { const existingState = sessionStorage.getItem(REMIX_DEV_TOOLS); const settings = localStorage.getItem(REMIX_DEV_TOOLS); const [state, dispatch] = useReducer(rdtReducer, { ...initialState, ...(existingState ? JSON.parse(existingState) : {}), - showRouteBoundaries, settings: settings ? { ...initialState.settings, ...JSON.parse(settings), port } : { ...initialState.settings, port }, diff --git a/src/RemixDevTools/context/rdtReducer.ts b/src/RemixDevTools/context/rdtReducer.ts index 2d145fe..e6258e1 100644 --- a/src/RemixDevTools/context/rdtReducer.ts +++ b/src/RemixDevTools/context/rdtReducer.ts @@ -6,7 +6,6 @@ export type RouteWildcards = Record | undefined>; export type RemixDevToolsState = { timeline: TimelineEvent[]; - showRouteBoundaries?: boolean; terminals: Terminal[]; settings: { routeWildcards: RouteWildcards; @@ -20,7 +19,6 @@ export type RemixDevToolsState = { export const initialState: RemixDevToolsState = { timeline: [], - showRouteBoundaries: false, terminals: [{ id: 0, locked: false, output: [], history: [] }], settings: { routeWildcards: {}, diff --git a/src/RemixDevTools/context/useRDTContext.ts b/src/RemixDevTools/context/useRDTContext.ts index c7255aa..0fa63aa 100644 --- a/src/RemixDevTools/context/useRDTContext.ts +++ b/src/RemixDevTools/context/useRDTContext.ts @@ -11,8 +11,7 @@ const useRDTContext = () => { throw new Error("useRDTContext must be used within a RDTContextProvider"); } const { state, dispatch } = context; - const { timeline, settings, showRouteBoundaries, terminals, persistOpen } = - state; + const { timeline, settings, terminals, persistOpen } = state; const { activeTab, shouldConnectWithForge, routeWildcards, port, height } = settings; @@ -140,7 +139,6 @@ const useRDTContext = () => { routeWildcards, port, height, - showRouteBoundaries, setHeight, setRouteWildcards, terminals, diff --git a/src/RemixDevTools/hooks/useOutletAugment.tsx b/src/RemixDevTools/hooks/useOutletAugment.tsx deleted file mode 100644 index b5daaff..0000000 --- a/src/RemixDevTools/hooks/useOutletAugment.tsx +++ /dev/null @@ -1,53 +0,0 @@ -import { useSubmit, useLocation } from "@remix-run/react"; -import clsx from "clsx"; -import { useEffect, useMemo } from "react"; - -const isHooked = Symbol("isHooked"); -export function useOutletAugment(shouldAugment: boolean) { - const submit = useSubmit(); - const location = useLocation(); - const searchParams = useMemo( - () => new URLSearchParams(location.search), - [location] - ); - useEffect(() => { - if (!shouldAugment) return; - if (window.__remixRouteModules[isHooked as any]) return; - - window.__remixRouteModules = new Proxy(window.__remixRouteModules, { - get: function (target, property) { - if (property === isHooked) return target[property as any]; - if (property === "root") return target[property]; - const value = target[property as any]; - - if (value?.default && value.default.name !== "hooked") { - return { - ...value, - default: function hooked() { - return ( - <> -
- - - ); - }, - }; - } - - return value; - }, - }); - - submit(searchParams, { - method: "get", - action: location.pathname, - }); - // We only want to run this once regardless of the dependencies and we want it to run after the first render - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); -} diff --git a/src/RemixDevTools/index.ts b/src/RemixDevTools/index.ts index 2b8f456..7733d13 100644 --- a/src/RemixDevTools/index.ts +++ b/src/RemixDevTools/index.ts @@ -1,2 +1,6 @@ export { RemixDevTools } from "./RemixDevTools"; export { useRemixForgeSocketExternal as useRemixForgeSocket } from "./hooks/useRemixForgeSocket"; +export { + initRouteBoundariesClient, + initRouteBoundariesServer, +} from "./methods/boundaries"; diff --git a/src/RemixDevTools/methods/boundaries.tsx b/src/RemixDevTools/methods/boundaries.tsx new file mode 100644 index 0000000..620871b --- /dev/null +++ b/src/RemixDevTools/methods/boundaries.tsx @@ -0,0 +1,64 @@ +import { EntryContext } from "@remix-run/server-runtime"; +import clsx from "clsx"; + +export const initRouteBoundariesServer = (context: EntryContext) => { + return { + ...context, + routeModules: Object.entries(context.routeModules).reduce( + (acc, [key, value]) => { + if (key === "root") { + return { ...acc, [key]: value }; + } + return { + ...acc, + [key]: { + ...value, + default: () => { + return ( + <> +
+ + + ); + }, + }, + }; + }, + {} + ), + }; +}; + +export const initRouteBoundariesClient = () => { + window.__remixRouteModules = new Proxy(window.__remixRouteModules, { + get: function (target, property) { + if (property === "root") return target[property]; + const value = target[property as any]; + if (value?.default && value.default.name !== "hooked") { + return { + ...value, + default: function hooked() { + return ( + <> +
+ + + ); + }, + }; + } + + return value; + }, + }); +}; diff --git a/src/RemixDevTools/tabs/PageTab.tsx b/src/RemixDevTools/tabs/PageTab.tsx index c81214b..cd77644 100644 --- a/src/RemixDevTools/tabs/PageTab.tsx +++ b/src/RemixDevTools/tabs/PageTab.tsx @@ -7,7 +7,6 @@ import { Tag } from "../components/Tag"; import { VsCodeButton } from "../components/VScodeButton"; import { useMemo } from "react"; import { isLayoutRoute } from "../utils/routing"; -import { useRDTContext } from "../context/useRDTContext"; export const ROUTE_COLORS: Record = { ROUTE: "rdt-bg-green-500 rdt-text-white", @@ -46,9 +45,7 @@ const PageTab = () => { const reversed = useMemo(() => routes.reverse(), [routes]); const { revalidate, state } = useRevalidator(); const { isConnected, sendJsonMessage } = useRemixForgeSocket(); - const { showRouteBoundaries } = useRDTContext(); const onHover = (path: string, type: "enter" | "leave") => { - if (!showRouteBoundaries) return; const classes = "rdt-bg-green-100 rdt-transition-all rdt-rounded rdt-apply-tw rdt-bg-gradient-to-r rdt-from-cyan-500/50 rdt-to-blue-500/50"; const isRoot = path === "root"; diff --git a/src/index.ts b/src/index.ts index a05a38f..c067354 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,5 +1,15 @@ import "./input.css"; -import { RemixDevTools, useRemixForgeSocket } from "./RemixDevTools"; -export { RemixDevTools, useRemixForgeSocket }; +import { + RemixDevTools, + useRemixForgeSocket, + initRouteBoundariesClient, + initRouteBoundariesServer, +} from "./RemixDevTools"; +export { + RemixDevTools, + useRemixForgeSocket, + initRouteBoundariesClient, + initRouteBoundariesServer, +}; //export * from "./monitor"; export default RemixDevTools; diff --git a/src/remix-app-for-testing/app/entry.client.tsx b/src/remix-app-for-testing/app/entry.client.tsx index 94d5dc0..3e6c738 100644 --- a/src/remix-app-for-testing/app/entry.client.tsx +++ b/src/remix-app-for-testing/app/entry.client.tsx @@ -4,15 +4,31 @@ * For more information, see https://remix.run/file-conventions/entry.client */ -import { RemixBrowser } from "@remix-run/react"; +import { RemixBrowser } from "@remix-run/react"; import { startTransition, StrictMode } from "react"; -import { hydrateRoot } from "react-dom/client"; +import { hydrateRoot } from "react-dom/client"; -startTransition(() => { - hydrateRoot( - document, - - - - ); -}); +if(process.env.NODE_ENV === "development") { + import("remix-development-tools").then(({ initRouteBoundariesClient }) => { + initRouteBoundariesClient(); + startTransition(() => { + hydrateRoot( + document, + + + + ); + }); + + }); +} else { + startTransition(() => { + hydrateRoot( + document, + + + + ); + }); + +} \ No newline at end of file diff --git a/src/remix-app-for-testing/app/entry.server.tsx b/src/remix-app-for-testing/app/entry.server.tsx index b4363f1..6be8f9a 100644 --- a/src/remix-app-for-testing/app/entry.server.tsx +++ b/src/remix-app-for-testing/app/entry.server.tsx @@ -83,19 +83,21 @@ function handleBotRequest( setTimeout(abort, ABORT_DELAY); }); -} - +} + function handleBrowserRequest( request: Request, responseStatusCode: number, responseHeaders: Headers, remixContext: EntryContext ) { - return new Promise((resolve, reject) => { + return new Promise(async (resolve, reject) => { let shellRendered = false; + const devTools = await import("remix-development-tools"); + const context = process.env.NODE_ENV === "development" ? devTools.initRouteBoundariesServer(remixContext) : remixContext; const { pipe, abort } = renderToPipeableStream( , diff --git a/src/remix-app-for-testing/app/root.tsx b/src/remix-app-for-testing/app/root.tsx index ac124e6..35417d1 100644 --- a/src/remix-app-for-testing/app/root.tsx +++ b/src/remix-app-for-testing/app/root.tsx @@ -86,7 +86,6 @@ export default function App() { )}