Admin Shell 内置的开发期调试模块,聚合 TanStack Query / Router 与 Jotai 三套 DevTools
| 项 | 值 |
|---|---|
| 模块 | @shell/devtools |
| 目录 | packages/web/admin/devtools |
| 分发 | 随 Admin Shell 源码复制进生成项目,不独立发布 |
| 依赖 | @tanstack/react-devtools、@tanstack/react-query-devtools、@tanstack/react-router-devtools、jotai-devtools |
| 入口 | @shell/devtools、@shell/devtools/jotai |
三套 DevTools 各自都会往页面右下角塞一个浮动按钮。这个模块把它们收进一个 TanStackDevtools 壳里:Query 和 Router 作为壳的两个 plugin 面板,Jotai 单独渲染但共用同一份主题和一套位置配置。
import { AdminDevtools } from '@shell/devtools';
<AdminDevtools
config={{
jotai: {
name: 'My Admin',
panel: true,
position: 'bottom-left',
timeline: true,
triggerOffset: { left: 248 }
},
position: 'bottom-right',
query: true,
router: true,
theme: 'dark'
}}
queryClient={queryClient}
router={router}
store={globalStore}
/>;三个实例参数都是可选的,但不传就没有对应面板:queryClient 缺席时 Query 面板不注册,router 缺席时 Router 面板不注册,store 缺席时 Jotai 读的是默认 store。
| 类别 | 符号 |
|---|---|
| 组件 | AdminDevtools |
| 类型 | AdminDevtoolsConfig、AdminDevtoolsProps、AdminJotaiDevtoolsConfig、AdminJotaiDevtoolsTriggerOffset |
interface AdminDevtoolsConfig {
/** 总开关,只有显式传 false 才整体不渲染 */
enabled?: boolean;
/** false 关闭 Jotai;传对象做细粒度配置 */
jotai?: boolean | AdminJotaiDevtoolsConfig;
/** TanStack 壳的触发器位置,默认 'bottom-right' */
position?: TanStackDevtoolsConfig['position'];
/** 启用 Query 面板,默认开 */
query?: boolean;
/** 启用 Router 面板,默认开 */
router?: boolean;
/** 三套面板共用的主题 */
theme?: 'dark' | 'light';
}
interface AdminJotaiDevtoolsConfig {
/** Redux DevTools 连接名,默认 'skyroc-admin' */
name?: string;
/** 内嵌 atom 检视面板,默认开 */
panel?: boolean;
/** 面板位置,默认 'bottom-left' */
position?: DevToolsProps['position'];
/** Redux DevTools 时间线记录,默认开 */
timeline?: boolean;
/** 浮动 trigger 的偏移,避开 sider 等应用外框 */
triggerOffset?: AdminJotaiDevtoolsTriggerOffset;
}
interface AdminJotaiDevtoolsTriggerOffset {
bottom?: number | string;
left?: number | string;
right?: number | string;
top?: number | string;
}几个容易踩的默认值语义:
| 字段 | 判定方式 | 结果 |
|---|---|---|
enabled | === false 才关 | 不传即渲染 |
query / router | !== false 即开 | 但还要对应实例存在 |
jotai | === false 才关 | 传 true 等价于传 {} |
panel / timeline | !== false 即开 | 两个都关掉时整块 Jotai 不渲染 |
theme 只在根配置上,不在 jotai 里——Jotai 面板的主题是从根配置继承下去的。
triggerOffset 的数字会补成 px,字符串原样写入,最终落到四个 CSS 变量上:
--skyroc-jotai-devtools-trigger-bottom
--skyroc-jotai-devtools-trigger-left
--skyroc-jotai-devtools-trigger-right
--skyroc-jotai-devtools-trigger-top@shell/devtools/jotaiimport '@shell/devtools/jotai';纯 side-effect,内容就是 import 'jotai-devtools/utils',作用是让之后每个 atom() 调用自动带上 debugLabel。必须在任何 atom 创建之前执行,所以 apps/admin/src/main.tsx 把它放在动态 import ./bootstrap 之前:
async function bootstrap() {
if (import.meta.env.DEV) {
await import('@shell/devtools/jotai');
}
await import('./bootstrap');
}顺序反了的话,先创建的那批 atom 在面板里会显示成 <no debugLabel>。
// App.tsx
const AdminDevtools = import.meta.env.DEV
? lazy(() => import('@shell/devtools').then(mod => ({ default: mod.AdminDevtools })))
: (_props: AdminDevtoolsProps) => null;
// ^^^ 生产环境是个 no-op 组件,整个模块不会进入生产构建
const Devtools = () => {
const { darkMode } = useSettingsTheme();
const config = useMemo<AdminDevtoolsProps['config']>(
() => ({ ...globalConfig.devtools, theme: darkMode ? 'dark' : 'light' }),
[darkMode]
);
return (
<Suspense fallback={null}>
<AdminDevtools
config={config}
queryClient={queryClient}
router={router}
store={globalStore}
/>
</Suspense>
);
};静态部分写在 globalConfig.devtools 里,theme 在运行时跟着 useSettingsTheme 的 darkMode 合并进去:
devtools: {
jotai: {
name: import.meta.env.VITE_APP_TITLE,
panel: true,
position: 'bottom-left',
timeline: true,
triggerOffset: {
left: defaultThemeSettings.sider.width + 8
}
},
position: 'bottom-right',
query: true,
router: true
} satisfies AdminDevtoolsConfigtriggerOffset.left 取的是 sider 宽度加 8px,让 Jotai 的浮动按钮正好落在侧边栏右侧而不是压在上面。
triggerOffset 再补一层像素级微调;import.meta.env.DEV 三元 + lazy 动态 import + tree-shaking 三层保护,生产 bundle 里不含任何 devtools 代码;lazy(),Jotai 的样式表也在 lazy 工厂里才 import。Last updated on