Git Product home page Git Product logo

remix-dev-tools's Introduction

Remix Development Tools

Remix Development Tools

GitHub Repo stars npm GitHub npm npm GitHub top language

Remix Development Tools is an open-source package designed to enhance your development workflow when working with Remix, a full-stack JavaScript framework for building web applications. This package provides a user-friendly interface consisting of three tabs, Active Page, Terminal, Settings, Errors and Routes, along with a side tab called Timeline. With Remix Development Tools, you can efficiently monitor and manage various aspects of your Remix projects, including page information, URL parameters, server responses, loader data, routes, and more. You can also track down hydration issues with the Errors tab and view your routes in a tree/list view with the Routes tab.

Documentation

Detailed documentation can be found here:

https://remix-development-tools.fly.dev/

Getting Started

  1. Install the package via npm:
npm install remix-development-tools -D
import { remixDevTools } from "remix-development-tools";

// Add it to your plugins array in vite.config.js
export default defineConfig({
  plugins: [remixDevTools(), remix(), tsconfigPaths()], 
});

That's it, you're done!

Support

If you like Remix Development Tools consider starring the repository. If you have any questions, comments, or suggestions, please feel free to reach out!

License

Remix Development Tools is open-source software released under the MIT License.

Acknowledgments

Remix Development Tools was inspired by the Remix framework and aims to enhance the development experience for Remix users.

Feel free to explore Remix Development Tools, and we hope it significantly improves your Remix development process. If you encounter any issues or have suggestions for enhancements, please don't hesitate to open an issue on our GitHub repository. Happy Remixing!

Thanks

Thanks to all the contributors on this project and the support to the community. You guys are awesome!


Devoted to my loving wife.

In loving memory of my late Grandfather, who taught me to always be curious, never stop learning, and to always be kind to others. I miss you, Grandpa.

remix-dev-tools's People

Contributors

ahmednawaf avatar alemtuzlak avatar brookslybrand avatar dong-qian avatar jvdneut avatar kandros avatar lean-dev avatar mitchelvanbever avatar muhammadjalabi avatar obviyus avatar rossipedia avatar rubber-duck-software avatar shafspecs avatar xhomu avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

remix-dev-tools's Issues

Error: Dynamic require of "remix-development-tools" is not supported

npm run dev

> [email protected] dev
> remix dev


 💿  remix dev

 info  building...
 info  built (4.6s)
Error: Dynamic require of "remix-development-tools" is not supported
    at file:///Users/sergiydybskiy/src/sea-people-web/build/index.js?t=1695932213829.837:11:9
    at file:///Users/sergiydybskiy/src/sea-people-web/build/index.js?t=1695932213829.837:1750:26
    at ModuleJob.run (node:internal/modules/esm/module_job:194:25)

root.tsx

let AppExport = App;

if (ENVIRONMENT === 'development') {
  const { withDevTools } = require('remix-development-tools');
  AppExport = withDevTools(AppExport);
}

export default withSentry(AppExport);

Was working locally with const { withDevTools } = await import("remix-development-tools"); but had to update to require for Vercel to build.

"@remix-run/react": "2.0.1",
"remix-development-tools": "^3.0.3",

