Univer 调试插件 @univerjs/debugger:为电子表格引擎打造开发期诊断工具箱
【免费下载链接】univerUniver is a full-stack framework for creating and editing spreadsheets / word processor / presentation on both web and server.项目地址: https://gitcode.com/GitHub_Trending/un/univer
本文基于 Univer 仓库中的 common/debugger/README.md 及其配套源码,系统讲解@univerjs/debugger调试插件的定位、安装方式、全部配置项含义、插件生命周期内的控制器装配逻辑,以及它在开发演示、FPS 性能监控和 Playwright E2E 自动化测试三类场景中的具体用法。读完后,你可以在自己的 Univer 应用中注册该插件、按需裁剪其功能开关,并理解 E2E 测试平台所依赖的window.E2EControllerAPI是如何被这个插件提供出来的。
一、插件定位与重要约束
@univerjs/debugger是 Univer 仓库内置的一个独立子包(位于 common/debugger),官方 README 对其定义非常直接:
This plugin provides a lot of utilities to help you debug Univer.
也就是说,它不是一个面向最终用户的功能模块,而是面向 Univer 二次开发者与测试平台的开发期工具集。README 在开头用醒目警告标出第一条使用约束:
⚠️ CAUTION: NEVER use this plugin in your production environment!
这一约束在源码中也有对应体现:插件会向全局窗口对象挂载调试 API、注册演示组件、打开屏幕录制等浏览器能力,且会引入@univerjs/mockdata等演示数据依赖(见 package.json 的dependencies:@univerjs/core、@univerjs/design、@univerjs/engine-render、@univerjs/sheets、@univerjs/ui、@univerjs/watermark、@univerjs/mockdata等),因此只应出现在开发环境与自动化测试环境中。
包级元信息(来自 README 的 Package Overview 表)如下:
| 项 | 值 |
|---|---|
| 包名 | @univerjs/debugger |
| UMD 命名空间 | UniverDebugger |
| 许可证 | Apache-2.0(见 package.json) |
| 是否包含 CSS | 是(入口 src/index.ts 首行import './global.css') |
| 是否内置 i18n | 否,文案通过配置项localeLoader动态加载 |
| 版本 | 仓库内为1.0.0-beta.2,且private: true,随 monorepo 统一发布 |
从 package.json 的exports字段可以看到,该包直接以源码./src/index.ts作为入口(".": "./src/index.ts","./*": "./src/*"),在 Univer 的 pnpm workspace 内由构建工具直接消费 TypeScript 源码。
二、安装与注册
README 给出的安装命令:
# Using npm npm install @univerjs/debugger # Using pnpm pnpm add @univerjs/debugger2.1 最小注册示例
插件导出面非常收敛,src/index.ts 只导出两样东西:
export type { IUniverDebuggerConfig, UniverDebuggerLocaleLoader } from './config/config'; export { UniverDebuggerPlugin } from './plugin';因此注册时只需引入UniverDebuggerPlugin并传入配置对象。仓库内的真实用法位于 sheets 示例入口 examples/src/sheets/main.ts:
// If we are running in e2e platform, we should immediately register the debugger plugin. if (IS_E2E) { univer.registerPlugin(UniverDebuggerPlugin, { fab: false, fabEntryUnitType: UniverInstanceType.UNIVER_SHEET, localeLoader: loadDebuggerLocale, performanceMonitor: { enabled: false, }, }); }这段代码透露了三个关键实践点:
- 按运行环境条件注册:示例通过
process.env.IS_E2E判断当前是否运行在 E2E 测试平台,是则注册插件。这与"生产环境禁用"的警告直接对应——插件只在测试态挂载。 localeLoader是必传项:类型为UniverDebuggerLocaleLoader,签名是(locale: LocaleType) => ILanguagePack | Promise<ILanguagePack>(定义于 config/config.ts)。仓库提供的实现是@univerjs/mockdata导出的loadDebuggerLocale(见 common/mockdata/src/index.ts)。- E2E 场景下关闭交互类功能:
fab: false隐藏浮动按钮、performanceMonitor.enabled: false关掉 FPS 监控,因为无头浏览器里这些界面元素没有意义,只需要保留挂在window上的 E2E API。
2.2 运行环境前提
从 package.json 的peerDependencies看,该插件要求宿主应用具备:
react: ^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc(FAB 按钮是 React 组件,见 views/Fab.tsx);rxjs: >=7.0.0(性能监控等控制器大量使用 RxJS 响应式链路)。
三、配置项全解:IUniverDebuggerConfig
全部配置类型定义在 config/config.ts:
export interface IUniverDebuggerConfig { menu?: MenuConfig; fab?: boolean; fabEntryUnitType: UniverInstanceType; localeLoader: UniverDebuggerLocaleLoader; performanceMonitor?: { enabled: boolean; }; }逐项说明:
| 配置项 | 类型 | 默认值 | 作用 |
|---|---|---|---|
menu | MenuConfig(来自@univerjs/ui) | 无 | 注入 Univer 菜单系统,会 merge 进全局menu配置 |
fab | boolean | true(见defaultPluginConfig) | 是否注册右下角浮动按钮(FAB),它是调试菜单的入口 |
fabEntryUnitType | UniverInstanceType | 必传,无默认值 | FAB 下拉菜单按"入口单元类型"裁剪调试项集合(SHEET / DOC / SLIDE / BASE 各不同) |
localeLoader | 函数 | 必传,无默认值 | 按LocaleType懒加载调试面板文案包 |
performanceMonitor.enabled | boolean | true(见defaultPluginConfig) | 是否装配 FPS 监控控制器 |
默认值由defaultPluginConfig给出(config/config.ts):
export const defaultPluginConfig: Pick<IUniverDebuggerConfig, 'fab' | 'performanceMonitor'> = { fab: true, performanceMonitor: { enabled: true, }, };注意这个默认值对象只声明了fab和performanceMonitor两个字段,menu、fabEntryUnitType、localeLoader不在其中——后两者(除menu外)属于调用方必须显式提供的"必填语义"配置。
3.1 配置的写入与读取流程
插件构造函数(plugin.ts)对配置做了两路分发:
const { menu, ...rest } = merge({}, defaultPluginConfig, this._config); if (menu) { this._configService.setConfig('menu', menu, { merge: true }); } this._configService.setConfig(DEBUGGER_PLUGIN_CONFIG_KEY, rest);menu被单独摘出来,以merge: true方式并入 Univer 全局menu配置,保证调试菜单与其他插件贡献的菜单项共存;- 其余配置写入键
DEBUGGER_PLUGIN_CONFIG_KEY(值为字符串'debugger.config',同时有对应的configSymbol)。后续DebuggerController、Fab组件都通过IConfigService.getConfig(DEBUGGER_PLUGIN_CONFIG_KEY)回读这份配置(见 debugger.controller.ts 与 Fab.tsx)。
这种"合并默认值 → 写入配置服务 → 各控制器按需回读"的模式,与 Univer 其他插件的配置管理方式保持一致。
四、插件生命周期与控制器装配
UniverDebuggerPlugin继承自@univerjs/core的Plugin基类(plugin.ts),静态标识pluginName = 'UNIVER_DEBUGGER_PLUGIN',版本号直接取自package.json。其生命周期把四个控制器分阶段装配进依赖注入容器:
override onStarting(): void { const dependencies: Dependency[] = [ [DebuggerController], [ComponentsController], [E2EController], ]; if (this._config.performanceMonitor?.enabled !== false) { dependencies.push([PerformanceMonitorController]); } registerDependencies(this._injector, dependencies); touchDependencies(this._injector, [[E2EController]]); } override onReady(): void { touchDependencies(this._injector, [[ComponentsController], [DebuggerController]]); } override onRendered(): void { touchDependencies(this._injector, [[PerformanceMonitorController]]); }对应关系如下:
| 生命周期 | 动作 | 说明 |
|---|---|---|
onStarting | 注册 4 个控制器(性能监控按配置开关) | 立即"触碰"E2EController,保证window.E2EControllerAPI尽早可用 |
onReady | 触碰ComponentsController、DebuggerController | 此时 UI 宿主容器已就绪,可注册演示组件与 FAB |
onRendered | 触碰PerformanceMonitorController | FPS 监控需要等渲染引擎完成首帧后才订阅渲染循环 |
这里有一个值得注意的配置判断细节:性能监控的跳过条件是enabled !== false时才注册,即只有显式传false才会禁用,与默认值true形成呼应;而Fab组件渲染 FPS 占位符时的判断是performanceMonitor?.enabled为真值才显示(Fab.tsx)。
四个控制器各自承担的职责,正是这个插件"调试工具集"的四大板块,下面逐一展开。
五、FAB 浮动按钮:按单元类型裁剪的调试菜单
DebuggerController(controllers/debugger.controller.ts)做两件事:
若配置
fab为真,向BuiltInUIPart.GLOBAL注册全局组件Fab,并让它的销毁跟随控制器销毁(disposeWithMe):this.disposeWithMe( this._uiPartsService.registerComponent(BuiltInUIPart.GLOBAL, () => connectInjector(Fab, this._injector)) );向注入器添加
RecordController(命令录制器,见第八节)。
Fab组件(views/Fab.tsx)渲染在页面右下角(data-u-comp="debugger-fab"),核心是一个DropdownMenu下拉菜单。菜单项集合根据fabEntryUnitType裁剪:
- SHEET 类型(完整调试项):locale、RTL、暗色模式、主题、水印,之后是通知(notification)、消息(message)、对话框(dialog)、侧边栏(sidebar)、浮层 DOM(floatingDom)、单元格内容读取(cellContent)、多实例管理(units)、快照(snapshot)、可编辑切换(editable)、当前用户(user)、销毁实例(dispose);
- DOC 类型:全局项 + 通知/消息/对话框/侧边栏 + 快照/可编辑/销毁 + 浮层 DOM;
- SLIDE / BASE 类型(lightweight):仅 locale、RTL、暗色模式、主题、水印五项。
这些调试项分别由 views 目录下的 15 个 hook 文件实现:use-locale.ts、use-rtl.ts、use-dark-mode.ts、use-theme.ts、use-watermark.ts、use-notification.ts、use-message.ts、use-dialog.ts、use-sidebar.ts、use-floating-dom.ts、use-cell-content.ts、use-units.ts、use-snapshot.ts、use-editable.ts、use-user.ts、use-dispose.ts。从源码结构看,每个 hook 封装"读取/触发某个 Univer 服务"的调试动作,例如use-snapshot用于读取当前文档快照、use-units用于多实例切换、use-dispose用于销毁当前 Univer 实例——它们本质上是把 Univer 服务层的 API 暴露成一键式 UI 操作,方便开发者在浏览器里直接触发和观察行为。
当performanceMonitor.enabled为真时,FAB 按钮下方还会渲染一个<span>lifecycleService.subscribeWithPrevious() .pipe(filter((stage) => stage === LifecycleStages.Rendered), take(1)) .subscribe(() => this._listenDocumentTypeChange());
跟随焦点单元:订阅IUniverInstanceService.focused$,当用户切换聚焦的工作簿/文档单元时,先取消旧订阅(_disposeCurrentObserver),再对新单元建立监听,保证监控目标始终跟随当前激活的渲染单元。
订阅渲染引擎帧事件:通过IRenderManagerService.getRenderUnitById(unitId)拿到渲染单元,订阅其engine.endFrame$事件,每帧结束时读取engine.getFps()并四舍五入写入 FAB 下的[data-u-comp=debugger-fps]元素:
this._currentUnitSub = engine.endFrame$.subscribe(() => { if (!this._containerElement) { this._containerElement = document.querySelector('[data-u-comp=debugger-fps]'); } else { this._containerElement.textContent = `FPS: ${Math.round(engine.getFps()).toString()}`; } });注意document.querySelector只在首帧执行一次做缓存,后续帧直接改写textContent,避免每帧触发 DOM 查询。dispose()时同步退订,避免内存泄漏。
这套实现展示了在 Univer 上做自定义性能监控的标准姿势:LifecycleService 定阶段 → RenderManagerService 拿引擎 → 订阅endFrame$。你在自己项目里做帧率、脏矩形面积等监控时,可以参照这条链路。
七、E2E 平台桥:window.E2EControllerAPI
E2EController(controllers/e2e/e2e.controller.ts)是调试插件中最"非 UI"的部分——它把一组测试辅助 API 挂到window.E2EControllerAPI,供 Playwright 等外部测试框架通过page.evaluate调用。源码中的注释明确说明:
This interface is copied to
e2e/e2e.d.ts. When you modify this interface, make sure the duplication is updated as well.
即接口契约与仓库 e2e/e2e.d.ts 保持手工同步。公开的能力(IE2EControllerAPI)包括:
| 方法 | 作用 |
|---|---|
loadAndRelease(id, loadTimeout?, disposeTimeout?) | 创建一个e2e{id}工作簿、等待加载后销毁,用于实例创建/销毁的内存泄漏类测试 |
loadDefaultSheet(loadTimeout?) | 载入默认工作簿 fixture(data/default-sheet.ts) |
loadDemoSheet() | 载入@univerjs/mockdata中的DEFAULT_WORKBOOK_DATA_DEMO演示大表 |
loadMergeCellSheet() | 载入演示数据中的合并单元格表(sheet-0003) |
loadDefaultStyleSheet() | 载入默认样式的演示工作簿 |
loadDefaultDoc(loadTimeout?)/loadDocLayoutFixture(flavor, loadTimeout?) | 载入默认文档 fixture,或按DocumentFlavor(TRADITIONAL / MODERN)构建特定布局文档 |
setDarkMode(darkMode) | 通过ThemeService切换暗色模式 |
disposeCurrSheetUnit(disposeTimeout?) | 销毁当前聚焦的电子表格单元并等待 |
disposeUniver() | 调用window.univer.dispose()并清空window.univer/window.univerAPI |
scrollAndClearCanvas(canvas, pixelRatio, scrollRenderInfos, dirtyBounds) | 把渲染引擎的scrollAndClearCanvas能力暴露给测试端,配合视觉对比测试 |
默认超时常量AWAIT_LOADING_TIMEOUT与AWAIT_DISPOSING_TIMEOUT均为 5000ms。
这与仓库根目录 examples/src/sheets/main.ts 中const IS_E2E: boolean = !!process.env.IS_E2E;的构建约定形成闭环:E2E 构建产物中注册调试插件后,测试脚本即可驱动一个"干净、可复现"的 Univer 环境。仓库 e2e 目录下的用例(如 e2e/disposing/disposing.spec.ts、e2e/memory/memory.spec.ts、e2e/visual-comparison)正是消费这套 API 的测试代码;其中内存测试与 dispose 测试直接依赖loadAndRelease/disposeUniver提供的"加载—等待—销毁"能力。
插件的getDebuggerController()方法(plugin.ts)则允许宿主在需要时从插件实例同步取出DebuggerController,进一步定制调试行为。
八、演示组件注册与本地录制工具
8.1 ComponentsController:演示组件注册表
ComponentsController(controllers/components.controller.ts)通过ComponentManager注册了一批可在菜单系统中引用的演示组件:
(['ImageDemo', ImageDemo], ['RangeLoading', RangeLoading], ['FloatButton', FloatButton], ['AIButton', AIButton], [WATERMARK_PANEL, WatermarkPanel], [WATERMARK_PANEL_FOOTER, WatermarkPanelFooter])对应组件源码在 components 目录(Image.tsx、RangeLoading.tsx、FloatButton.tsx等),水印面板则在 views/watermark 下。从源码结构看,该文件还保留了两段被注释的注册代码:VueComponent.vue(framework: 'vue3')与 Web Component(framework: 'web-component'),说明该注册机制设计上支持 React 之外的框架组件接入,与 Univer 的 UI 适配层(如ui-adapter-vue3、ui-adapter-web-component)能力呼应。
8.2 RecordController:屏幕录制与命令录制
RecordController(controllers/local-save/record.controller.ts)提供两个开发者向的工具方法:
record():返回一个 RxJSObservable,调用navigator.mediaDevices.getDisplayMedia请求屏幕捕获,用MediaRecorder(优先video/webm; codecs=vp9)录制,dataavailable收集分片,stop时拼装成Blob发出{ type: 'finish', data }。典型用途是本地复现 bug 时录屏,便于提交缺陷报告;startSaveCommands():挂到ICommandService.beforeCommandExecuted钩子上,按[秒级时间戳, 命令 id, 命令类型, JSON 参数]四元组记录执行序列,调用返回的清理函数可取回完整列表。这是排查"多步操作后状态错乱"类问题的利器——把用户操作序列完整回放出来。
这两个方法属于命令式 API(通过注入器获取控制器后调用),并非开箱即用的 UI,适合开发者在控制台或自定义调试面板中按需启用。
九、使用建议与边界
结合 README 警告与源码实现,给出落地建议:
- 只在开发/测试环境注册:E2E 场景推荐照抄 examples/src/sheets/main.ts 的写法(
fab: false+performanceMonitor.enabled: false),只保留 E2E API 桥,减小运行时开销; - 本地开发调试:保持默认配置(
fab: true+ FPS 监控开启)即可获得右下角调试菜单与帧率显示,用fabEntryUnitType选择与主工作单元匹配的类型以获得最完整的调试项; - 必填配置不可省:
fabEntryUnitType与localeLoader无默认值,localeLoader可直接复用@univerjs/mockdata导出的loadDebuggerLocale; - 能力边界:该插件不提供 i18n 静态文案(README 标注 Contains i18n locales 为否)、不做服务端能力、也不承诺 API 稳定性(包标记
private: true,以 monorepo 版本1.0.0-beta.2随整体 beta 节奏演进); - 改动 E2E 接口需同步:若你 fork 仓库并扩展
IE2EControllerAPI,源码注释明确要求同步更新 e2e/e2e.d.ts,否则 Playwright 侧类型将失配。
十、小结
@univerjs/debugger用一组轻量的控制器,把 Univer 各服务层能力(生命周期、渲染引擎、配置服务、菜单/组件系统、命令服务)聚合成一个开发期诊断面:FAB 调试菜单负责"一键触发服务",FPS 监控展示"渲染链路健康度",E2E 桥提供"可编程的测试环境",录制工具则负责"操作与画面的留证"。理解它的内部装配方式(plugin.ts的生命周期分发、IConfigService的配置读写),也等于掌握了在 Univer 中自建一个调试/诊断插件的完整模板。
【免费下载链接】univerUniver is a full-stack framework for creating and editing spreadsheets / word processor / presentation on both web and server.项目地址: https://gitcode.com/GitHub_Trending/un/univer
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考