npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@react-devtools-plus/api

v0.9.7

Published

Public API for React DevTools Plus plugins

Readme

@react-devtools-plus/api

React DevTools Plus 插件开发 API。

本包提供创建 DevTools 插件所需的所有 API,包括插件定义、宿主脚本、视图通信等。

安装

npm install @react-devtools-plus/api
# or
pnpm add @react-devtools-plus/api

核心 API

defineDevToolsPlugin - 定义插件

创建 DevTools 插件的主要入口。

import { defineDevToolsPlugin } from '@react-devtools-plus/api'
import type { DevToolsPluginProps } from '@react-devtools-plus/api'

// 插件面板组件
function MyPanel({ tree, selectedNodeId, theme }: DevToolsPluginProps) {
  return (
    <div style={{ background: theme.mode === 'dark' ? '#1a1a1a' : '#fff' }}>
      <p>Selected: {selectedNodeId ?? 'None'}</p>
    </div>
  )
}

// 定义插件
export const MyPlugin = defineDevToolsPlugin({
  meta: {
    name: 'my-plugin',
    title: 'My Plugin',
    icon: 'lucide:puzzle',
    // npm 包信息(用于生产环境加载)
    packageName: '@my-org/devtools-plugin',
    viewExportName: 'MyPanel',
    bundlePath: 'dist/index.mjs',
  },
  view: {
    src: MyPanel,
  },
  defaultOptions: {
    enabled: true,
  },
})

使用方式:

// vite.config.ts
import { reactDevToolsPlus } from 'react-devtools-plus/vite'
import { MyPlugin } from '@my-org/devtools-plugin'

export default {
  plugins: [
    reactDevToolsPlus({
      plugins: [
        MyPlugin(),           // 默认配置
        MyPlugin({ enabled: false }), // 自定义配置
      ],
    }),
  ],
}

defineHostPlugin - 定义宿主脚本

宿主脚本运行在宿主应用的主线程中,可以拦截网络请求、操作 DOM、与 DevTools UI 通信。

// src/host.ts
import { defineHostPlugin } from '@react-devtools-plus/api'

const requests: Map<string, any> = new Map()

export default defineHostPlugin({
  name: 'network-inspector',

  // RPC 方法 - View 层可调用
  rpc: {
    getRequests() {
      return Array.from(requests.values())
    },
    clearRequests() {
      requests.clear()
    },
  },

  // 初始化逻辑
  setup(ctx) {
    // 获取用户配置
    const options = ctx.getOptions<{ maxRequests: number }>()

    // 拦截 fetch 请求
    ctx.network.onFetch({
      onRequest(request) {
        ctx.emit('request:start', { url: request.url })
      },
      onResponse(response, request) {
        ctx.emit('request:complete', {
          url: request.url,
          status: response.status,
        })
      },
    })

    // 返回清理函数
    return () => {
      requests.clear()
    }
  },
})

View 层 Hooks

在插件面板组件中使用这些 Hooks 与宿主脚本通信。

usePluginRpc - RPC 客户端

import { usePluginRpc } from '@react-devtools-plus/api'

function MyPanel() {
  const rpc = usePluginRpc()

  const handleFetch = async () => {
    const data = await rpc.call('getRequests')
    console.log(data)
  }

  return <button onClick={handleFetch}>获取数据</button>
}

usePluginEvent - 监听事件

import { usePluginEvent } from '@react-devtools-plus/api'
import { useState } from 'react'

function MyPanel() {
  const [requests, setRequests] = useState([])

  // 监听宿主脚本发送的事件
  usePluginEvent('request:add', (request) => {
    setRequests(prev => [...prev, request])
  })

  return <div>{requests.length} 个请求</div>
}

usePluginOptions - 获取插件选项

import { usePluginOptions } from '@react-devtools-plus/api'

interface MyPluginOptions {
  maxItems: number
  showDebug: boolean
}