`npm run dev` produces error: "remix-development-tools" was not found in your node_modules`

This looks great, sorry if I'm missing something obvious.

Issue

I installed the package and added everything to root.tsx detailed here. I'm getting an error in the terminal when I run npm run dev; "remix-development-tools" was not found in your node_modules. Which is odd, cuz it's there.

Versions:

"@remix-run/node": "^1.14.3",
"@remix-run/react": "^1.14.1",
"@remix-run/dev": "^1.12.0",
"remix-development-tools": "^1.0.4",
node: 18.16.1
npm: 9.5.1

What I tried

I tried adding the package to remix.config.js, as suggested here, but getting the same error.

serverDependenciesToBundle: [
    /^remix-development-tools.*/,
],

Assumption

I didn't try upgrading my remix versions, but I'm assuming since you're using 1.18.1 I need to upgrade. If that's the case, I'd be happy to make a PR to add it to the README.

Please advise.
Thank you.

Feature: Undock dev tools

Always dock at the bottom and can lead to bad UX when using the app as it does not reduce the viewport so scrolling is messed up

image

here, the next button is hidden and I can't get to it unless I close the devtools

Form POST via conform for multiselect show only last item

I use react-select with isMulti as multiselect

const schema = z.object({
  tags: z.string().array(),
})
...
export async function loader() {
  return json({ tags: db.tag.findMany({})})
}

export async function action() {
   ... 
  console.log(submission.value.tags)   
}

export default function Form() {
 return (
   ...
   <CreatableSelect name="tags" isMulti options={tags} />
   ...
  )
}

In function action console.log(submission.value.tags) log [ '1', '4', 'aaa' ]
but in RDT in Active page tab on right side show POST only last value for tags: tags: string "aaa"

Hide DevTools?

I want an option where the dev tools icon is hidden unless you mouse over the bottom right.

RDT increases HMR rebuild time

RDT adds a decent overhead to the hmr rebuild atm, adding up to half second to a clean template on an older machine.

This is probably because the lucide dependency, here's a tree-map comparison with/without RDT installed:

image image

We should able to at least treeshake out lucide by using @jacobparis' Sly https://sly-cli.fly.dev/

Identifier 'AppExport' has already been declared

I am using Remix Vite and have followed all the instructions, but I get the following error when running remix vite:dev

[vite] Error when evaluating SSR module /app/root.tsx:
|- SyntaxError: Identifier 'AppExport' has already been declared
    at new AsyncFunction (<anonymous>)
    at instantiateModule (file:///xxx)

10:24:24 AM [vite] Error when evaluating SSR module virtual:remix/server-build: failed to import "/app/root.tsx"
|- SyntaxError: Identifier 'AppExport' has already been declared
    at new AsyncFunction (<anonymous>)
    at instantiateModule (file:///xxx)

10:24:24 AM [vite] Internal server error: Identifier 'AppExport' has already been declared
      at new AsyncFunction (<anonymous>)
      at instantiateModule (file:///xxx)

These are the changes I made in root.tsx

import rdtStylesheet from "remix-development-tools/index.css"; 
export const links: LinksFunction = () => [

  ...(process.env.NODE_ENV === "development" ? [{ rel: "stylesheet", href: rdtStylesheet }] : []),
] 

function App() {
//
}
 
let AppExport = App;

if(process.env.NODE_ENV === 'development') { 
  const { withDevTools } = await import("remix-development-tools"); 
  AppExport = withDevTools(AppExport);
}

export default AppExport;

process undefined in link

CleanShot 2024-01-11 at 16 58 39@2x

Sorry I don't quite understand here. When I put this in the app, it just throw error because process is undefined in client.

`remix-development-tools/server` has no exports

image

We've recently upgraded our app to Remix version 2, and I've just come to update RDT to the latest version. We use a custom server, so have to do the server side set-up as well.

After following the documentation I've found that remix-development-tools/server doesn't export the functions required by the documentation.

This is with the latest version of RDT.

Hydration Error... React 18 issue?

WHAT?!?@?!#?!

I followed the docs to upgrade to remix-development-tools": "2.0.0".
I'm getting errors after upgrading.

Screenshot 2023-08-05 at 2 13 07 PM

Hydration failed because the initial UI does not match what was rendered on the server seems to be the most relevant error...

Versions

"@remix-run/node": "^1.14.3",
"@remix-run/react": "^1.14.1",
"@remix-run/dev": "^1.12.0",
"remix-development-tools": "2.0.0",
node: 18.16.1
npm: 9.5.1

Action Items

  • The consumer uses remix-island; see remix-hydration-fix; Maybe, I haven't looked into it...
  • Add docs about using react v17 or remix-island with Remix-Dev-Tools? Likely...
  • I'm wrong and there's something obvious I need to do? Also likely...
  • Fix on your end? Unlikely, but you know better then me.

[3.1.0] Error importing date-fns

I am installing the latest dev tools (3.1.0) on a remix v2 app, and I got this error:

Error: Directory import '/Users/+++++/src/exps/+++++/node_modules/.pnpm/[email protected]_@[email protected]_@[email protected]_@types+react@18._k62rikz2xgll26ruaktbuytbpi/node_modules/date-fns/add' is not supported resolving ES modules imported from /Users/+++++/src/exps/+++++/node_modules/.pnpm/[email protected]_@[email protected]_@[email protected]_@types+react@18._k62rikz2xgll26ruaktbuytbpi/node_modules/remix-development-tools/dist/index.js
Did you mean to import [email protected]/node_modules/date-fns/add/index.js?
    at new NodeError (node:internal/errors:406:5)
    at finalizeResolution (node:internal/modules/esm/resolve:227:11)
    at moduleResolve (node:internal/modules/esm/resolve:845:10)
    at defaultResolve (node:internal/modules/esm/resolve:1043:11)
    at ModuleLoader.defaultResolve (node:internal/modules/esm/loader:383:12)
    at ModuleLoader.resolve (node:internal/modules/esm/loader:352:25)
    at ModuleLoader.getModuleJob (node:internal/modules/esm/loader:228:38)
    at ModuleWrap.<anonymous> (node:internal/modules/esm/module_job:85:39)
    at link (node:internal/modules/esm/module_job:84:36)

Downgrading to 3.0.0 resolved the issue. Can I provide any more information to help?

Uncaught ReferenceError: global is not defined

When trying to run with the latest Cloudflare Pages adapter template the wrangler cli won't start due to the following error:

service core:user:worker: Uncaught ReferenceError: global is not defined
  at oqu69tjsd1.js:57552:18 in singleton
  at oqu69tjsd1.js:57555:5 in node_modules/remix-development-tools/dist/index.cjs
  at oqu69tjsd1.js:82:50
  at oqu69tjsd1.js:62684:26
X [ERROR] MiniflareCoreError [ERR_RUNTIME_FAILURE]: The Workers runtime failed to start. There is likely additional logging output above.

Steps to reproduce

  1. Start a new remix project using Cloudflare Pages adapter npx create-remix --template remix-run/remix/templates/cloudflare-pages
  2. Install and configure RDT yarn add remix-development-tools -D
  3. Start the project with yarn dev

Environment information

- node: 20.8.0
- yarn: 1.22.19
- remix: 2.1.0
- remix-development-tools: 3.2.2
- wrangler: 3.1.1
- windows: 11

Possible Causes

Since wrangler run in another JavaScript runtime that is not node and since the singleton function uses the global object that is not available in the Cloudflare runtime so its throws an error.

https://github.com/Code-Forge-Net/Remix-Dev-Tools/blob/50d55572cde95f6514d0f1cccb184cc01575632c/src/dev-server/singleton.ts#L6

Feature: Show errors

Right now, uncaught errors are shown in the terminal. It would be nice to see them somehow within the dev tools

Show even if NODE_ENV is not "development"

The readme already shows how to conditionally show based on NODE_ENV. However, I really do want to show the development tools even on a production build. (In my case I am using a special cookie to decide to show/hide dev tools).

Since the readme already shows how to use NODE_ENV to hide, this package should not make the decision to hide internally.

Relaying only on the naming of route.id breaks layout route detection

Simple breaking demo:

Add three route files:
other.tsx: A layout route file
other._index.tsx: Index route
other.page.tsx: Normal page route

In the routes tab all three routes are shown, the other path twice.

With custom routing or remix-flat-routes there are more breaks to expect.

Issues with showRouteBoundaries

There are issues with the current way showRouteBoundaries work. we need to fix this by implementing a more bulletproof way of handling this that covers both the server and client

Display number of elements in array

Hey, great work with this project!

Similar to Chrome console, where the number of elements in an array are displayed like this (10)
Bildschirmfoto 2023-07-18 um 11 29 32

it would be nice to see something similar for route loader data somewhere in this UI
Bildschirmfoto 2023-07-18 um 11 28 55

show/load conditionally

rather than always showing the remix devtools, maybe it can be configured such that it only loads/shows up when there is a specific search param (?remix-dev=true) in the url? This makes it easier to toggle on and off.

websocket port

I'm using RDT version 3 with remix 2.3
dev: "PORT=4000 dotenv -e .env.local remix dev -c \"rdt-serve ./build/index.js\" --manual"
I'm actually using remix dev for the server without any custom server and port 80 for third api with remix and i can't replaced with any other port. but websocket of RDT is taking 8080
const port = (config == null ? void 0 : config.wsPort) || 8080;
how i can change that port without creating a custom server ? it's possible to use flag (--port 8181) ?

Trying to use remix developement tools in blue stack

`import { cssBundleHref } from "@remix-run/css-bundle";
import type { LinksFunction, LoaderFunctionArgs } from "@remix-run/node";
import { json } from "@remix-run/node";
import {
Links,
LiveReload,
Meta,
Outlet,
Scripts,
ScrollRestoration,
} from "@remix-run/react";
import rdtStylesheet from "remix-development-tools/index.css";

