react-scan icon indicating copy to clipboard operation
react-scan copied to clipboard

Conditionally enable react-scan

Open jordan-cutler opened this issue 1 year ago • 11 comments

Question

Hi there 👋, the README suggests the following to use react-scan programmatically:

import { scan } from 'react-scan'; // import this BEFORE react
import React from 'react';

if (typeof window !== 'undefined') {
  scan({
    enabled: true,
    log: true, // logs render info to console (default: false)
  });
}

However, if we want to only load react-scan in local builds, and allow for easy enabling and disabling, is that possible? If so, how?

Ideal API

My ideal API would be something like this:

import React from 'react';

function enableScanning() {
  import('react-scan').then(({ scan }) => {
    scan({
      enabled: true,
      log: true,
    });
  });
}

const AdminPanel = () => {
   return (
       ...
      <button onClick={enableScanning}>Enable React Scan</button>
      <button onClick={disableScanning}>Disable React Scan</button>
       ...
   )
}

But given the comment about react-scan needing to be imported before react, it seems like that won't be possible. Is that accurate?

jordan-cutler avatar Nov 28 '24 03:11 jordan-cutler

This shouldn't be a problem to add (but currently not possible). We can probably add this in the next release

RobPruzan avatar Nov 28 '24 19:11 RobPruzan

@jordan-cutler You can do something like this in your Layout.

export default function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode;
}>) {
  return (
    <html>
      {process.env.NODE_ENV === "development" && (
        <head>
          <script
            src="https://unpkg.com/react-scan/dist/auto.global.js"
            async
          />
        </head>
      )}
      <body>
        ...
      </body>
    </html>
  );
}

NisargIO avatar Dec 02 '24 22:12 NisargIO

Definitely second this! Would be super useful to be able to toggle it on and off on a developer build.

cameronb23 avatar Dec 04 '24 21:12 cameronb23

Im using vite/typescript (not sure) conditional imports. Here's a complete configuration.

  1. Add the imports key into package.json
  2. Add the devtools.dev.ts (with react-scan) and devtools.prod.ts (empty) files
  3. Import the files before react import (in your app entrypoint)
  4. Switch by --mode production or --mode development like this
vite --mode development
vite build --mode production
From 5816eba0a62c14d386bb590bad4b0426c2a8a4d1 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Tomasz=20D=C5=82uski?= 
Date: Sun, 20 Aug 2023 03:38:58 +0200
Subject: [PATCH] feat: make react-devtools import conditional (only on dev
 build)

---
 extension/package.json          | 6 ++++++
 extension/src/content/index.tsx | 2 +-
 extension/src/devtools.dev.ts   | 1 +
 extension/src/devtools.prod.ts  | 0
 extension/tsconfig.json         | 3 ++-
 5 files changed, 10 insertions(+), 2 deletions(-)
 create mode 100644 extension/src/devtools.dev.ts
 create mode 100644 extension/src/devtools.prod.ts

diff --git a/extension/package.json b/extension/package.json
index 51ece72d..73a673da 100644
--- a/extension/package.json
+++ b/extension/package.json
@@ -98,5 +98,11 @@
     "vite": "^4.4.6",
     "vite-plugin-eslint": "^1.8.1",
     "vitest": "^0.33.0"
+  },
+  "imports": {
+    "#env-import/*.ts": {
+      "development": "./src/*.dev.ts",
+      "production": "./src/*.prod.ts"
+    }
   }
 }
diff --git a/extension/src/content/index.tsx b/extension/src/content/index.tsx
index 07e630eb..254243dd 100644
--- a/extension/src/content/index.tsx
+++ b/extension/src/content/index.tsx
@@ -1,4 +1,4 @@
-import 'react-devtools';
+import '#env-import/devtools.ts';
 import React from 'react';
 import { createRoot } from 'react-dom/client';
 
diff --git a/extension/src/devtools.dev.ts b/extension/src/devtools.dev.ts
new file mode 100644
index 00000000..b33e2a07
--- /dev/null
+++ b/extension/src/devtools.dev.ts
@@ -0,0 +1 @@
+import 'react-devtools';
\ No newline at end of file
diff --git a/extension/src/devtools.prod.ts b/extension/src/devtools.prod.ts
new file mode 100644
index 00000000..e69de29b
diff --git a/extension/tsconfig.json b/extension/tsconfig.json
index 52defc17..569dd3f9 100644
--- a/extension/tsconfig.json
+++ b/extension/tsconfig.json
@@ -11,7 +11,7 @@
             "ESNext"
         ],
         "module": "ESNext",
-        "moduleResolution": "node",
+        "moduleResolution": "bundler",
         "noImplicitReturns": true,
         "noUnusedLocals": true,
         "strict": true,
