Univer 调试插件 @univerjs/debugger:为电子表格引擎打造开发期诊断工具箱
2026/9/14 7:43:45 网站建设 项目流程

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/debugger

2.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, }, }); }

这段代码透露了三个关键实践点:

  1. 按运行环境条件注册:示例通过process.env.IS_E2E判断当前是否运行在 E2E 测试平台,是则注册插件。这与"生产环境禁用"的警告直接对应——插件只在测试态挂载。
  2. localeLoader是必传项:类型为UniverDebuggerLocaleLoader,签名是(locale: LocaleType) => ILanguagePack | Promise<ILanguagePack>(定义于 config/config.ts)。仓库提供的实现是@univerjs/mockdata导出的loadDebuggerLocale(见 common/mockdata/src/index.ts)。
  3. 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; }; }

逐项说明:

配置项类型默认值作用
menuMenuConfig(来自@univerjs/ui注入 Univer 菜单系统,会 merge 进全局menu配置
fabbooleantrue(见defaultPluginConfig是否注册右下角浮动按钮(FAB),它是调试菜单的入口
fabEntryUnitTypeUniverInstanceType必传,无默认值FAB 下拉菜单按"入口单元类型"裁剪调试项集合(SHEET / DOC / SLIDE / BASE 各不同)
localeLoader函数必传,无默认值LocaleType懒加载调试面板文案包
performanceMonitor.enabledbooleantrue(见defaultPluginConfig是否装配 FPS 监控控制器

默认值由defaultPluginConfig给出(config/config.ts):

export const defaultPluginConfig: Pick<IUniverDebuggerConfig, 'fab' | 'performanceMonitor'> = { fab: true, performanceMonitor: { enabled: true, }, };

注意这个默认值对象只声明了fabperformanceMonitor两个字段,menufabEntryUnitTypelocaleLoader不在其中——后两者(除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)。后续DebuggerControllerFab组件都通过IConfigService.getConfig(DEBUGGER_PLUGIN_CONFIG_KEY)回读这份配置(见 debugger.controller.ts 与 Fab.tsx)。

这种"合并默认值 → 写入配置服务 → 各控制器按需回读"的模式,与 Univer 其他插件的配置管理方式保持一致。

四、插件生命周期与控制器装配

UniverDebuggerPlugin继承自@univerjs/corePlugin基类(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触碰ComponentsControllerDebuggerController此时 UI 宿主容器已就绪,可注册演示组件与 FAB
onRendered触碰PerformanceMonitorControllerFPS 监控需要等渲染引擎完成首帧后才订阅渲染循环

这里有一个值得注意的配置判断细节:性能监控的跳过条件是enabled !== false时才注册,即只有显式传false才会禁用,与默认值true形成呼应;而Fab组件渲染 FPS 占位符时的判断是performanceMonitor?.enabled为真值才显示(Fab.tsx)。

四个控制器各自承担的职责,正是这个插件"调试工具集"的四大板块,下面逐一展开。

五、FAB 浮动按钮:按单元类型裁剪的调试菜单

DebuggerController(controllers/debugger.controller.ts)做两件事:

  1. 若配置fab为真,向BuiltInUIPart.GLOBAL注册全局组件Fab,并让它的销毁跟随控制器销毁(disposeWithMe):

    this.disposeWithMe( this._uiPartsService.registerComponent(BuiltInUIPart.GLOBAL, () => connectInjector(Fab, this._injector)) );
  2. 向注入器添加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.tsuse-rtl.tsuse-dark-mode.tsuse-theme.tsuse-watermark.tsuse-notification.tsuse-message.tsuse-dialog.tsuse-sidebar.tsuse-floating-dom.tsuse-cell-content.tsuse-units.tsuse-snapshot.tsuse-editable.tsuse-user.tsuse-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 toe2e/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_TIMEOUTAWAIT_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.tsxRangeLoading.tsxFloatButton.tsx等),水印面板则在 views/watermark 下。从源码结构看,该文件还保留了两段被注释的注册代码:VueComponent.vueframework: 'vue3')与 Web Component(framework: 'web-component'),说明该注册机制设计上支持 React 之外的框架组件接入,与 Univer 的 UI 适配层(如ui-adapter-vue3ui-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 警告与源码实现,给出落地建议:

    1. 只在开发/测试环境注册:E2E 场景推荐照抄 examples/src/sheets/main.ts 的写法(fab: false+performanceMonitor.enabled: false),只保留 E2E API 桥,减小运行时开销;
    2. 本地开发调试:保持默认配置(fab: true+ FPS 监控开启)即可获得右下角调试菜单与帧率显示,用fabEntryUnitType选择与主工作单元匹配的类型以获得最完整的调试项;
    3. 必填配置不可省fabEntryUnitTypelocaleLoader无默认值,localeLoader可直接复用@univerjs/mockdata导出的loadDebuggerLocale
    4. 能力边界:该插件不提供 i18n 静态文案(README 标注 Contains i18n locales 为否)、不做服务端能力、也不承诺 API 稳定性(包标记private: true,以 monorepo 版本1.0.0-beta.2随整体 beta 节奏演进);
    5. 改动 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),仅供参考

    需要专业的网站建设服务?

    联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

    立即咨询