import { getUser } from "/session.server";
import stylesheet from "
/tailwind.css";

export const links: LinksFunction = () => [
{ rel: "stylesheet", href: stylesheet },
...(cssBundleHref ? [{ rel: "stylesheet", href: cssBundleHref }] : []),
...(process.env.NODE_ENV === "development"
? [{ rel: "stylesheet", href: rdtStylesheet }]
: []),
];

export const loader = async ({ request }: LoaderFunctionArgs) => {
return json({ user: await getUser(request) });
};

function App() {
return (














);
}

let AppExport = App;
if (process.env.NODE_ENV === "development") {
const {
withDevTools,
} = async () => {
await import("remix-development-tools");
};
AppExport = withDevTools(AppExport);
}

export default AppExport;
`

getting the following error

/Users/stelang/projects/remix/remix-app/app/root.tsx:55
AppExport = withDevTools(AppExport);
^
TypeError: withDevTools is not a function
at Object. (/Users/stelang/projects/remix/remix-app/app/root.tsx:55:15)
at Module._compile (node:internal/modules/cjs/loader:1256:14)
at Object.Module._extensions..js (node:internal/modules/cjs/loader:1310:10)
at Module.load (node:internal/modules/cjs/loader:1119:32)
at Function.Module._load (node:internal/modules/cjs/loader:960:12)
at ModuleWrap. (node:internal/modules/esm/translators:169:29)
at ModuleJob.run (node:internal/modules/esm/module_job:194:25)

TypeError: linksExport is not a function or its return value is not iterable

Problem

Getting title error on a fresh project.

image

Reproduction

System info

  System:
    OS: macOS 14.2.1
    CPU: (8) arm64 Apple M1
    Memory: 73.50 MB / 16.00 GB
    Shell: 5.9 - /bin/zsh
  Binaries:
    Node: 20.10.0 - ~/Library/pnpm/node
    Yarn: 1.22.21 - /opt/homebrew/bin/yarn
    npm: 10.2.3 - ~/Library/pnpm/npm
    pnpm: 8.15.1 - ~/Library/pnpm/pnpm
    bun: 1.0.26 - ~/.bun/bin/bun
    Watchman: 2023.12.04.00 - /opt/homebrew/bin/watchman
  Browsers:
    Brave Browser: 108.1.46.140
    Safari: 17.2.1
  npmPackages:
    @remix-run/dev: ^2.6.0 => 2.6.0
    @remix-run/node: ^2.6.0 => 2.6.0
    @remix-run/react: ^2.6.0 => 2.6.0
    @remix-run/serve: ^2.6.0 => 2.6.0
    remix-development-tools: 3.7.4 => 3.7.4
    vite: ^5.0.0 => 5.0.12

Unable to see the dev tools in the DOM

Hi, I followed the pattern in the example to setup devtools but I do not see the devtools in the DOM. The style sheet gets added though.

This is my root.tsx


import { cssBundleHref } from "@remix-run/css-bundle";
import React, { useContext, useState } from "react";
import rdtStylesheet from "remix-development-tools/stylesheet.css";
import ReactDOM from "react-dom";
import type { LinksFunction, V2_MetaFunction } from "@remix-run/node";
import {
  Links,
  LiveReload,
  Meta,
  Outlet,
  Scripts,
  ScrollRestoration,
  useCatch,
  useLoaderData,
} from "@remix-run/react";
import { withEmotionCache } from "@emotion/react";
import { unstable_useEnhancedEffect as useEnhancedEffect } from "@mui/material";
import {
  Hydrate,
  QueryClient,
  QueryClientProvider,
} from "@tanstack/react-query";

import { useDehydratedState } from "use-dehydrated-state";
import ClientStyleContext from "~/src/ClientStyleContext";
import Layout from "~/src/Layout";
import theme from "~/src/theme";
import { LicenseManager } from "ag-grid-enterprise";
import "ag-grid-community/styles/ag-grid.min.css";
import "ag-grid-community/styles/ag-theme-alpine.min.css";
import "react-toastify/dist/ReactToastify.min.css";
import styles from "./styles/style.css";
import { json } from "@remix-run/node";
import type { Environment } from "~/environment.server";
import { getPublicKeys } from "~/environment.server";
import { PublicEnv } from "~/ui/public-env";
import { useIsBot } from "~/isBotProvider";
import { ToastContainer } from "react-toastify";
import * as process from "process";
import { compact } from "lodash";

const RemixDevTools =
  process.env.NODE_ENV === "development"
    ? React.lazy(() => import("remix-development-tools"))
    : undefined;

if (process.env.NODE_ENV !== "production" && typeof window !== "undefined") {
  const axe = require("@axe-core/react");
  axe(React, ReactDOM, 1000);
}

interface DocumentProps {
  children: React.ReactNode;
  title?: string;
  env?: Pick<Environment, "AG_GRID_LICENSE_KEY">;
}

export const meta: V2_MetaFunction = () => {
  return [{ name: "description", content: "Master Data Management!" }];
};

export const links: LinksFunction = () => [
  ...(cssBundleHref
    ? compact([
        { rel: "stylesheet", href: cssBundleHref },
        {
          rel: "stylesheet",
          href: styles,
        },
        {
          rel: "stylesheet",
          href: "https://fonts.googleapis.com/css2?family=Roboto+Mono&display=swap",
        },
        {
          rel: "stylesheet",
          href: "https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap",
        },
        rdtStylesheet && process.env.NODE_ENV === "development"
          ? { rel: "stylesheet", href: rdtStylesheet }
          : null,
      ])
    : []),
];

const Document = withEmotionCache(
  ({ children, title, env }: DocumentProps, emotionCache) => {
    let isBot = useIsBot();

    const clientStyleData = useContext(ClientStyleContext);

    LicenseManager.setLicenseKey(env?.AG_GRID_LICENSE_KEY as string);

    // Only executed on client
    useEnhancedEffect(() => {
      // re-link sheet container
      emotionCache.sheet.container = document.head;
      // re-inject tags
      const tags = emotionCache.sheet.tags;
      emotionCache.sheet.flush();
      tags.forEach((tag) => {
        // eslint-disable-next-line no-underscore-dangle
        (emotionCache.sheet as any)._insertTag(tag);
      });
      // reset cache to reapply global styles
      clientStyleData.reset();
      // eslint-disable-next-line react-hooks/exhaustive-deps
    }, []);

    return (
      <html lang="en">
        <head>
          <meta charSet="utf-8" />
          <meta name="viewport" content="width=device-width,initial-scale=1" />
          <meta name="theme-color" content={theme.palette.primary.main} />
          <link
            rel="icon"
            href="https://trinitylifesciences.com/wp-content/uploads/2021/04/cropped-favicon-32x32-1-32x32.png"
            sizes="32x32"
          />
          <link
            rel="icon"
            href="https://trinitylifesciences.com/wp-content/uploads/2021/04/cropped-favicon-32x32-1-192x192.png"
            sizes="192x192"
          />
          <link
            rel="apple-touch-icon"
            href="https://trinitylifesciences.com/wp-content/uploads/2021/04/cropped-favicon-32x32-1-180x180.png"
          />
          {title ? <title>{title}</title> : null}
          <Meta />
          <Links />
          <link rel="preconnect" href="https://fonts.googleapis.com" />
          <link
            rel="preconnect"
            href="https://fonts.gstatic.com"
            crossOrigin=""
          />
          <meta
            name="emotion-insertion-point"
            content="emotion-insertion-point"
          />
        </head>
        <body>
          <PublicEnv {...env} />
          {children}
          <ToastContainer
            position="top-center"
            autoClose={5000}
            hideProgressBar={false}
            newestOnTop={false}
            closeOnClick
            rtl={false}
            pauseOnFocusLoss
            draggable
            theme="colored"
          />
          <ScrollRestoration />
          {isBot ? null : <Scripts />}
          <LiveReload />
          {RemixDevTools && <RemixDevTools position="top-right" defaultOpen />}
        </body>
      </html>
    );
  },
);

export function loader() {
  return json(getPublicKeys());
}

export default function App() {
  const [queryClient] = useState(() => new QueryClient());

  const dehydratedState = useDehydratedState();

  const { publicKeys } = useLoaderData<typeof loader>();

  return (
    <QueryClientProvider client={queryClient}>
      <Hydrate state={dehydratedState}>
        <Document title="Master Data Management" env={publicKeys}>
          <Layout>
            <Outlet />
          </Layout>
        </Document>
      </Hydrate>
    </QueryClientProvider>
  );
}

// https://remix.run/docs/en/v1/api/conventions#errorboundary
export function ErrorBoundary({ error }: { error: Error }) {
  console.error(error);

  return (
    <Document title="Error!">
      <Layout>
        <div>
          <h1>There was an error</h1>
          <p>{error.message}</p>
          <hr />
          <p>
            Hey, developer, you should replace this with what you want your
            users to see.
          </p>
        </div>
      </Layout>
    </Document>
  );
}

// https://remix.run/docs/en/v1/api/conventions#catchboundary
export function CatchBoundary() {
  const caught = useCatch();

  let message;
  switch (caught.status) {
    case 401:
      message = (
        <p>
          Oops! Looks like you tried to visit a page that you do not have access
          to.
        </p>
      );
      break;
    case 404:
      message = (
        <p>Oops! Looks like you tried to visit a page that does not exist.</p>
      );
      break;

    default:
      throw new Error(caught.data || caught.statusText);
  }

  return (
    <Document title={`${caught.status} ${caught.statusText}`}>
      <Layout>
        <h1>
          {caught.status}: {caught.statusText}
        </h1>
        {message}
      </Layout>
    </Document>
  );
}

Make header same heihgt

Small UI tweak
image

Make sure the height of each card header is the same so that they appear cohesive

Feature: Copy data sent to clipboard as markdown

So that one can quickly paste this in github issues

So instead of

{
formData:"{"values":{"2860":[],"2872":[{"text":"2023-08-24T00:00:00.000Z","answerId":2840,"questionId":2872}],"2873":[{"answerId":2843,"questionId":2873}],"2874":[{"answerId":2821,"questionId":2874,"text":"NN"}],"2879":[],"6054":[{"answerId":3391,"questionId":6054}],"6055":[{"answerId":3175,"questionId":6055}],"6056":[{"answerId":2822,"questionId":6056}]},"additionalInfo":{}}"
}

have

{
  "formData": {
    "values": {
      "2860": [],
      "2872": [{ "text": "2023-08-24T00:00:00.000Z", "answerId": 2840, "questionId": 2872 }],
      "2873": [{ "answerId": 2843, "questionId": 2873 }],
      "2874": [{ "answerId": 2821, "questionId": 2874, "text": "NN" }],
      "2879": [],
      "6054": [{ "answerId": 3391, "questionId": 6054 }],
      "6055": [{ "answerId": 3175, "questionId": 6055 }],
      "6056": [{ "answerId": 2822, "questionId": 6056 }]
    },
    "additionalInfo": {}
  }
}

also the format the json instead of having one string. That would be nicer to read?

TS import error on VSCode

While the extension is working properly, VSCode is complaining about missing support for dynamic import.

Dynamic imports are only supported when the '--module' flag is set to 'es2020', 'es2022', 'esnext', 'commonjs', 'amd', 'system', 'umd', 'node16', or 'nodenext'.ts(1323)

I don't know is this is related to the remix v1/v2 state, but I'm wondering if the README can provide better help on how to properly fix this error.

[3.5.0] [Vite] cannot clone body after it is used

Took 3.5.0 out for a test drive with Vite and it crashed when I submitted a form.

Stacktrace:

Error: cannot clone body after it is used
    at clone (C:\Users\user\dev\project\node_modules\@remix-run\web-fetch\dist\lib.node.cjs:588:9)
    at new Request (C:\Users\user\dev\project\node_modules\@remix-run\web-fetch\dist\lib.node.cjs:1456:6)
    at Request.clone (C:\Users\user\dev\project\node_modules\@remix-run\web-fetch\dist\lib.node.cjs:1604:10)
    at extractDataFromResponseOrRequest (file:///C:/Users/user/dev/project/node_modules/remix-development-tools/dist/server.js:702:32)
    at storeAndEmitActionOrLoaderInfo (file:///C:/Users/user/dev/project/node_modules/remix-development-tools/dist/server.js:732:26)
    at file:///C:/Users/user/dev/project/node_modules/remix-development-tools/dist/server.js:774:7
    at unAwaited (file:///C:/Users/user/dev/project/node_modules/remix-development-tools/dist/server.js:667:3)
    at file:///C:/Users/user/dev/project/node_modules/remix-development-tools/dist/server.js:772:5

Killing server does not kill all node processes

Every time I kill the server with ctrl + c on mac my list of node processes grows by 1. I have confirmed this behavior stops when I remove the dev tools from my startup script. This happens regardless of the terminal i use (vscode integrated, warp, etc).

Version: v3.4.0
Startup Script: "remix dev -c \"rdt-cjs-serve ./build/index.js\" --manual"

Screenshot 2023-12-06 at 11 48 08 AM

No server logs displayed in terminal

Hi,

I made a custom remix stack based on Remix Blues Stack. I recently upgraded to Remix v2.
Im trying to add Remix Dev Tools. I followed the guide but the part about custom server config is a bit confusing.

As a result, i managed to make the dev tool ui working but server logs are not logged.

Here is the relevant code:

// server.ts

import fs from 'node:fs'
import path from 'node:path'
import url from 'node:url'

import prom from '@isaacs/express-prometheus-middleware'
import { createRequestHandler } from '@remix-run/express'
import type { ServerBuild } from '@remix-run/node'
import { broadcastDevReady, installGlobals } from '@remix-run/node'
import compression from 'compression'
import type { RequestHandler } from 'express'
import express from 'express'
import morgan from 'morgan'
import {
	defineServerConfig,
	withServerDevTools,
} from 'remix-development-tools/server'
import sourceMapSupport from 'source-map-support'

const rdtConfig = {
	logs: {
		cookies: true,
		defer: true,
		actions: true,
		loaders: true,
		cache: true,
		siteClear: true,
	},
}

sourceMapSupport.install()
installGlobals()
run()

async function run() {
	const BUILD_PATH = path.resolve('build/index.js')
	const VERSION_PATH = path.resolve('build/version.txt')
	const initialBuild = await reimportServer()

	const app = express()
	const metricsApp = express()
	app.use(
		prom({
			metricsPath: '/metrics',
			collectDefaultMetrics: true,
			metricsApp,
		}),
	)

	app.use((req, res, next) => {
		// helpful headers:
		res.set('Strict-Transport-Security', `max-age=${60 * 60 * 24 * 365 * 100}`)

		// /clean-urls/ -> /clean-urls
		if (req.path.endsWith('/') && req.path.length > 1) {
			const query = req.url.slice(req.path.length)
			const safepath = req.path.slice(0, -1).replace(/\/+/g, '/')
			res.redirect(301, safepath + query)
			return
		}
		next()
	})

	app.use(compression())

	// http://expressjs.com/en/advanced/best-practice-security.html#at-a-minimum-disable-x-powered-by-header
	app.disable('x-powered-by')

	// Remix fingerprints its assets so we can cache forever.
	app.use(
		'/build',
		express.static('public/build', { immutable: true, maxAge: '1y' }),
	)

	// Everything else (like favicon.ico) is cached for an hour. You may want to be
	// more aggressive with this caching.
	app.use(express.static('public', { maxAge: '1h' }))

	app.use(morgan('tiny'))

	app.all('*', async (...args) => {
		const handler =
			process.env.NODE_ENV === 'development'
				? await createDevRequestHandler(initialBuild)
				: createRequestHandler({
						build: initialBuild,
						mode: initialBuild.mode,
				  })

		return handler(...args)
	})

	const port = process.env.PORT || 3000
	app.listen(port, () => {
		console.log(`✅ app ready: http://localhost:${port}`)

		if (process.env.NODE_ENV === 'development') {
			broadcastDevReady(initialBuild)
		}
	})

	const metricsPort = process.env.METRICS_PORT || 3010

	metricsApp.listen(metricsPort, () => {
		console.log(`✅ metrics ready: http://localhost:${metricsPort}/metrics`)
	})

	async function reimportServer(): Promise<ServerBuild> {
		// cjs: manually remove the server build from the require cache
		Object.keys(require.cache).forEach(key => {
			if (key.startsWith(BUILD_PATH)) {
				delete require.cache[key]
			}
		})

		const stat = fs.statSync(BUILD_PATH)

		// convert build path to URL for Windows compatibility with dynamic `import`
		const BUILD_URL = url.pathToFileURL(BUILD_PATH).href

		// use a timestamp query parameter to bust the import cache
		return import(BUILD_URL + '?t=' + stat.mtimeMs)
	}

	async function createDevRequestHandler(
		initialBuild: ServerBuild,
	): Promise<RequestHandler> {
		let build = initialBuild
		async function handleServerUpdate() {
			// 1. re-import the server build
			build = await reimportServer()
			const devBuild = withServerDevTools(build, defineServerConfig(rdtConfig))
			// 2. tell Remix that this app server is now up-to-date and ready
			broadcastDevReady(devBuild)
		}

		const chokidar = await import('chokidar')
		chokidar
			.watch(VERSION_PATH, { ignoreInitial: true })
			.on('add', handleServerUpdate)
			.on('change', handleServerUpdate)

		// wrap request handler to make sure its recreated with the latest build for every request
		return async (req, res, next) => {
			try {
				return createRequestHandler({
					build,
					mode: 'development',
				})(req, res, next)
			} catch (error) {
				next(error)
			}
		}
	}
}
// root.tsx