@@ -25,6 +25,7 @@
         "isolatedModules": true,
         "noEmit": true,
         "allowJs": true,
+        "allowImportingTsExtensions": true,
         "typeRoots": [
             "./types",
             "./node_modules/@types"
-- 
6.45.2.windows.1


Toumash avatar Dec 07 '24 18:12 Toumash

I'd like to add react scan locally in dev, but also be able to toggle it on/off.

For example, I have some local-only UI widget that has some common dev utils. I'd like to add a button there toggle react scan on/off. Is this possible?

WickyNilliams avatar Dec 10 '24 12:12 WickyNilliams

I'd like to add react scan locally in dev, but also be able to toggle it on/off.

For example, I have some local-only UI widget that has some common dev utils. I'd like to add a button there toggle react scan on/off. Is this possible?

https://github.com/aidenybai/react-scan/issues/79

Toumash avatar Dec 10 '24 12:12 Toumash

I hacked together a custom vite plugin for now, but would love to see a vite-plugin as a first-class citizen:

import { Plugin } from 'vite';

export default function reactScanPlugin(options = {}) {
    const defaultScanConfig = {
        enabled: true,
        log: true,
    };
    const scanConfig = { ...defaultScanConfig, ...options };

    return {
        name: 'vite-plugin-react-scan',
        enforce: 'post',
        transform(code, id) {
            if (id.endsWith('main.jsx') || id.endsWith('main.tsx')) {
                const scanScript = `
import { scan } from 'react-scan';

if (typeof window !== 'undefined') {
    scan(${JSON.stringify(scanConfig)});
}
                `;
                return {
                    code: `${scanScript}\n${code}`,
                    map: null,
                };
            }
        },
    };
}

DAcodedBEAT avatar Jan 07 '25 16:01 DAcodedBEAT

I hacked together a custom vite plugin for now, but would love to see a vite-plugin as a first-class citizen:

import { Plugin } from 'vite';

export default function reactScanPlugin(options = {}) {
    const defaultScanConfig = {
        enabled: true,
        log: true,
    };
    const scanConfig = { ...defaultScanConfig, ...options };

    return {
        name: 'vite-plugin-react-scan',
        enforce: 'post',
        transform(code, id) {
            if (id.endsWith('main.jsx') || id.endsWith('main.tsx')) {
                const scanScript = `
import { scan } from 'react-scan';

if (typeof window !== 'undefined') {
    scan(${JSON.stringify(scanConfig)});
}
                `;
                return {
                    code: `${scanScript}\n${code}`,
                    map: null,
                };
            }
        },
    };
}

How is that different than conditional import apart it wont work that easily in chrome extensions or other targets where the main.tsx is gonna have a different name?

Toumash avatar Jan 08 '25 09:01 Toumash

I hacked together a custom vite plugin for now, but would love to see a vite-plugin as a first-class citizen:

import { Plugin } from 'vite';

export default function reactScanPlugin(options = {}) {
    const defaultScanConfig = {
        enabled: true,
        log: true,
    };
    const scanConfig = { ...defaultScanConfig, ...options };

    return {
        name: 'vite-plugin-react-scan',
        enforce: 'post',
        transform(code, id) {
            if (id.endsWith('main.jsx') || id.endsWith('main.tsx')) {
                const scanScript = `
import { scan } from 'react-scan';

if (typeof window !== 'undefined') {
    scan(${JSON.stringify(scanConfig)});
}
                `;
                return {
                    code: `${scanScript}\n${code}`,
                    map: null,
                };
            }
        },
    };
}

How is that different than conditional import apart it wont work that easily in chrome extensions or other targets where the main.tsx is gonna have a different name?

@Toumash you're free to do whatever works best for your setup. Vite plugins seem to be the standard for layering devtools into a JavaScript bundle with vite, and since that's how my current application is structured, I didn’t want to use any alternative mechanisms.

You’re right that this won't support entrypoints that are not named 'main' - there’s probably a better way to determine the entry point. Worst-case scenario, it could be explicitly provided as a parameter to the plugin.

For now, I just wanted to share my approach in case it inspires others to improve on it or build a more robust and extendable Vite plugin that can handle a variety of workflows.

DAcodedBEAT avatar Jan 08 '25 20:01 DAcodedBEAT

Ok, thank you for clarification. I'm just being curious.

For now, I just wanted to share my approach in case it inspires others to improve on it or build a more robust and extendable Vite plugin that can handle a variety of workflows.

I'm actually seeing a vite plugin's code for the first time - thank you!

Toumash avatar Jan 08 '25 20:01 Toumash

@Toumash @DAcodedBEAT you guys should trye this one: @pivanov/vite-plugin-react-scan :)

please let me know how it's work! then we can close this one, right?

pivanov avatar Jan 16 '25 21:01 pivanov