├── .nvmrc
├── .node-version
├── packages
├── playground
│ ├── react17-scss
│ │ ├── .nvmrc
│ │ ├── src
│ │ │ ├── pages
│ │ │ │ └── Theme.tsx
│ │ │ ├── App.tsx
│ │ │ ├── main.tsx
│ │ │ └── components
│ │ │ │ └── Layout.tsx
│ │ ├── .gitignore
│ │ ├── index.html
│ │ ├── tsconfig.json
│ │ ├── vite.config.ts
│ │ └── package.json
│ ├── react17-webpack4
│ │ ├── .nvmrc
│ │ ├── src
│ │ │ ├── pages
│ │ │ │ └── Theme.jsx
│ │ │ ├── App.jsx
│ │ │ ├── main.jsx
│ │ │ └── components
│ │ │ │ └── Layout.jsx
│ │ ├── .gitignore
│ │ ├── public
│ │ │ └── index.html
│ │ ├── tsconfig.json
│ │ ├── package.json
│ │ └── webpack.config.js
│ ├── react
│ │ ├── src
│ │ │ ├── pages
│ │ │ │ └── Theme.tsx
│ │ │ ├── App.tsx
│ │ │ └── main.tsx
│ │ ├── tsconfig.json
│ │ ├── index.html
│ │ ├── package.json
│ │ └── vite.config.ts
│ ├── react-webpack
│ │ ├── src
│ │ │ ├── pages
│ │ │ │ └── Theme.jsx
│ │ │ ├── App.jsx
│ │ │ ├── main.jsx
│ │ │ └── plugins
│ │ │ │ └── MyPlugin.jsx
│ │ ├── public
│ │ │ └── index.html
│ │ ├── package.json
│ │ ├── README.md
│ │ └── webpack.config.js
│ ├── umi
│ │ ├── .gitignore
│ │ ├── plugin.ts
│ │ ├── src
│ │ │ ├── app.tsx
│ │ │ ├── global.less
│ │ │ ├── layouts
│ │ │ │ └── index.tsx
│ │ │ └── pages
│ │ │ │ ├── index.less
│ │ │ │ ├── index.tsx
│ │ │ │ └── about.less
│ │ ├── typings.d.ts
│ │ ├── package.json
│ │ ├── .umirc.ts
│ │ └── tsconfig.json
│ ├── react19
│ │ ├── tsconfig.json
│ │ ├── index.html
│ │ ├── src
│ │ │ ├── main.tsx
│ │ │ ├── style.css
│ │ │ └── App.tsx
│ │ ├── vite.config.ts
│ │ └── package.json
│ ├── my-devtools-play
│ │ ├── src
│ │ │ ├── main.tsx
│ │ │ └── App.tsx
│ │ ├── index.html
│ │ ├── vite.config.ts
│ │ ├── package.json
│ │ └── tsconfig.json
│ └── my-devtools
│ │ ├── package.json
│ │ ├── tsconfig.json
│ │ ├── tsup.config.ts
│ │ └── src
│ │ ├── index.ts
│ │ └── plugins
│ │ └── index.ts
├── react-devtools-ui
│ ├── src
│ │ ├── components
│ │ │ ├── Tag
│ │ │ │ ├── index.ts
│ │ │ │ └── Tag.tsx
│ │ │ ├── Select
│ │ │ │ ├── index.ts
│ │ │ │ ├── Select.tsx
│ │ │ │ └── Select.module.css
│ │ │ ├── Switch
│ │ │ │ ├── index.ts
│ │ │ │ └── Switch.tsx
│ │ │ ├── Checkbox
│ │ │ │ ├── index.ts
│ │ │ │ ├── Checkbox.tsx
│ │ │ │ └── Checkbox.module.css
│ │ │ ├── Card
│ │ │ │ ├── index.ts
│ │ │ │ ├── Card.module.css
│ │ │ │ └── Card.tsx
│ │ │ ├── Badge
│ │ │ │ ├── index.ts
│ │ │ │ ├── Badge.module.css
│ │ │ │ └── Badge.tsx
│ │ │ ├── Input
│ │ │ │ ├── index.ts
│ │ │ │ └── Input.tsx
│ │ │ ├── Button
│ │ │ │ ├── index.ts
│ │ │ │ └── Button.tsx
│ │ │ └── index.ts
│ │ ├── types.d.ts
│ │ ├── index.ts
│ │ ├── theme
│ │ │ └── types.ts
│ │ └── styles
│ │ │ └── base.css
│ ├── tsconfig.node.json
│ ├── tsconfig.json
│ ├── package.json
│ └── vite.config.ts
├── react-devtools
│ ├── src
│ │ ├── scan.ts
│ │ ├── config
│ │ │ └── index.ts
│ │ ├── utils
│ │ │ └── index.ts
│ │ ├── shims.d.ts
│ │ ├── overlay.ts
│ │ ├── integrations
│ │ │ └── index.ts
│ │ ├── middleware
│ │ │ ├── client-server.ts
│ │ │ ├── index.ts
│ │ │ ├── plugins.ts
│ │ │ └── open-in-editor.ts
│ │ ├── compat
│ │ │ └── index.ts
│ │ ├── codegen
│ │ │ ├── index.ts
│ │ │ ├── devtools-hook.ts
│ │ │ └── config-injector.ts
│ │ ├── dir.ts
│ │ ├── webpack.ts
│ │ ├── vite.ts
│ │ ├── umi.ts
│ │ └── index.ts
│ ├── tsconfig.json
│ ├── README.md
│ ├── package.json
│ └── client
│ │ └── index.html
├── react-devtools-kit
│ ├── src
│ │ ├── messaging
│ │ │ ├── types
│ │ │ │ ├── index.ts
│ │ │ │ └── channel.ts
│ │ │ └── presets
│ │ │ │ ├── iframe
│ │ │ │ ├── context.ts
│ │ │ │ ├── client.ts
│ │ │ │ └── server.ts
│ │ │ │ ├── index.ts
│ │ │ │ └── broadcast
│ │ │ │ ├── client.ts
│ │ │ │ └── server.ts
│ │ ├── index.ts
│ │ └── core
│ │ │ ├── index.ts
│ │ │ └── open-in-editor
│ │ │ └── index.ts
│ ├── tsconfig.json
│ ├── tsdown.config.ts
│ └── package.json
├── shared
│ ├── src
│ │ ├── index.ts
│ │ ├── constants.ts
│ │ └── env.ts
│ ├── README.md
│ ├── tsdown.config.ts
│ └── package.json
├── react-devtools-overlay
│ ├── src
│ │ └── composables
│ │ │ ├── useOverlay.ts
│ │ │ └── usePanelVisible.ts
│ ├── tsconfig.json
│ └── package.json
├── react-devtools-client
│ ├── src
│ │ ├── components
│ │ │ ├── graph
│ │ │ │ ├── index.ts
│ │ │ │ └── GraphNavbar.tsx
│ │ │ └── assets
│ │ │ │ └── ReactLogo.tsx
│ │ ├── global.css
│ │ ├── types
│ │ │ ├── plugin.ts
│ │ │ └── messages.ts
│ │ ├── events.ts
│ │ ├── composables
│ │ │ └── useComponentTreeHook.ts
│ │ └── main.tsx
│ ├── tsconfig.json
│ ├── vite.config.ts
│ ├── package.json
│ └── index.html
├── react-devtools-core
│ ├── src
│ │ ├── plugin
│ │ │ └── index.ts
│ │ ├── index.ts
│ │ └── rpc
│ │ │ ├── types.ts
│ │ │ └── index.ts
│ ├── tsconfig.json
│ ├── tsup.config.ts
│ ├── package.json
│ └── examples
│ │ └── performance-plugin.ts
├── react-devtools-scan
│ ├── tsconfig.json
│ ├── tsup.config.ts
│ └── package.json
└── ui-story
│ ├── tsconfig.json
│ ├── .storybook
│ ├── preview.ts
│ └── main.ts
│ ├── package.json
│ └── stories
│ ├── Switch.stories.tsx
│ ├── Checkbox.stories.tsx
│ ├── Select.stories.tsx
│ ├── Card.stories.tsx
│ └── Badge.stories.tsx
├── .npmrc
├── docs
├── postcss.config.js
├── public
│ ├── screenshots
│ │ ├── scan.png
│ │ ├── assets.png
│ │ ├── modules.png
│ │ ├── overview.png
│ │ ├── timeline.png
│ │ ├── inspector.png
│ │ ├── assets-detail.png
│ │ ├── scan-detail.png
│ │ ├── component-tree.png
│ │ ├── timeline-detail.png
│ │ └── component-tree-detail.png
│ └── favicon.svg
├── metadata.json
├── src
│ ├── types.ts
│ ├── main.tsx
│ ├── pages
│ │ ├── index.ts
│ │ └── docs
│ │ │ └── FAQ.tsx
│ ├── components
│ │ ├── ui
│ │ │ ├── Spotlight.tsx
│ │ │ └── Button.tsx
│ │ ├── Footer.tsx
│ │ └── Stats.tsx
│ └── hooks
│ │ └── useScrollAnimation.ts
├── vite.config.ts
├── tsconfig.json
├── index.html
├── package.json
└── tailwind.config.ts
├── uno.config.ts
├── .editorconfig
├── vitest.config.ts
├── bump.config.ts
├── pnpm-workspace.yaml
├── LICENSE
├── .vscode
└── settings.json
├── tsconfig.json
├── eslint.config.ts
├── turbo.json
└── .gitignore
/.nvmrc:
--------------------------------------------------------------------------------
1 | 20
2 |
3 |
--------------------------------------------------------------------------------
/.node-version:
--------------------------------------------------------------------------------
1 | lts-latest
2 |
--------------------------------------------------------------------------------
/packages/playground/react17-scss/.nvmrc:
--------------------------------------------------------------------------------
1 | 14.0.0
2 |
3 |
--------------------------------------------------------------------------------
/packages/playground/react17-webpack4/.nvmrc:
--------------------------------------------------------------------------------
1 | 14.0.0
2 |
3 |
--------------------------------------------------------------------------------
/packages/react-devtools-ui/src/components/Tag/index.ts:
--------------------------------------------------------------------------------
1 | export * from './Tag'
2 |
--------------------------------------------------------------------------------
/packages/react-devtools/src/scan.ts:
--------------------------------------------------------------------------------
1 | export * from '@react-devtools-plus/scan'
2 |
--------------------------------------------------------------------------------
/packages/react-devtools-kit/src/messaging/types/index.ts:
--------------------------------------------------------------------------------
1 | export * from './channel'
2 |
--------------------------------------------------------------------------------
/packages/react-devtools-ui/src/components/Select/index.ts:
--------------------------------------------------------------------------------
1 | export * from './Select'
2 |
--------------------------------------------------------------------------------
/packages/react-devtools-ui/src/components/Switch/index.ts:
--------------------------------------------------------------------------------
1 | export * from './Switch'
2 |
--------------------------------------------------------------------------------
/packages/react-devtools-ui/src/components/Checkbox/index.ts:
--------------------------------------------------------------------------------
1 | export * from './Checkbox'
2 |
--------------------------------------------------------------------------------
/packages/shared/src/index.ts:
--------------------------------------------------------------------------------
1 | export * from './constants'
2 | export * from './env'
3 | export * from './general'
4 |
--------------------------------------------------------------------------------
/.npmrc:
--------------------------------------------------------------------------------
1 | shamefully-hoist=true
2 | strict-peer-dependencies=false
3 | side-effects-cache=false
4 | shell-emulator=true
5 |
--------------------------------------------------------------------------------
/docs/postcss.config.js:
--------------------------------------------------------------------------------
1 | export default {
2 | plugins: {
3 | tailwindcss: {},
4 | autoprefixer: {},
5 | },
6 | }
7 |
--------------------------------------------------------------------------------
/docs/public/screenshots/scan.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wzc520pyfm/react-devtools-plus/HEAD/docs/public/screenshots/scan.png
--------------------------------------------------------------------------------
/packages/react-devtools-overlay/src/composables/useOverlay.ts:
--------------------------------------------------------------------------------
1 | export function getShowHostComponents() {
2 | return false
3 | }
4 |
--------------------------------------------------------------------------------
/packages/react-devtools-ui/src/components/Card/index.ts:
--------------------------------------------------------------------------------
1 | export { Card } from './Card'
2 | export type { CardProps } from './Card'
3 |
--------------------------------------------------------------------------------
/packages/shared/README.md:
--------------------------------------------------------------------------------
1 | # @react-devtools-plus/shared
2 |
3 | > Internal utility types shared across react-devtools packages.
4 |
--------------------------------------------------------------------------------
/docs/public/screenshots/assets.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wzc520pyfm/react-devtools-plus/HEAD/docs/public/screenshots/assets.png
--------------------------------------------------------------------------------
/docs/public/screenshots/modules.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wzc520pyfm/react-devtools-plus/HEAD/docs/public/screenshots/modules.png
--------------------------------------------------------------------------------
/docs/public/screenshots/overview.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wzc520pyfm/react-devtools-plus/HEAD/docs/public/screenshots/overview.png
--------------------------------------------------------------------------------
/docs/public/screenshots/timeline.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wzc520pyfm/react-devtools-plus/HEAD/docs/public/screenshots/timeline.png
--------------------------------------------------------------------------------
/packages/react-devtools-ui/src/components/Badge/index.ts:
--------------------------------------------------------------------------------
1 | export { Badge } from './Badge'
2 | export type { BadgeProps } from './Badge'
3 |
--------------------------------------------------------------------------------
/packages/react-devtools-ui/src/components/Input/index.ts:
--------------------------------------------------------------------------------
1 | export { Input } from './Input'
2 | export type { InputProps } from './Input'
3 |
--------------------------------------------------------------------------------
/docs/public/screenshots/inspector.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wzc520pyfm/react-devtools-plus/HEAD/docs/public/screenshots/inspector.png
--------------------------------------------------------------------------------
/packages/react-devtools-ui/src/components/Button/index.ts:
--------------------------------------------------------------------------------
1 | export { Button } from './Button'
2 | export type { ButtonProps } from './Button'
3 |
--------------------------------------------------------------------------------
/docs/public/screenshots/assets-detail.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wzc520pyfm/react-devtools-plus/HEAD/docs/public/screenshots/assets-detail.png
--------------------------------------------------------------------------------
/docs/public/screenshots/scan-detail.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wzc520pyfm/react-devtools-plus/HEAD/docs/public/screenshots/scan-detail.png
--------------------------------------------------------------------------------
/docs/public/screenshots/component-tree.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wzc520pyfm/react-devtools-plus/HEAD/docs/public/screenshots/component-tree.png
--------------------------------------------------------------------------------
/docs/public/screenshots/timeline-detail.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wzc520pyfm/react-devtools-plus/HEAD/docs/public/screenshots/timeline-detail.png
--------------------------------------------------------------------------------
/docs/public/screenshots/component-tree-detail.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wzc520pyfm/react-devtools-plus/HEAD/docs/public/screenshots/component-tree-detail.png
--------------------------------------------------------------------------------
/packages/playground/react/src/pages/Theme.tsx:
--------------------------------------------------------------------------------
1 | import { ThemeDemo } from '../ThemeDemo'
2 |
3 | export default function Theme() {
4 | return
5 | }
6 |
--------------------------------------------------------------------------------
/packages/playground/react-webpack/src/pages/Theme.jsx:
--------------------------------------------------------------------------------
1 | import { ThemeDemo } from '../ThemeDemo'
2 |
3 | export default function Theme() {
4 | return
5 | }
6 |
--------------------------------------------------------------------------------
/packages/playground/react17-webpack4/src/pages/Theme.jsx:
--------------------------------------------------------------------------------
1 | import { ThemeDemo } from '../ThemeDemo'
2 |
3 | export default function Theme() {
4 | return
5 | }
6 |
--------------------------------------------------------------------------------
/packages/react-devtools/src/config/index.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Configuration module exports
3 | * 配置模块导出
4 | */
5 |
6 | export * from './normalize'
7 | export * from './types'
8 |
--------------------------------------------------------------------------------
/packages/react-devtools/src/utils/index.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Utilities module exports
3 | * 工具模块导出
4 | */
5 |
6 | export * from './babel-transform'
7 | export * from './paths'
8 |
--------------------------------------------------------------------------------
/packages/react-devtools/src/shims.d.ts:
--------------------------------------------------------------------------------
1 | declare module 'virtual:react-devtools-options' {
2 | const options: {
3 | base: string
4 | }
5 | export default options
6 | }
7 |
--------------------------------------------------------------------------------
/packages/react-devtools-kit/src/messaging/types/channel.ts:
--------------------------------------------------------------------------------
1 | export interface MergeableChannelOptions {
2 | post: (data: any) => void
3 | on: (handler: (data: any) => void) => void
4 | }
5 |
--------------------------------------------------------------------------------
/packages/playground/react17-scss/src/pages/Theme.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { ThemeDemo } from '../ThemeDemo'
3 |
4 | export default function Theme() {
5 | return
6 | }
7 |
--------------------------------------------------------------------------------
/packages/react-devtools/src/overlay.ts:
--------------------------------------------------------------------------------
1 | // This file is a placeholder that will be replaced by the overlay package build output
2 | // The actual overlay code is in packages/react-devtools-overlay
3 |
--------------------------------------------------------------------------------
/packages/react-devtools-client/src/components/graph/index.ts:
--------------------------------------------------------------------------------
1 | export { GraphDrawer } from './GraphDrawer'
2 | export { GraphFileType } from './GraphFileType'
3 | export { GraphNavbar } from './GraphNavbar'
4 |
--------------------------------------------------------------------------------
/packages/react-devtools-core/src/plugin/index.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Plugin system exports
3 | * 插件系统导出
4 | */
5 |
6 | export type { DevToolsPlugin, PluginContext } from '../types'
7 | export * from './manager'
8 |
--------------------------------------------------------------------------------
/packages/react-devtools-kit/src/index.ts:
--------------------------------------------------------------------------------
1 | export * from './core'
2 | export * from './messaging'
3 | export { setIframeServerContext } from './messaging/presets/iframe/context'
4 | export * from './types'
5 |
--------------------------------------------------------------------------------
/packages/react-devtools/src/integrations/index.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Integration module exports
3 | * 构建工具集成模块导出
4 | */
5 |
6 | export * from './umi'
7 | export * from './vite'
8 | export * from './webpack'
9 |
--------------------------------------------------------------------------------
/packages/playground/umi/.gitignore:
--------------------------------------------------------------------------------
1 | # umi
2 | .umi
3 | .umi-production
4 | .umi-test
5 | dist
6 |
7 | # dependencies
8 | node_modules
9 |
10 | # misc
11 | .DS_Store
12 | *.log
13 |
14 | # IDE
15 | .idea
16 | .vscode
17 |
--------------------------------------------------------------------------------
/packages/react-devtools-kit/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.json",
3 | "compilerOptions": {
4 | "outDir": "./dist"
5 | },
6 | "include": ["src/**/*"],
7 | "exclude": ["node_modules", "dist"]
8 | }
9 |
--------------------------------------------------------------------------------
/packages/playground/react/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../../tsconfig.json",
3 | "compilerOptions": {
4 | "jsx": "react-jsx",
5 | "types": ["vite/client"]
6 | },
7 | "include": [
8 | "src"
9 | ]
10 | }
11 |
--------------------------------------------------------------------------------
/packages/react-devtools-core/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.json",
3 | "compilerOptions": {
4 | "outDir": "dist"
5 | },
6 | "include": ["src/**/*"],
7 | "exclude": ["node_modules", "dist", "examples"]
8 | }
9 |
--------------------------------------------------------------------------------
/packages/playground/react19/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../../tsconfig.json",
3 | "compilerOptions": {
4 | "jsx": "react-jsx",
5 | "types": ["vite/client"]
6 | },
7 | "include": [
8 | "src"
9 | ]
10 | }
11 |
--------------------------------------------------------------------------------
/packages/react-devtools-scan/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.json",
3 | "compilerOptions": {
4 | "rootDir": "src",
5 | "outDir": "dist"
6 | },
7 | "include": ["src/**/*"],
8 | "exclude": ["dist", "node_modules"]
9 | }
10 |
--------------------------------------------------------------------------------
/packages/react-devtools-ui/src/types.d.ts:
--------------------------------------------------------------------------------
1 | declare module '*.module.css' {
2 | const classes: { [key: string]: string }
3 | export default classes
4 | }
5 |
6 | declare module '*.css' {
7 | const content: string
8 | export default content
9 | }
10 |
--------------------------------------------------------------------------------
/packages/ui-story/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.json",
3 | "compilerOptions": {
4 | "jsx": "react-jsx",
5 | "skipLibCheck": true
6 | },
7 | "include": [
8 | ".storybook/**/*",
9 | "stories/**/*"
10 | ]
11 | }
12 |
--------------------------------------------------------------------------------
/docs/metadata.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "React DevTools Plus Landing",
3 | "description": "A high-performance, aesthetically stunning marketing landing page for React DevTools Plus, featuring dark mode, beam animations, and glassmorphism.",
4 | "requestFramePermissions": []
5 | }
6 |
--------------------------------------------------------------------------------
/uno.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'unocss'
2 |
3 | import config from './packages/react-devtools-client/uno.config'
4 |
5 | export default defineConfig({
6 | ...config,
7 | configDeps: [
8 | './packages/react-devtools-client/uno.config.ts',
9 | ],
10 | })
11 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | charset = utf-8
5 | indent_size = 2
6 | indent_style = space
7 | end_of_line = lf
8 | insert_final_newline = true
9 | trim_trailing_whitespace = true
10 |
11 | [*.md]
12 | insert_final_newline = false
13 | trim_trailing_whitespace = false
14 |
--------------------------------------------------------------------------------
/packages/react-devtools-ui/src/index.ts:
--------------------------------------------------------------------------------
1 | // Export components
2 | // Import base styles
3 | import './styles/base.css'
4 |
5 | export * from './components'
6 |
7 | // Export composables
8 | export * from './composables/useTheme'
9 |
10 | // Export theme
11 | export * from './theme'
12 |
--------------------------------------------------------------------------------
/packages/react-devtools/src/middleware/client-server.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Client serving middleware
3 | * 客户端服务中间件
4 | */
5 | import sirv from 'sirv'
6 |
7 | export function serveClient(servePath: string) {
8 | return sirv(servePath, {
9 | single: true,
10 | dev: true,
11 | })
12 | }
13 |
--------------------------------------------------------------------------------
/packages/playground/my-devtools-play/src/main.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import ReactDOM from 'react-dom/client'
3 | import App from './App.tsx'
4 |
5 | ReactDOM.createRoot(document.getElementById('root')!).render(
6 |
7 |
8 | ,
9 | )
10 |
--------------------------------------------------------------------------------
/packages/react-devtools-ui/tsconfig.node.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "composite": true,
4 | "module": "ESNext",
5 | "moduleResolution": "bundler",
6 | "allowSyntheticDefaultImports": true,
7 | "skipLibCheck": true
8 | },
9 | "include": ["vite.config.ts"]
10 | }
11 |
--------------------------------------------------------------------------------
/packages/react-devtools/src/compat/index.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Compatibility Layer
3 | *
4 | * - Webpack 4/5
5 | * - webpack-dev-server 3/4+
6 | * - React 17/18+
7 | */
8 |
9 | export * from './react-version'
10 | export * from './webpack-dev-server-version'
11 | export * from './webpack-version'
12 |
--------------------------------------------------------------------------------
/packages/react-devtools-ui/src/components/index.ts:
--------------------------------------------------------------------------------
1 | // Export all components
2 | export * from './Badge'
3 | export * from './Button'
4 | export * from './Card'
5 | export * from './Checkbox'
6 | export * from './Input'
7 | export * from './Select'
8 | export * from './Switch'
9 | export * from './Tag'
10 |
--------------------------------------------------------------------------------
/packages/react-devtools/src/middleware/index.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Middleware module exports
3 | * 中间件模块导出
4 | */
5 |
6 | export * from './assets'
7 | export * from './client-server'
8 | export * from './graph'
9 | export * from './open-in-editor'
10 | export * from './plugin-file'
11 | export * from './plugins'
12 |
--------------------------------------------------------------------------------
/packages/shared/tsdown.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'tsdown'
2 |
3 | export default defineConfig({
4 | entry: [
5 | 'src/index.ts',
6 | ],
7 | clean: true,
8 | format: ['esm', 'cjs'],
9 | target: 'node14',
10 | dts: true,
11 | shims: true,
12 | noExternal: ['rfdc'],
13 | })
14 |
--------------------------------------------------------------------------------
/vitest.config.ts:
--------------------------------------------------------------------------------
1 | import React from '@vitejs/plugin-react'
2 | import { defineConfig } from 'vitest/config'
3 |
4 | export default defineConfig({
5 | plugins: [React()],
6 | define: {
7 | __DEV__: true,
8 | __FEATURE_PROD_DEVTOOLS__: true,
9 | },
10 | test: {
11 | environment: 'jsdom',
12 | globals: true,
13 | },
14 | })
15 |
--------------------------------------------------------------------------------
/packages/playground/react-webpack/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | React DevTools Webpack Playground
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/packages/shared/src/constants.ts:
--------------------------------------------------------------------------------
1 | export const VIEW_MODE_STORAGE_KEY = '__react-devtools-view-mode__'
2 | export const VITE_PLUGIN_DETECTED_STORAGE_KEY = '__react-devtools-vite-plugin-detected__'
3 | export const VITE_PLUGIN_CLIENT_URL_STORAGE_KEY = '__react-devtools-vite-plugin-client-url__'
4 | export const BROADCAST_CHANNEL_NAME = '__react-devtools-broadcast-channel__'
5 |
--------------------------------------------------------------------------------
/packages/react-devtools-client/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.json",
3 | "compilerOptions": {
4 | "jsx": "react-jsx",
5 | "baseUrl": ".",
6 | "rootDir": "./src",
7 | "paths": {
8 | "~/*": ["./src/*"]
9 | },
10 | "outDir": "./dist"
11 | },
12 | "include": ["src/**/*"],
13 | "exclude": ["node_modules", "dist"]
14 | }
15 |
--------------------------------------------------------------------------------
/packages/ui-story/.storybook/preview.ts:
--------------------------------------------------------------------------------
1 | import type { Preview } from '@storybook/react'
2 | import '@react-devtools-plus/ui/style.css'
3 |
4 | const preview: Preview = {
5 | parameters: {
6 | controls: {
7 | matchers: {
8 | color: /(background|color)$/i,
9 | date: /Date$/i,
10 | },
11 | },
12 | },
13 | }
14 |
15 | export default preview
16 |
--------------------------------------------------------------------------------
/packages/react-devtools-client/src/global.css:
--------------------------------------------------------------------------------
1 | :root {
2 | background-color: transparent;
3 | color-scheme: light dark;
4 | }
5 |
6 | html,
7 | body {
8 | width: 100%;
9 | height: 100%;
10 | margin: 0;
11 | background-color: var(--surface-base, #0f172a);
12 | }
13 |
14 | body {
15 | overflow: hidden;
16 | }
17 |
18 | #root {
19 | width: 100%;
20 | height: 100%;
21 | }
22 |
--------------------------------------------------------------------------------
/packages/playground/react/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | React DevTools Playground
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/packages/react-devtools-core/src/index.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * React DevTools Core
3 | * React DevTools 核心功能
4 | */
5 |
6 | // Export client
7 | export * from './client'
8 |
9 | // Export events
10 | export * from './events'
11 |
12 | // Export plugin system
13 | export * from './plugin'
14 |
15 | // Export RPC
16 | export * from './rpc'
17 |
18 | // Export types
19 | export type * from './types'
20 |
--------------------------------------------------------------------------------
/packages/playground/react19/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | React 19 DevTools Playground
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/packages/react-devtools-overlay/src/composables/usePanelVisible.ts:
--------------------------------------------------------------------------------
1 | import { useState } from 'react'
2 |
3 | export function usePanelVisible() {
4 | const [panelVisible, setPanelVisible] = useState(false)
5 |
6 | const togglePanel = () => {
7 | setPanelVisible(prev => !prev)
8 | }
9 |
10 | return {
11 | panelVisible,
12 | setPanelVisible,
13 | togglePanel,
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/packages/playground/react17-scss/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | pnpm-debug.log*
8 | lerna-debug.log*
9 |
10 | node_modules
11 | dist
12 | dist-ssr
13 | *.local
14 |
15 | # Editor directories and files
16 | .vscode/*
17 | !.vscode/extensions.json
18 | .idea
19 | .DS_Store
20 | *.suo
21 | *.ntvs*
22 | *.njsproj
23 | *.sln
24 | *.sw?
25 |
26 |
--------------------------------------------------------------------------------
/packages/playground/react17-webpack4/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | pnpm-debug.log*
8 | lerna-debug.log*
9 |
10 | node_modules
11 | dist
12 | dist-ssr
13 | *.local
14 |
15 | # Editor directories and files
16 | .vscode/*
17 | !.vscode/extensions.json
18 | .idea
19 | .DS_Store
20 | *.suo
21 | *.ntvs*
22 | *.njsproj
23 | *.sln
24 | *.sw?
25 |
26 |
--------------------------------------------------------------------------------
/docs/src/types.ts:
--------------------------------------------------------------------------------
1 | export interface Feature {
2 | title: string
3 | description: string
4 | icon: React.ComponentType<{ className?: string }>
5 | className?: string
6 | gradient?: string
7 | }
8 |
9 | export interface Testimonial {
10 | quote: string
11 | author: string
12 | role: string
13 | avatar?: string
14 | }
15 |
16 | export interface NavLink {
17 | label: string
18 | href: string
19 | }
20 |
--------------------------------------------------------------------------------
/packages/playground/react17-scss/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | React DevTools Playground - React 17 + SCSS
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/bump.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'bumpp'
2 | import fg from 'fast-glob'
3 |
4 | export default defineConfig({
5 | files: fg.sync(['./packages/*/package.json'], {
6 | ignore: [
7 | // 不需要发布的包
8 | './packages/playground/*/package.json',
9 | ],
10 | }),
11 | // 自动 push commit 和 tag 到远程
12 | push: true,
13 | // tag 格式
14 | tag: true,
15 | // commit 消息格式
16 | commit: 'release: v%s',
17 | })
18 |
--------------------------------------------------------------------------------
/packages/playground/umi/plugin.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * React DevTools Plus - Umi Plugin
3 | *
4 | * This plugin integrates react-devtools-plus with Umi's dev server
5 | */
6 | import { createUmiPlugin } from 'react-devtools-plus/umi'
7 |
8 | export default createUmiPlugin({
9 | enabledEnvironments: ['development', 'test'],
10 | scan: {
11 | enabled: true,
12 | showToolbar: false,
13 | animationSpeed: 'fast',
14 | },
15 | })
16 |
--------------------------------------------------------------------------------
/packages/react-devtools-kit/src/core/index.ts:
--------------------------------------------------------------------------------
1 | export * from './context'
2 | export * from './fiber/details'
3 | export * from './fiber/highlight'
4 | export * from './fiber/props'
5 | export * from './fiber/state'
6 | export * from './fiber/tree'
7 | export * from './fiber/utils'
8 | export * from './hook'
9 | export * from './inspector'
10 | export * from './open-in-editor'
11 | export * from './router'
12 | export * from './timeline'
13 |
--------------------------------------------------------------------------------
/packages/playground/react19/src/main.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import ReactDOM from 'react-dom/client'
3 | import App from './App'
4 | import './style.css'
5 |
6 | // Note: React Scan is now auto-injected via vite.config.ts
7 | // 注意:React Scan 现在通过 vite.config.ts 自动注入
8 |
9 | ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
10 |
11 |
12 | ,
13 | )
14 |
--------------------------------------------------------------------------------
/packages/playground/react17-webpack4/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | React DevTools Playground - React 17 + Webpack 4 + SCSS
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/packages/react-devtools/src/codegen/index.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Code Generation Module
3 | *
4 | * - React DevTools Hook initialization
5 | * - React globals setup
6 | * - React Scan initialization
7 | * - CSS injection
8 | * - Configuration injection
9 | */
10 |
11 | export * from './config-injector'
12 | export * from './css-injector'
13 | export * from './devtools-hook'
14 | export * from './react-globals'
15 | export * from './scan-init'
16 |
--------------------------------------------------------------------------------
/packages/react-devtools-kit/src/messaging/presets/iframe/context.ts:
--------------------------------------------------------------------------------
1 | let iframeServerContext: HTMLIFrameElement | null = null
2 |
3 | export function setIframeServerContext(iframe: HTMLIFrameElement | null) {
4 | iframeServerContext = iframe
5 | }
6 |
7 | export function getIframeServerContext() {
8 | return iframeServerContext
9 | }
10 |
11 | export const __REACT_DEVTOOLS_KIT_IFRAME_MESSAGING_EVENT_KEY = '__REACT_DEVTOOLS_KIT_IFRAME_MESSAGE__'
12 |
--------------------------------------------------------------------------------
/packages/react-devtools-overlay/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.json",
3 | "compilerOptions": {
4 | "target": "esnext",
5 | "jsx": "react-jsx",
6 | "lib": ["esnext", "dom"],
7 | "module": "esnext",
8 | "moduleResolution": "bundler",
9 | "types": ["vite/client"],
10 | "strict": true,
11 | "outDir": "./dist",
12 | "skipLibCheck": true
13 | },
14 | "include": ["src/**/*.ts", "src/**/*.tsx"]
15 | }
16 |
--------------------------------------------------------------------------------
/packages/playground/my-devtools-play/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | My DevTools Playground
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/packages/playground/react17-webpack4/src/App.jsx:
--------------------------------------------------------------------------------
1 | import { Route, Switch } from 'react-router-dom'
2 | import Layout from './components/Layout'
3 | import Home from './pages/Home'
4 | import Theme from './pages/Theme'
5 |
6 | export default function App() {
7 | return (
8 |
9 |
10 |
11 |
12 |
13 |
14 | )
15 | }
16 |
--------------------------------------------------------------------------------
/docs/src/main.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import ReactDOM from 'react-dom/client'
3 | import App from './App'
4 | import './lib/i18n'
5 | import './styles/globals.css'
6 |
7 | const rootElement = document.getElementById('root')
8 | if (!rootElement) {
9 | throw new Error('Could not find root element to mount to')
10 | }
11 |
12 | const root = ReactDOM.createRoot(rootElement)
13 | root.render(
14 |
15 |
16 | ,
17 | )
18 |
--------------------------------------------------------------------------------
/packages/react-devtools-kit/tsdown.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'tsdown'
2 |
3 | export default defineConfig({
4 | entry: ['src/index.ts'],
5 | clean: true,
6 | format: ['esm', 'cjs'],
7 | // Use es2017 for maximum compatibility with Webpack 4
8 | target: 'es2017',
9 | dts: true,
10 | shims: true,
11 | // Bundle these dependencies to avoid pnpm resolution issues in Webpack 4
12 | noExternal: ['superjson', 'birpc', 'copy-anything', 'is-what'],
13 | })
14 |
--------------------------------------------------------------------------------
/packages/playground/react/src/App.tsx:
--------------------------------------------------------------------------------
1 | import { Route, Routes } from 'react-router-dom'
2 | import Layout from './components/Layout'
3 | import Home from './pages/Home'
4 | import Theme from './pages/Theme'
5 |
6 | export default function App() {
7 | return (
8 |
9 | }>
10 | } />
11 | } />
12 |
13 |
14 | )
15 | }
16 |
--------------------------------------------------------------------------------
/packages/playground/react-webpack/src/App.jsx:
--------------------------------------------------------------------------------
1 | import { Route, Routes } from 'react-router-dom'
2 | import Layout from './components/Layout'
3 | import Home from './pages/Home'
4 | import Theme from './pages/Theme'
5 |
6 | export default function App() {
7 | return (
8 |
9 | }>
10 | } />
11 | } />
12 |
13 |
14 | )
15 | }
16 |
--------------------------------------------------------------------------------
/packages/react-devtools/src/dir.ts:
--------------------------------------------------------------------------------
1 | import { resolve } from 'node:path'
2 |
3 | /**
4 | * Directory constants
5 | * __dirname is provided by:
6 | * - CJS: Node.js built-in
7 | * - ESM: tsup banner polyfill (see tsup.config.ts)
8 | */
9 | export const DIR_DIST = __dirname
10 |
11 | // The overlay is in src/overlay relative to package root
12 | // DIR_DIST points to dist/, so we need to go up one level and into src/overlay
13 | export const DIR_OVERLAY = resolve(DIR_DIST, '../src/overlay')
14 |
--------------------------------------------------------------------------------
/packages/react-devtools/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.json",
3 | "compilerOptions": {
4 | "composite": false,
5 | "target": "ES2017",
6 | "rootDir": "src",
7 | "module": "ESNext",
8 | "moduleResolution": "bundler",
9 | "types": [
10 | "vite/client"
11 | ],
12 | "declaration": true,
13 | "declarationMap": false,
14 | "emitDeclarationOnly": true,
15 | "outDir": "dist"
16 | },
17 | "include": [
18 | "src"
19 | ]
20 | }
21 |
--------------------------------------------------------------------------------
/packages/playground/umi/src/app.tsx:
--------------------------------------------------------------------------------
1 | /**
2 | * Umi runtime configuration
3 | * This file is used to expose React to window for react-devtools-plus overlay
4 | */
5 | import React from 'react'
6 | import ReactDOM from 'react-dom'
7 |
8 | // Expose React and ReactDOM to window for react-devtools-plus overlay
9 | if (typeof window !== 'undefined') {
10 | ;(window as any).React = React
11 | ;(window as any).ReactDOM = ReactDOM
12 | }
13 |
14 | // Export empty object for umi runtime config
15 | export {}
16 |
--------------------------------------------------------------------------------
/packages/react-devtools/src/webpack.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Webpack Plugin Entry
3 | *
4 | * @example
5 | * ```js
6 | * const { reactDevToolsPlus } = require('react-devtools-plus/webpack')
7 | *
8 | * module.exports = {
9 | * plugins: [
10 | * reactDevToolsPlus(),
11 | * ],
12 | * }
13 | * ```
14 | */
15 |
16 | import { webpack } from './unplugin.js'
17 |
18 | export { webpack as reactDevToolsPlus }
19 | export default webpack
20 | export type { ReactDevToolsPluginOptions } from './unplugin.js'
21 |
--------------------------------------------------------------------------------
/docs/vite.config.ts:
--------------------------------------------------------------------------------
1 | import path from 'node:path'
2 | import react from '@vitejs/plugin-react'
3 | import { defineConfig } from 'vite'
4 |
5 | export default defineConfig({
6 | server: {
7 | port: 3000,
8 | host: '0.0.0.0',
9 | },
10 | plugins: [react()],
11 | resolve: {
12 | alias: {
13 | '@': path.resolve(__dirname, './src'),
14 | },
15 | dedupe: ['react', 'react-dom'],
16 | },
17 | optimizeDeps: {
18 | include: ['react', 'react-dom', 'react-router-dom'],
19 | },
20 | })
21 |
--------------------------------------------------------------------------------
/packages/react-devtools/src/vite.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Vite Plugin Entry
3 | *
4 | * @example
5 | * ```ts
6 | * import { reactDevToolsPlus } from 'react-devtools-plus/vite'
7 | *
8 | * export default defineConfig({
9 | * plugins: [
10 | * react(),
11 | * reactDevToolsPlus(),
12 | * ],
13 | * })
14 | * ```
15 | */
16 |
17 | import { vite } from './unplugin.js'
18 |
19 | export { vite as reactDevToolsPlus }
20 | export default vite
21 | export type { ReactDevToolsPluginOptions } from './unplugin.js'
22 |
--------------------------------------------------------------------------------
/packages/ui-story/.storybook/main.ts:
--------------------------------------------------------------------------------
1 | import type { StorybookConfig } from '@storybook/react-vite'
2 |
3 | const config: StorybookConfig = {
4 | stories: ['../stories/**/*.mdx', '../stories/**/*.stories.@(js|jsx|mjs|ts|tsx)'],
5 | addons: [
6 | '@storybook/addon-links',
7 | '@storybook/addon-essentials',
8 | '@storybook/addon-interactions',
9 | ],
10 | framework: {
11 | name: '@storybook/react-vite',
12 | options: {},
13 | },
14 | docs: {
15 | autodocs: 'tag',
16 | },
17 | }
18 |
19 | export default config
20 |
--------------------------------------------------------------------------------
/packages/playground/react17-scss/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../../tsconfig.json",
3 | "compilerOptions": {
4 | "target": "ES2017",
5 | "jsx": "react",
6 | "jsxFactory": "React.createElement",
7 | "jsxFragmentFactory": "React.Fragment",
8 | "lib": ["ES2017", "DOM", "DOM.Iterable"],
9 | "baseUrl": ".",
10 | "paths": {
11 | "@/*": ["./src/*"]
12 | },
13 | "allowSyntheticDefaultImports": true,
14 | "skipLibCheck": true
15 | },
16 | "include": ["src"],
17 | "exclude": ["node_modules", "dist"]
18 | }
19 |
--------------------------------------------------------------------------------
/packages/react-devtools-client/src/types/plugin.ts:
--------------------------------------------------------------------------------
1 | export interface UserPluginView {
2 | title: string
3 | icon: string
4 | src: string
5 | }
6 |
7 | export interface UserPlugin {
8 | name: string
9 | view?: UserPluginView
10 | }
11 |
12 | export interface DevToolsPluginContext {
13 | tree: any
14 | selectedNodeId: string | null
15 | theme: any
16 | // We can add more RPC-like helpers here later if needed
17 | }
18 |
19 | export interface LoadedPlugin extends UserPlugin {
20 | component?: React.ComponentType
21 | }
22 |
--------------------------------------------------------------------------------
/packages/playground/my-devtools-play/vite.config.ts:
--------------------------------------------------------------------------------
1 | import react from '@vitejs/plugin-react'
2 | import MyDevTools from 'my-devtools'
3 | import { defineConfig } from 'vite'
4 |
5 | // https://vitejs.dev/config/
6 | export default defineConfig({
7 | plugins: [
8 | // User just uses MyDevTools, and gets the "ContextInspector" plugin for free!
9 | MyDevTools.vite({
10 | theme: {
11 | mode: 'dark', // auto or light or dark
12 | primaryColor: 'orange', // Custom primary color
13 | },
14 | }),
15 | react(),
16 | ],
17 | })
18 |
--------------------------------------------------------------------------------
/packages/playground/umi/typings.d.ts:
--------------------------------------------------------------------------------
1 | declare module '*.less' {
2 | const classes: { readonly [key: string]: string }
3 | export default classes
4 | }
5 |
6 | declare module '*.css' {
7 | const classes: { readonly [key: string]: string }
8 | export default classes
9 | }
10 |
11 | declare module '*.svg' {
12 | const content: string
13 | export default content
14 | }
15 |
16 | declare module '*.png' {
17 | const content: string
18 | export default content
19 | }
20 |
21 | declare module '*.jpg' {
22 | const content: string
23 | export default content
24 | }
25 |
--------------------------------------------------------------------------------
/packages/playground/umi/src/global.less:
--------------------------------------------------------------------------------
1 | * {
2 | box-sizing: border-box;
3 | }
4 |
5 | html,
6 | body {
7 | margin: 0;
8 | padding: 0;
9 | font-family:
10 | -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Fira Sans', 'Droid Sans',
11 | 'Helvetica Neue', sans-serif;
12 | -webkit-font-smoothing: antialiased;
13 | -moz-osx-font-smoothing: grayscale;
14 | }
15 |
16 | a {
17 | text-decoration: none;
18 | }
19 |
20 | ul {
21 | list-style: none;
22 | margin: 0;
23 | padding: 0;
24 | }
25 |
26 | button {
27 | font-family: inherit;
28 | }
29 |
--------------------------------------------------------------------------------
/packages/react-devtools-client/src/types/messages.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Message types for communication between client iframe and overlay
3 | */
4 |
5 | export interface InstallComponentTreeHookMessage {
6 | type: '__REACT_DEVTOOLS_INSTALL_COMPONENT_TREE_HOOK__'
7 | }
8 |
9 | export type DevToolsMessage = InstallComponentTreeHookMessage
10 |
11 | /**
12 | * Type guard to check if a message is a valid DevTools message
13 | */
14 | export function isDevToolsMessage(data: any): data is DevToolsMessage {
15 | return data && typeof data.type === 'string' && data.type.startsWith('__REACT_DEVTOOLS_')
16 | }
17 |
--------------------------------------------------------------------------------
/packages/playground/react19/vite.config.ts:
--------------------------------------------------------------------------------
1 | import react from '@vitejs/plugin-react'
2 | import { reactDevToolsPlus } from 'react-devtools-plus/vite'
3 | import { defineConfig } from 'vite'
4 |
5 | export default defineConfig({
6 | plugins: [
7 | reactDevToolsPlus({
8 | enabledEnvironments: ['development', 'test'],
9 | // Enable React Scan auto-injection
10 | scan: {
11 | enabled: true, // Enable injection, but controlled by showToolbar/DevTools
12 | showToolbar: false,
13 | animationSpeed: 'fast',
14 | },
15 | }),
16 | react(),
17 | ],
18 | })
19 |
--------------------------------------------------------------------------------
/packages/react-devtools-client/src/components/assets/ReactLogo.tsx:
--------------------------------------------------------------------------------
1 | export default function ReactLogo({ className }: { className?: string }) {
2 | return (
3 |
11 | )
12 | }
13 |
--------------------------------------------------------------------------------
/packages/react-devtools-scan/tsup.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'tsup'
2 |
3 | export default defineConfig({
4 | entry: {
5 | index: 'src/index.ts',
6 | },
7 | format: ['esm', 'cjs'],
8 | // Use es2017 for maximum compatibility with Webpack 4
9 | // Node 14+ and modern browsers support ES2017
10 | target: 'es2017',
11 | dts: {
12 | resolve: true,
13 | },
14 | outDir: 'dist',
15 | clean: true,
16 | shims: true,
17 | splitting: false,
18 | sourcemap: true,
19 | external: ['react', 'react-dom'],
20 | noExternal: ['react-scan'],
21 | skipNodeModulesBundle: false,
22 | })
23 |
--------------------------------------------------------------------------------
/packages/playground/my-devtools-play/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "my-devtools-play",
3 | "type": "module",
4 | "version": "0.0.0",
5 | "private": true,
6 | "scripts": {
7 | "dev": "vite",
8 | "build": "tsc && vite build",
9 | "preview": "vite preview"
10 | },
11 | "dependencies": {
12 | "my-devtools": "workspace:*",
13 | "react": "^18.3.1",
14 | "react-dom": "^18.3.1"
15 | },
16 | "devDependencies": {
17 | "@types/react": "^18.3.3",
18 | "@types/react-dom": "^18.3.0",
19 | "@vitejs/plugin-react": "^4.3.1",
20 | "typescript": "^5.4.5",
21 | "vite": "^5.2.11"
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/packages/playground/umi/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@react-devtools-plus/playground-umi",
3 | "version": "0.0.0",
4 | "private": true,
5 | "scripts": {
6 | "dev": "umi dev",
7 | "build": "umi build",
8 | "setup": "umi setup",
9 | "start": "npm run dev"
10 | },
11 | "dependencies": {
12 | "@react-devtools-plus/ui": "workspace:*",
13 | "react": "^18.3.1",
14 | "react-dom": "^18.3.1",
15 | "umi": "^4.6.5"
16 | },
17 | "devDependencies": {
18 | "@types/react": "^18.2.72",
19 | "@types/react-dom": "^18.2.23",
20 | "react-devtools-plus": "workspace:*",
21 | "typescript": "^5.9.3"
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/packages/playground/react17-webpack4/src/main.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import ReactDOM from 'react-dom'
3 | import { BrowserRouter } from 'react-router-dom'
4 | import App from './App'
5 | import './style.scss'
6 |
7 | // Note: React Scan is now auto-injected via webpack.config.js
8 | // 注意:React Scan 现在通过 webpack.config.js 自动注入
9 |
10 | // React 17 uses ReactDOM.render (not createRoot which is React 18+)
11 | // eslint-disable-next-line react-dom/no-render
12 | ReactDOM.render(
13 |
14 |
15 |
16 |
17 | ,
18 | document.getElementById('root'),
19 | )
20 |
--------------------------------------------------------------------------------
/packages/playground/umi/.umirc.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'umi'
2 |
3 | export default defineConfig({
4 | // 路由配置
5 | routes: [
6 | { path: '/', component: 'index' },
7 | { path: '/about', component: 'about' },
8 | { path: '/theme', component: 'theme' },
9 | { path: '/counter', component: 'counter' },
10 | ],
11 |
12 | // umi 会自动加载项目根目录下的 plugin.ts 文件
13 | // 无需在 plugins 中手动配置
14 |
15 | // 开发服务器配置
16 | devtool: 'source-map',
17 |
18 | // 禁用 MFSU 以便更好地测试 react-devtools-plus
19 | // 你可以根据需要启用
20 | mfsu: false,
21 |
22 | // 其他 umi 配置
23 | npmClient: 'pnpm',
24 | title: 'React DevTools Plus - Umi Playground',
25 | })
26 |
--------------------------------------------------------------------------------
/packages/playground/my-devtools-play/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ES2020",
4 | "jsx": "react-jsx",
5 | "lib": ["ES2020", "DOM", "DOM.Iterable"],
6 | "useDefineForClassFields": true,
7 | "module": "ESNext",
8 | "moduleResolution": "bundler",
9 | "resolveJsonModule": true,
10 | "types": ["vite/client"],
11 | "allowImportingTsExtensions": true,
12 | "strict": true,
13 | "noFallthroughCasesInSwitch": true,
14 | "noUnusedLocals": true,
15 | "noUnusedParameters": true,
16 | "noEmit": true,
17 | "isolatedModules": true,
18 | "skipLibCheck": true
19 | },
20 | "include": ["src"]
21 | }
22 |
--------------------------------------------------------------------------------
/packages/playground/react17-scss/src/App.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { Route, Switch } from 'react-router-dom'
3 | import Layout from './components/Layout'
4 | import Home from './pages/Home'
5 | import Theme from './pages/Theme'
6 |
7 | export default function App() {
8 | return (
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 | )
24 | }
25 |
--------------------------------------------------------------------------------
/packages/react-devtools/src/umi.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Umi Plugin Entry
3 | *
4 | * @example
5 | * ```ts
6 | * // plugin.ts (in your Umi project)
7 | * import { createUmiPlugin } from 'react-devtools-plus/umi'
8 | *
9 | * export default createUmiPlugin({
10 | * scan: { enabled: true },
11 | * })
12 | * ```
13 | *
14 | * Then add to .umirc.ts:
15 | * ```ts
16 | * export default defineConfig({
17 | * plugins: ['./plugin.ts'],
18 | * })
19 | * ```
20 | */
21 |
22 | import { createUmiPlugin } from './integrations/umi.js'
23 |
24 | export { createUmiPlugin }
25 | export default createUmiPlugin
26 | export type { ReactDevToolsPluginOptions } from './config/types.js'
27 |
--------------------------------------------------------------------------------
/packages/playground/react19/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@react-devtools-plus/playground-react19",
3 | "type": "module",
4 | "version": "0.0.0",
5 | "private": true,
6 | "scripts": {
7 | "dev": "vite",
8 | "build": "NODE_ENV=test vite build --mode test",
9 | "preview": "NODE_ENV=test vite preview --mode test"
10 | },
11 | "dependencies": {
12 | "react": "^19.0.0",
13 | "react-dom": "^19.0.0"
14 | },
15 | "devDependencies": {
16 | "@types/react": "^19.0.0",
17 | "@types/react-dom": "^19.0.0",
18 | "@vitejs/plugin-react": "^4.3.2",
19 | "react-devtools-plus": "workspace:*",
20 | "typescript": "^5.9.3",
21 | "vite": "^7.1.10"
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/docs/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ES2022",
4 | "jsx": "react-jsx",
5 | "lib": ["ES2022", "DOM", "DOM.Iterable"],
6 | "moduleDetection": "force",
7 | "useDefineForClassFields": false,
8 | "experimentalDecorators": true,
9 | "baseUrl": ".",
10 | "module": "ESNext",
11 | "moduleResolution": "bundler",
12 | "paths": {
13 | "@/*": ["./src/*"]
14 | },
15 | "types": ["node"],
16 | "allowImportingTsExtensions": true,
17 | "allowJs": true,
18 | "strict": true,
19 | "noEmit": true,
20 | "isolatedModules": true,
21 | "skipLibCheck": true
22 | },
23 | "include": ["src/**/*", "*.config.ts"],
24 | "exclude": ["node_modules"]
25 | }
26 |
--------------------------------------------------------------------------------
/packages/playground/my-devtools/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "my-devtools",
3 | "version": "0.0.1",
4 | "private": true,
5 | "exports": {
6 | ".": {
7 | "types": "./dist/index.d.ts",
8 | "import": "./dist/index.mjs",
9 | "require": "./dist/index.js"
10 | }
11 | },
12 | "main": "./dist/index.js",
13 | "module": "./dist/index.mjs",
14 | "types": "./dist/index.d.ts",
15 | "scripts": {
16 | "build": "tsup",
17 | "dev": "tsup --watch"
18 | },
19 | "dependencies": {
20 | "react-devtools-plus": "workspace:*"
21 | },
22 | "devDependencies": {
23 | "@types/node": "^20.12.7",
24 | "react": "^18.3.1",
25 | "tsup": "^8.0.2",
26 | "typescript": "^5.4.5"
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/docs/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | React DevTools Plus
7 |
8 |
9 |
10 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/packages/playground/react-webpack/src/main.jsx:
--------------------------------------------------------------------------------
1 | import { ThemeProvider } from '@react-devtools-plus/ui'
2 | import React from 'react'
3 | import ReactDOM from 'react-dom/client'
4 | import { BrowserRouter } from 'react-router-dom'
5 | import App from './App'
6 | import '@react-devtools-plus/ui/style.css'
7 | import './style.css'
8 |
9 | // Note: React Scan is now auto-injected via webpack.config.js
10 | // 注意:React Scan 现在通过 webpack.config.js 自动注入
11 |
12 | ReactDOM.createRoot(document.getElementById('root')).render(
13 |
14 |
15 |
16 |
17 |
18 |
19 | ,
20 | )
21 |
--------------------------------------------------------------------------------
/packages/playground/react/src/main.tsx:
--------------------------------------------------------------------------------
1 | import { ThemeProvider } from '@react-devtools-plus/ui'
2 | import React from 'react'
3 | import ReactDOM from 'react-dom/client'
4 | import { BrowserRouter } from 'react-router-dom'
5 | import App from './App'
6 | import '@react-devtools-plus/ui/style.css'
7 | import './style.css'
8 |
9 | // Note: React Scan is now auto-injected via vite.config.ts
10 | // 注意:React Scan 现在通过 vite.config.ts 自动注入
11 |
12 | ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
13 |
14 |
15 |
16 |
17 |
18 |
19 | ,
20 | )
21 |
--------------------------------------------------------------------------------
/packages/playground/umi/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ESNext",
4 | "jsx": "react-jsx",
5 | "lib": ["DOM", "DOM.Iterable", "ESNext"],
6 | "baseUrl": ".",
7 | "module": "ESNext",
8 | "moduleResolution": "bundler",
9 | "paths": {
10 | "@/*": ["./*"]
11 | },
12 | "resolveJsonModule": true,
13 | "strict": true,
14 | "noEmit": true,
15 | "esModuleInterop": true,
16 | "forceConsistentCasingInFileNames": true,
17 | "isolatedModules": true,
18 | "skipLibCheck": true
19 | },
20 | "include": [
21 | "**/*.ts",
22 | "**/*.tsx",
23 | ".umirc.ts"
24 | ],
25 | "exclude": [
26 | "node_modules",
27 | "dist",
28 | ".umi",
29 | ".umi-production"
30 | ]
31 | }
32 |
--------------------------------------------------------------------------------
/packages/react-devtools-client/vite.config.ts:
--------------------------------------------------------------------------------
1 | import { resolve } from 'node:path'
2 | import react from '@vitejs/plugin-react'
3 | import UnoCSS from 'unocss/vite'
4 | import { defineConfig } from 'vite'
5 |
6 | export default defineConfig({
7 | plugins: [
8 | react(),
9 | UnoCSS(),
10 | ],
11 | resolve: {
12 | alias: {
13 | '~': resolve(__dirname, './src'),
14 | '@react-devtools-plus/kit': resolve(__dirname, '../react-devtools-kit/src/index.ts'),
15 | },
16 | },
17 | build: {
18 | target: 'esnext',
19 | minify: true,
20 | emptyOutDir: true,
21 | outDir: 'dist',
22 | rollupOptions: {
23 | input: {
24 | main: resolve(__dirname, 'index.html'),
25 | },
26 | },
27 | },
28 | base: './',
29 | })
30 |
--------------------------------------------------------------------------------
/packages/react-devtools-core/tsup.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'tsup'
2 |
3 | export default defineConfig({
4 | entry: {
5 | 'index': 'src/index.ts',
6 | 'rpc/index': 'src/rpc/index.ts',
7 | 'client/index': 'src/client/index.ts',
8 | 'plugin/index': 'src/plugin/index.ts',
9 | },
10 | format: ['esm', 'cjs'],
11 | // Use es2017 for maximum compatibility with Webpack 4
12 | target: 'es2017',
13 | dts: {
14 | resolve: true,
15 | },
16 | outDir: 'dist',
17 | shims: true,
18 | clean: true,
19 | splitting: false,
20 | sourcemap: true,
21 | // Bundle these dependencies to avoid pnpm resolution issues in Webpack 4
22 | noExternal: ['superjson', 'birpc', 'copy-anything', 'is-what'],
23 | skipNodeModulesBundle: false,
24 | })
25 |
--------------------------------------------------------------------------------
/packages/playground/my-devtools/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ES2020",
4 | "jsx": "react-jsx",
5 | "lib": ["ES2020", "DOM", "DOM.Iterable"],
6 | "useDefineForClassFields": true,
7 | "baseUrl": ".",
8 | "module": "ESNext",
9 | "moduleResolution": "bundler",
10 | "paths": {
11 | "react-devtools-plus": ["../../react-devtools/src/index.ts"]
12 | },
13 | "resolveJsonModule": true,
14 | "types": ["node"],
15 | "allowImportingTsExtensions": true,
16 | "strict": true,
17 | "noFallthroughCasesInSwitch": true,
18 | "noUnusedLocals": true,
19 | "noUnusedParameters": true,
20 | "noEmit": true,
21 | "isolatedModules": true,
22 | "skipLibCheck": true
23 | },
24 | "include": ["src"]
25 | }
26 |
--------------------------------------------------------------------------------
/pnpm-workspace.yaml:
--------------------------------------------------------------------------------
1 | packages:
2 | - packages/*
3 | - docs
4 | - packages/playground/**
5 |
6 | catalog:
7 | '@iconify/json': ^2.2.395
8 | '@types/node': ^24.7.2
9 | '@types/react': ^18.2.72
10 | '@types/react-dom': ^18.2.23
11 | '@unocss/reset': ^66.5.4
12 | '@vitejs/plugin-react': ^4.3.2
13 | birpc: ^0.2.19
14 | colord: ^2.9.3
15 | mitt: ^3.0.1
16 | pathe: ^2.0.3
17 | perfect-debounce: ^2.0.0
18 | react: ^18.3.1
19 | react-dom: ^18.3.1
20 | sass-embedded: ^1.93.2
21 | serve: ^14.2.5
22 | shiki: ^3.13.0
23 | tsdown: ^0.15.7
24 | tsup: ^8.5.1
25 | typescript: ^5.9.3
26 | unocss: ^66.5.4
27 | unplugin-auto-import: ^20.2.0
28 | vite: ^7.1.10
29 | vite-hot-client: ^2.1.0
30 | vite-plugin-dts: ^4.5.4
31 | vite-plugin-inspect: ^11.3.3
32 |
--------------------------------------------------------------------------------
/packages/playground/react17-webpack4/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../../tsconfig.json",
3 | "compilerOptions": {
4 | "target": "ES2017",
5 | "jsx": "react",
6 | "jsxFactory": "React.createElement",
7 | "jsxFragmentFactory": "React.Fragment",
8 | "lib": ["ES2017", "DOM", "DOM.Iterable"],
9 | "baseUrl": ".",
10 | "module": "ESNext",
11 | "moduleResolution": "node",
12 | "paths": {
13 | "@/*": ["./src/*"]
14 | },
15 | "resolveJsonModule": true,
16 | "allowJs": true,
17 | "noEmit": true,
18 | "allowSyntheticDefaultImports": true,
19 | "forceConsistentCasingInFileNames": true,
20 | "isolatedModules": true,
21 | "skipLibCheck": true
22 | },
23 | "include": ["src"],
24 | "exclude": ["node_modules", "dist"]
25 | }
26 |
--------------------------------------------------------------------------------
/packages/playground/react17-scss/src/main.tsx:
--------------------------------------------------------------------------------
1 | import { ThemeProvider } from '@react-devtools-plus/ui'
2 | import React from 'react'
3 | import { createRoot } from 'react-dom/client'
4 | import { BrowserRouter } from 'react-router-dom'
5 | import App from './App'
6 | import '@react-devtools-plus/ui/style.css'
7 | import './style.scss'
8 |
9 | // Note: React Scan is now auto-injected via vite.config.ts
10 | // 注意:React Scan 现在通过 vite.config.ts 自动注入
11 |
12 | // React 17 uses ReactDOM.render instead of createRoot
13 | createRoot(document.getElementById('root')).render(
14 |
15 |
16 |
17 |
18 |
19 |
20 | ,
21 | )
22 |
--------------------------------------------------------------------------------
/packages/playground/react/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@react-devtools-plus/playground-react",
3 | "type": "module",
4 | "version": "0.0.0",
5 | "private": true,
6 | "scripts": {
7 | "dev": "vite",
8 | "build": "NODE_ENV=test vite build --mode test",
9 | "preview": "NODE_ENV=test vite preview --mode test"
10 | },
11 | "dependencies": {
12 | "@react-devtools-plus/ui": "workspace:*",
13 | "@types/react-router-dom": "^5.3.3",
14 | "react": "^18.3.1",
15 | "react-dom": "^18.3.1",
16 | "react-router-dom": "^6.26.0"
17 | },
18 | "devDependencies": {
19 | "@types/react": "^18.2.72",
20 | "@types/react-dom": "^18.2.23",
21 | "@vitejs/plugin-react": "^4.3.2",
22 | "react-devtools-plus": "workspace:*",
23 | "typescript": "^5.9.3",
24 | "vite": "^7.1.10"
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/docs/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-devtools-plus-landing",
3 | "type": "module",
4 | "version": "0.0.0",
5 | "private": true,
6 | "scripts": {
7 | "dev": "vite",
8 | "build": "vite build",
9 | "preview": "vite preview"
10 | },
11 | "dependencies": {
12 | "@iconify/react": "^6.0.2",
13 | "i18next": "^23.10.1",
14 | "lucide-react": "^0.556.0",
15 | "react": "^19.2.1",
16 | "react-dom": "^19.2.1",
17 | "react-i18next": "^14.1.3",
18 | "react-router-dom": "^7.0.0",
19 | "shiki": "^3.19.0"
20 | },
21 | "devDependencies": {
22 | "@types/node": "^22.14.0",
23 | "@vitejs/plugin-react": "^5.0.0",
24 | "autoprefixer": "^10.4.20",
25 | "postcss": "^8.5.3",
26 | "tailwindcss": "^3.4.17",
27 | "typescript": "~5.8.2",
28 | "vite": "^6.2.0"
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/packages/playground/react17-scss/vite.config.ts:
--------------------------------------------------------------------------------
1 | import react from '@vitejs/plugin-react'
2 | import ReactDevTools from 'react-devtools-plus'
3 | import { defineConfig } from 'vite'
4 |
5 | export default defineConfig({
6 | plugins: [
7 | ReactDevTools.vite({
8 | enabledEnvironments: ['development', 'test'],
9 | // Enable React Scan auto-injection
10 | scan: {
11 | enabled: true, // Enable injection, but controlled by showToolbar/DevTools
12 | showToolbar: false,
13 | animationSpeed: 'fast',
14 | },
15 | }),
16 | react(),
17 | ],
18 | css: {
19 | preprocessorOptions: {
20 | scss: {
21 | // Enable modern SCSS API for better compatibility
22 | api: 'modern-compiler',
23 | },
24 | },
25 | },
26 | build: {
27 | target: 'es2017',
28 | },
29 | })
30 |
--------------------------------------------------------------------------------
/packages/playground/my-devtools/tsup.config.ts:
--------------------------------------------------------------------------------
1 | import { cpSync, existsSync, mkdirSync } from 'node:fs'
2 | import { resolve } from 'node:path'
3 | import { defineConfig } from 'tsup'
4 |
5 | export default defineConfig({
6 | entry: ['src/index.ts'],
7 | format: ['esm', 'cjs'],
8 | dts: true,
9 | clean: true,
10 | external: ['react-devtools-plus', 'react'],
11 | shims: true, // Inject shims for __dirname, import.meta.url, etc.
12 | onSuccess: async () => {
13 | // Copy plugin source files to dist so they can be loaded by the DevTools server
14 | const srcPlugins = resolve(__dirname, 'src/plugins')
15 | const distPlugins = resolve(__dirname, 'dist/plugins')
16 |
17 | if (!existsSync(distPlugins)) {
18 | mkdirSync(distPlugins, { recursive: true })
19 | }
20 |
21 | cpSync(srcPlugins, distPlugins, { recursive: true })
22 | },
23 | })
24 |
--------------------------------------------------------------------------------
/packages/shared/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@react-devtools-plus/shared",
3 | "type": "module",
4 | "version": "0.4.0",
5 | "author": "wzc520pyfm",
6 | "license": "MIT",
7 | "repository": {
8 | "directory": "packages/shared",
9 | "type": "git",
10 | "url": "git+https://github.com/vuejs/devtools.git"
11 | },
12 | "exports": {
13 | ".": {
14 | "import": "./dist/index.js",
15 | "require": "./dist/index.cjs"
16 | }
17 | },
18 | "main": "./dist/index.cjs",
19 | "module": "./dist/index.js",
20 | "files": [
21 | "dist"
22 | ],
23 | "scripts": {
24 | "build": "tsdown",
25 | "prepare:type": "tsdown --dts-only",
26 | "stub": "tsdown --watch --onSuccess 'tsdown --dts-only'"
27 | },
28 | "dependencies": {
29 | "rfdc": "^1.4.1"
30 | },
31 | "devDependencies": {
32 | "@types/node": "catalog:"
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/packages/react-devtools-kit/src/messaging/presets/index.ts:
--------------------------------------------------------------------------------
1 | import { MergeableChannelOptions } from '../types/channel'
2 | import { createBroadcastClientChannel } from './broadcast/client.js'
3 | import { createBroadcastServerChannel } from './broadcast/server.js'
4 | import { createIframeClientChannel } from './iframe/client.js'
5 | import { createIframeServerChannel } from './iframe/server.js'
6 |
7 | export type Presets = 'iframe' | 'broadcast'
8 |
9 | export function getChannel(preset: Presets, host: 'client' | 'server' = 'client'): MergeableChannelOptions {
10 | const channels = {
11 | iframe: {
12 | client: createIframeClientChannel,
13 | server: createIframeServerChannel,
14 | },
15 | broadcast: {
16 | client: createBroadcastClientChannel,
17 | server: createBroadcastServerChannel,
18 | },
19 | }[preset]
20 | return channels[host]()
21 | }
22 |
--------------------------------------------------------------------------------
/packages/playground/react17-webpack4/src/components/Layout.jsx:
--------------------------------------------------------------------------------
1 | import { Link, useLocation } from 'react-router-dom'
2 |
3 | export default function Layout({ children }) {
4 | const location = useLocation()
5 |
6 | const isActive = (path) => {
7 | return location.pathname === path ? 'active' : ''
8 | }
9 |
10 | return (
11 |
12 |
16 |
24 |
{children}
25 |
26 | )
27 | }
28 |
--------------------------------------------------------------------------------
/packages/react-devtools-ui/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ES2017",
4 | "jsx": "react-jsx",
5 | "lib": ["ES2020", "DOM", "DOM.Iterable"],
6 | "useDefineForClassFields": true,
7 |
8 | /* Paths */
9 | "baseUrl": ".",
10 | "module": "ESNext",
11 |
12 | /* Bundler mode */
13 | "moduleResolution": "bundler",
14 | "paths": {
15 | "@/*": ["./src/*"]
16 | },
17 | "resolveJsonModule": true,
18 | "allowImportingTsExtensions": true,
19 |
20 | /* Linting */
21 | "strict": true,
22 | "noFallthroughCasesInSwitch": true,
23 | "noUnusedLocals": true,
24 | "noUnusedParameters": true,
25 | "declaration": true,
26 | "declarationMap": true,
27 | "noEmit": true,
28 | "isolatedModules": true,
29 | "skipLibCheck": true
30 | },
31 | "references": [{ "path": "./tsconfig.node.json" }],
32 | "include": ["src"]
33 | }
34 |
--------------------------------------------------------------------------------
/packages/playground/my-devtools-play/src/App.tsx:
--------------------------------------------------------------------------------
1 | import { useState } from 'react'
2 |
3 | function App() {
4 | const [count, setCount] = useState(0)
5 |
6 | return (
7 |
8 |
My DevTools Playground
9 |
10 |
15 |
16 | Press
17 | {' '}
18 | Option+Shift+D
19 | {' '}
20 | to open DevTools.
21 |
22 |
23 | You should see the
24 | {' '}
25 | "Inspector"
26 | {' '}
27 | tab which comes pre-installed with MyDevTools!
28 |
29 |
30 |
31 | )
32 | }
33 |
34 | export default App
35 |
--------------------------------------------------------------------------------
/packages/playground/react17-scss/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@react-devtools-plus/playground-react17-scss",
3 | "type": "module",
4 | "version": "0.0.0",
5 | "private": true,
6 | "engines": {
7 | "node": ">=14.0.0"
8 | },
9 | "scripts": {
10 | "dev": "vite",
11 | "build": "NODE_ENV=test vite build --mode test",
12 | "preview": "NODE_ENV=test vite preview --mode test"
13 | },
14 | "dependencies": {
15 | "@react-devtools-plus/ui": "workspace:*",
16 | "@types/react-router-dom": "^5.3.3",
17 | "react": "^17.0.2",
18 | "react-dom": "^17.0.2",
19 | "react-router-dom": "^5.3.4"
20 | },
21 | "devDependencies": {
22 | "@types/react": "^17.0.75",
23 | "@types/react-dom": "^17.0.25",
24 | "@vitejs/plugin-react": "^4.3.2",
25 | "react-devtools-plus": "workspace:*",
26 | "sass": "^1.83.4",
27 | "typescript": "^5.9.3",
28 | "vite": "^7.1.10"
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/packages/shared/src/env.ts:
--------------------------------------------------------------------------------
1 | export const isBrowser = typeof navigator !== 'undefined'
2 | export const target = (typeof window !== 'undefined'
3 | ? window
4 | : typeof globalThis !== 'undefined'
5 | ? globalThis
6 | // eslint-disable-next-line no-restricted-globals
7 | : typeof global !== 'undefined'
8 | // eslint-disable-next-line no-restricted-globals
9 | ? global
10 | : {}) as typeof globalThis
11 |
12 | export const isInChromePanel = typeof target.chrome !== 'undefined' && !!target.chrome.devtools
13 | export const isInIframe = isBrowser && target.self !== target.top
14 | export const isInElectron = typeof navigator !== 'undefined' && navigator.userAgent?.toLowerCase().includes('electron')
15 | // @ts-expect-error skip type check
16 | export const isNuxtApp = typeof window !== 'undefined' && !!window.__NUXT__
17 | export const isInSeparateWindow = !isInIframe && !isInChromePanel && !isInElectron
18 |
--------------------------------------------------------------------------------
/docs/src/pages/index.ts:
--------------------------------------------------------------------------------
1 | export { Community } from './Community'
2 | export { Assets } from './docs/Assets'
3 | export { ComponentTree } from './docs/ComponentTree'
4 |
5 | export { Configuration } from './docs/Configuration'
6 | export { Contributing } from './docs/Contributing'
7 | export { FAQ } from './docs/FAQ'
8 | export { Installation } from './docs/Installation'
9 | // Doc Pages
10 | export { Introduction } from './docs/Introduction'
11 | export { ModuleGraph } from './docs/ModuleGraph'
12 | export { OpenInEditor } from './docs/OpenInEditor'
13 | export { QuickStart } from './docs/QuickStart'
14 | export { Scan } from './docs/Scan'
15 | export { Timeline } from './docs/Timeline'
16 | export { Troubleshooting } from './docs/Troubleshooting'
17 | export { ViteSetup } from './docs/ViteSetup'
18 | export { WebpackSetup } from './docs/WebpackSetup'
19 | // Main Pages
20 | export { Features } from './Features'
21 | export { Integration } from './Integration'
22 |
--------------------------------------------------------------------------------
/packages/react-devtools-kit/src/messaging/presets/broadcast/client.ts:
--------------------------------------------------------------------------------
1 | import { isBrowser } from '@react-devtools-plus/shared'
2 | import SuperJSON from 'superjson'
3 | import { MergeableChannelOptions } from '../../types/channel'
4 |
5 | const __REACT_DEVTOOLS_KIT_BROADCAST_MESSAGING_EVENT_KEY = '__REACT_DEVTOOLS_KIT_BROADCAST_MESSAGE__'
6 |
7 | export function createBroadcastClientChannel(): MergeableChannelOptions {
8 | if (!isBrowser) {
9 | return {
10 | post: () => {},
11 | on: () => {},
12 | }
13 | }
14 |
15 | const channel = new BroadcastChannel(__REACT_DEVTOOLS_KIT_BROADCAST_MESSAGING_EVENT_KEY)
16 |
17 | return {
18 | post: data => channel.postMessage(SuperJSON.stringify(data)),
19 | on: handler => channel.addEventListener('message', (event) => {
20 | try {
21 | handler(SuperJSON.parse(event.data))
22 | }
23 | catch (e) {
24 | // ignore parsing errors
25 | }
26 | }),
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/packages/react-devtools-kit/src/messaging/presets/broadcast/server.ts:
--------------------------------------------------------------------------------
1 | import { isBrowser } from '@react-devtools-plus/shared'
2 | import SuperJSON from 'superjson'
3 | import { MergeableChannelOptions } from '../../types/channel'
4 |
5 | const __REACT_DEVTOOLS_KIT_BROADCAST_MESSAGING_EVENT_KEY = '__REACT_DEVTOOLS_KIT_BROADCAST_MESSAGE__'
6 |
7 | export function createBroadcastServerChannel(): MergeableChannelOptions {
8 | if (!isBrowser) {
9 | return {
10 | post: () => {},
11 | on: () => {},
12 | }
13 | }
14 |
15 | const channel = new BroadcastChannel(__REACT_DEVTOOLS_KIT_BROADCAST_MESSAGING_EVENT_KEY)
16 |
17 | return {
18 | post: data => channel.postMessage(SuperJSON.stringify(data)),
19 | on: handler => channel.addEventListener('message', (event) => {
20 | try {
21 | handler(SuperJSON.parse(event.data))
22 | }
23 | catch (e) {
24 | // ignore parsing errors
25 | }
26 | }),
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/packages/playground/react-webpack/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@react-devtools-plus/playground-react-webpack",
3 | "version": "0.0.0",
4 | "private": true,
5 | "scripts": {
6 | "dev": "webpack serve --mode development",
7 | "build": "webpack --mode production"
8 | },
9 | "dependencies": {
10 | "@react-devtools-plus/scan": "workspace:*",
11 | "@react-devtools-plus/ui": "workspace:*",
12 | "react": "^18.3.1",
13 | "react-dom": "^18.3.1",
14 | "react-router-dom": "^6.26.0"
15 | },
16 | "devDependencies": {
17 | "@babel/core": "^7.28.4",
18 | "@babel/preset-react": "^7.28.4",
19 | "@babel/preset-typescript": "^7.28.4",
20 | "babel-loader": "^9.2.1",
21 | "css-loader": "^7.1.2",
22 | "html-webpack-plugin": "^5.6.3",
23 | "react-devtools-plus": "workspace:*",
24 | "style-loader": "^4.0.0",
25 | "webpack": "^5.97.1",
26 | "webpack-cli": "^5.1.4",
27 | "webpack-dev-server": "^5.1.0"
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/packages/react-devtools-client/src/events.ts:
--------------------------------------------------------------------------------
1 | type EventHandler = (event: T) => void
2 |
3 | class EventEmitter {
4 | private events: Map> = new Map()
5 |
6 | on(type: string, handler: EventHandler) {
7 | if (!this.events.has(type)) {
8 | this.events.set(type, new Set())
9 | }
10 | this.events.get(type)!.add(handler)
11 | }
12 |
13 | off(type: string, handler: EventHandler) {
14 | const handlers = this.events.get(type)
15 | if (handlers) {
16 | handlers.delete(handler)
17 | }
18 | }
19 |
20 | emit(type: string, event: T) {
21 | const handlers = this.events.get(type)
22 | if (handlers) {
23 | handlers.forEach((handler) => {
24 | try {
25 | handler(event)
26 | }
27 | catch (e) {
28 | console.error('Error in event handler', e)
29 | }
30 | })
31 | }
32 | }
33 | }
34 |
35 | export const pluginEvents = new EventEmitter()
36 |
--------------------------------------------------------------------------------
/packages/playground/react17-scss/src/components/Layout.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { Link, useLocation } from 'react-router-dom'
3 |
4 | interface LayoutProps {
5 | children: React.ReactNode
6 | }
7 |
8 | export default function Layout({ children }: LayoutProps) {
9 | const location = useLocation()
10 |
11 | const isActive = (path: string) => {
12 | return location.pathname === path ? 'active' : ''
13 | }
14 |
15 | return (
16 |
17 |
21 |
29 |
{children}
30 |
31 | )
32 | }
33 |
--------------------------------------------------------------------------------
/packages/react-devtools-client/src/composables/useComponentTreeHook.ts:
--------------------------------------------------------------------------------
1 | import type { InstallComponentTreeHookMessage } from '~/types/messages'
2 | import { useEffect, useRef } from 'react'
3 |
4 | /**
5 | * Custom hook to ensure component tree hook is installed
6 | * Only sends the installation request once when tree is not available
7 | */
8 | export function useComponentTreeHook(tree: any) {
9 | const installRequestedRef = useRef(false)
10 |
11 | useEffect(() => {
12 | // Only request installation once and only if tree is not available
13 | if (!installRequestedRef.current && !tree) {
14 | installRequestedRef.current = true
15 |
16 | // Request the overlay (parent window) to install the component tree hook
17 | // Since we're in an iframe, we need to send message to parent
18 | const message: InstallComponentTreeHookMessage = {
19 | type: '__REACT_DEVTOOLS_INSTALL_COMPONENT_TREE_HOOK__',
20 | }
21 | window.parent.postMessage(message, '*')
22 | }
23 | }, [tree])
24 | }
25 |
--------------------------------------------------------------------------------
/packages/react-devtools-overlay/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@react-devtools-plus/overlay",
3 | "type": "module",
4 | "version": "0.4.0",
5 | "private": true,
6 | "author": "wzc520pyfm",
7 | "license": "MIT",
8 | "exports": {
9 | "./*": "./dist/*"
10 | },
11 | "files": [
12 | "dist"
13 | ],
14 | "engines": {
15 | "node": ">=v14.21.3"
16 | },
17 | "scripts": {
18 | "build": "vite build",
19 | "stub": "vite build --watch"
20 | },
21 | "dependencies": {
22 | "@react-devtools-plus/core": "workspace:^",
23 | "@react-devtools-plus/kit": "workspace:^",
24 | "@react-devtools-plus/scan": "workspace:^",
25 | "react": "catalog:",
26 | "react-dom": "catalog:"
27 | },
28 | "devDependencies": {
29 | "@types/node": "catalog:",
30 | "@types/react": "catalog:",
31 | "@types/react-dom": "catalog:",
32 | "@vitejs/plugin-react": "catalog:",
33 | "rollup-plugin-external-globals": "^0.13.0",
34 | "typescript": "catalog:",
35 | "vite": "catalog:"
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/packages/react-devtools-client/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@react-devtools-plus/client",
3 | "type": "module",
4 | "version": "0.4.0",
5 | "private": true,
6 | "author": "wzc520pyfm",
7 | "license": "MIT",
8 | "exports": {
9 | "./*": "./dist/*"
10 | },
11 | "files": [
12 | "dist"
13 | ],
14 | "engines": {
15 | "node": ">=v14.21.3"
16 | },
17 | "scripts": {
18 | "build": "vite build",
19 | "stub": "vite build --watch"
20 | },
21 | "dependencies": {
22 | "@react-devtools-plus/kit": "workspace:^",
23 | "@react-devtools-plus/ui": "workspace:^",
24 | "react": "catalog:",
25 | "react-dom": "catalog:",
26 | "react-router-dom": "^6.26.0",
27 | "vis-network": "^9.1.9"
28 | },
29 | "devDependencies": {
30 | "@types/node": "catalog:",
31 | "@types/react": "catalog:",
32 | "@types/react-dom": "catalog:",
33 | "@unocss/reset": "catalog:",
34 | "@vitejs/plugin-react": "catalog:",
35 | "typescript": "catalog:",
36 | "unocss": "catalog:",
37 | "vite": "catalog:"
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/packages/react-devtools/src/index.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * React DevTools Plugin
3 | *
4 | * A universal plugin for Vite and Webpack that provides:
5 | * - Component tree inspection
6 | * - React Scan performance monitoring
7 | * - Source code location tracking
8 | * - Open in editor functionality
9 | *
10 | * Supports:
11 | * - Webpack 4/5
12 | * - webpack-dev-server 3/4+
13 | * - React 17/18+
14 | * - Vite 4+
15 | *
16 | * @example
17 | * ```ts
18 | * // Vite
19 | * import ReactDevTools from 'react-devtools-plus'
20 | * export default defineConfig({
21 | * plugins: [
22 | * ReactDevTools(),
23 | * ],
24 | * })
25 | *
26 | * // Webpack
27 | * import { webpack as ReactDevTools } from 'react-devtools
28 | * module.exports = {
29 | * plugins: [
30 | * ReactDevTools(),
31 | * ],
32 | * }
33 | * ```
34 | */
35 |
36 | export type {
37 | EnabledEnvironments,
38 | ResolvedPluginConfig,
39 | ScanConfig,
40 | SourcePathMode,
41 | } from './config/types.js'
42 |
43 | export { default, vite, webpack } from './unplugin.js'
44 | export type { ReactDevToolsPluginOptions } from './unplugin.js'
45 |
--------------------------------------------------------------------------------
/packages/react-devtools-kit/src/messaging/presets/iframe/client.ts:
--------------------------------------------------------------------------------
1 | import { isBrowser } from '@react-devtools-plus/shared'
2 | import SuperJSON from 'superjson'
3 | import { MergeableChannelOptions } from '../../types/channel'
4 | import { __REACT_DEVTOOLS_KIT_IFRAME_MESSAGING_EVENT_KEY } from './context'
5 |
6 | export function createIframeClientChannel(): MergeableChannelOptions {
7 | if (!isBrowser) {
8 | return {
9 | post: () => {},
10 | on: () => {},
11 | }
12 | }
13 |
14 | return {
15 | post: data => window.parent.postMessage(SuperJSON.stringify({
16 | event: __REACT_DEVTOOLS_KIT_IFRAME_MESSAGING_EVENT_KEY,
17 | data,
18 | }), '*'),
19 | on: handler => window.addEventListener('message', (event) => {
20 | try {
21 | const parsed = SuperJSON.parse<{ event: string, data: unknown }>(event.data)
22 | if (event.source === window.parent && parsed.event === __REACT_DEVTOOLS_KIT_IFRAME_MESSAGING_EVENT_KEY) {
23 | handler(parsed.data)
24 | }
25 | }
26 | catch (e) {
27 | // ignore parsing errors
28 | }
29 | }),
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 webfansplz
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/packages/ui-story/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@react-devtools-plus/ui-story",
3 | "type": "module",
4 | "version": "0.4.0",
5 | "private": true,
6 | "description": "Storybook for React DevTools UI components",
7 | "scripts": {
8 | "dev": "storybook dev -p 6006",
9 | "build": "storybook build",
10 | "preview": "http-server storybook-static -p 6006",
11 | "prepare:type": "echo 'Skipping type generation for ui-story'"
12 | },
13 | "dependencies": {
14 | "@react-devtools-plus/ui": "workspace:*",
15 | "react": "catalog:",
16 | "react-dom": "catalog:"
17 | },
18 | "devDependencies": {
19 | "@storybook/addon-essentials": "^8.5.3",
20 | "@storybook/addon-interactions": "^8.5.3",
21 | "@storybook/addon-links": "^8.5.3",
22 | "@storybook/blocks": "^8.5.3",
23 | "@storybook/react": "^8.5.3",
24 | "@storybook/react-vite": "^8.5.3",
25 | "@storybook/test": "^8.5.3",
26 | "@types/react": "catalog:",
27 | "@types/react-dom": "catalog:",
28 | "http-server": "^14.1.1",
29 | "storybook": "^8.5.3",
30 | "typescript": "catalog:",
31 | "vite": "catalog:"
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/packages/playground/my-devtools/src/index.ts:
--------------------------------------------------------------------------------
1 | import type { ReactDevToolsPluginOptions } from 'react-devtools-plus'
2 | import ReactDevTools from 'react-devtools-plus'
3 | import { getBuiltinPlugins } from './plugins'
4 |
5 | // Merge user options with our defaults
6 | function resolveOptions(options: ReactDevToolsPluginOptions = {}): ReactDevToolsPluginOptions {
7 | const builtinPlugins = getBuiltinPlugins()
8 |
9 | return {
10 | ...options,
11 | // Ensure scan is enabled by default if not provided
12 | scan: options.scan ?? {
13 | enabled: true,
14 | showToolbar: false,
15 | animationSpeed: 'fast',
16 | },
17 | plugins: [
18 | ...builtinPlugins,
19 | ...(options.plugins || []),
20 | ],
21 | }
22 | }
23 |
24 | export const vite = (options?: ReactDevToolsPluginOptions) => {
25 | return ReactDevTools.vite(resolveOptions(options))
26 | }
27 |
28 | export const webpack = (options?: ReactDevToolsPluginOptions) => {
29 | return ReactDevTools.webpack(resolveOptions(options))
30 | }
31 |
32 | export default {
33 | vite,
34 | webpack,
35 | }
36 |
37 | export type { ReactDevToolsPluginOptions }
38 |
--------------------------------------------------------------------------------
/packages/react-devtools-kit/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@react-devtools-plus/kit",
3 | "type": "module",
4 | "version": "0.4.0",
5 | "author": "wzc520pyfm",
6 | "license": "MIT",
7 | "repository": {
8 | "directory": "packages/react-devtools-kit",
9 | "type": "git",
10 | "url": "git+https://github.com/vuejs/devtools.git"
11 | },
12 | "exports": {
13 | ".": {
14 | "import": "./dist/index.js",
15 | "require": "./dist/index.cjs"
16 | }
17 | },
18 | "main": "./dist/index.cjs",
19 | "module": "./dist/index.js",
20 | "files": [
21 | "**.d.ts",
22 | "dist"
23 | ],
24 | "scripts": {
25 | "build": "tsdown --clean",
26 | "prepare:type": "tsdown --dts-only",
27 | "stub": "tsdown --watch --onSuccess 'tsdown --dts-only'"
28 | },
29 | "dependencies": {
30 | "@react-devtools-plus/shared": "workspace:^",
31 | "birpc": "^2.6.1",
32 | "hookable": "^5.5.3",
33 | "mitt": "catalog:",
34 | "perfect-debounce": "catalog:",
35 | "superjson": "^2.2.2"
36 | },
37 | "devDependencies": {
38 | "@types/node": "catalog:",
39 | "tsdown": "catalog:",
40 | "typescript": "^5.5.0"
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/packages/playground/my-devtools/src/plugins/index.ts:
--------------------------------------------------------------------------------
1 | import path from 'node:path'
2 | import { fileURLToPath } from 'node:url'
3 |
4 | // Helper to get absolute path to plugin files
5 | // In a real package build, you might need to copy these files to dist or handle them differently
6 | // For this monorepo playground, we point to the source
7 | const __dirname = path.dirname(fileURLToPath(import.meta.url))
8 |
9 | export const getBuiltinPlugins = () => {
10 | // When running from dist/index.js, __dirname is .../packages/playground/my-devtools/dist
11 | // We copy the plugins to dist/plugins during build
12 | const pluginPath = path.resolve(__dirname, './plugins/ContextInspector.tsx')
13 |
14 | return [
15 | {
16 | name: 'context-inspector',
17 | view: {
18 | title: 'Inspector',
19 | // Eye icon
20 | icon: '',
21 | src: pluginPath,
22 | },
23 | },
24 | ]
25 | }
26 |
--------------------------------------------------------------------------------
/packages/playground/react17-webpack4/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@react-devtools-plus/playground-react17-webpack4",
3 | "version": "0.0.0",
4 | "private": true,
5 | "engines": {
6 | "node": ">=16.0.0"
7 | },
8 | "scripts": {
9 | "dev": "webpack-dev-server --mode development",
10 | "build": "webpack --mode production"
11 | },
12 | "dependencies": {
13 | "react": "^17.0.2",
14 | "react-dom": "^17.0.2",
15 | "react-router-dom": "^5.3.4"
16 | },
17 | "devDependencies": {
18 | "@babel/core": "^7.26.0",
19 | "@babel/preset-env": "^7.26.0",
20 | "@babel/preset-react": "^7.26.3",
21 | "@babel/preset-typescript": "^7.26.0",
22 | "@types/react": "^17.0.75",
23 | "@types/react-dom": "^17.0.25",
24 | "@types/react-router-dom": "^5.3.3",
25 | "babel-loader": "^8.2.5",
26 | "css-loader": "^5.2.7",
27 | "html-webpack-plugin": "^4.5.2",
28 | "react-devtools-plus": "workspace:*",
29 | "sass": "^1.83.4",
30 | "sass-loader": "^10.5.2",
31 | "style-loader": "^2.0.0",
32 | "typescript": "^5.9.3",
33 | "webpack": "^4.46.0",
34 | "webpack-cli": "^3.3.12",
35 | "webpack-dev-server": "^3.11.3"
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "eslint.useFlatConfig": true,
3 | "prettier.enable": false,
4 | "editor.codeActionsOnSave": {
5 | "source.fixAll": "explicit"
6 | },
7 | "editor.formatOnSave": false,
8 | // Silent the stylistic rules in you IDE, but still auto fix them
9 | "eslint.rules.customizations": [
10 | { "rule": "style/*", "severity": "off" },
11 | { "rule": "*-indent", "severity": "off" },
12 | { "rule": "*-spacing", "severity": "off" },
13 | { "rule": "*-spaces", "severity": "off" },
14 | { "rule": "*-order", "severity": "off" },
15 | { "rule": "*-dangle", "severity": "off" },
16 | { "rule": "*-newline", "severity": "off" },
17 | { "rule": "*quotes", "severity": "off" },
18 | { "rule": "*semi", "severity": "off" }
19 | ],
20 | // Enable eslint for all supported languages
21 | "eslint.validate": [
22 | "javascript",
23 | "javascriptreact",
24 | "typescript",
25 | "typescriptreact",
26 | "vue",
27 | "html",
28 | "markdown",
29 | "json",
30 | "jsonc",
31 | "yaml"
32 | ],
33 | "scss.lint.unknownAtRules": "ignore",
34 | "typescript.tsdk": "node_modules/typescript/lib",
35 | "cSpell.words": [
36 | "Mergeable"
37 | ]
38 | }
39 |
--------------------------------------------------------------------------------
/packages/react-devtools-ui/src/components/Checkbox/Checkbox.tsx:
--------------------------------------------------------------------------------
1 | import type { InputHTMLAttributes } from 'react'
2 | import { forwardRef } from 'react'
3 | import styles from './Checkbox.module.css'
4 |
5 | export interface CheckboxProps extends Omit, 'onChange'> {
6 | checked?: boolean
7 | onChange?: (checked: boolean) => void
8 | label?: string
9 | }
10 |
11 | export const Checkbox = forwardRef(({ checked, onChange, label, disabled, className = '', ...props }, ref) => {
12 | return (
13 |
30 | )
31 | })
32 |
33 | Checkbox.displayName = 'Checkbox'
34 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es2017",
4 | "jsx": "react-jsx",
5 | "lib": ["esnext", "dom"],
6 | "module": "esnext",
7 | "moduleResolution": "bundler",
8 | "paths": {
9 | "@react-devtools-plus/shared": ["./packages/shared/src/index.ts"],
10 | "@react-devtools-plus/kit": ["./packages/react-devtools-kit/src/index.ts"],
11 | "@react-devtools-plus/client": ["./packages/react-devtools-client/src/index.ts"],
12 | "@react-devtools-plus/overlay": ["./packages/react-devtools-overlay/src/index.ts"],
13 | "react-devtools-plus": ["./packages/react-devtools/src/index.ts"]
14 | },
15 | "resolveJsonModule": true,
16 | "types": [
17 | "chrome",
18 | "vite/client",
19 | "vitest/globals"
20 | ],
21 | "allowJs": true,
22 | "strict": true,
23 | "strictNullChecks": true,
24 | "noImplicitAny": false,
25 | // We use tsup/vite instead of tsc to build the package, so we don't need to care about this option.
26 | // Add outDir option to avoid tsconfig error in monorepo.
27 | "outDir": "dist",
28 | "esModuleInterop": true
29 | },
30 | "exclude": [
31 | "**/vite/src/overlay/**",
32 | "**/dist/**",
33 | "**/node_modules/**",
34 | "**/**/*.js"
35 | ]
36 | }
37 |
--------------------------------------------------------------------------------
/packages/playground/react-webpack/README.md:
--------------------------------------------------------------------------------
1 | # React Webpack Playground
2 |
3 | This is a playground for React applications using Webpack with React DevTools support via unplugin.
4 |
5 | ## Features
6 |
7 | - ✅ React DevTools support via `react-devtools/webpack`
8 | - ✅ Hot Module Replacement (HMR)
9 | - ✅ Development server with DevTools client
10 |
11 | ## Usage
12 |
13 | ```bash
14 | # Install dependencies
15 | pnpm install
16 |
17 | # Start development server
18 | pnpm dev
19 |
20 | # Build for production
21 | pnpm build
22 | ```
23 |
24 | ## React DevTools
25 |
26 | The playground uses the unplugin-based React DevTools plugin, which supports both Vite and Webpack.
27 |
28 | To configure DevTools, edit `webpack.config.js`:
29 |
30 | ```javascript
31 | const ReactDevToolsPlugin = require('react-devtools/webpack')
32 |
33 | module.exports = {
34 | plugins: [
35 | ReactDevToolsPlugin({
36 | enabledEnvironments: ['development', 'test'],
37 | }),
38 | ],
39 | }
40 | ```
41 |
42 | ## How It Works
43 |
44 | The plugin uses [unplugin](https://github.com/unjs/unplugin) to provide a unified plugin interface that works with both Vite and Webpack. This means:
45 |
46 | - Same API for both build tools
47 | - Consistent behavior across environments
48 | - Easy migration between Vite and Webpack
49 |
--------------------------------------------------------------------------------
/packages/react-devtools-ui/src/components/Card/Card.module.css:
--------------------------------------------------------------------------------
1 | .card {
2 | background-color: var(--color-bg-elevated);
3 | border-radius: var(--radius-lg);
4 | transition: all var(--duration-base) var(--timing-easeInOut);
5 | }
6 |
7 | .card.bordered {
8 | border: 1px solid var(--color-border-base);
9 | }
10 |
11 | .card.hoverable {
12 | cursor: pointer;
13 | }
14 |
15 | .card.hoverable:hover {
16 | border-color: var(--color-border-hover);
17 | box-shadow: var(--shadow-md);
18 | transform: translateY(-2px);
19 | }
20 |
21 | .header {
22 | padding: var(--spacing-4) var(--spacing-4) var(--spacing-2);
23 | border-bottom: 1px solid var(--color-border-base);
24 | }
25 |
26 | .title {
27 | margin: 0;
28 | font-size: var(--font-size-base);
29 | font-weight: var(--font-weight-semibold);
30 | color: var(--color-text-primary);
31 | }
32 |
33 | .body {
34 | color: var(--color-text-secondary);
35 | }
36 |
37 | /* Padding variants */
38 | .padding-none {
39 | padding: 0;
40 | }
41 |
42 | .padding-sm {
43 | padding: var(--spacing-2);
44 | }
45 |
46 | .padding-md {
47 | padding: var(--spacing-4);
48 | }
49 |
50 | .padding-lg {
51 | padding: var(--spacing-6);
52 | }
53 |
54 | /* When there's a header, remove top padding from body */
55 | .card:has(.header) .body {
56 | padding-top: 0;
57 | }
58 |
--------------------------------------------------------------------------------
/packages/react-devtools-kit/src/messaging/presets/iframe/server.ts:
--------------------------------------------------------------------------------
1 | import { isBrowser } from '@react-devtools-plus/shared'
2 | import SuperJSON from 'superjson'
3 | import { MergeableChannelOptions } from '../../types/channel'
4 | import { __REACT_DEVTOOLS_KIT_IFRAME_MESSAGING_EVENT_KEY, getIframeServerContext } from './context'
5 |
6 | export function createIframeServerChannel(): MergeableChannelOptions {
7 | if (!isBrowser) {
8 | return {
9 | post: () => {},
10 | on: () => {},
11 | }
12 | }
13 |
14 | return {
15 | post: (data) => {
16 | const iframe = getIframeServerContext()
17 | iframe?.contentWindow?.postMessage(SuperJSON.stringify({
18 | event: __REACT_DEVTOOLS_KIT_IFRAME_MESSAGING_EVENT_KEY,
19 | data,
20 | }), '*')
21 | },
22 | on: (handler) => {
23 | window.addEventListener('message', (event) => {
24 | const iframe = getIframeServerContext()
25 | try {
26 | const parsed = SuperJSON.parse<{ event: string, data: unknown }>(event.data)
27 | if (event.source === iframe?.contentWindow && parsed.event === __REACT_DEVTOOLS_KIT_IFRAME_MESSAGING_EVENT_KEY) {
28 | handler(parsed.data)
29 | }
30 | }
31 | catch (e) {
32 | // ignore parsing errors
33 | }
34 | })
35 | },
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/packages/react-devtools-ui/src/components/Switch/Switch.tsx:
--------------------------------------------------------------------------------
1 | import type { FC, InputHTMLAttributes } from 'react'
2 | import styles from './Switch.module.css'
3 |
4 | export type SwitchSize = 'sm' | 'md' | 'lg'
5 |
6 | export interface SwitchProps extends Omit, 'onChange' | 'size'> {
7 | checked?: boolean
8 | onChange?: (checked: boolean) => void
9 | label?: string
10 | size?: SwitchSize
11 | }
12 |
13 | export const Switch: FC = ({
14 | checked,
15 | onChange,
16 | label,
17 | className = '',
18 | disabled,
19 | size = 'md',
20 | ...props
21 | }) => {
22 | const containerClass = [
23 | styles.switch,
24 | disabled && styles.disabled,
25 | className,
26 | ].filter(Boolean).join(' ')
27 |
28 | const trackClass = [
29 | styles.track,
30 | styles[`size-${size}`],
31 | ].filter(Boolean).join(' ')
32 |
33 | return (
34 |
50 | )
51 | }
52 |
53 | Switch.displayName = 'Switch'
54 |
--------------------------------------------------------------------------------
/packages/react-devtools/README.md:
--------------------------------------------------------------------------------
1 | # react-devtools-plus
2 |
3 | > Experimental in-app component tree inspector for React projects powered by Unplugin to support Vite and Webpack.
4 |
5 | ## Installation
6 |
7 | ```bash
8 | pnpm add -D react-devtools-plus
9 | ```
10 |
11 | ## Usage
12 |
13 | ```ts
14 | import react from '@vitejs/plugin-react'
15 | import { reactDevToolsPlus } from 'react-devtools-plus/vite'
16 | // vite.config.ts
17 | import { defineConfig } from 'vite'
18 |
19 | export default defineConfig({
20 | plugins: [
21 | reactDevToolsPlus(),
22 | react(),
23 | ],
24 | })
25 | ```
26 |
27 | ### Keyboard shortcuts
28 |
29 | - Press `Alt + Shift + D` (`Option + Shift + D` on macOS) to toggle the DevTools panel open/close
30 | - Press `Alt + Shift + R` (`Option + Shift + R` on macOS) to toggle the overlay visibility (show/hide)
31 |
32 | ## What’s included
33 |
34 | - Minimal Vite plugin that injects a lightweight overlay during development.
35 | - Automatic instrumentation of React Fiber roots to mirror the component tree.
36 | - Standalone preview page at `/__react_devtools__/` with project status.
37 |
38 | ## Limitations
39 |
40 | - This plugin only targets the development server. Production builds are unaffected.
41 | - The overlay renders a simplified tree and does not expose props/state editing yet.
42 | - Running the official React DevTools extension simultaneously may produce duplicate updates; close one of them if you notice conflicts.
43 |
--------------------------------------------------------------------------------
/eslint.config.ts:
--------------------------------------------------------------------------------
1 | import antfu from '@antfu/eslint-config'
2 |
3 | export default antfu({
4 | // force enable react and typescript rules
5 | react: true,
6 | vue: false,
7 | typescript: true,
8 | unocss: true,
9 |
10 | formatters: {
11 | css: true,
12 | html: true,
13 | markdown: 'prettier', // 使用 prettier 格式化,但不检查代码块中的代码
14 | },
15 |
16 | // override default rules
17 | rules: {
18 | 'no-console': 'off',
19 | 'antfu/top-level-function': 'off',
20 | 'unused-imports/no-unused-vars': 'off',
21 |
22 | 'node/prefer-global/process': 'off',
23 |
24 | 'ts/no-invalid-this': 'off',
25 | 'ts/consistent-type-imports': 'off',
26 | 'ts/ban-types': 'off',
27 | 'ts/no-unused-expressions': 'off',
28 | 'ts/no-unsafe-function-type': 'off',
29 |
30 | 'unicorn/consistent-function-scoping': 'off',
31 |
32 | // Disable React 19 migration rules to support React 18 types
33 | '@eslint-react/no-forward-ref': 'off',
34 | '@eslint-react/no-context-provider': 'off',
35 | '@eslint-react/no-use-context': 'off',
36 | },
37 | }, {
38 | ignores: [
39 | 'dist',
40 | 'node_modules',
41 | '.cursor',
42 | '.history',
43 | 'ci.yml',
44 | 'release.yml',
45 | '**.svg',
46 | 'packages/react-devtools-overlay/dist/*',
47 | 'packages/react-devtools-client/dist/*',
48 | 'packages/playground/**/dist/*',
49 | 'eslint.config.js',
50 | // 忽略文档文件中的代码块 lint
51 | '**/*.md/**',
52 | ],
53 | })
54 |
--------------------------------------------------------------------------------
/packages/react-devtools-scan/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@react-devtools-plus/scan",
3 | "type": "module",
4 | "version": "0.4.0",
5 | "description": "React Scan integration for React DevTools",
6 | "keywords": [
7 | "react",
8 | "react-devtools-plus",
9 | "react-scan",
10 | "performance",
11 | "profiler"
12 | ],
13 | "exports": {
14 | ".": {
15 | "types": "./dist/index.d.ts",
16 | "import": "./dist/index.js",
17 | "require": "./dist/index.cjs",
18 | "default": "./dist/index.js"
19 | },
20 | "./package.json": "./package.json"
21 | },
22 | "main": "./dist/index.cjs",
23 | "module": "./dist/index.js",
24 | "types": "./dist/index.d.ts",
25 | "scripts": {
26 | "build": "tsup",
27 | "dev": "tsup --watch",
28 | "stub": "tsup --watch",
29 | "prepare:type": "tsc --declaration --emitDeclarationOnly --outDir dist"
30 | },
31 | "peerDependencies": {
32 | "@react-devtools-plus/core": "workspace:*",
33 | "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
34 | "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
35 | },
36 | "peerDependenciesMeta": {
37 | "@react-devtools-plus/core": {
38 | "optional": true
39 | }
40 | },
41 | "dependencies": {
42 | "react-scan": "^0.4.3"
43 | },
44 | "devDependencies": {
45 | "@types/react": "^18.2.72",
46 | "@types/react-dom": "^18.2.23",
47 | "tsup": "^8.0.0",
48 | "typescript": "^5.9.3"
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/packages/react-devtools/src/middleware/plugins.ts:
--------------------------------------------------------------------------------
1 | import type { IncomingMessage, ServerResponse } from 'node:http'
2 | import type { ResolvedPluginConfig, UserPlugin } from '../config/types'
3 |
4 | export function createPluginsMiddleware(
5 | config: ResolvedPluginConfig,
6 | transformPath?: (path: string) => string,
7 | ) {
8 | return (req: IncomingMessage, res: ServerResponse, next: () => void) => {
9 | const rawUrl = req.url || ''
10 | const url = new URL(rawUrl, 'http://localhost')
11 |
12 | // We mount this middleware globally in both Vite and Webpack.
13 | // This ensures consistent behavior and allows us to strictly match the full path.
14 | const isMatch = url.pathname === '/__react_devtools__/plugins-manifest.json'
15 |
16 | if (isMatch) {
17 | // console.log('[React DevTools] Serving plugins manifest from:', rawUrl)
18 | res.setHeader('Content-Type', 'application/json')
19 | res.setHeader('Access-Control-Allow-Origin', '*')
20 |
21 | const plugins = (config.plugins || []).map((plugin): UserPlugin => {
22 | if (plugin.view && plugin.view.src && transformPath) {
23 | return {
24 | ...plugin,
25 | view: {
26 | ...plugin.view,
27 | src: transformPath(plugin.view.src),
28 | },
29 | }
30 | }
31 | return plugin
32 | })
33 |
34 | res.end(JSON.stringify(plugins))
35 | return
36 | }
37 | next()
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/packages/playground/react/vite.config.ts:
--------------------------------------------------------------------------------
1 | import path from 'node:path'
2 | import { fileURLToPath } from 'node:url'
3 | import react from '@vitejs/plugin-react'
4 | import { reactDevToolsPlus } from 'react-devtools-plus/vite'
5 | import { defineConfig } from 'vite'
6 |
7 | const __dirname = path.dirname(fileURLToPath(import.meta.url))
8 |
9 | export default defineConfig({
10 | plugins: [
11 | reactDevToolsPlus({
12 | enabledEnvironments: ['development', 'test'],
13 | plugins: [
14 | {
15 | name: 'my-plugin',
16 | view: {
17 | title: 'My Plugin',
18 | icon: '',
19 | src: path.resolve(__dirname, './src/plugins/MyPlugin.tsx'),
20 | },
21 | },
22 | ],
23 | // Enable React Scan auto-injection
24 | scan: {
25 | enabled: true, // Enable injection, but controlled by showToolbar/DevTools
26 | showToolbar: false,
27 | animationSpeed: 'fast',
28 | },
29 | // theme: {
30 | // mode: 'light', // auto or light or dark
31 | // primaryColor: 'yellow', // Custom primary color
32 | // },
33 | }),
34 | react(),
35 | ],
36 | })
37 |
--------------------------------------------------------------------------------
/packages/react-devtools-ui/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@react-devtools-plus/ui",
3 | "type": "module",
4 | "version": "0.4.0",
5 | "description": "UI components library for React DevTools",
6 | "author": "wzc520pyfm",
7 | "license": "MIT",
8 | "exports": {
9 | ".": {
10 | "types": "./dist/index.d.ts",
11 | "import": "./dist/index.js",
12 | "require": "./dist/index.cjs"
13 | },
14 | "./style.css": "./dist/ui.css",
15 | "./theme": {
16 | "types": "./dist/theme.d.ts",
17 | "import": "./dist/theme.js",
18 | "require": "./dist/theme.cjs"
19 | }
20 | },
21 | "main": "./dist/index.cjs",
22 | "module": "./dist/index.js",
23 | "types": "./dist/index.d.ts",
24 | "files": [
25 | "dist"
26 | ],
27 | "scripts": {
28 | "build": "vite build",
29 | "dev": "vite build --watch",
30 | "prepare:type": "tsc --emitDeclarationOnly --outDir dist",
31 | "stub": "vite build --watch",
32 | "typecheck": "tsc --noEmit"
33 | },
34 | "peerDependencies": {
35 | "react": ">=18.0.0",
36 | "react-dom": ">=18.0.0"
37 | },
38 | "dependencies": {
39 | "@react-devtools-plus/shared": "workspace:*",
40 | "colord": "^2.9.3"
41 | },
42 | "devDependencies": {
43 | "@types/react": "catalog:",
44 | "@types/react-dom": "catalog:",
45 | "@vitejs/plugin-react": "catalog:",
46 | "react": "catalog:",
47 | "react-dom": "catalog:",
48 | "typescript": "catalog:",
49 | "vite": "catalog:",
50 | "vite-plugin-dts": "catalog:"
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/docs/public/favicon.svg:
--------------------------------------------------------------------------------
1 |
29 |
--------------------------------------------------------------------------------
/packages/ui-story/stories/Switch.stories.tsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable react-hooks/rules-of-hooks */
2 | import type { Meta, StoryObj } from '@storybook/react'
3 | import { Switch } from '@react-devtools-plus/ui'
4 | import { useState } from 'react'
5 |
6 | const meta: Meta = {
7 | title: 'Components/Switch',
8 | component: Switch,
9 | tags: ['autodocs'],
10 | argTypes: {
11 | checked: {
12 | control: 'boolean',
13 | },
14 | disabled: {
15 | control: 'boolean',
16 | },
17 | label: {
18 | control: 'text',
19 | },
20 | },
21 | }
22 |
23 | export default meta
24 | type Story = StoryObj
25 |
26 | export const Default: Story = {
27 | args: {
28 | label: 'Enable feature',
29 | },
30 | render: (args) => {
31 | const [checked, setChecked] = useState(args.checked || false)
32 | return
33 | },
34 | }
35 |
36 | export const Checked: Story = {
37 | args: {
38 | checked: true,
39 | label: 'Enabled',
40 | },
41 | render: (args) => {
42 | const [checked, setChecked] = useState(args.checked || true)
43 | // eslint-disable-next-line ts/ban-ts-comment
44 | // @ts-expect-error
45 | return
46 | },
47 | }
48 |
49 | export const Disabled: Story = {
50 | args: {
51 | disabled: true,
52 | label: 'Disabled switch',
53 | },
54 | }
55 |
56 | export const DisabledChecked: Story = {
57 | args: {
58 | checked: true,
59 | disabled: true,
60 | label: 'Disabled checked switch',
61 | },
62 | }
63 |
--------------------------------------------------------------------------------
/packages/react-devtools-ui/vite.config.ts:
--------------------------------------------------------------------------------
1 | import { resolve } from 'node:path'
2 | import react from '@vitejs/plugin-react'
3 | import { defineConfig } from 'vite'
4 | import dts from 'vite-plugin-dts'
5 |
6 | export default defineConfig({
7 | plugins: [
8 | react(),
9 | dts({
10 | insertTypesEntry: true,
11 | rollupTypes: true,
12 | tsconfigPath: './tsconfig.json',
13 | }),
14 | ],
15 | build: {
16 | target: 'es2017',
17 | lib: {
18 | entry: {
19 | index: resolve(__dirname, 'src/index.ts'),
20 | theme: resolve(__dirname, 'src/theme/index.ts'),
21 | },
22 | formats: ['es', 'cjs'],
23 | fileName: (format, entryName) => {
24 | const ext = format === 'es' ? 'js' : 'cjs'
25 | return `${entryName}.${ext}`
26 | },
27 | },
28 | rollupOptions: {
29 | external: ['react', 'react-dom', 'react/jsx-runtime'],
30 | output: {
31 | globals: {
32 | 'react': 'React',
33 | 'react-dom': 'ReactDOM',
34 | 'react/jsx-runtime': 'react/jsx-runtime',
35 | },
36 | // Preserve CSS modules
37 | assetFileNames: (assetInfo) => {
38 | if (assetInfo.name === 'style.css') {
39 | return 'style.css'
40 | }
41 | return assetInfo.name!
42 | },
43 | },
44 | },
45 | cssCodeSplit: false,
46 | sourcemap: true,
47 | outDir: 'dist',
48 | emptyOutDir: true,
49 | },
50 | css: {
51 | modules: {
52 | localsConvention: 'camelCase',
53 | generateScopedName: '[name]__[local]___[hash:base64:5]',
54 | },
55 | },
56 | })
57 |
--------------------------------------------------------------------------------
/packages/react-devtools-ui/src/components/Card/Card.tsx:
--------------------------------------------------------------------------------
1 | import type { CSSProperties, FC, ReactNode } from 'react'
2 | import styles from './Card.module.css'
3 |
4 | export interface CardProps {
5 | /**
6 | * Card title
7 | */
8 | title?: string
9 |
10 | /**
11 | * Card content
12 | */
13 | children?: ReactNode
14 |
15 | /**
16 | * Additional CSS class
17 | */
18 | className?: string
19 |
20 | /**
21 | * Show border
22 | * @default true
23 | */
24 | bordered?: boolean
25 |
26 | /**
27 | * Hoverable effect
28 | * @default false
29 | */
30 | hoverable?: boolean
31 |
32 | /**
33 | * Card padding size
34 | * @default 'md'
35 | */
36 | padding?: 'none' | 'sm' | 'md' | 'lg'
37 |
38 | /**
39 | * Custom styles
40 | */
41 | style?: CSSProperties
42 |
43 | /**
44 | * Click handler
45 | */
46 | onClick?: () => void
47 | }
48 |
49 | export const Card: FC = ({
50 | title,
51 | children,
52 | className = '',
53 | bordered = true,
54 | hoverable = false,
55 | padding = 'md',
56 | style,
57 | onClick,
58 | }) => {
59 | const classNames = [
60 | styles.card,
61 | bordered && styles.bordered,
62 | hoverable && styles.hoverable,
63 | styles[`padding-${padding}`],
64 | className,
65 | ].filter(Boolean).join(' ')
66 |
67 | return (
68 |
73 | {title && (
74 |
75 |
{title}
76 |
77 | )}
78 |
79 | {children}
80 |
81 |
82 | )
83 | }
84 |
--------------------------------------------------------------------------------
/packages/react-devtools-core/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@react-devtools-plus/core",
3 | "type": "module",
4 | "version": "0.4.0",
5 | "description": "Core functionality for React DevTools including RPC, client management, and plugin system",
6 | "author": "wzc520pyfm",
7 | "license": "MIT",
8 | "exports": {
9 | ".": {
10 | "types": "./dist/index.d.ts",
11 | "import": "./dist/index.js",
12 | "require": "./dist/index.cjs"
13 | },
14 | "./rpc": {
15 | "types": "./dist/rpc.d.ts",
16 | "import": "./dist/rpc.js",
17 | "require": "./dist/rpc.cjs"
18 | },
19 | "./client": {
20 | "types": "./dist/client.d.ts",
21 | "import": "./dist/client.js",
22 | "require": "./dist/client.cjs"
23 | },
24 | "./plugin": {
25 | "types": "./dist/plugin.d.ts",
26 | "import": "./dist/plugin.js",
27 | "require": "./dist/plugin.cjs"
28 | }
29 | },
30 | "main": "./dist/index.cjs",
31 | "module": "./dist/index.js",
32 | "types": "./dist/index.d.ts",
33 | "files": [
34 | "dist"
35 | ],
36 | "scripts": {
37 | "build": "tsup",
38 | "dev": "tsup --watch",
39 | "prepare:type": "tsup --dts-only",
40 | "stub": "tsup --watch --dts-only",
41 | "typecheck": "tsc --noEmit"
42 | },
43 | "peerDependencies": {
44 | "vite": ">=5.0.0"
45 | },
46 | "peerDependenciesMeta": {
47 | "vite": {
48 | "optional": true
49 | }
50 | },
51 | "dependencies": {
52 | "@react-devtools-plus/shared": "workspace:*",
53 | "birpc": "^0.2.19"
54 | },
55 | "devDependencies": {
56 | "@types/node": "catalog:",
57 | "tsup": "catalog:",
58 | "typescript": "catalog:",
59 | "vite": "catalog:"
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/packages/react-devtools-ui/src/components/Select/Select.tsx:
--------------------------------------------------------------------------------
1 | import type { SelectHTMLAttributes } from 'react'
2 | import { forwardRef } from 'react'
3 | import styles from './Select.module.css'
4 |
5 | export interface SelectOption {
6 | label: string
7 | value: string | number
8 | }
9 |
10 | export type SelectSize = 'sm' | 'md' | 'lg'
11 |
12 | export interface SelectProps extends Omit, 'onChange' | 'size'> {
13 | options?: SelectOption[]
14 | onChange?: (value: string) => void
15 | value?: string | number
16 | size?: SelectSize
17 | }
18 |
19 | export const Select = forwardRef(({ options = [], onChange, className = '', size = 'md', children, ...props }, ref) => {
20 | const selectClass = [
21 | styles.select,
22 | styles[`size-${size}`],
23 | ].filter(Boolean).join(' ')
24 |
25 | const arrowClass = [
26 | styles.arrow,
27 | size === 'sm' && styles['arrow-sm'],
28 | ].filter(Boolean).join(' ')
29 |
30 | return (
31 |
32 |
45 |
48 |
49 | )
50 | })
51 |
52 | Select.displayName = 'Select'
53 |
--------------------------------------------------------------------------------
/packages/react-devtools/src/codegen/devtools-hook.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * React DevTools Hook Code Generation
3 | *
4 | * The DevTools hook must be installed before React loads to properly
5 | * intercept React's registration with the hook.
6 | */
7 |
8 | /**
9 | * Generate the DevTools global hook initialization code
10 | *
11 | * This code creates the __REACT_DEVTOOLS_GLOBAL_HOOK__ object the React
12 | * uses to communicate with DevTools. Must be injected before React loads.
13 | */
14 | export function generateDevToolsHookCode(): string {
15 | return `
16 | if (typeof window !== 'undefined' && !window.__REACT_DEVTOOLS_GLOBAL_HOOK__) {
17 | (function() {
18 | var renderers = new Map();
19 | window.__REACT_DEVTOOLS_GLOBAL_HOOK__ = {
20 | __IS_OUR_MOCK__: true,
21 | checkDCE: function() {},
22 | supportsFiber: true,
23 | renderers: renderers,
24 | onScheduleFiberRoot: function() {},
25 | onCommitFiberRoot: function(rendererID, root, priorityLevel) {
26 | // This might be overwritten by bippy
27 | },
28 | onCommitFiberUnmount: function() {},
29 | inject: function(renderer) {
30 | var id = Math.random().toString(36).slice(2);
31 | renderers.set(id, renderer);
32 | return id;
33 | }
34 | };
35 | })();
36 | }
37 | `.trim()
38 | }
39 |
40 | /**
41 | * Generate inline script tag content for DevTools hook
42 | */
43 | export function generateDevToolsHookScriptTag(): {
44 | tag: 'script'
45 | attrs: Record
46 | children: string
47 | injectTo: 'head-prepend'
48 | } {
49 | return {
50 | tag: 'script',
51 | attrs: {},
52 | children: generateDevToolsHookCode(),
53 | injectTo: 'head-prepend',
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/packages/ui-story/stories/Checkbox.stories.tsx:
--------------------------------------------------------------------------------
1 | import type { Meta, StoryObj } from '@storybook/react'
2 | import { Checkbox } from '@react-devtools-plus/ui'
3 | import { useState } from 'react'
4 |
5 | const meta: Meta = {
6 | title: 'UI/Checkbox',
7 | component: Checkbox,
8 | tags: ['autodocs'],
9 | argTypes: {
10 | checked: { control: 'boolean' },
11 | disabled: { control: 'boolean' },
12 | label: { control: 'text' },
13 | },
14 | }
15 |
16 | export default meta
17 | type Story = StoryObj
18 |
19 | export const Default: Story = {
20 | render: (args) => {
21 | // eslint-disable-next-line react-hooks/rules-of-hooks
22 | const [checked, setChecked] = useState(false)
23 | return
24 | },
25 | }
26 |
27 | export const Checked: Story = {
28 | args: {
29 | checked: true,
30 | label: 'Checked State',
31 | },
32 | render: (args) => {
33 | // eslint-disable-next-line react-hooks/rules-of-hooks
34 | const [checked, setChecked] = useState(true)
35 | return
36 | },
37 | }
38 |
39 | export const WithLabel: Story = {
40 | args: {
41 | label: 'Checkbox with Label',
42 | },
43 | render: (args) => {
44 | // eslint-disable-next-line react-hooks/rules-of-hooks
45 | const [checked, setChecked] = useState(false)
46 | return
47 | },
48 | }
49 |
50 | export const Disabled: Story = {
51 | args: {
52 | disabled: true,
53 | label: 'Disabled Checkbox',
54 | },
55 | }
56 |
57 | export const DisabledChecked: Story = {
58 | args: {
59 | disabled: true,
60 | checked: true,
61 | label: 'Disabled Checked',
62 | },
63 | }
64 |
--------------------------------------------------------------------------------
/packages/react-devtools-core/src/rpc/types.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * RPC types for React DevTools
3 | * React DevTools RPC 类型定义
4 | */
5 |
6 | import type { ClientFunctions, ServerFunctions } from '../types'
7 |
8 | /**
9 | * RPC channel interface
10 | * RPC 通道接口
11 | */
12 | export interface RPCChannel {
13 | /**
14 | * Send message
15 | * 发送消息
16 | */
17 | send: (message: any) => void
18 |
19 | /**
20 | * Receive message handler
21 | * 接收消息处理器
22 | */
23 | onMessage: (handler: (message: any) => void) => () => void
24 |
25 | /**
26 | * Close channel
27 | * 关闭通道
28 | */
29 | close?: () => void
30 | }
31 |
32 | /**
33 | * RPC options
34 | * RPC 选项
35 | */
36 | export interface RPCOptions {
37 | /**
38 | * Timeout for RPC calls (ms)
39 | * RPC 调用超时时间(毫秒)
40 | * @default 30000
41 | */
42 | timeout?: number
43 |
44 | /**
45 | * Enable logging
46 | * 启用日志
47 | * @default false
48 | */
49 | logging?: boolean
50 | }
51 |
52 | /**
53 | * RPC connection state
54 | * RPC 连接状态
55 | */
56 | export type RPCConnectionState = 'disconnected' | 'connecting' | 'connected' | 'error'
57 |
58 | /**
59 | * RPC connection info
60 | * RPC 连接信息
61 | */
62 | export interface RPCConnectionInfo {
63 | state: RPCConnectionState
64 | error?: Error
65 | connectedAt?: number
66 | }
67 |
68 | /**
69 | * Client RPC instance type
70 | * 客户端 RPC 实例类型
71 | */
72 | export interface ClientRPC extends ClientFunctions {
73 | $functions: ClientFunctions
74 | $channel: RPCChannel
75 | $state: RPCConnectionInfo
76 | $close: () => void
77 | }
78 |
79 | /**
80 | * Server RPC instance type
81 | * 服务端 RPC 实例类型
82 | */
83 | export interface ServerRPC extends ServerFunctions {
84 | $functions: ServerFunctions
85 | $channel: RPCChannel
86 | $state: RPCConnectionInfo
87 | $close: () => void
88 | }
89 |
--------------------------------------------------------------------------------
/packages/react-devtools-ui/src/components/Select/Select.module.css:
--------------------------------------------------------------------------------
1 | .wrapper {
2 | position: relative;
3 | display: inline-block;
4 | width: 100%;
5 | }
6 |
7 | .select {
8 | appearance: none;
9 | background-color: var(--color-bg-base); /* bg-gray-50 */
10 | border: 1px solid var(--color-neutral-300); /* border-gray-300 */
11 | border-radius: 8px; /* rounded-lg */
12 | color: var(--color-text-primary); /* text-gray-900 / dark:text-gray-300 */
13 | width: 100%;
14 | cursor: pointer;
15 | transition: all var(--duration-fast) var(--timing-easeInOut);
16 | }
17 |
18 | /* Sizes */
19 | .size-sm {
20 | padding: 4px 28px 4px 8px;
21 | font-size: 12px;
22 | height: 28px;
23 | }
24 |
25 | .size-md {
26 | padding: 6px 32px 6px 12px;
27 | font-size: 14px;
28 | height: 34px;
29 | }
30 |
31 | .size-lg {
32 | padding: 8px 32px 8px 12px;
33 | font-size: 16px;
34 | height: 40px;
35 | }
36 |
37 | .select:focus {
38 | outline: none;
39 | border-color: var(--color-primary-500);
40 | box-shadow: 0 0 0 1px var(--color-primary-500); /* ring-1 ring-primary-500 */
41 | }
42 |
43 | .select:disabled {
44 | background-color: var(--color-neutral-100);
45 | color: var(--color-text-secondary);
46 | cursor: not-allowed;
47 | opacity: 0.6;
48 | }
49 |
50 | [data-theme='dark'] .select:disabled {
51 | background-color: var(--color-neutral-800);
52 | border-color: var(--color-neutral-700);
53 | color: var(--color-text-secondary);
54 | }
55 |
56 | .select:disabled + .arrow {
57 | opacity: 0.6;
58 | }
59 |
60 | .arrow {
61 | position: absolute;
62 | right: 12px;
63 | top: 50%;
64 | transform: translateY(-50%);
65 | width: 16px;
66 | height: 16px;
67 | pointer-events: none;
68 | color: var(--color-text-secondary);
69 | }
70 |
71 | .arrow-sm {
72 | right: 8px;
73 | width: 14px;
74 | height: 14px;
75 | }
76 |
--------------------------------------------------------------------------------
/docs/src/components/ui/Spotlight.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | interface SpotlightProps {
4 | className?: string
5 | fill?: string
6 | }
7 |
8 | export const Spotlight: React.FC = ({ className = '', fill = 'white' }) => {
9 | return (
10 |
59 | )
60 | }
61 |
--------------------------------------------------------------------------------
/packages/react-devtools-ui/src/theme/types.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Color palette with shades from 50 to 950
3 | */
4 | export interface ColorPalette {
5 | 50: string
6 | 100: string
7 | 200: string
8 | 300: string
9 | 400: string
10 | 500: string
11 | 600: string
12 | 700: string
13 | 800: string
14 | 900: string
15 | 950: string
16 | }
17 |
18 | /**
19 | * Theme mode
20 | */
21 | export type ThemeMode = 'light' | 'dark' | 'auto'
22 |
23 | /**
24 | * Theme configuration
25 | */
26 | export interface ThemeConfig {
27 | /**
28 | * Theme mode
29 | * @default 'auto'
30 | */
31 | mode?: ThemeMode
32 |
33 | /**
34 | * Primary color (preset name or hex color)
35 | * @default 'react' (#61dafb)
36 | */
37 | primaryColor?: string
38 |
39 | /**
40 | * Custom color palettes
41 | */
42 | colors?: {
43 | primary?: Partial
44 | success?: Partial
45 | warning?: Partial
46 | error?: Partial
47 | info?: Partial
48 | }
49 | }
50 |
51 | /**
52 | * Complete theme object
53 | */
54 | export interface Theme {
55 | mode: 'light' | 'dark'
56 | colors: {
57 | primary: ColorPalette
58 | success: ColorPalette
59 | warning: ColorPalette
60 | error: ColorPalette
61 | info: ColorPalette
62 | neutral: ColorPalette
63 | }
64 | spacing: Record
65 | borderRadius: Record
66 | shadows: Record
67 | typography: {
68 | fontFamily: Record
69 | fontSize: Record
70 | fontWeight: Record
71 | lineHeight: Record
72 | }
73 | transitions: {
74 | duration: Record
75 | timing: Record
76 | }
77 | zIndex: Record
78 | cssVars: Record
79 | }
80 |
--------------------------------------------------------------------------------
/packages/react-devtools-ui/src/components/Tag/Tag.tsx:
--------------------------------------------------------------------------------
1 | import type { CSSProperties, FC, ReactNode } from 'react'
2 | import styles from './Tag.module.css'
3 |
4 | export type TagColor = 'primary' | 'success' | 'warning' | 'error' | 'info' | 'neutral'
5 | export type TagVariant = 'solid' | 'outline'
6 | export type TagSize = 'sm' | 'md'
7 |
8 | export interface TagProps {
9 | children?: ReactNode
10 | color?: TagColor
11 | variant?: TagVariant
12 | size?: TagSize
13 | closable?: boolean
14 | onClose?: (event: React.MouseEvent) => void
15 | onClick?: (event: React.MouseEvent) => void
16 | className?: string
17 | style?: CSSProperties
18 | }
19 |
20 | export const Tag: FC = ({
21 | children,
22 | color = 'neutral',
23 | variant = 'outline',
24 | size = 'md',
25 | closable = false,
26 | onClose,
27 | onClick,
28 | className = '',
29 | style,
30 | }) => {
31 | const TagElement: 'button' | 'span' = onClick ? 'button' : 'span'
32 |
33 | const tagClassName = [
34 | styles.tag,
35 | styles[`size-${size}`],
36 | styles[`variant-${variant}`],
37 | styles[`color-${color}`],
38 | onClick && styles.clickable,
39 | closable && styles.closable,
40 | className,
41 | ].filter(Boolean).join(' ')
42 |
43 | return (
44 |
50 | {children}
51 | {closable && (
52 |
63 | )}
64 |
65 | )
66 | }
67 |
--------------------------------------------------------------------------------
/packages/playground/umi/src/layouts/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { Link, Outlet, useLocation } from 'umi'
3 | import styles from './index.less'
4 |
5 | interface NavItem {
6 | path: string
7 | label: string
8 | icon: string
9 | }
10 |
11 | const navItems: NavItem[] = [
12 | { path: '/', label: 'Home', icon: '🏠' },
13 | { path: '/about', label: 'About', icon: '📖' },
14 | { path: '/theme', label: 'Theme', icon: '🎨' },
15 | { path: '/counter', label: 'Counter', icon: '⚡' },
16 | ]
17 |
18 | export default function Layout() {
19 | const location = useLocation()
20 |
21 | return (
22 |
23 |
55 |
56 |
57 |
58 |
59 | )
60 | }
61 |
--------------------------------------------------------------------------------
/packages/react-devtools-kit/src/core/open-in-editor/index.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Try to open a file in the editor using URL protocol (fallback)
3 | */
4 | function tryOpenWithProtocol(fileName: string, line: number, column: number): boolean {
5 | try {
6 | // Try to get editor from localStorage (user preference)
7 | const editor = localStorage.getItem('react_devtools_editor') || 'vscode'
8 |
9 | // Use URL protocol as fallback
10 | // Format: vscode://file/path/to/file:line:column
11 | const protocolUrl = `${editor}://file/${fileName}:${line}:${column}`
12 |
13 | const link = document.createElement('a')
14 | link.href = protocolUrl
15 | link.click()
16 | link.remove()
17 |
18 | return true
19 | }
20 | catch (e) {
21 | console.warn('[React DevTools] Failed to open with URL protocol:', e)
22 | return false
23 | }
24 | }
25 |
26 | export function openInEditor(fileName: string, line: number, column: number) {
27 | try {
28 | // Primary method: Use server endpoint (works with both Vite and Webpack)
29 | const url = `/__open-in-editor?file=${encodeURIComponent(`${fileName}:${line}:${column}`)}`
30 |
31 | fetch(url)
32 | .then((response) => {
33 | if (!response.ok) {
34 | // If server endpoint fails (e.g., 404), try URL protocol as fallback
35 | console.warn('[React DevTools] Server endpoint failed, trying URL protocol fallback')
36 | tryOpenWithProtocol(fileName, line, column)
37 | }
38 | })
39 | .catch(() => {
40 | // If fetch fails (e.g., network error), try URL protocol as fallback
41 | console.warn('[React DevTools] Fetch failed, trying URL protocol fallback')
42 | tryOpenWithProtocol(fileName, line, column)
43 | })
44 | }
45 | catch (e) {
46 | console.error('[React DevTools] Failed to open in editor:', e)
47 | // Last resort: try URL protocol
48 | tryOpenWithProtocol(fileName, line, column)
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/packages/ui-story/stories/Select.stories.tsx:
--------------------------------------------------------------------------------
1 | import type { Meta, StoryObj } from '@storybook/react'
2 | import { Select } from '@react-devtools-plus/ui'
3 | import { useState } from 'react'
4 |
5 | const meta: Meta = {
6 | title: 'UI/Select',
7 | component: Select,
8 | tags: ['autodocs'],
9 | argTypes: {
10 | disabled: { control: 'boolean' },
11 | value: { control: 'text' },
12 | },
13 | }
14 |
15 | export default meta
16 | type Story = StoryObj
17 |
18 | const options = [
19 | { label: 'Option 1', value: 'option1' },
20 | { label: 'Option 2', value: 'option2' },
21 | { label: 'Option 3', value: 'option3' },
22 | ]
23 |
24 | export const Default: Story = {
25 | render: (args) => {
26 | // eslint-disable-next-line react-hooks/rules-of-hooks
27 | const [value, setValue] = useState('option1')
28 | return (
29 |
30 |
36 |
37 | )
38 | },
39 | }
40 |
41 | export const Disabled: Story = {
42 | args: {
43 | disabled: true,
44 | options,
45 | value: 'option1',
46 | },
47 | render: (args) => {
48 | return (
49 |
50 |
51 |
52 | )
53 | },
54 | }
55 |
56 | export const WithManyOptions: Story = {
57 | render: (args) => {
58 | // eslint-disable-next-line react-hooks/rules-of-hooks
59 | const [value, setValue] = useState('1')
60 | const manyOptions = Array.from({ length: 10 }, (_, i) => ({
61 | label: `Option ${i + 1}`,
62 | value: String(i + 1),
63 | }))
64 |
65 | return (
66 |
67 |
73 |
74 | )
75 | },
76 | }
77 |
--------------------------------------------------------------------------------
/packages/react-devtools-core/src/rpc/index.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * RPC system for React DevTools
3 | * React DevTools RPC 系统
4 | */
5 |
6 | import type { ClientFunctions, ServerFunctions } from '../types'
7 | import type { ClientRPC, RPCChannel, RPCOptions, ServerRPC } from './types'
8 | import { createBirpc } from 'birpc'
9 |
10 | /**
11 | * Create client RPC instance
12 | * 创建客户端 RPC 实例
13 | */
14 | export function createClientRPC(
15 | functions: ClientFunctions,
16 | channel: RPCChannel,
17 | options: RPCOptions = {},
18 | ): ClientRPC {
19 | const { timeout = 30000 } = options
20 |
21 | const rpc = createBirpc(functions, {
22 | post: (data) => {
23 | channel.send(data)
24 | },
25 | on: (fn) => {
26 | return channel.onMessage(fn)
27 | },
28 | timeout,
29 | })
30 |
31 | return Object.assign(rpc, functions, {
32 | $functions: functions,
33 | $channel: channel,
34 | $state: {
35 | state: 'connected' as const,
36 | connectedAt: Date.now(),
37 | },
38 | $close: () => {
39 | channel.close?.()
40 | },
41 | }) as unknown as ClientRPC
42 | }
43 |
44 | /**
45 | * Create server RPC instance
46 | * 创建服务端 RPC 实例
47 | */
48 | export function createServerRPC(
49 | functions: ServerFunctions,
50 | channel: RPCChannel,
51 | options: RPCOptions = {},
52 | ): ServerRPC {
53 | const { timeout = 30000 } = options
54 |
55 | const rpc = createBirpc(functions, {
56 | post: (data) => {
57 | channel.send(data)
58 | },
59 | on: (fn) => {
60 | return channel.onMessage(fn)
61 | },
62 | timeout,
63 | })
64 |
65 | return Object.assign(rpc, functions, {
66 | $functions: functions,
67 | $channel: channel,
68 | $state: {
69 | state: 'connected' as const,
70 | connectedAt: Date.now(),
71 | },
72 | $close: () => {
73 | channel.close?.()
74 | },
75 | }) as unknown as ServerRPC
76 | }
77 |
78 | // Export channel creators
79 | export * from './channel'
80 | export * from './types'
81 |
--------------------------------------------------------------------------------
/docs/src/components/Footer.tsx:
--------------------------------------------------------------------------------
1 | import { Github } from 'lucide-react'
2 | import React from 'react'
3 | import { useTranslation } from 'react-i18next'
4 |
5 | export const Footer: React.FC = () => {
6 | const { t } = useTranslation()
7 | return (
8 |
39 | )
40 | }
41 |
--------------------------------------------------------------------------------
/docs/src/hooks/useScrollAnimation.ts:
--------------------------------------------------------------------------------
1 | import { useEffect, useRef, useState } from 'react'
2 |
3 | interface UseScrollAnimationOptions {
4 | threshold?: number
5 | rootMargin?: string
6 | triggerOnce?: boolean
7 | }
8 |
9 | export function useScrollAnimation(options: UseScrollAnimationOptions = {}) {
10 | const { threshold = 0.2, rootMargin = '0px', triggerOnce = true } = options
11 | const ref = useRef(null)
12 | const [isVisible, setIsVisible] = useState(false)
13 |
14 | useEffect(() => {
15 | const element = ref.current
16 | if (!element)
17 | return
18 |
19 | const observer = new IntersectionObserver(
20 | ([entry]) => {
21 | if (entry.isIntersecting) {
22 | setIsVisible(true)
23 | if (triggerOnce) {
24 | observer.unobserve(element)
25 | }
26 | }
27 | else if (!triggerOnce) {
28 | setIsVisible(false)
29 | }
30 | },
31 | { threshold, rootMargin },
32 | )
33 |
34 | observer.observe(element)
35 |
36 | return () => observer.disconnect()
37 | }, [threshold, rootMargin, triggerOnce])
38 |
39 | return { ref, isVisible }
40 | }
41 |
42 | // Helper function to get staggered animation delay
43 | export function getStaggerDelay(index: number, baseDelay = 0.1): string {
44 | return `${index * baseDelay}s`
45 | }
46 |
47 | // Animation class names for different effects
48 | export const animationClasses = {
49 | // Fade up animation
50 | fadeUp: {
51 | initial: 'opacity-0 translate-y-8',
52 | animate: 'opacity-100 translate-y-0',
53 | },
54 | // Fade in animation
55 | fadeIn: {
56 | initial: 'opacity-0',
57 | animate: 'opacity-100',
58 | },
59 | // Scale up animation
60 | scaleUp: {
61 | initial: 'opacity-0 scale-95',
62 | animate: 'opacity-100 scale-100',
63 | },
64 | // Slide from left
65 | slideLeft: {
66 | initial: 'opacity-0 -translate-x-12',
67 | animate: 'opacity-100 translate-x-0',
68 | },
69 | // Slide from right
70 | slideRight: {
71 | initial: 'opacity-0 translate-x-12',
72 | animate: 'opacity-100 translate-x-0',
73 | },
74 | }
75 |
--------------------------------------------------------------------------------
/turbo.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://turbo.build/schema.json",
3 | "tasks": {
4 | "@react-devtools-plus/shared#stub": {
5 | "cache": false
6 | },
7 | "@react-devtools-plus/kit#stub": {
8 | "cache": false,
9 | "dependsOn": [
10 | "@react-devtools-plus/shared#stub",
11 | "@react-devtools-plus/shared#prepare:type"
12 | ]
13 | },
14 | "@react-devtools-plus/core#stub": {
15 | "cache": false,
16 | "dependsOn": [
17 | "@react-devtools-plus/shared#stub",
18 | "@react-devtools-plus/shared#prepare:type"
19 | ]
20 | },
21 | "@react-devtools-plus/client#build": {
22 | "dependsOn": [
23 | "@react-devtools-plus/kit#build",
24 | "^build"
25 | ],
26 | "outputs": [
27 | "dist/**"
28 | ]
29 | },
30 | "@react-devtools-plus/client#stub": {
31 | "cache": false,
32 | "dependsOn": [
33 | "@react-devtools-plus/kit#stub"
34 | ]
35 | },
36 | "@react-devtools-plus/overlay#build": {
37 | "dependsOn": [
38 | "@react-devtools-plus/kit#build",
39 | "^build"
40 | ],
41 | "outputs": [
42 | "dist/**"
43 | ]
44 | },
45 | "@react-devtools-plus/overlay#stub": {
46 | "cache": false,
47 | "dependsOn": [
48 | "@react-devtools-plus/kit#stub"
49 | ]
50 | },
51 | "react-devtools#build": {
52 | "dependsOn": [
53 | "^build"
54 | ],
55 | "outputs": [
56 | "dist/**"
57 | ]
58 | },
59 | "build": {
60 | "dependsOn": [
61 | "^build"
62 | ],
63 | "outputs": [
64 | "dist/**/*.?{c|m}js",
65 | "dist/**/*.d.ts"
66 | ]
67 | },
68 | "dev": {
69 | "cache": false,
70 | "persistent": true
71 | },
72 | "prepare:type": {
73 | "dependsOn": [
74 | "^prepare:type"
75 | ],
76 | "outputs": [
77 | "dist/**/*.d.ts"
78 | ]
79 | },
80 | "stub": {
81 | "cache": false,
82 | "dependsOn": [
83 | "^prepare:type"
84 | ]
85 | }
86 | },
87 | "ui": "stream"
88 | }
89 |
--------------------------------------------------------------------------------
/docs/src/components/ui/Button.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | interface ButtonProps extends React.ButtonHTMLAttributes {
4 | variant?: 'primary' | 'secondary' | 'ghost'
5 | withBeam?: boolean
6 | }
7 |
8 | export const Button: React.FC = ({
9 | children,
10 | className = '',
11 | variant = 'primary',
12 | withBeam = false,
13 | ...props
14 | }) => {
15 | const baseStyles
16 | = 'relative inline-flex items-center justify-center rounded-full px-6 py-3 text-sm font-medium transition-all duration-200 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-brand-400/40 focus-visible:ring-offset-0 active:scale-[0.97] disabled:pointer-events-none disabled:opacity-50'
17 |
18 | const variants = {
19 | primary: 'bg-slate-950 text-white shadow-[0_0_20px_-5px_rgba(14,165,233,0.3)] hover:shadow-[0_0_25px_-5px_rgba(14,165,233,0.5)] hover:scale-[1.02] active:scale-[0.98]',
20 | secondary: 'bg-white/10 text-white hover:bg-white/15 backdrop-blur-sm border border-white/10',
21 | ghost: 'text-slate-400 hover:text-white hover:bg-white/5',
22 | }
23 |
24 | if (withBeam) {
25 | return (
26 |
35 | )
36 | }
37 |
38 | return (
39 |
42 | )
43 | }
44 |
--------------------------------------------------------------------------------
/packages/react-devtools-client/src/main.tsx:
--------------------------------------------------------------------------------
1 | import type { Root } from 'react-dom/client'
2 | import { ThemeProvider } from '@react-devtools-plus/ui'
3 | import React, { StrictMode } from 'react'
4 | import { createRoot } from 'react-dom/client'
5 | import { HashRouter } from 'react-router-dom'
6 | import { App } from './App'
7 | import './global.css'
8 | import '@unocss/reset/tailwind.css'
9 | import 'virtual:uno.css'
10 | import '@react-devtools-plus/ui/style.css'
11 |
12 | // Hack: Hack to suppress "can't detect preamble" error when loading plugins from Vite server
13 | // in the built DevTools client.
14 | // @ts-expect-error - global variable
15 | window.__vite_plugin_react_preamble_installed__ = true
16 |
17 | // Expose React for plugins loaded in non-Vite environments (like Webpack)
18 | // where module resolution for 'react' might not be available.
19 | // @ts-expect-error - global variable
20 | window.React = React
21 |
22 | let root: Root | null = null
23 |
24 | function init() {
25 | const container = document.getElementById('root')
26 | if (!container)
27 | return
28 |
29 | let config = (window as any).__REACT_DEVTOOLS_CONFIG__
30 |
31 | // If not found in current window (e.g. iframe), try parent window
32 | if (!config && window.parent && window.parent !== window) {
33 | try {
34 | config = (window.parent as any).__REACT_DEVTOOLS_CONFIG__
35 | }
36 | catch (e) {
37 | // Ignore cross-origin errors
38 | }
39 | }
40 |
41 | const themeConfig = {
42 | mode: config?.theme?.mode || 'auto',
43 | primaryColor: config?.theme?.primaryColor || 'react',
44 | }
45 |
46 | root = createRoot(container)
47 | root.render(
48 |
49 |
50 |
51 |
52 |
53 |
54 | ,
55 | )
56 | }
57 |
58 | if (document.readyState === 'loading')
59 | document.addEventListener('DOMContentLoaded', init, { once: true })
60 | else
61 | init()
62 |
63 | if (import.meta.hot) {
64 | import.meta.hot.dispose(() => {
65 | root?.unmount()
66 | root = null
67 | })
68 | }
69 |
--------------------------------------------------------------------------------
/packages/react-devtools-ui/src/components/Input/Input.tsx:
--------------------------------------------------------------------------------
1 | import type { InputHTMLAttributes, ReactNode } from 'react'
2 | import { forwardRef } from 'react'
3 | import styles from './Input.module.css'
4 |
5 | export interface InputProps extends Omit, 'size' | 'prefix'> {
6 | /**
7 | * Input size
8 | * @default 'md'
9 | */
10 | size?: 'sm' | 'md' | 'lg'
11 |
12 | /**
13 | * Input status
14 | */
15 | status?: 'error' | 'warning' | 'success'
16 |
17 | /**
18 | * Prefix icon or text
19 | */
20 | prefix?: ReactNode
21 |
22 | /**
23 | * Suffix icon or text
24 | */
25 | suffix?: ReactNode
26 |
27 | /**
28 | * Allow clear
29 | * @default false
30 | */
31 | allowClear?: boolean
32 |
33 | /**
34 | * Clear callback
35 | */
36 | onClear?: () => void
37 |
38 | /**
39 | * Full width
40 | * @default false
41 | */
42 | block?: boolean
43 | }
44 |
45 | export const Input = forwardRef(({ size = 'md', status, prefix, suffix, allowClear = false, onClear, block = false, className = '', disabled, value, ...rest }, ref) => {
46 | const wrapperClassNames = [
47 | styles.wrapper,
48 | styles[`size-${size}`],
49 | status && styles[`status-${status}`],
50 | disabled && styles.disabled,
51 | block && styles.block,
52 | className,
53 | ].filter(Boolean).join(' ')
54 |
55 | const showClear = allowClear && value && !disabled
56 |
57 | return (
58 |
59 | {prefix && (
60 | {prefix}
61 | )}
62 |
69 | {showClear && (
70 |
78 | )}
79 | {suffix && !showClear && (
80 | {suffix}
81 | )}
82 |
83 | )
84 | })
85 |
86 | Input.displayName = 'Input'
87 |
--------------------------------------------------------------------------------
/packages/playground/umi/src/pages/index.less:
--------------------------------------------------------------------------------
1 | .container {
2 | min-height: 100vh;
3 | padding: 0 20px;
4 | font-family:
5 | -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Fira Sans', 'Droid Sans',
6 | 'Helvetica Neue', sans-serif;
7 | }
8 |
9 | .header {
10 | text-align: center;
11 | padding: 40px 0;
12 |
13 | h1 {
14 | font-size: 2.5rem;
15 | margin: 0;
16 | background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
17 | -webkit-background-clip: text;
18 | -webkit-text-fill-color: transparent;
19 | background-clip: text;
20 | }
21 |
22 | .subtitle {
23 | color: #666;
24 | font-size: 1.1rem;
25 | margin-top: 8px;
26 | }
27 | }
28 |
29 | .main {
30 | max-width: 1000px;
31 | margin: 0 auto;
32 | }
33 |
34 | .hero {
35 | text-align: center;
36 | margin-bottom: 40px;
37 |
38 | h2 {
39 | font-size: 1.8rem;
40 | margin-bottom: 16px;
41 | color: #333;
42 | }
43 |
44 | p {
45 | color: #666;
46 | font-size: 1.1rem;
47 | max-width: 600px;
48 | margin: 0 auto 24px;
49 | line-height: 1.6;
50 | }
51 | }
52 |
53 | .button {
54 | background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
55 | color: white;
56 | border: none;
57 | padding: 12px 24px;
58 | font-size: 1rem;
59 | border-radius: 8px;
60 | cursor: pointer;
61 | transition: all 0.2s ease;
62 |
63 | &:hover {
64 | transform: scale(1.05);
65 | box-shadow: 0 4px 20px rgba(102, 126, 234, 0.4);
66 | }
67 |
68 | &:active {
69 | transform: scale(0.98);
70 | }
71 | }
72 |
73 | .features {
74 | display: grid;
75 | grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
76 | gap: 24px;
77 | padding: 20px 0;
78 | }
79 |
80 | .card {
81 | display: block;
82 | padding: 24px;
83 | background: white;
84 | border-radius: 12px;
85 | text-decoration: none;
86 | transition: all 0.3s ease;
87 | border: 1px solid #eee;
88 |
89 | h3 {
90 | font-size: 1.2rem;
91 | color: #333;
92 | margin: 0 0 12px;
93 | }
94 |
95 | p {
96 | color: #666;
97 | margin: 0;
98 | line-height: 1.5;
99 | }
100 | }
101 |
--------------------------------------------------------------------------------
/packages/react-devtools-ui/src/components/Checkbox/Checkbox.module.css:
--------------------------------------------------------------------------------
1 | .checkbox {
2 | display: inline-flex;
3 | align-items: center;
4 | cursor: pointer;
5 | user-select: none;
6 | position: relative;
7 | }
8 |
9 | .input {
10 | position: absolute;
11 | opacity: 0;
12 | width: 0;
13 | height: 0;
14 | }
15 |
16 | .box {
17 | width: 16px; /* h-4 */
18 | height: 16px; /* w-4 */
19 | border: 1px solid var(--border-subtle); /* border */
20 | border-radius: 4px; /* rounded */
21 | background-color: var(--color-bg-base);
22 | display: flex;
23 | align-items: center;
24 | justify-content: center;
25 | transition: all var(--duration-fast) var(--timing-easeInOut);
26 | color: var(--on-accent);
27 | }
28 |
29 | .input:focus-visible + .box {
30 | outline: 2px solid var(--accent);
31 | outline-offset: 2px;
32 | }
33 |
34 | .input:checked + .box {
35 | background-color: var(--accent);
36 | border-color: var(--accent);
37 | }
38 |
39 | .input:checked:disabled + .box {
40 | background-color: var(--accent-weak);
41 | border-color: var(--accent-weak);
42 | color: var(--on-accent);
43 | }
44 |
45 | .input:disabled + .box {
46 | background-color: var(--color-bg-hover);
47 | border-color: var(--border-subtle);
48 | cursor: not-allowed;
49 | }
50 |
51 | [data-theme='dark'] .input:disabled + .box {
52 | background-color: var(--color-neutral-800);
53 | border-color: var(--color-neutral-700);
54 | }
55 |
56 | [data-theme='dark'] .input:checked:disabled + .box {
57 | background-color: var(--color-primary-900);
58 | border-color: var(--color-primary-800);
59 | color: var(--on-accent);
60 | }
61 |
62 | .checkIcon {
63 | width: 12px;
64 | height: 12px;
65 | fill: none;
66 | stroke: currentColor;
67 | stroke-width: 3;
68 | stroke-linecap: round;
69 | stroke-linejoin: round;
70 | opacity: 0;
71 | transform: scale(0.5);
72 | transition: all var(--duration-fast) var(--timing-easeInOut);
73 | }
74 |
75 | .input:checked + .box .checkIcon {
76 | opacity: 1;
77 | transform: scale(1);
78 | }
79 |
80 | .label {
81 | margin-left: var(--spacing-2);
82 | font-size: var(--font-size-sm);
83 | color: var(--color-text-primary);
84 | }
85 |
86 | .disabled {
87 | cursor: not-allowed;
88 | opacity: 0.5;
89 | }
90 |
--------------------------------------------------------------------------------
/packages/react-devtools-ui/src/components/Badge/Badge.module.css:
--------------------------------------------------------------------------------
1 | .wrapper {
2 | position: relative;
3 | display: inline-flex;
4 | }
5 |
6 | .badge {
7 | position: absolute;
8 | top: 0;
9 | right: 0;
10 | display: inline-flex;
11 | align-items: center;
12 | justify-content: center;
13 | min-width: 20px;
14 | height: 20px;
15 | padding: 0 6px;
16 | font-size: 12px;
17 | font-weight: var(--font-weight-medium);
18 | line-height: 1;
19 | white-space: nowrap;
20 | border-radius: 10px;
21 | transform: translate(50%, -50%);
22 | transform-origin: 100% 0%;
23 | z-index: 1;
24 | }
25 |
26 | .standalone {
27 | display: inline-flex;
28 | align-items: center;
29 | justify-content: center;
30 | min-width: 20px;
31 | height: 20px;
32 | padding: 0 6px;
33 | font-size: 12px;
34 | font-weight: var(--font-weight-medium);
35 | line-height: 1;
36 | white-space: nowrap;
37 | border-radius: 10px;
38 | }
39 |
40 | /* Dot style */
41 | .dot {
42 | min-width: 8px;
43 | width: 8px;
44 | height: 8px;
45 | padding: 0;
46 | border-radius: 50%;
47 | }
48 |
49 | /* Sizes */
50 | .size-sm {
51 | font-size: 10px;
52 | min-width: 16px;
53 | height: 16px;
54 | padding: 0 4px;
55 | }
56 |
57 | .size-sm.dot {
58 | width: 6px;
59 | height: 6px;
60 | }
61 |
62 | .size-lg {
63 | font-size: 14px;
64 | min-width: 24px;
65 | height: 24px;
66 | padding: 0 8px;
67 | }
68 |
69 | .size-lg.dot {
70 | width: 10px;
71 | height: 10px;
72 | }
73 |
74 | /* Colors */
75 | .color-primary {
76 | background-color: var(--color-primary-500);
77 | color: var(--on-accent);
78 | }
79 |
80 | .color-success {
81 | background-color: var(--color-success-500);
82 | color: var(--on-success);
83 | }
84 |
85 | .color-warning {
86 | background-color: var(--color-warning-500);
87 | color: var(--on-warning);
88 | }
89 |
90 | .color-error {
91 | background-color: var(--color-error-500);
92 | color: var(--on-error);
93 | }
94 |
95 | .color-info {
96 | background-color: var(--color-info-500);
97 | color: var(--on-info);
98 | }
99 |
100 | .color-neutral {
101 | background-color: var(--color-neutral-500);
102 | color: var(--on-neutral);
103 | }
104 |
--------------------------------------------------------------------------------
/docs/tailwind.config.ts:
--------------------------------------------------------------------------------
1 | import type { Config } from 'tailwindcss'
2 |
3 | export default {
4 | darkMode: 'class',
5 | content: ['./index.html', './src/**/*.{js,ts,jsx,tsx}'],
6 | theme: {
7 | extend: {
8 | fontFamily: {
9 | sans: ['Inter', 'sans-serif'],
10 | },
11 | colors: {
12 | brand: {
13 | 50: '#f0f9ff',
14 | 100: '#e0f2fe',
15 | 200: '#bae6fd',
16 | 300: '#7dd3fc',
17 | 400: '#38bdf8',
18 | 500: '#0ea5e9',
19 | 600: '#0284c7',
20 | 700: '#0369a1',
21 | 800: '#075985',
22 | 900: '#0c4a6e',
23 | 950: '#082f49',
24 | },
25 | },
26 | animation: {
27 | 'spin-slow': 'spin 8s linear infinite',
28 | 'border-beam': 'border-beam calc(var(--duration)*1s) infinite linear',
29 | 'shimmer': 'shimmer 2s linear infinite',
30 | 'slide-up-fade': 'slide-up-fade 0.8s cubic-bezier(0.16, 1, 0.3, 1) both',
31 | 'marquee': 'marquee var(--duration) linear infinite',
32 | 'line-grow': 'line-grow 0.8s ease-out forwards',
33 | },
34 | keyframes: {
35 | 'border-beam': {
36 | '100%': {
37 | 'offset-distance': '100%',
38 | },
39 | },
40 | 'shimmer': {
41 | from: {
42 | backgroundPosition: '0 0',
43 | },
44 | to: {
45 | backgroundPosition: '-200% 0',
46 | },
47 | },
48 | 'slide-up-fade': {
49 | '0%': {
50 | opacity: '0',
51 | transform: 'translateY(20px) scale(0.96)',
52 | filter: 'blur(4px)',
53 | },
54 | '100%': {
55 | opacity: '1',
56 | transform: 'translateY(0) scale(1)',
57 | filter: 'blur(0)',
58 | },
59 | },
60 | 'marquee': {
61 | from: { transform: 'translateX(0)' },
62 | to: { transform: 'translateX(calc(-100% - var(--gap)))' },
63 | },
64 | 'line-grow': {
65 | '0%': {
66 | transform: 'scaleX(0)',
67 | },
68 | '100%': {
69 | transform: 'scaleX(1)',
70 | },
71 | },
72 | },
73 | },
74 | },
75 | plugins: [],
76 | } satisfies Config
77 |
--------------------------------------------------------------------------------
/packages/playground/react17-webpack4/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require('node:path')
2 | const HtmlWebpackPlugin = require('html-webpack-plugin')
3 | const { reactDevToolsPlus } = require('react-devtools-plus/webpack')
4 |
5 | module.exports = {
6 | mode: 'development',
7 | entry: './src/main.jsx',
8 | output: {
9 | path: path.resolve(__dirname, 'dist'),
10 | filename: '[name].js',
11 | publicPath: '/',
12 | },
13 | module: {
14 | rules: [
15 | {
16 | test: /\.(js|jsx|ts|tsx)$/,
17 | exclude: /node_modules/,
18 | use: {
19 | loader: 'babel-loader',
20 | options: {
21 | presets: [
22 | ['@babel/preset-react', {
23 | runtime: 'classic',
24 | development: true,
25 | }],
26 | ['@babel/preset-typescript', { isTSX: true, allExtensions: true }],
27 | ],
28 | },
29 | },
30 | },
31 | {
32 | test: /\.css$/,
33 | use: ['style-loader', 'css-loader'],
34 | },
35 | {
36 | test: /\.scss$/,
37 | use: ['style-loader', 'css-loader', 'sass-loader'],
38 | },
39 | ],
40 | },
41 | resolve: {
42 | extensions: ['.js', '.jsx', '.ts', '.tsx'],
43 | // Force all packages to use the same React version from this package's node_modules
44 | // This fixes "Invalid hook call" errors caused by multiple React copies in pnpm monorepo
45 | alias: {
46 | 'react': path.resolve(__dirname, 'node_modules/react'),
47 | 'react-dom': path.resolve(__dirname, 'node_modules/react-dom'),
48 | // React 17 doesn't have react-dom/client, so alias it to react-dom to prevent errors
49 | // The code in react-globals-init.js has try-catch to handle this gracefully
50 | 'react-dom/client': path.resolve(__dirname, 'node_modules/react-dom'),
51 | },
52 | },
53 | plugins: [
54 | new HtmlWebpackPlugin({
55 | template: './public/index.html',
56 | }),
57 | reactDevToolsPlus({
58 | enabledEnvironments: ['development', 'test'],
59 | scan: {
60 | enabled: true,
61 | showToolbar: false,
62 | animationSpeed: 'fast',
63 | },
64 | }),
65 | ],
66 | devServer: {
67 | contentBase: './public',
68 | port: 3007,
69 | hot: true,
70 | },
71 | }
72 |
--------------------------------------------------------------------------------
/docs/src/components/Stats.tsx:
--------------------------------------------------------------------------------
1 | import { Gauge, ShieldCheck, Sparkles, Timer } from 'lucide-react'
2 | import React from 'react'
3 |
4 | const stats = [
5 | {
6 | label: 'Engineering teams',
7 | value: '12k+',
8 | detail: 'Using DevTools+ weekly',
9 | icon: Sparkles,
10 | },
11 | {
12 | label: 'Render insight speed',
13 | value: '24ms',
14 | detail: 'Avg. time-to-signal',
15 | icon: Timer,
16 | },
17 | {
18 | label: 'Fewer production bugs',
19 | value: '38%',
20 | detail: 'Reported after 30 days',
21 | icon: Gauge,
22 | },
23 | {
24 | label: 'Enterprise ready',
25 | value: 'SOC2',
26 | detail: 'Security by default',
27 | icon: ShieldCheck,
28 | },
29 | ]
30 |
31 | export const Stats: React.FC = () => {
32 | return (
33 |
34 |
35 |
36 |
37 | {stats.map(stat => (
38 |
42 |
43 |
44 |
45 |
{stat.label}
46 |
47 | {stat.value}
48 | {stat.detail}
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 | ))}
57 |
58 |
59 |
60 | )
61 | }
62 |
--------------------------------------------------------------------------------
/packages/ui-story/stories/Card.stories.tsx:
--------------------------------------------------------------------------------
1 | import type { Meta, StoryObj } from '@storybook/react'
2 | import { Card, ThemeProvider } from '@react-devtools-plus/ui'
3 |
4 | const meta = {
5 | title: 'Components/Card',
6 | component: Card,
7 | parameters: {
8 | layout: 'centered',
9 | },
10 | tags: ['autodocs'],
11 | argTypes: {
12 | bordered: {
13 | control: 'boolean',
14 | },
15 | hoverable: {
16 | control: 'boolean',
17 | },
18 | padding: {
19 | control: 'select',
20 | options: ['none', 'sm', 'md', 'lg'],
21 | },
22 | },
23 | decorators: [
24 | Story => (
25 |
26 |
27 |
28 |
29 |
30 | ),
31 | ],
32 | } satisfies Meta
33 |
34 | export default meta
35 | type Story = StoryObj
36 |
37 | export const Default: Story = {
38 | args: {
39 | children: 'This is a default card component with some content inside.',
40 | bordered: true,
41 | padding: 'md',
42 | },
43 | }
44 |
45 | export const WithTitle: Story = {
46 | args: {
47 | title: 'Card Title',
48 | children: 'This card has a title.',
49 | bordered: true,
50 | padding: 'md',
51 | },
52 | }
53 |
54 | export const Hoverable: Story = {
55 | args: {
56 | title: 'Hoverable Card',
57 | children: 'Hover over this card to see the effect.',
58 | bordered: true,
59 | hoverable: true,
60 | padding: 'md',
61 | },
62 | }
63 |
64 | export const NoBorder: Story = {
65 | args: {
66 | title: 'No Border',
67 | children: 'This card has no border.',
68 | bordered: false,
69 | padding: 'md',
70 | },
71 | }
72 |
73 | export const SmallPadding: Story = {
74 | args: {
75 | title: 'Small Padding',
76 | children: 'This card has small padding.',
77 | bordered: true,
78 | padding: 'sm',
79 | },
80 | }
81 |
82 | export const LargePadding: Story = {
83 | args: {
84 | title: 'Large Padding',
85 | children: 'This card has large padding.',
86 | bordered: true,
87 | padding: 'lg',
88 | },
89 | }
90 |
91 | export const NoPadding: Story = {
92 | args: {
93 | title: 'No Padding',
94 | children: 'This card has no padding.',
95 | bordered: true,
96 | padding: 'none',
97 | },
98 | }
99 |
--------------------------------------------------------------------------------
/packages/react-devtools/src/codegen/config-injector.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Configuration Injection Code Generation
3 | *
4 | * Generates code to inject DevTools configuration into the page.
5 | * Useful for micro-frontend scenarios (singleSpa) where config needs
6 | * to be passed from the build tool to the overlay at runtime.
7 | */
8 |
9 | export interface DevToolsRuntimeConfig {
10 | /** Custom URL for DevTools client panel */
11 | clientUrl?: string
12 | /** CSS selector for the root container of the React app to inspect */
13 | rootSelector?: string
14 | /** Theme configuration */
15 | theme?: {
16 | mode?: 'auto' | 'light' | 'dark'
17 | primaryColor?: string
18 | }
19 | /** Assets panel configuration */
20 | assets?: {
21 | files?: string[]
22 | }
23 | }
24 |
25 | /**
26 | * Generate code to inject DevTools runtime configuration
27 | */
28 | export function generateConfigInjectionCode(config: DevToolsRuntimeConfig): string {
29 | if (!config.clientUrl && !config.rootSelector && !config.theme && !config.assets) {
30 | return ''
31 | }
32 |
33 | const configParts: string[] = []
34 | if (config.clientUrl) {
35 | configParts.push(`window.__REACT_DEVTOOLS_CONFIG__.clientUrl = ${JSON.stringify(config.clientUrl)};`)
36 | }
37 | if (config.rootSelector) {
38 | configParts.push(`window.__REACT_DEVTOOLS_CONFIG__.rootSelector = ${JSON.stringify(config.rootSelector)};`)
39 | }
40 | if (config.theme) {
41 | configParts.push(`window.__REACT_DEVTOOLS_CONFIG__.theme = ${JSON.stringify(config.theme)};`)
42 | }
43 | if (config.assets) {
44 | configParts.push(`window.__REACT_DEVTOOLS_CONFIG__.assets = ${JSON.stringify(config.assets)};`)
45 | }
46 |
47 | return `
48 | if (typeof window !== 'undefined') {
49 | window.__REACT_DEVTOOLS_CONFIG__ = window.__REACT_DEVTOOLS_CONFIG__ || {};
50 | ${configParts.join('\n ')}
51 | }
52 | `.trim()
53 | }
54 |
55 | /**
56 | * Generate script tag for config injection
57 | */
58 | export function generateConfigScriptTag(config: DevToolsRuntimeConfig): {
59 | tag: 'script'
60 | attrs: Record
61 | children: string
62 | injectTo: 'head-prepend'
63 | } | null {
64 | const code = generateConfigInjectionCode(config)
65 | if (!code) {
66 | return null
67 | }
68 |
69 | return {
70 | tag: 'script',
71 | attrs: { },
72 | children: code,
73 | injectTo: 'head-prepend',
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Created by .ignore support plugin (hsz.mobi)
2 | ### Node template
3 | # Logs
4 | logs
5 | *.log
6 | npm-debug.log*
7 | yarn-debug.log*
8 | yarn-error.log*
9 |
10 | # Runtime data
11 | pids
12 | *.pid
13 | *.seed
14 | *.pid.lock
15 |
16 | # Directory for instrumented libs generated by jscoverage/JSCover
17 | lib-cov
18 |
19 | # Coverage directory used by tools like istanbul
20 | coverage
21 |
22 | # nyc test coverage
23 | .nyc_output
24 |
25 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
26 | .grunt
27 |
28 | # Bower dependency directory (https://bower.io/)
29 | bower_components
30 |
31 | # node-waf configuration
32 | .lock-wscript
33 |
34 | # Compiled binary addons (https://nodejs.org/api/addons.html)
35 | build/Release
36 |
37 | # Dependency directories
38 | node_modules/
39 | jspm_packages/
40 |
41 | # TypeScript v1 declaration files
42 | typings/
43 |
44 | # Optional npm cache directory
45 | .npm
46 |
47 | # Optional eslint cache
48 | .eslintcache
49 |
50 | # Optional REPL history
51 | .node_repl_history
52 |
53 | # Output of 'npm pack'
54 | *.tgz
55 |
56 | # Yarn Integrity file
57 | .yarn-integrity
58 |
59 | # dotenv environment variables file
60 | .env
61 |
62 | # parcel-bundler cache (https://parceljs.org/)
63 | .cache
64 |
65 | # next.js build output
66 | .next
67 |
68 | # nuxt.js build output
69 | .nuxt
70 |
71 | # Nuxt generate
72 | dist
73 |
74 | # vuepress build output
75 | .vuepress/dist
76 |
77 | # Serverless directories
78 | .serverless
79 |
80 | # IDE
81 | .idea
82 | .cursor
83 |
84 | .vite-inspect
85 | auto-imports.d.ts
86 | components.d.ts
87 |
88 | # histoire
89 | .histoire
90 |
91 | # Project
92 |
93 | packages/chrome-extension/overlay/**/*
94 | packages/chrome-extension/client/**/*
95 | packages/firefox-extension/client/**/*
96 | packages/firefox-extension/overlay/**/*
97 | packages/electron/client/**/*
98 | packages/vite/src/overlay/**/*
99 | packages/vite/client/**/*
100 | docs/.vitepress/cache/
101 |
102 | # for scripts
103 | clones
104 |
105 | .DS_Store
106 | *.timestamp-*.*
107 |
108 | # turborepo
109 | .turbo
110 |
111 | .history
112 | .pnpm-store
113 | storybook-static
114 |
115 | # react
116 | packages/react-devtools/src/overlay/**/*
117 | packages/react-devtools/client/**/*
118 | packages/react-devtools-client/dist/**/*
119 | packages/react-devtools-overlay/dist/**/*
120 |
--------------------------------------------------------------------------------
/packages/react-devtools/src/middleware/open-in-editor.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Open in editor middleware
3 | * 在编辑器中打开的中间件
4 | */
5 |
6 | import type { SourcePathMode } from '../config/types'
7 | import { exec } from 'node:child_process'
8 | import { convertToEditorPath, parseEditorPath, resolveRelativeToAbsolute } from '../utils/paths'
9 |
10 | /**
11 | * Open file in editor
12 | * 在编辑器中打开文件
13 | */
14 | export async function openFileInEditor(
15 | filePath: string,
16 | projectRoot: string,
17 | sourcePathMode: SourcePathMode,
18 | ): Promise {
19 | return new Promise((resolve, reject) => {
20 | // Parse file:line:column
21 | const { filePath: file, line, column } = parseEditorPath(filePath)
22 |
23 | // Resolve to absolute path if needed
24 | const absolutePath = resolveRelativeToAbsolute(file, projectRoot, sourcePathMode)
25 |
26 | // Build editor path
27 | const editorPath = convertToEditorPath(absolutePath, line, column)
28 |
29 | // Get editor command from environment
30 | const editorCmd = process.env.EDITOR || 'cursor'
31 | const cmd = `${editorCmd} -g "${editorPath}"`
32 |
33 | exec(cmd, (error) => {
34 | if (error) {
35 | console.error('[React DevTools] Failed to open editor:', error)
36 | reject(error)
37 | }
38 | else {
39 | resolve()
40 | }
41 | })
42 | })
43 | }
44 |
45 | /**
46 | * Create Express/Connect middleware for handling open-in-editor requests
47 | * 创建用于处理"在编辑器中打开"请求的中间件
48 | */
49 | export function createOpenInEditorMiddleware(
50 | projectRoot: string,
51 | sourcePathMode: SourcePathMode,
52 | ) {
53 | return async (req: any, res: any, next?: () => void) => {
54 | // Check if this is an open-in-editor request
55 | if (!req.url?.startsWith('/__open-in-editor')) {
56 | next?.()
57 | return
58 | }
59 |
60 | // Parse query parameters
61 | const url = new URL(req.url, `http://${req.headers.host}`)
62 | const file = url.searchParams.get('file')
63 |
64 | if (!file) {
65 | res.statusCode = 400
66 | res.end('Missing file parameter')
67 | return
68 | }
69 |
70 | try {
71 | await openFileInEditor(file, projectRoot, sourcePathMode)
72 | res.statusCode = 200
73 | res.end('OK')
74 | }
75 | catch (error: any) {
76 | console.error('[React DevTools] Failed to execute editor command:', error.message)
77 | res.statusCode = 500
78 | res.end('Failed to open editor')
79 | }
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/packages/react-devtools-ui/src/components/Button/Button.tsx:
--------------------------------------------------------------------------------
1 | import type { ButtonHTMLAttributes, CSSProperties, FC, ReactNode } from 'react'
2 | import styles from './Button.module.css'
3 |
4 | export interface ButtonProps extends Omit, 'type'> {
5 | /**
6 | * Button content
7 | */
8 | children?: ReactNode
9 |
10 | /**
11 | * Button type
12 | * @default 'button'
13 | */
14 | htmlType?: 'button' | 'submit' | 'reset'
15 |
16 | /**
17 | * Button variant
18 | * @default 'default'
19 | */
20 | variant?: 'default' | 'primary' | 'success' | 'warning' | 'error' | 'ghost' | 'text'
21 |
22 | /**
23 | * Button size
24 | * @default 'md'
25 | */
26 | size?: 'sm' | 'md' | 'lg'
27 |
28 | /**
29 | * Disabled state
30 | * @default false
31 | */
32 | disabled?: boolean
33 |
34 | /**
35 | * Loading state
36 | * @default false
37 | */
38 | loading?: boolean
39 |
40 | /**
41 | * Block level button
42 | * @default false
43 | */
44 | block?: boolean
45 |
46 | /**
47 | * Icon before content
48 | */
49 | icon?: ReactNode
50 |
51 | /**
52 | * Additional CSS class
53 | */
54 | className?: string
55 |
56 | /**
57 | * Custom styles
58 | */
59 | style?: CSSProperties
60 | }
61 |
62 | export const Button: FC = ({
63 | children,
64 | htmlType = 'button',
65 | variant = 'default',
66 | size = 'md',
67 | disabled = false,
68 | loading = false,
69 | block = false,
70 | icon,
71 | className = '',
72 | style,
73 | onClick,
74 | ...rest
75 | }) => {
76 | const classNames = [
77 | styles.button,
78 | styles[`variant-${variant}`],
79 | styles[`size-${size}`],
80 | block && styles.block,
81 | (disabled || loading) && styles.disabled,
82 | loading && styles.loading,
83 | className,
84 | ].filter(Boolean).join(' ')
85 |
86 | return (
87 |
105 | )
106 | }
107 |
--------------------------------------------------------------------------------
/packages/react-devtools-core/examples/performance-plugin.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Example: Performance Monitoring Plugin
3 | * 示例:性能监控插件
4 | *
5 | * This plugin monitors component render performance and provides analytics
6 | * 此插件监控组件渲染性能并提供分析
7 | */
8 |
9 | import type { DevToolsPlugin, PerformanceMetrics } from '../src/types'
10 |
11 | export const performancePlugin: DevToolsPlugin = {
12 | id: 'performance-monitor',
13 | name: 'Performance Monitor',
14 | description: 'Monitor and analyze component render performance',
15 |
16 | // Plugin state
17 | metrics: [] as PerformanceMetrics[],
18 | slowComponents: new Set(),
19 | threshold: 16, // 16ms (60fps)
20 |
21 | async setup(context) {
22 | console.log('[Performance Plugin] Initializing...')
23 |
24 | // Register custom RPC functions
25 | context.registerRPC('getSlowComponents', () => {
26 | return Array.from(this.slowComponents)
27 | })
28 |
29 | context.registerRPC('getPerformanceMetrics', () => {
30 | return this.metrics
31 | })
32 |
33 | context.registerRPC('clearMetrics', () => {
34 | this.metrics = []
35 | this.slowComponents.clear()
36 | })
37 |
38 | context.registerRPC('setThreshold', (threshold: number) => {
39 | this.threshold = threshold
40 | })
41 |
42 | // Subscribe to component update events
43 | context.on('component-updated', (event) => {
44 | // This would be called when a component updates
45 | // In a real implementation, we'd collect actual performance data
46 | const metric: PerformanceMetrics = {
47 | componentId: event.componentId,
48 | renderTime: Math.random() * 50, // Simulated render time
49 | updateCount: 1,
50 | timestamp: Date.now(),
51 | }
52 |
53 | this.metrics.push(metric)
54 |
55 | // Track slow components
56 | if (metric.renderTime > this.threshold) {
57 | this.slowComponents.add(event.componentId)
58 |
59 | // Emit custom event for slow renders
60 | context.emit({
61 | type: 'component-updated',
62 | componentId: event.componentId,
63 | })
64 | }
65 |
66 | // Keep only last 1000 metrics
67 | if (this.metrics.length > 1000) {
68 | this.metrics = this.metrics.slice(-1000)
69 | }
70 | })
71 |
72 | console.log('[Performance Plugin] Initialized successfully')
73 | },
74 |
75 | async teardown() {
76 | console.log('[Performance Plugin] Cleaning up...')
77 | this.metrics = []
78 | this.slowComponents.clear()
79 | },
80 | }
81 |
--------------------------------------------------------------------------------
/packages/react-devtools-ui/src/components/Badge/Badge.tsx:
--------------------------------------------------------------------------------
1 | import type { CSSProperties, FC, ReactNode } from 'react'
2 | import styles from './Badge.module.css'
3 |
4 | export interface BadgeProps {
5 | /**
6 | * Badge content
7 | */
8 | children?: ReactNode
9 |
10 | /**
11 | * Badge count or text
12 | */
13 | count?: number | string
14 |
15 | /**
16 | * Show dot instead of count
17 | * @default false
18 | */
19 | dot?: boolean
20 |
21 | /**
22 | * Max count to display
23 | * @default 99
24 | */
25 | max?: number
26 |
27 | /**
28 | * Badge color
29 | * @default 'primary'
30 | */
31 | color?: 'primary' | 'success' | 'warning' | 'error' | 'info' | 'neutral'
32 |
33 | /**
34 | * Badge size
35 | * @default 'md'
36 | */
37 | size?: 'sm' | 'md' | 'lg'
38 |
39 | /**
40 | * Show zero count
41 | * @default false
42 | */
43 | showZero?: boolean
44 |
45 | /**
46 | * Additional CSS class
47 | */
48 | className?: string
49 |
50 | /**
51 | * Custom styles
52 | */
53 | style?: CSSProperties
54 | }
55 |
56 | export const Badge: FC = ({
57 | children,
58 | count = 0,
59 | dot = false,
60 | max = 99,
61 | color = 'primary',
62 | size = 'md',
63 | showZero = false,
64 | className = '',
65 | style,
66 | }) => {
67 | const numericCount = typeof count === 'number' ? count : 0
68 | const displayCount = numericCount > max ? `${max}+` : count
69 | const showBadge = dot || showZero || (numericCount > 0) || (typeof count === 'string' && count)
70 |
71 | if (!children) {
72 | // Standalone badge
73 | return (
74 |
84 | {!dot && displayCount}
85 |
86 | )
87 | }
88 |
89 | return (
90 |
91 | {children}
92 | {showBadge && (
93 |
101 | {!dot && displayCount}
102 |
103 | )}
104 |
105 | )
106 | }
107 |
--------------------------------------------------------------------------------
/packages/playground/umi/src/pages/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react'
2 | import { Link } from 'umi'
3 | import styles from './index.less'
4 |
5 | interface CardProps {
6 | title: string
7 | description: string
8 | link: string
9 | }
10 |
11 | function FeatureCard({ title, description, link }: CardProps) {
12 | const [isHovered, setIsHovered] = useState(false)
13 |
14 | return (
15 | setIsHovered(true)}
19 | onMouseLeave={() => setIsHovered(false)}
20 | style={{
21 | transform: isHovered ? 'translateY(-4px)' : 'translateY(0)',
22 | boxShadow: isHovered
23 | ? '0 8px 30px rgba(0, 0, 0, 0.12)'
24 | : '0 2px 8px rgba(0, 0, 0, 0.08)',
25 | }}
26 | >
27 | {title}
28 | {description}
29 |
30 | )
31 | }
32 |
33 | export default function HomePage() {
34 | const [clickCount, setClickCount] = useState(0)
35 |
36 | return (
37 |
38 |
42 |
43 |
44 |
45 | Welcome to the Umi Playground
46 |
47 | This playground demonstrates the integration of React DevTools Plus
48 | with Umi framework. Explore the features below!
49 |
50 |
60 |
61 |
62 |
79 |
80 |
81 | )
82 | }
83 |
--------------------------------------------------------------------------------
/packages/playground/react-webpack/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require('node:path')
2 | const HtmlWebpackPlugin = require('html-webpack-plugin')
3 | const { reactDevToolsPlus } = require('react-devtools-plus/webpack')
4 | const webpack = require('webpack')
5 |
6 | module.exports = {
7 | entry: './src/main.jsx',
8 | output: {
9 | path: path.resolve(__dirname, 'dist'),
10 | filename: '[name].js',
11 | clean: true,
12 | },
13 | module: {
14 | rules: [
15 | {
16 | oneOf: [
17 | {
18 | test: /\.(js|jsx|ts|tsx)$/,
19 | exclude: /node_modules/,
20 | use: {
21 | loader: 'babel-loader',
22 | options: {
23 | presets: [
24 | ['@babel/preset-react', {
25 | runtime: 'automatic',
26 | // development 开启时,React Fiber 下存在_debugSource,可用于定位源码位置
27 | development: true,
28 | }],
29 | ['@babel/preset-typescript', { isTSX: true, allExtensions: true }],
30 | ],
31 | },
32 | },
33 | },
34 | {
35 | test: /\.css$/,
36 | use: ['style-loader', 'css-loader'],
37 | },
38 | ],
39 | },
40 | ],
41 | },
42 | resolve: {
43 | extensions: ['.js', '.jsx', '.ts', '.tsx'],
44 | },
45 | plugins: [
46 | new HtmlWebpackPlugin({
47 | template: './public/index.html',
48 | }),
49 | // 自动注入 React
50 | new webpack.ProvidePlugin({
51 | React: 'react',
52 | }),
53 | reactDevToolsPlus({
54 | enabledEnvironments: ['development', 'test'],
55 | plugins: [
56 | {
57 | name: 'my-plugin',
58 | view: {
59 | title: 'My Plugin',
60 | icon: '',
61 | src: path.resolve(__dirname, './src/plugins/MyPlugin.jsx'),
62 | },
63 | },
64 | ],
65 | // Enable React Scan auto-injection
66 | scan: {
67 | enabled: true, // Enable injection, but controlled by showToolbar/DevTools
68 | showToolbar: false,
69 | animationSpeed: 'fast',
70 | },
71 | }),
72 | ],
73 | devServer: {
74 | host: 'localhost',
75 | port: 3004,
76 | hot: true,
77 | open: false,
78 | },
79 | }
80 |
--------------------------------------------------------------------------------
/packages/react-devtools/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-devtools-plus",
3 | "type": "module",
4 | "version": "0.4.0",
5 | "author": {
6 | "name": "wzc520pyfm",
7 | "email": "wzc520pyf@163.com",
8 | "url": "https://github.com/wzc520pyfm"
9 | },
10 | "license": "MIT",
11 | "repository": {
12 | "type": "git"
13 | },
14 | "keywords": [
15 | "react-devtools-plus",
16 | "react-devtools",
17 | "dx",
18 | "devtools",
19 | "react",
20 | "react-scan",
21 | "scan",
22 | "components",
23 | "debug",
24 | "inspector",
25 | "dev"
26 | ],
27 | "exports": {
28 | ".": {
29 | "types": "./dist/index.d.ts",
30 | "import": "./dist/index.js",
31 | "require": "./dist/index.cjs"
32 | },
33 | "./vite": {
34 | "types": "./dist/vite.d.ts",
35 | "import": "./dist/vite.js",
36 | "require": "./dist/vite.cjs"
37 | },
38 | "./webpack": {
39 | "types": "./dist/webpack.d.ts",
40 | "import": "./dist/webpack.js",
41 | "require": "./dist/webpack.cjs"
42 | },
43 | "./scan": {
44 | "types": "./dist/scan.d.ts",
45 | "import": "./dist/scan.js",
46 | "require": "./dist/scan.cjs"
47 | },
48 | "./umi": {
49 | "types": "./dist/umi.d.ts",
50 | "import": "./dist/umi.js",
51 | "require": "./dist/umi.cjs"
52 | },
53 | "./package.json": "./package.json"
54 | },
55 | "main": "./dist/index.cjs",
56 | "module": "./dist/index.js",
57 | "types": "./dist/index.d.ts",
58 | "scripts": {
59 | "build": "tsup",
60 | "dev": "tsup --watch",
61 | "prepare:type": "tsup --dts-only",
62 | "stub": "tsup --watch --dts-only"
63 | },
64 | "peerDependencies": {
65 | "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
66 | "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
67 | "vite": "^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0",
68 | "webpack": "^4.0.0 || ^5.0.0"
69 | },
70 | "peerDependenciesMeta": {
71 | "vite": {
72 | "optional": true
73 | },
74 | "webpack": {
75 | "optional": true
76 | }
77 | },
78 | "dependencies": {
79 | "@babel/core": "^7.26.0",
80 | "@babel/plugin-syntax-jsx": "^7.25.9",
81 | "@babel/plugin-syntax-typescript": "^7.25.9",
82 | "@babel/preset-react": "^7.26.3",
83 | "@babel/preset-typescript": "^7.26.4",
84 | "@babel/types": "^7.26.0",
85 | "kolorist": "^1.8.0",
86 | "launch-editor": "^2.9.1",
87 | "sirv": "^2.0.4",
88 | "unplugin": "^2.3.10"
89 | },
90 | "devDependencies": {
91 | "@react-devtools-plus/scan": "workspace:*",
92 | "tsup": "^8.3.5",
93 | "typescript": "^5.5.0"
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/packages/ui-story/stories/Badge.stories.tsx:
--------------------------------------------------------------------------------
1 | import type { Meta, StoryObj } from '@storybook/react'
2 | import { Badge, Button, ThemeProvider } from '@react-devtools-plus/ui'
3 |
4 | const meta = {
5 | title: 'Components/Badge',
6 | component: Badge,
7 | parameters: {
8 | layout: 'centered',
9 | },
10 | tags: ['autodocs'],
11 | argTypes: {
12 | color: {
13 | control: 'select',
14 | options: ['default', 'primary', 'success', 'warning', 'error', 'info'],
15 | },
16 | display: {
17 | control: 'select',
18 | options: ['dot', 'count'],
19 | },
20 | overflowCount: {
21 | control: 'number',
22 | },
23 | },
24 | decorators: [
25 | Story => (
26 |
27 |
28 |
29 | ),
30 | ],
31 | } satisfies Meta
32 |
33 | export default meta
34 | type Story = StoryObj
35 |
36 | export const Default: Story = {
37 | args: {
38 | count: 5,
39 | children: ,
40 | },
41 | }
42 |
43 | export const Primary: Story = {
44 | args: {
45 | count: 5,
46 | color: 'primary',
47 | children: ,
48 | },
49 | }
50 |
51 | export const Success: Story = {
52 | args: {
53 | count: 5,
54 | color: 'success',
55 | children: ,
56 | },
57 | }
58 |
59 | export const Warning: Story = {
60 | args: {
61 | count: 5,
62 | color: 'warning',
63 | children: ,
64 | },
65 | }
66 |
67 | export const Error: Story = {
68 | args: {
69 | count: 5,
70 | color: 'error',
71 | children: ,
72 | },
73 | }
74 |
75 | export const Info: Story = {
76 | args: {
77 | count: 5,
78 | color: 'info',
79 | children: ,
80 | },
81 | }
82 |
83 | export const Dot: Story = {
84 | args: {
85 | display: 'dot',
86 | children: ,
87 | },
88 | }
89 |
90 | export const LargeCount: Story = {
91 | args: {
92 | count: 99,
93 | children: ,
94 | },
95 | }
96 |
97 | export const OverflowCount: Story = {
98 | args: {
99 | count: 1000,
100 | overflowCount: 99,
101 | children: ,
102 | },
103 | }
104 |
105 | export const Standalone: Story = {
106 | args: {
107 | count: 5,
108 | color: 'error',
109 | },
110 | }
111 |
112 | export const StandaloneDot: Story = {
113 | args: {
114 | display: 'dot',
115 | color: 'success',
116 | },
117 | }
118 |
--------------------------------------------------------------------------------
/packages/react-devtools-client/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | React DevTools
7 |
49 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
--------------------------------------------------------------------------------
/docs/src/pages/docs/FAQ.tsx:
--------------------------------------------------------------------------------
1 | import { ChevronDown, HelpCircle } from 'lucide-react'
2 | import React, { useState } from 'react'
3 | import { useTranslation } from 'react-i18next'
4 |
5 | interface FAQItemProps {
6 | question: string
7 | answer: string
8 | }
9 |
10 | const FAQItem: React.FC = ({ question, answer }) => {
11 | const [isOpen, setIsOpen] = useState(false)
12 |
13 | return (
14 |
15 |
24 | {isOpen && (
25 |
26 | {answer}
27 |
28 | )}
29 |
30 | )
31 | }
32 |
33 | export const FAQ: React.FC = () => {
34 | const { t } = useTranslation()
35 |
36 | const faqs = t('docs.faq.items', { returnObjects: true }) as Array<{
37 | question: string
38 | answer: string
39 | }>
40 |
41 | return (
42 |
43 |
{t('docs.faq.title')}
44 |
45 |
46 | {t('docs.faq.description')}
47 |
48 |
49 |
71 |
72 |
73 | {faqs.map((faq, idx) => (
74 |
75 | ))}
76 |
77 |
78 | )
79 | }
80 |
--------------------------------------------------------------------------------
/packages/playground/react-webpack/src/plugins/MyPlugin.jsx:
--------------------------------------------------------------------------------
1 | export default function MyPlugin(props) {
2 | const { tree, selectedNodeId, theme } = props
3 |
4 | return (
5 |
6 |
My Custom Plugin (Webpack)
7 |
8 |
9 | {/* Theme Info */}
10 |
11 |
Theme Context
12 |
13 | Mode:
14 |
15 | {theme?.mode || 'unknown'}
16 |
17 |
18 |
19 |
20 | {/* Selection Info */}
21 |
22 |
Selected Node
23 | {selectedNodeId
24 | ? (
25 |
26 |
27 | ID:
28 | {' '}
29 | {selectedNodeId}
30 |
31 |
Select components in the tree to see updates here.
32 |
33 | )
34 | : (
35 |
No component selected
36 | )}
37 |
38 |
39 | {/* Tree Info */}
40 |
41 |
Tree Stats
42 |
43 | {tree
44 | ? (
45 |
46 | Tree data available (root ID:
47 | {' '}
48 | {tree.rootID || 'unknown'}
49 | )
50 |
51 | )
52 | : (
53 |
Waiting for tree data...
54 | )}
55 |
56 |
57 |
58 | {/* Raw Context JSON */}
59 |
60 |
Raw Context Data
61 |
62 | {JSON.stringify(props, null, 2)}
63 |
64 |
65 |
66 |
67 | )
68 | }
69 |
--------------------------------------------------------------------------------
/packages/react-devtools/client/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | React DevTools
7 |
49 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
--------------------------------------------------------------------------------
/packages/react-devtools-ui/src/styles/base.css:
--------------------------------------------------------------------------------
1 | /**
2 | * Base styles and CSS reset
3 | * 基础样式和CSS重置
4 | */
5 |
6 | *,
7 | *::before,
8 | *::after {
9 | box-sizing: border-box;
10 | }
11 |
12 | * {
13 | margin: 0;
14 | padding: 0;
15 | }
16 |
17 | html {
18 | -webkit-font-smoothing: antialiased;
19 | -moz-osx-font-smoothing: grayscale;
20 | text-rendering: optimizeLegibility;
21 | }
22 |
23 | body {
24 | font-family: var(--font-family-sans);
25 | font-size: var(--font-size-base);
26 | line-height: var(--line-height-normal);
27 | color: var(--color-text-primary);
28 | background-color: var(--color-bg-base);
29 | }
30 |
31 | /* Remove default button styles */
32 | button {
33 | font-family: inherit;
34 | font-size: inherit;
35 | line-height: inherit;
36 | background: none;
37 | border: none;
38 | padding: 0;
39 | cursor: pointer;
40 | }
41 |
42 | /* Remove default input styles */
43 | input,
44 | textarea,
45 | select {
46 | font-family: inherit;
47 | font-size: inherit;
48 | line-height: inherit;
49 | }
50 |
51 | /* Remove default list styles */
52 | ul,
53 | ol {
54 | list-style: none;
55 | }
56 |
57 | /* Remove default link styles */
58 | a {
59 | color: inherit;
60 | text-decoration: none;
61 | }
62 |
63 | /* Remove default table styles */
64 | table {
65 | border-collapse: collapse;
66 | border-spacing: 0;
67 | }
68 |
69 | /* Scrollbar styles */
70 | ::-webkit-scrollbar {
71 | width: 6px;
72 | height: 6px;
73 | }
74 |
75 | ::-webkit-scrollbar-track {
76 | background: var(--color-bg-base);
77 | }
78 |
79 | ::-webkit-scrollbar-thumb {
80 | background: var(--color-neutral-400);
81 | border-radius: var(--radius-full);
82 | }
83 |
84 | ::-webkit-scrollbar-thumb:hover {
85 | background: var(--color-neutral-500);
86 | }
87 |
88 | [data-theme='dark'] ::-webkit-scrollbar-thumb {
89 | background: var(--color-neutral-600);
90 | }
91 |
92 | [data-theme='dark'] ::-webkit-scrollbar-thumb:hover {
93 | background: var(--color-neutral-500);
94 | }
95 |
96 | /* Focus visible */
97 | :focus-visible {
98 | outline: 2px solid var(--color-primary-500);
99 | outline-offset: 2px;
100 | }
101 |
102 | /* Selection */
103 | ::selection {
104 | background-color: var(--color-primary-200);
105 | color: var(--color-text-primary);
106 | }
107 |
108 | [data-theme='dark'] ::selection {
109 | background-color: var(--color-primary-800);
110 | }
111 |
112 | /* View Transition support */
113 | ::view-transition-old(root),
114 | ::view-transition-new(root) {
115 | animation: none;
116 | mix-blend-mode: normal;
117 | }
118 |
119 | ::view-transition-old(root) {
120 | z-index: 1;
121 | }
122 |
123 | ::view-transition-new(root) {
124 | z-index: 2147483646; /* Ensure it's above everything */
125 | }
126 |
--------------------------------------------------------------------------------
/packages/playground/react19/src/style.css:
--------------------------------------------------------------------------------
1 | * {
2 | box-sizing: border-box;
3 | }
4 |
5 | body {
6 | margin: 0;
7 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
8 | background-color: #f5f5f5;
9 | color: #333;
10 | }
11 |
12 | .app {
13 | max-width: 800px;
14 | margin: 0 auto;
15 | padding: 20px;
16 | }
17 |
18 | header {
19 | text-align: center;
20 | margin-bottom: 40px;
21 | }
22 |
23 | header h1 {
24 | color: #087ea4;
25 | font-size: 2rem;
26 | }
27 |
28 | main {
29 | display: flex;
30 | flex-direction: column;
31 | gap: 24px;
32 | }
33 |
34 | .react-info,
35 | .counter,
36 | .todo-list {
37 | background: white;
38 | border-radius: 12px;
39 | padding: 24px;
40 | box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
41 | }
42 |
43 | h2 {
44 | margin-top: 0;
45 | color: #087ea4;
46 | font-size: 1.25rem;
47 | }
48 |
49 | .info-text {
50 | color: #666;
51 | line-height: 1.6;
52 | }
53 |
54 | kbd {
55 | background: #eee;
56 | border-radius: 4px;
57 | padding: 2px 8px;
58 | font-family: monospace;
59 | font-size: 0.9em;
60 | border: 1px solid #ddd;
61 | }
62 |
63 | /* Counter styles */
64 | .counter p {
65 | font-size: 1.25rem;
66 | }
67 |
68 | .button-group {
69 | display: flex;
70 | gap: 12px;
71 | }
72 |
73 | button {
74 | padding: 8px 16px;
75 | border: none;
76 | border-radius: 6px;
77 | background: #087ea4;
78 | color: white;
79 | cursor: pointer;
80 | font-size: 1rem;
81 | transition: background-color 0.2s;
82 | }
83 |
84 | button:hover {
85 | background: #066d8f;
86 | }
87 |
88 | button:active {
89 | background: #055a77;
90 | }
91 |
92 | /* Todo list styles */
93 | .todo-input {
94 | display: flex;
95 | gap: 12px;
96 | margin-bottom: 16px;
97 | }
98 |
99 | .todo-input input {
100 | flex: 1;
101 | padding: 10px 14px;
102 | border: 2px solid #ddd;
103 | border-radius: 6px;
104 | font-size: 1rem;
105 | outline: none;
106 | transition: border-color 0.2s;
107 | }
108 |
109 | .todo-input input:focus {
110 | border-color: #087ea4;
111 | }
112 |
113 | .todo-list ul {
114 | list-style: none;
115 | padding: 0;
116 | margin: 0;
117 | }
118 |
119 | .todo-list li {
120 | display: flex;
121 | align-items: center;
122 | justify-content: space-between;
123 | padding: 12px 0;
124 | border-bottom: 1px solid #eee;
125 | }
126 |
127 | .todo-list li:last-child {
128 | border-bottom: none;
129 | }
130 |
131 | .todo-list li button {
132 | background: #ff6b6b;
133 | width: 28px;
134 | height: 28px;
135 | padding: 0;
136 | display: flex;
137 | align-items: center;
138 | justify-content: center;
139 | font-size: 1.25rem;
140 | }
141 |
142 | .todo-list li button:hover {
143 | background: #ee5253;
144 | }
145 |
--------------------------------------------------------------------------------
/packages/playground/umi/src/pages/about.less:
--------------------------------------------------------------------------------
1 | .container {
2 | min-height: 100vh;
3 | padding: 0 20px;
4 | font-family:
5 | -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Fira Sans', 'Droid Sans',
6 | 'Helvetica Neue', sans-serif;
7 | background: #fafafa;
8 | }
9 |
10 | .header {
11 | max-width: 800px;
12 | margin: 0 auto;
13 | padding: 40px 0;
14 |
15 | h1 {
16 | font-size: 2rem;
17 | margin: 0 0 12px;
18 | color: #333;
19 | }
20 |
21 | .subtitle {
22 | color: #666;
23 | font-size: 1.1rem;
24 | margin: 0;
25 | }
26 |
27 | .backLink {
28 | display: inline-block;
29 | margin-bottom: 20px;
30 | color: #667eea;
31 | text-decoration: none;
32 | font-weight: 500;
33 |
34 | &:hover {
35 | text-decoration: underline;
36 | }
37 | }
38 | }
39 |
40 | .main {
41 | max-width: 800px;
42 | margin: 0 auto;
43 | padding-bottom: 60px;
44 | }
45 |
46 | .features {
47 | display: flex;
48 | flex-direction: column;
49 | gap: 16px;
50 | margin-bottom: 40px;
51 | }
52 |
53 | .featureItem {
54 | display: flex;
55 | align-items: flex-start;
56 | padding: 20px;
57 | background: white;
58 | border-radius: 12px;
59 | box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
60 | cursor: pointer;
61 | transition: all 0.2s ease;
62 |
63 | &:hover {
64 | box-shadow: 0 4px 16px rgba(0, 0, 0, 0.1);
65 | }
66 |
67 | .icon {
68 | font-size: 1.5rem;
69 | margin-right: 16px;
70 | }
71 |
72 | .content {
73 | flex: 1;
74 |
75 | h3 {
76 | margin: 0 0 8px;
77 | font-size: 1.1rem;
78 | color: #333;
79 | }
80 |
81 | p {
82 | margin: 0;
83 | color: #666;
84 | line-height: 1.5;
85 | }
86 | }
87 |
88 | .expandedContent {
89 | margin-top: 12px;
90 | padding-top: 12px;
91 | border-top: 1px solid #eee;
92 |
93 | p {
94 | font-size: 0.9rem;
95 | color: #888;
96 | }
97 | }
98 | }
99 |
100 | .info {
101 | background: white;
102 | padding: 30px;
103 | border-radius: 12px;
104 | box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
105 |
106 | h2 {
107 | font-size: 1.4rem;
108 | margin: 0 0 16px;
109 | color: #333;
110 | }
111 |
112 | p {
113 | color: #666;
114 | line-height: 1.6;
115 |
116 | code {
117 | background: #f0f0f0;
118 | padding: 2px 6px;
119 | border-radius: 4px;
120 | font-size: 0.9em;
121 | }
122 | }
123 | }
124 |
125 | .codeBlock {
126 | background: #1a1a2e;
127 | color: #e0e0e0;
128 | padding: 20px;
129 | border-radius: 8px;
130 | overflow-x: auto;
131 | font-family: 'Fira Code', 'Monaco', 'Consolas', monospace;
132 | font-size: 0.85rem;
133 | line-height: 1.6;
134 | margin-top: 16px;
135 | }
136 |
--------------------------------------------------------------------------------
/packages/react-devtools-client/src/components/graph/GraphNavbar.tsx:
--------------------------------------------------------------------------------
1 | /**
2 | * GraphNavbar component - Search and filter controls for graph
3 | * 图表导航栏组件 - 搜索和过滤控制
4 | */
5 |
6 | import type { GraphSettings } from '~/types/graph'
7 | import { Checkbox, Input } from '@react-devtools-plus/ui'
8 |
9 | interface GraphNavbarProps {
10 | searchText: string
11 | onSearchChange: (value: string) => void
12 | settings: GraphSettings
13 | onSettingsChange: (settings: GraphSettings) => void
14 | filterNodeId: string
15 | onClearFilter: () => void
16 | }
17 |
18 | const selectableItems: Array<[keyof GraphSettings, string?]> = [
19 | ['node_modules'],
20 | ['virtual', 'virtual module'],
21 | ['lib', 'library module'],
22 | ]
23 |
24 | export function GraphNavbar({
25 | searchText,
26 | onSearchChange,
27 | settings,
28 | onSettingsChange,
29 | filterNodeId,
30 | onClearFilter,
31 | }: GraphNavbarProps) {
32 | const handleSettingChange = (key: keyof GraphSettings) => {
33 | onSettingsChange({
34 | ...settings,
35 | [key]: !settings[key],
36 | })
37 | }
38 |
39 | return (
40 |
41 | {/* Search input */}
42 |
43 | onSearchChange(e.target.value)}
47 | placeholder="Search modules..."
48 | allowClear
49 | onClear={() => onSearchChange('')}
50 | className="w-48"
51 | />
52 |
53 |
54 | {/* Filter checkboxes */}
55 | {selectableItems.map(([key, label]) => (
56 |
57 | handleSettingChange(key)}
60 | label={`Show ${label ?? key}`}
61 | />
62 |
63 | ))}
64 |
65 | {/* Spacer */}
66 |
67 |
68 | {/* Clear filter button */}
69 | {filterNodeId && (
70 |
80 | )}
81 |
82 | )
83 | }
84 |
--------------------------------------------------------------------------------
/packages/playground/react19/src/App.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react'
2 |
3 | function Counter() {
4 | const [count, setCount] = useState(0)
5 |
6 | return (
7 |
8 |
Counter Component
9 |
10 | Current count:
11 | {count}
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 | )
20 | }
21 |
22 | function TodoList() {
23 | const [todos, setTodos] = useState(['Learn React 19', 'Test DevTools'])
24 | const [input, setInput] = useState('')
25 |
26 | const addTodo = () => {
27 | if (input.trim()) {
28 | setTodos([...todos, input.trim()])
29 | setInput('')
30 | }
31 | }
32 |
33 | const removeTodo = (index: number) => {
34 | setTodos(todos.filter((_, i) => i !== index))
35 | }
36 |
37 | return (
38 |
39 |
Todo List Component
40 |
41 | setInput(e.target.value)}
45 | onKeyDown={e => e.key === 'Enter' && addTodo()}
46 | placeholder="Add a new todo..."
47 | />
48 |
49 |
50 |
51 | {todos.map((todo, index) => (
52 | -
53 | {todo}
54 |
55 |
56 | ))}
57 |
58 |
59 | )
60 | }
61 |
62 | function ReactInfo() {
63 | // @ts-expect-error - React.version is available at runtime
64 | const reactVersion = React.version || 'unknown'
65 |
66 | return (
67 |
68 |
React Version Info
69 |
70 | Running on React
71 | {' '}
72 | {reactVersion}
73 |
74 |
75 | This playground tests react-devtools-plus compatibility with React 19.
76 | Open the DevTools panel using
77 | {' '}
78 | Alt+Shift+D
79 | {' '}
80 | or click the floating button.
81 |
82 |
83 | )
84 | }
85 |
86 | export default function App() {
87 | return (
88 |
89 |
90 | React 19 DevTools Playground
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 | )
99 | }
100 |
--------------------------------------------------------------------------------