import { cssBundleHref } from '@remix-run/css-bundle'
import type { LinksFunction, LoaderFunctionArgs } from '@remix-run/node'
import { json } from '@remix-run/node'
import {
	Links,
	LiveReload,
	Meta,
	Outlet,
	Scripts,
	ScrollRestoration,
} from '@remix-run/react'

import rdtStylesheet from 'remix-development-tools/index.css'
import { getUser } from '~/utils/auth.server'
import appStylesHref from './styles/app.css?inline'
import tailwindStylesHref from './styles/tailwind.css?inline'

export const links: LinksFunction = () => [
	{ rel: 'stylesheet', href: tailwindStylesHref },
	{ rel: 'stylesheet', href: appStylesHref },
	...(cssBundleHref ? [{ rel: 'stylesheet', href: cssBundleHref }] : []),
	...(process.env.NODE_ENV === 'development'
		? [{ rel: 'stylesheet', href: rdtStylesheet }]
		: []),
]

export const loader = async ({ request }: LoaderFunctionArgs) => {
	return json({ user: await getUser(request) } as const)
}

function App() {
	return (
		<html lang="en" className="h-full">
			<head>
				<meta charSet="utf-8" />
				<meta name="viewport" content="width=device-width,initial-scale=1" />
				<Meta />
				<Links />
			</head>
			<body className="h-full">
				<Outlet />
				<ScrollRestoration />
				<Scripts />
				<LiveReload />
			</body>
		</html>
	)
}

