cherry-studio 表格扩展演进实录:从 @tiptap/extension-table 到 @cherrystudio/extension-table-plus 的迁移、重构与源码解析

📅 发布时间:2026/9/12 16:23:43
cherry-studio 表格扩展演进实录:从 @tiptap/extension-table 到 @cherrystudio/extension-table-plus 的迁移、重构与源码解析
cherry-studio 表格扩展演进实录从 tiptap/extension-table 到 cherrystudio/extension-table-plus 的迁移、重构与源码解析【免费下载链接】cherry-studioAI productivity studio with smart chat, autonomous agents, and 300 assistants. Unified access to frontier LLMs项目地址: https://gitcode.com/GitHub_Trending/ch/cherry-studio本文以packages/extension-table-plus/CHANGELOG-OLD.md为主线梳理 cherry-studio 表格扩展TableKit 聚合、单包化改造、Table 系列扩展拆分、TableView 增强等的完整演进脉络并结合当前仓库中的源码实现与渲染层接入代码讲清楚每一步改动背后的动机、破坏性影响和实际用法帮助你快速理解这套 Tiptap 表格扩展的现状并完成自己的迁移。一、背景这份 CHANGELOG-OLD 记录了什么packages/extension-table-plus/CHANGELOG-OLD.md是 cherry-studio 表格扩展在 v3.0.11 之前的历史变更日志本质上继承自上游tiptap/extension-table包含 2.x、3.0.0-next、3.0.0-beta 等阶段并持续维护至今。当前包被 fork 后的最新变更记录在 CHANGELOG.mdv3.0.11 起基于 changesets 发布历史变更则统一归档在 CHANGELOG-OLD.md。与新版 changelog 不同旧日志遵循 Conventional Commits 风格将变更分为三类变更类型含义典型示例Major Changes破坏性变更需要开发者手动调整代码tsup 构建不再支持 UMD默认导出改为命名导出Minor Changes新增功能向后兼容新增TableKit聚合扩展表格包重打包Patch Changes修复与内部调整不影响对外 API修复 CJS 打包默认导出问题强制类型导入日志中大量条目形如- tiptap/core3.0.9 / - tiptap/pm3.0.9表示该版本仅同步上游核心依赖版本- Updated dependencies [commit hash]表示依赖升级伴随某次具体提交。这类纯依赖同步版本在 2.x 时代占据了多数说明该扩展长期与 Tiptap 主仓同频发版。当前包的实际信息见 package.json包名为cherrystudio/extension-table-plus版本 3.0.12peerDependencies 为tiptap/core与tiptap/pm^3.0.9采用 pnpm 工作区 tsdown 构建build: tsdown。二、TableKit一次配置四个表格扩展2.1 引入动机与使用方式版本 3.0.1对应 commit131c7d0引入了一个里程碑式的能力将全部表格扩展聚合进tiptap/extension-table单一包并新增TableKit导出。日志原文明确指出TheTableKitexport allows configuring the entire table with one extension, and is the recommended way of using the table extensions.此前配置表格需要分别引入并配置Table、TableCell、TableHeader、TableRow四个扩展有了TableKit之后一行配置即可同时管理四个子扩展且支持对每个子扩展单独传参。日志给出的标准用法如下import { TableKit } from tiptap/extension-table new Editor({ extensions: [ TableKit.configure({ table: { HTMLAttributes: { class: table, }, }, tableCell: { HTMLAttributes: { class: table-cell, }, }, tableHeader: { HTMLAttributes: { class: table-header, }, }, tableRow: { HTMLAttributes: { class: table-row, }, }, }), ], })2.2 源码实现TableKit 如何工作在当前仓库中TableKit的实现位于 packages/extension-table-plus/src/kit/index.ts。它是一个 TiptapExtension核心逻辑集中在addExtensions()钩子中export const TableKit Extension.createTableKitOptions({ name: tableKit, addExtensions() { const extensions: Node[] [] if (this.options.table ! false) { extensions.push(Table.configure(this.options.table)) } if (this.options.tableCell ! false) { extensions.push(TableCell.configure(this.options.tableCell)) } if (this.options.tableHeader ! false) { extensions.push(TableHeader.configure(this.options.tableHeader)) } if (this.options.tableRow ! false) { extensions.push(TableRow.configure(this.options.tableRow)) } return extensions } })对应的TableKitOptions类型定义同一文件顶部同样值得注意——四个子扩展的选项都声明为PartialXxxOptions | falseexport interface TableKitOptions { table: PartialTableOptions | false tableCell: PartialTableCellOptions | false tableHeader: PartialTableHeaderOptions | false tableRow: PartialTableRowOptions | false }关键点传入false可以关闭某个子扩展。例如TableKit.configure({ tableCell: false })会跳过TableCell的注册适用于不想启用单元格节点或要完全自定义 cell 的场景。这种聚合 可裁剪的设计让TableKit既是一站式入口又不牺牲细粒度控制。三、表格包重打包四个包合并为一个3.1 迁移步骤与TableKit同批次的重大变更同样来自131c7d0在 2.8.0 中就已落地是表格系列包的重打包tiptap/extension-table-header、tiptap/extension-table-cell、tiptap/extension-table-row三个独立包被并入tiptap/extension-table。日志给出的迁移命令# 移除旧包 npm uninstall tiptap/extension-table-header tiptap/extension-table-cell tiptap/extension-table-row # 安装统一的新包 npm install tiptap/extension-table对于 fork 后的cherrystudio/extension-table-plus等价操作是安装cherrystudio/extension-table-plus并删除对旧tiptap/extension-table-*的依赖。当前 package.json 的exports字段也印证了这一拆分策略除了根入口.之外还提供了./table、./cell、./header、./kit、./row五个子路径导出每个子路径都有独立的import/require/types指向dist/下的产物。也就是说即使合并成一个包你依然可以按需深度导入某个子模块。3.2 想单独使用扩展迁移对照表日志明确指出如果想保留更精细的控制仍可以单独使用这些扩展。此时需要从默认导出迁移到命名导出具体 diff 如下扩展迁移前默认导出迁移后命名导出Tableimport Table from tiptap/extension-tableimport { Table } from tiptap/extension-tableTableCellimport TableCell from tiptap/extension-table-cellimport { TableCell } from tiptap/extension-tableTableHeaderimport TableHeader from tiptap/extension-table-headerimport { TableHeader } from tiptap/extension-tableTableRowimport TableRow from tiptap/extension-table-rowimport { TableRow } from tiptap/extension-table这一默认导出 → 命名导出的迁移是 3.0.1及3.0.0-next.6的破坏性变更之一升级时需逐一替换 import 语句。当前仓库的聚合入口 packages/extension-table-plus/src/index.ts 正是按命名导出组织的export * from ./cell/index.js export * from ./header/index.js export * from ./kit/index.js export * from ./row/index.js export * from ./table/index.js export * from ./table/TableView.js四、构建与打包体系的变化4.1 tsup 取代 UMD 构建3.0.1commita92f4a6同样见于3.0.0-next.6、3.0.0-next.1宣布We are now building packages with tsup which does not support UMD builds, please repackage if you require UMD builds这是一项Major Change项目改用 tsup 构建当前仓库 tsdown.config.ts 与 package.json 中的build: tsdown均印证了这条路线而 tsup 不产出 UMD 格式。如果您的使用场景依赖 UMD 文件例如直接通过script标签引入需要自行重新打包或改用 ESM/CJS 产物。这也解释了为什么当前 package.json 的main/module/exports只指向dist/index.cjs与dist/index.js。4.2 类型导入与 Tree-shaking紧随其后的两个 Patch Changes 同样值得关注89bd9c7强制使用类型导入type imports让打包器在生成 dist 的index.js时忽略 TypeScript 类型导入从而减小产物体积、避免运行时引用被擦除的类型符号。1b4c82b改用 pnpm package aliases 做版本锁定更好地固定 monorepo 中各包的依赖版本——这与当前仓库根目录的pnpm-workspace.yaml工作区管理方式一致。4.3 CJS 默认导出的兼容处理在 2.5.4 中commitdd7f9ac修复过一个 CJS 打包问题There was an issue with the cjs bundling of packages and default exports, now we resolve default exports in legacy compatible way即在 CommonJS 环境下默认导出对象曾被错误解析该版本改为以兼容旧版的方式解析默认导出。升级到包含此修复的版本后require(...).default的互操作行为会变化使用 CJS 的消费方需要留意。五、TableView 与表格交互增强5.1 新增 TableView 类导出TableView是表格的 NodeView 渲染器负责生成表格 DOM 结构、hover 按钮与列宽同步逻辑。它在多个版本中被持续增强991f43c3.0.1 与 3.0.0-beta.1新增TableView类的导出让开发者可以拿到并覆写默认的表格视图实现。a44a3112.11.7同样的导出增强在 2.x 分支落地。2.0.0-beta.27commit239a2e3当editable为 false 时禁止表格拖拽缩放对应 issue #1549。在当前仓库中TableView位于 packages/extension-table-plus/src/table/TableView.ts其主要职责包括构建.tableWrapper .table-container table colgroup tbody的 DOM 结构通过updateColumns()根据单元格的colwidth属性同步colgroup的宽度声明并在所有列都有固定宽度时设置table.style.width、否则退化为min-width方案维护添加行/添加列的 hover 按钮add-row-button/add-column-button并在只读模式下通过syncEditableState()禁用它们新增行/列操作触发器row-action-trigger/column-action-trigger配合选区变化监听与requestAnimationFrame调度更新覆盖层位置提供selectRow()、selectColumn()、setSelectionToTable()等选区操作内部基于TableMap与CellSelection计算单元格范围。TableView默认作为Table扩展的View选项使用见 packages/extension-table-plus/src/table/table.ts 的addNodeView()会透传onRowActionClick/onColumnActionClick两个回调因此默认导出TableView后开发者可以在Table.configure({ View: CustomTableView })中整体替换表格视图或仅通过行/列操作回调扩展交互。5.2 只读模式下禁止拖拽缩放Table扩展在addProseMirrorPlugins()中只有在resizable editor.isEditable时才会启用columnResizing插件源码见 table.ts这与 2.0.0-beta.27 修复的行为一脉相承表格只读时不应出现可拖拽的列宽调整手柄。5.3 选区装饰插件与单元格选中样式在TableCell扩展packages/extension-table-plus/src/cell/table-cell.ts中额外内置了一个选区样式装饰插件当出现CellSelection时通过Decoration.node为选中的单元格添加selectedCell以及selection-top/bottom/left/right类名用于高亮整行/整列的选中范围。同时它实现了colspan、rowspan、colwidth三个属性的解析与渲染其中colwidth以逗号分隔的字符串形式存取。六、命令系统与键盘交互6.1 从 prosemirror-tables 到 tiptap/pm/tables早期版本2.0.0-beta.207commitc187e0e曾将prosemirror-tables加入 peerDependencies后续2.0.0-beta.210commitf387ad3引入新的 prosemirror 依赖解析包再到 2.0.0-beta.203commitc1a0c3a将 ESM 模块统一重命名为esm.js。如今在Table扩展源码中所有底层表格操作addColumnAfter、addRowAfter、deleteColumn、mergeCells、splitCell、toggleHeader、goToNextCell、fixTables、tableEditing、columnResizing等均直接来自tiptap/pm/tables。6.2 完整命令清单Table扩展在addCommands()中注册了完整的命令集均声明在 packages/extension-table-plus/src/table/table.ts 的模块声明中可直接通过editor.commands.*调用命令作用示例insertTable插入表格editor.commands.insertTable({ rows: 3, cols: 3, withHeaderRow: true })addColumnBefore/addColumnAfter在当前列前/后插入列editor.commands.addColumnAfter()deleteColumn删除当前列editor.commands.deleteColumn()addRowBefore/addRowAfter在当前行前/后插入行editor.commands.addRowAfter()deleteRow删除当前行editor.commands.deleteRow()deleteTable删除整个表格editor.commands.deleteTable()mergeCells合并选中单元格editor.commands.mergeCells()splitCell拆分选中单元格editor.commands.splitCell()toggleHeaderColumn切换表头列editor.commands.toggleHeaderColumn()toggleHeaderRow切换表头行editor.commands.toggleHeaderRow()toggleHeaderCell切换单元格为表头editor.commands.toggleHeaderCell()mergeOrSplit优先合并、失败则拆分editor.commands.mergeOrSplit()setCellAttribute设置单元格属性editor.commands.setCellAttribute(align, right)goToNextCell/goToPreviousCell移动到下一/上一单元格editor.commands.goToNextCell()fixTables修复表格结构editor.commands.fixTables()setCellSelection设置单元格选区editor.commands.setCellSelection({ anchorCell: 1, headCell: 2 })其中setCellSelection命令在 2.0.0-beta.12commiteb7e92f中引入。另外insertTable有一个值得一提的细节当TableCell的allowNestedNodes为 false默认值时命令会检查选区深度禁止在列表、引用块、嵌套表格等深层节点内插入表格$from.depth 1时直接返回 false。6.3 键盘快捷键addKeyboardShortcuts()内置了以下快捷键见 table.ts按键行为Tab移动到下一单元格若已在末尾则自动追加一行再移动Shift-Tab移动到上一单元格Backspace/Mod-Backspace当所有单元格被选中时删除整个表格Delete/Mod-Delete同上其中Backspace/Delete系列走deleteTableWhenAllCellsSelected工具packages/extension-table-plus/src/table/utilities/deleteTableWhenAllCellsSelected.ts保证全选表格后按删除键这一直觉行为能整表删除而不是逐个清空单元格。七、表格选项Options参考Table扩展的完整选项及其默认值如下源码见 table.ts 的addOptions()选项默认值说明HTMLAttributes{}渲染到table元素上的 HTML 属性如{ class: foo }resizablefalse是否允许拖拽调整列宽handleWidth5列宽调整手柄的宽度像素cellMinWidth25单元格最小宽度像素ViewTableView渲染表格的 NodeView 类可替换为自定义实现lastColumnResizabletrue是否允许调整最后一列的宽度allowTableNodeSelectionfalse是否允许直接选中整个表格节点onRowActionClick—行操作触发回调参数{ rowIndex, view, position? }onColumnActionClick—列操作触发回调参数{ colIndex, view, position? }各子扩展的选项TableCellHTMLAttributes默认{}、allowNestedNodes默认false是否允许在单元格内嵌套节点内容模型为(paragraph | image)TableHeaderHTMLAttributes默认{}内容模型为paragraphTableRowHTMLAttributes默认{}内容模型为(tableCell | tableHeader)*。这三个子扩展的解析/渲染规则分别是td、th、tr并各自携带colspan/rowspan/colwidth属性支持见 table-cell.ts、table-header.ts、table-row.ts。另外packages/extension-table-plus/src/types.ts 通过模块声明扩展了tiptap/core的NodeConfig新增可选的tableRole配置项默认table允许按需调整节点在 ProseMirror tables 体系中的角色标识。八、在 cherry-studio 中的实际接入方式作为佐证当前仓库的富文本编辑器RichEditor正是以这套表格扩展为底座src/renderer/components/RichEditor/createExtensions.ts 中从cherrystudio/extension-table-plus导入TableCell、TableHeader、TableRow并使用MarkdownTable.configure({ resizable: true, allowTableNodeSelection: true, onRowActionClick, onColumnActionClick })开启列宽拖拽、表格节点选中与行/列操作菜单回调src/renderer/components/RichEditor/extensions/markdownTable.ts 通过Table.extend()为基座表格扩展补充 GFM 表格的 Markdown 解析与序列化钩子parseMarkdown/renderMarkdown使 markdown 与table - tableRow - tableHeader|tableCell - paragraph的节点树可以双向往返并刻意不序列化对齐元数据以保持与旧转换逻辑一致。这两处代码直接体现了TableKit 聚合 子扩展单独配置两种模式在真实项目中的取舍生产代码选择逐个配置子扩展以获得精确控制同时通过自定义 NodeView 回调与 Markdown 钩子扩展能力。九、常见问题与升级要点基于整份 changelog 的历史教训升级时可以重点关注以下几点导入路径变化3.0.1 起所有表格扩展统一从tiptap/extension-tablefork 后为cherrystudio/extension-table-plus导入旧的tiptap/extension-table-header等包需卸载默认导出全部改为命名导出。UMD 用户需重新打包tsup 构建不产出 UMD直接script引用的场景需自行处理。CJS 默认导出互操作2.5.4 起按 legacy 方式解析默认导出require消费方的行为可能有细微差异。依赖版本同步大量 Patch Changes 只是跟随tiptap/core与tiptap/pm升级升级表格扩展时建议同时升级 peer 依赖到^3.0.9及以上当前包要求避免版本漂移。只读与嵌套行为editablefalse时列宽拖拽与新增行列按钮会被禁用allowNestedNodesfalse默认时禁止在嵌套节点内插入表格。十、结语从CHANGELOG-OLD.md可以看到这套表格扩展经历了多包拆分 → 单包聚合TableKit→ 构建体系现代化tsup/type-only imports→ 交互增强TableView/选区装饰/行列表操作的完整演进最终沉淀为 cherry-studio 富文本编辑器中稳定可用的表格能力。对开发者而言理解这段历史有助于在升级时规避破坏性变更、在自定义表格时选对扩展入口TableKit 或独立子扩展并借助TableView、onRowActionClick/onColumnActionClick、Markdown 钩子等扩展点实现深度定制。【免费下载链接】cherry-studioAI productivity studio with smart chat, autonomous agents, and 300 assistants. Unified access to frontier LLMs项目地址: https://gitcode.com/GitHub_Trending/ch/cherry-studio创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考