function MyPanel() {
  const options = usePluginOptions<MyPluginOptions>()

  return <div>最大条目: {options.maxItems}</div>
}

非 Hook API

在 React 组件外部使用。

createRpcClient - 创建 RPC 客户端

import { createRpcClient } from '@react-devtools-plus/api'

const rpc = createRpcClient('my-plugin')
const data = await rpc.call('getData')

getPluginOptions - 获取选项

import { getPluginOptions } from '@react-devtools-plus/api'

const options = getPluginOptions<MyOptions>('my-plugin')

完整插件示例

插件入口 (src/index.ts)

import { defineDevToolsPlugin } from '@react-devtools-plus/api'
import MyPanel from './Panel'

export interface MyPluginOptions {
  maxRequests?: number
  debug?: boolean
}

export const MyPlugin = defineDevToolsPlugin<MyPluginOptions>({
  meta: {
    name: 'my-plugin',
    title: 'My Plugin',
    icon: 'lucide:activity',
    packageName: '@my-org/my-plugin',
    viewExportName: 'MyPanel',
    bundlePath: 'dist/index.mjs',
  },
  view: {
    src: MyPanel,
  },
  // 宿主脚本
  host: {
    src: './src/host.ts',
    inject: 'head',  // 或使用函数精确控制
  },
  // 注入额外的 HTML 内容
  htmlInject: [
    {
      tag: 'link',
      attrs: { rel: 'stylesheet', href: '/my-plugin.css' },
      inject: 'head',
    },
  ],
  defaultOptions: {
    maxRequests: 100,
    debug: false,
  },
})

export { default as MyPanel } from './Panel'

宿主脚本 (src/host.ts)

import { defineHostPlugin } from '@react-devtools-plus/api'
import type { MyPluginOptions } from './index'

const logs: string[] = []

export default defineHostPlugin({
  name: 'my-plugin',

  rpc: {
    getLogs: () => logs,
    clearLogs: () => { logs.length = 0 },
  },

  setup(ctx) {
    const options = ctx.getOptions<MyPluginOptions>()

    if (options.debug) {
      console.log('[MyPlugin] Debug mode enabled')
    }

    // 监听点击事件
    const handleClick = (e: MouseEvent) => {
      const target = e.target as HTMLElement
      logs.push(`Click: ${target.tagName}`)
      ctx.emit('log:add', { message: `Click: ${target.tagName}` })
    }

    document.addEventListener('click', handleClick)

    // 返回清理函数
    return () => {
      document.removeEventListener('click', handleClick)
    }
  },
})

面板组件 (src/Panel.tsx)

import { usePluginRpc, usePluginEvent, usePluginOptions } from '@react-devtools-plus/api'
import type { DevToolsPluginProps } from '@react-devtools-plus/api'
import type { MyPluginOptions } from './index'
import { useState, useEffect } from 'react'

export default function MyPanel({ theme }: DevToolsPluginProps) {
  const rpc = usePluginRpc()
  const options = usePluginOptions<MyPluginOptions>()
  const [logs, setLogs] = useState<string[]>([])

  // 初始加载
  useEffect(() => {
    rpc.call<string[]>('getLogs').then(setLogs)
  }, [])

  // 监听新日志
  usePluginEvent('log:add', ({ message }) => {
    setLogs(prev => [...prev.slice(-options.maxRequests! + 1), message])
  })

  const handleClear = async () => {
    await rpc.call('clearLogs')
    setLogs([])
  }

  const isDark = theme.mode === 'dark'

  return (
    <div style={{
      padding: 16,
      background: isDark ? '#1a1a1a' : '#fff',
      color: isDark ? '#fff' : '#000',
    }}>
      <h2>My Plugin</h2>
      <button onClick={handleClear}>清空</button>
      <ul>
        {logs.map((log, i) => (
          <li key={i}>{log}</li>
        ))}
      </ul>
    </div>
  )
}

HTML 内容注入

除了宿主脚本,还可以注入任意 HTML 内容:

export const MyPlugin = defineDevToolsPlugin({
  // ...
  htmlInject: [
    // 注入 importmap
    {
      tag: 'script',
      attrs: { type: 'importmap' },
      children: JSON.stringify({
        imports: { 'lodash': 'https://cdn.jsdelivr.net/npm/lodash-es/+esm' }
      }),
      inject: 'head-prepend',
    },

    // 注入样式表
    {
      tag: 'link',
      attrs: { rel: 'stylesheet', href: '/plugin.css' },
      inject: 'head',
    },

    // 使用函数精确定位
    {
      tag: 'meta',
      attrs: { name: 'plugin-version', content: '1.0.0' },
      inject: (html, content) => {
        return html.replace(/<head>/, `<head>\n${content}`)
      },
    },
  ],
})

注入位置

inject 选项控制脚本或 HTML 内容的注入位置:

简单字符串

| 值 | 说明 | | ---------------- | ----------------------------------- | | 'head' | 在 <head> 末尾注入 | | 'head-prepend' | 在 <head> 开头注入(最早执行) | | 'body' | 在 <body> 末尾注入 | | 'body-prepend' | 在 <body> 开头注入 | | 'idle' | 使用 requestIdleCallback 延迟注入 |

函数式注入

inject: (html, content) => {
  // 在 React 脚本之后注入
  return html.replace(
    /(<script[^>]*src="[^"]*react[^"]*"[^>]*><\/script>)/i,
    `$1\n${content}`
  )
}

类型定义

DevToolsPluginProps

插件面板组件接收的 Props:

interface DevToolsPluginProps {
  /** 组件树数据 */
  tree: ComponentTreeNode | null
  /** 当前选中的节点 ID */
  selectedNodeId: string | null
  /** 主题配置 */
  theme: DevToolsTheme
}

HostPluginContext

宿主脚本 setup 函数接收的上下文:

interface HostPluginContext {
  /** 向 View 层发送事件 */
  emit: (eventName: string, data?: any) => void

  /** 获取插件选项 */
  getOptions: <T>() => T

  /** 网络拦截器 */
  network: {
    onFetch: (handler: FetchInterceptHandler) => () => void
    onXHR: (handler: XHRInterceptHandler) => () => void
    onResource: (handler: (entry: PerformanceResourceTiming) => void) => () => void
  }

  /** DevTools 能力 */
  devtools: {
    getTree: () => ComponentTreeNode | null
    getSelectedNodeId: () => string | null
    highlightNode: (fiberId: string) => void
    hideHighlight: () => void
  }
}

PluginRpcClient

RPC 客户端接口:

interface PluginRpcClient {
  /** 调用宿主脚本的 RPC 方法 */
  call: <T = any>(method: string, ...args: any[]) => Promise<T>

  /** 监听宿主脚本发送的事件 */
  on: (eventName: string, handler: (data: any) => void) => () => void

  /** 一次性监听事件 */
  once: (eventName: string, handler: (data: any) => void) => () => void
}

网络拦截

宿主脚本可以拦截和监控网络请求:

setup(ctx) {
  // 拦截 fetch 请求
  ctx.network.onFetch({
    onRequest(request) {
      // 请求发送前
      console.log('Fetch:', request.url)
    },
    onResponse(response, request) {
      // 响应接收后
      console.log('Response:', response.status)
    },
    onError(error, request) {
      // 请求失败
      console.error('Error:', error)
    },
  })

  // 拦截 XHR 请求
  ctx.network.onXHR({
    onOpen(method, url, xhr) {
      console.log('XHR:', method, url)
    },
    onSend(body, xhr) {
      console.log('XHR send:', body)
    },
    onLoad(xhr) {
      console.log('XHR complete:', xhr.status)
    },
  })

  // 监控资源加载
  ctx.network.onResource((entry) => {
    console.log('Resource:', entry.name, entry.duration)
  })
}

许可证

MIT