let AppExport = App

if (process.env.NODE_ENV === 'development') {
	const { withDevTools } = require('remix-development-tools')
	AppExport = withDevTools(AppExport)
}

export default AppExport

Build scripts

...
"build": "run-s build:*",
"build:remix": "remix build",
"build:server": "esbuild --platform=node --format=cjs ./server.ts --outdir=build --bundle --external:fsevents",
"dev": "run-p dev:*",
"dev:server": "cross-env NODE_ENV=development npm run build:server -- --watch",
"dev:remix": "remix dev --manual -c \"node --require ./mocks ./build/server.js\"",
...

Terminal

 💿  remix dev

 info  building...
 info  built (2.9s)
🔶 Mock server running
✅ app ready: http://localhost:3000
✅ metrics ready: http://localhost:3010/metrics

GET /login?index 200 - - 48.300 ms
GET /login?index 200 - - 7.975 ms
GET /login 200 - - 10.662 ms
GET / 200 - - 7.115 ms

Thanks!

a11y issue - remix-development-tools should not capture focus (at least: not when closed)

I'm developing an accessibility focused Remix application and then I found my navigation with keyboard stopped working for "a while" (some TAB clicks).

Finally I found that development tools are capturing keyboard navigation.

This maybe can be a good think (if you plan to have this extension accessible), but:

  • focus should be visible
  • I expect that when the widget is closed, just one focus change is captured by the plugin

or, alternatively: disable the keyboard navigation entirely when Remix-Dev-Tools is closed.

Resource routes should open in a new window

On the routes tab, if you have a resource route and click the button to open the route, it will open in the same window, but data from the resource route is not shown as it requires a full browser reload to work.

P.S. Thanks for this, it's pretty cool :)

HMR Breaks with DevTools installed

When I make a change in the Epic Stack and I get an HMR reload, I get the following error in the console:

The error
error-boundary.tsx:38 Error: A component suspended while responding to synchronous input. This will cause the UI to be replaced with a loading indicator. To fix, updates that suspend should be wrapped with startTransition.
    at throwException (react-dom.development.js:19055:35)
    at handleError (react-dom.development.js:26311:7)
    at renderRootSync (react-dom.development.js:26437:7)
    at performSyncWorkOnRoot (react-dom.development.js:26085:20)
    at flushSyncCallbacks (react-dom.development.js:12042:22)
    at flushSync (react-dom.development.js:26201:7)
    at scheduleRefresh (react-dom.development.js:27795:5)
    at renderer.scheduleRefresh (renderer.js:584:1)
    at react-refresh-runtime.development.js:265:17
    at Set.forEach (<anonymous>) '\n    at GeneralErrorBoundary (http://localhost:3000/build/_shared/chunk-GHJAN7NM.js:42:3)\n    at body\n    at html\n    at Document (http://localhost:3000/build/root-XJYL7V5A.js?t=1690338998889:4736:3)\n    at ErrorBoundary (http://localhost:3000/build/root-XJYL7V5A.js?t=1690338998889:4928:17)\n    at RemixRouteError (http://localhost:3000/build/_shared/chunk-DJH6CC34.js:8710:3)\n    at RenderErrorBoundary (http://localhost:3000/build/_shared/chunk-DJH6CC34.js:3640:9)\n    at DataRoutes (http://localhost:3000/build/_shared/chunk-DJH6CC34.js:3354:5)\n    at Router (http://localhost:3000/build/_shared/chunk-DJH6CC34.js:3433:15)\n    at RouterProvider (http://localhost:3000/build/_shared/chunk-DJH6CC34.js:3303:5)\n    at RemixErrorBoundary (http://localhost:3000/build/_shared/chunk-DJH6CC34.js:8278:5)\n    at RemixBrowser (http://localhost:3000/build/_shared/chunk-DJH6CC34.js:10443:40)'
overrideMethod @ console.js:213
GeneralErrorBoundary @ error-boundary.tsx:38
renderWithHooks @ react-dom.development.js:16305
mountIndeterminateComponent @ react-dom.development.js:20074
beginWork @ react-dom.development.js:21587
beginWork$1 @ react-dom.development.js:27426
performUnitOfWork @ react-dom.development.js:26557
workLoopSync @ react-dom.development.js:26466
renderRootSync @ react-dom.development.js:26434
performSyncWorkOnRoot @ react-dom.development.js:26085
flushSyncCallbacks @ react-dom.development.js:12042
flushSync @ react-dom.development.js:26201
scheduleRefresh @ react-dom.development.js:27795
renderer.scheduleRefresh @ renderer.js:584
(anonymous) @ react-refresh-runtime.development.js:265
performReactRefresh @ react-refresh-runtime.development.js:254
(anonymous) @ browser.js:72
setTimeout (async)
(anonymous) @ browser.js:70
(anonymous) @ router.ts:977
updateState @ router.ts:977
completeNavigation @ router.ts:1058
startNavigation @ router.ts:1356
await in startNavigation (async)
revalidate @ router.ts:1206
(anonymous) @ browser.js:77
await in (anonymous) (async)
emit @ hmr-runtime:remix:hmr:64
ws.onmessage @ verify:73
Show 2 more frames
Show less
error-boundary.tsx:38 Error: A component suspended while responding to synchronous input. This will cause the UI to be replaced with a loading indicator. To fix, updates that suspend should be wrapped with startTransition.
    at throwException (react-dom.development.js:19055:35)
    at handleError (react-dom.development.js:26311:7)
    at renderRootSync (react-dom.development.js:26437:7)
    at recoverFromConcurrentError (react-dom.development.js:25850:20)
    at performSyncWorkOnRoot (react-dom.development.js:26096:20)
    at flushSyncCallbacks (react-dom.development.js:12042:22)
    at flushSync (react-dom.development.js:26201:7)
    at scheduleRefresh (react-dom.development.js:27795:5)
    at renderer.scheduleRefresh (renderer.js:584:1)
    at react-refresh-runtime.development.js:265:17 '\n    at GeneralErrorBoundary (http://localhost:3000/build/_shared/chunk-GHJAN7NM.js:42:3)\n    at body\n    at html\n    at Document (http://localhost:3000/build/root-XJYL7V5A.js?t=1690338998889:4736:3)\n    at ErrorBoundary (http://localhost:3000/build/root-XJYL7V5A.js?t=1690338998889:4928:17)\n    at RemixRouteError (http://localhost:3000/build/_shared/chunk-DJH6CC34.js:8710:3)\n    at RenderErrorBoundary (http://localhost:3000/build/_shared/chunk-DJH6CC34.js:3640:9)\n    at DataRoutes (http://localhost:3000/build/_shared/chunk-DJH6CC34.js:3354:5)\n    at Router (http://localhost:3000/build/_shared/chunk-DJH6CC34.js:3433:15)\n    at RouterProvider (http://localhost:3000/build/_shared/chunk-DJH6CC34.js:3303:5)\n    at RemixErrorBoundary (http://localhost:3000/build/_shared/chunk-DJH6CC34.js:8278:5)\n    at RemixBrowser (http://localhost:3000/build/_shared/chunk-DJH6CC34.js:10443:40)'
overrideMethod @ console.js:213
GeneralErrorBoundary @ error-boundary.tsx:38
renderWithHooks @ react-dom.development.js:16305
mountIndeterminateComponent @ react-dom.development.js:20074
beginWork @ react-dom.development.js:21587
beginWork$1 @ react-dom.development.js:27426
performUnitOfWork @ react-dom.development.js:26557
workLoopSync @ react-dom.development.js:26466
renderRootSync @ react-dom.development.js:26434
recoverFromConcurrentError @ react-dom.development.js:25850
performSyncWorkOnRoot @ react-dom.development.js:26096
flushSyncCallbacks @ react-dom.development.js:12042
flushSync @ react-dom.development.js:26201
scheduleRefresh @ react-dom.development.js:27795
renderer.scheduleRefresh @ renderer.js:584
(anonymous) @ react-refresh-runtime.development.js:265
performReactRefresh @ react-refresh-runtime.development.js:254
(anonymous) @ browser.js:72
setTimeout (async)
(anonymous) @ browser.js:70
(anonymous) @ router.ts:977
updateState @ router.ts:977
completeNavigation @ router.ts:1058
startNavigation @ router.ts:1356
await in startNavigation (async)
revalidate @ router.ts:1206
(anonymous) @ browser.js:77
await in (anonymous) (async)
emit @ hmr-runtime:remix:hmr:64
ws.onmessage @ verify:73
Show 2 more frames
Show less
react-dom.development.js:18687 The above error occurred in one of your React components:

    at Lazy
    at body
    at html
    at Document (http://localhost:3000/build/root-XJYL7V5A.js?t=1690338998889:4736:3)
    at App (http://localhost:3000/build/root-XJYL7V5A.js?t=1690338998889:4773:16)
    at SentryRoot
    at RemixRoute (http://localhost:3000/build/_shared/chunk-DJH6CC34.js:8690:3)
    at RenderedRoute (http://localhost:3000/build/_shared/chunk-DJH6CC34.js:3056:5)
    at RenderErrorBoundary (http://localhost:3000/build/_shared/chunk-DJH6CC34.js:3640:9)
    at DataRoutes (http://localhost:3000/build/_shared/chunk-DJH6CC34.js:3354:5)
    at Router (http://localhost:3000/build/_shared/chunk-DJH6CC34.js:3433:15)
    at RouterProvider (http://localhost:3000/build/_shared/chunk-DJH6CC34.js:3303:5)
    at RemixErrorBoundary (http://localhost:3000/build/_shared/chunk-DJH6CC34.js:8278:5)
    at RemixBrowser (http://localhost:3000/build/_shared/chunk-DJH6CC34.js:10443:40)

React will try to recreate this component tree from scratch using the error boundary you provided, RenderErrorBoundary.
overrideMethod @ console.js:213
logCapturedError @ react-dom.development.js:18687
callback @ react-dom.development.js:18755
callCallback @ react-dom.development.js:13923
commitUpdateQueue @ react-dom.development.js:13944
commitLayoutEffectOnFiber @ react-dom.development.js:23364
commitLayoutMountEffects_complete @ react-dom.development.js:24688
commitLayoutEffects_begin @ react-dom.development.js:24674
commitLayoutEffects @ react-dom.development.js:24612
commitRootImpl @ react-dom.development.js:26823
commitRoot @ react-dom.development.js:26682
performSyncWorkOnRoot @ react-dom.development.js:26117
flushSyncCallbacks @ react-dom.development.js:12042
flushSync @ react-dom.development.js:26201
scheduleRefresh @ react-dom.development.js:27795
renderer.scheduleRefresh @ renderer.js:584
(anonymous) @ react-refresh-runtime.development.js:265
performReactRefresh @ react-refresh-runtime.development.js:254
(anonymous) @ browser.js:72
setTimeout (async)
(anonymous) @ browser.js:70
(anonymous) @ router.ts:977
updateState @ router.ts:977
completeNavigation @ router.ts:1058
startNavigation @ router.ts:1356
await in startNavigation (async)
revalidate @ router.ts:1206
(anonymous) @ browser.js:77
await in (anonymous) (async)
emit @ hmr-runtime:remix:hmr:64
ws.onmessage @ verify:73
Show 2 more frames
Show less
hooks.tsx:608 React Router caught the following error during render Error: A component suspended while responding to synchronous input. This will cause the UI to be replaced with a loading indicator. To fix, updates that suspend should be wrapped with startTransition.
    at throwException (react-dom.development.js:19055:35)
    at handleError (react-dom.development.js:26311:7)
    at renderRootSync (react-dom.development.js:26437:7)
    at recoverFromConcurrentError (react-dom.development.js:25850:20)
    at performSyncWorkOnRoot (react-dom.development.js:26096:20)
    at flushSyncCallbacks (react-dom.development.js:12042:22)
    at flushSync (react-dom.development.js:26201:7)
    at scheduleRefresh (react-dom.development.js:27795:5)
    at renderer.scheduleRefresh (renderer.js:584:1)
    at react-refresh-runtime.development.js:265:17 {componentStack: '\n    at Lazy\n    at body\n    at html\n    at Docume…st:3000/build/_shared/chunk-DJH6CC34.js:10443:40)'} 
    at RenderErrorBoundary (http://localhost:3000/build/_shared/chunk-DJH6CC34.js:3640:9)
    at DataRoutes (http://localhost:3000/build/_shared/chunk-DJH6CC34.js:3354:5)
    at Router (http://localhost:3000/build/_shared/chunk-DJH6CC34.js:3433:15)
    at RouterProvider (http://localhost:3000/build/_shared/chunk-DJH6CC34.js:3303:5)
    at RemixErrorBoundary (http://localhost:3000/build/_shared/chunk-DJH6CC34.js:8278:5)
    at RemixBrowser (http://localhost:3000/build/_shared/chunk-DJH6CC34.js:10443:40)

I haven't spent any time debugging this, but if I remove the dev tools then the issue goes away.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.