跳至正文
App — Univer 架构与 Office 功能实现分析

Univer 架构与 Office 功能实现分析

https://github.com/dream-num/univer

AI 参与说明(Agent:Codex):本文基于 Univer 固定提交与官方文档整理,覆盖模型、渲染、编辑、公式、第三方代码来源及开源与 Pro 边界。执行入口为 Codex Desktop;完整模型标识与 reasoning effort 未取得任务级运行记录。源码基线与官方资料日期见正文。

Univer 是可嵌入业务系统的 Office SDK,提供 Sheets、Docs、Slides 的数据模型、编辑界面和运行时。理解它的关键,是分清内容模型、屏幕上的编辑行为和 Office 文件转换各由哪一层负责。

术语导读

英文术语中文名称简要解释
Sheets / Docs / Slides表格 / 文档 / 演示文稿Univer 的三类内容与编辑能力;与外部 Office 文件格式分开
Unit内容单元具有独立身份的一份工作簿、文档或演示文稿
snapshot快照某一时刻可保存、加载的内容数据
Facade API外观接口面向业务开发者的上层 API,封装内部服务与命令
Command / Mutation / Operation命令 / 数据变更 / 操作分别编排业务动作、修改持久化数据、处理本地状态;正文会说明例外
Scene / Layer / Viewport场景 / 图层 / 视口分别组织画布对象、绘制层级和可见区域
skeleton布局骨架内容排版后的页面、行列与文字坐标
AST抽象语法树将公式表示为函数、运算和引用等结构
OT操作变换调整并发操作,使它们能在变化后的内容位置上应用
Exchange保留原名Univer Pro 中负责 Office 文件与内部数据互转的模块

Univer implements its own editing models, Canvas rendering pipeline, document layout, and formula engine. It also reuses general-purpose libraries and selected open-source algorithms.

“核心自研”仍包含两类复用:一类是 React、RxJS、依赖注入、OT 等基础库;另一类是直接纳入仓库的断行、断词和数字格式代码。Office 文件导入导出、完整协同服务及若干高级功能属于独立的 Pro 扩展,其内部实现不能由本开源仓库推定。

版本基线为 2026 年 10 月 28 日默认分支 dev 的提交 b52b1ded90c25efb9befa60c4ff4e0452faa31e2,该快照的包版本为 1.2.0。下文源码链接固定到此提交;官网功能与服务端说明按同日在线文档理解。源码架构结论与文件兼容性承诺应分别评估。

概念与功能对应

名称在 Univer 中的含义与微软 Office 的关系
Sheets工作簿、工作表、单元格、公式及表格编辑器对应 Excel 一类的电子表格能力;XLS、XLSX 是需要转换的外部格式
Docs富文本文档模型、排版与文档编辑器对应 Word 一类的文档能力;DOC、DOCX 是外部格式
Slides幻灯片页、图形、文本及演示文稿编辑器对应 PowerPoint 一类的能力;PPT、PPTX 是外部格式
PPT通常指 PowerPoint 文件或演示文稿在这里不应单列为 Slides 之外的第四种编辑内核

内部实时编辑使用的是 Univer 自己的模型和 snapshot,而不是直接操作 XLSX、DOCX、PPTX 文件。文件转换负责把外部文件变成内部数据,再把编辑结果导出;渲染与交互负责让这些数据在屏幕上可见、可编辑。两者是不同的技术层。

总体架构

运行时与包的分层

flowchart TB
    Host[业务应用与 Facade API] --> Runtime[Univer 运行时与插件容器]
    Runtime --> Core[core 模型 命令 历史 服务]
    Core --> Sheets[Sheets 工作簿与单元格]
    Core --> Docs[Docs 文本与文档]
    Core --> Slides[Slides 页面与元素]
    Sheets --> Formula[公式引擎]
    Sheets --> Layout[各类型布局与渲染适配]
    Docs --> Layout
    Slides --> Layout
    Layout --> Render[共享 Canvas 引擎 Scene Layer Viewport]
    UI[React 工作台 菜单 面板 输入桥] --> Runtime
    UI --> Render
主要目录或包职责
packages/coreUniver 入口、基础模型、命令、撤销重做、插件、服务、Facade 基础
packages/engine-renderCanvas 封装、场景、图层、视口,以及文档和表格的布局与绘制
packages/engine-formula公式词法分析、语法树、解释执行、依赖管理、函数实现
packages/sheets 与 sheets-*表格逻辑及筛选、排序、校验、公式等扩展
packages/docs 与 docs-*文档编辑逻辑、历史、列表、链接、绘图等扩展
packages/slides 与 slides-ui开源演示文稿模型、元素适配与编辑界面
packages/ui 与 designReact 工作台、菜单、面板、对话框和设计组件
presets预先组合好的插件集合,简化集成
packages/rpc 与 rpc-node跨线程或进程通信、模型副本同步基础设施

Univer 创建依赖注入容器,并为 UNIVER_SHEET、UNIVER_DOC、UNIVER_SLIDE 注册 Workbook、DocumentDataModel、SlideDataModel。创建某类型的第一个 Unit 时,会启动该类型的插件。因此,一个 Unit 可以理解为有独立身份的一份工作簿、文档或演示文稿。

插件声明依赖后,PluginService 按依赖关系安排加载,并调用 Starting → Ready → Rendered → Steady 生命周期。插件可以注册服务、命令、渲染控制器和 UI。依赖注入由 @wendellhu/redi 提供;跨服务状态变化大量使用 RxJS。

源码入口:Unit 创建与模型注册、插件依赖与生命周期。

命令与数据变更

通用命令系统区分三种类型:

类型设计职责例子
Command编排一次业务动作,生成正向变更和撤销所需的变更删除行、设置单元格内容
Mutation修改需要进入 snapshot 的数据,也是协同冲突处理的基本单位写入单元格、插入行
Operation按设计用于不进入 snapshot 的本地状态滚动位置、侧栏状态

在 Sheets 的典型编辑流程中,Command 先读取旧值、生成逆向 Mutation,再执行正向 Mutation,将两组操作压入 UndoRedoService。撤销重做通过执行这些操作实现,不需要为每一步复制整个工作簿。统一入口也让 API 编辑、UI 编辑和变更监听能够使用同一条路径。

这套划分是架构规范,具体模块存在例外:当前开源 Slides 的部分 Operation 会直接写入页面元素,尚未完整沿用 Sheets、Docs 的持久化变更链路。不能由共享命令系统推导出三类编辑器的协同与历史能力完全相同。

源码:三种命令的定义、本地撤销重做。

渲染原理

React 外壳与 Canvas 编辑表面

React renders the workbench and controls; the content surface is drawn through Univer’s Canvas engine. 工作台、菜单和面板是 React 与 DOM,文档正文、表格网格和幻灯片主体由 Canvas 绘制。Canvas 最终调用浏览器 getContext('2d'),并处理设备像素比和尺寸;Engine 管理画布、事件和渲染循环。

引擎中几个概念各有职责:Scene 组织一个内容场景;Layer 管理绘制顺序、缓存和脏状态;Viewport 决定可见区域、滚动和裁剪;具体对象负责文本、表格、图片、矩形等绘制。配置缓存后,可以复用已有像素,在滚动时重绘暴露区域或失效部分。

这避免了为大量单元格创建 DOM 节点,但相应地,坐标命中、光标、选区、图形控制框和文本输入桥需要项目自己实现。实际速度仍受文档规模、复杂格式、公式、字体和缓存命中影响,Canvas 架构本身不构成性能数值保证。

源码:Canvas 2D 与像素比、渲染循环、图层缓存、React 工作台。

数据模型与布局骨架分离

模型回答“内容是什么”,布局骨架回答“内容应当画在哪里”。例如,一个段落的文本和样式属于模型,断成几行、落在哪一页、各字形的坐标属于 skeleton。表格中,行高、列宽及单元格数据属于模型;累计尺寸、可视范围、文字溢出和绘制缓存属于布局与渲染层。

这一分离使相同内容能以不同宿主配置呈现,也解释了 Docs 的文本能力为什么能复用于单元格和幻灯片。共享的是模型和排版基础,各宿主仍需处理自己的边界、对齐、旋转和交互。

Sheets 与 Excel 类功能

工作簿与稀疏单元格模型

IWorkbookData 保存工作簿标识、工作表映射、工作表顺序、样式池、计算设置及插件资源。IWorksheetData 保存行列数量、行高列宽、冻结、合并区域和单元格等。

单元格的关键字段为 v 原值、t 类型、s 样式或样式 ID、f 公式、p 富文本文档。cellData 采用按行号和列号索引的对象矩阵,Worksheet 将它封装为 ObjectMatrix;没有内容的格子无需逐一保存。

下面是零起始索引的简化结构,用于说明模型,不是完整初始化参数:

ts
cellData: {
  0: {
    0: { v: 10 },              // A1
    1: { f: '=A1*2', v: 20 },  // B1 公式与计算结果
  },
}

源码:工作簿和工作表类型、单元格类型、ObjectMatrix。

网格布局与编辑

SheetSkeleton 将行高列宽转为累计几何信息,支持像素位置与行列索引之间的转换。SpreadsheetSkeleton 增加可视区域、字体、背景、边框和文本溢出等信息;Spreadsheet 再按视口绘制,利用缓存及滚动差分减少重复工作。冻结区域也由视口划分参与处理。

富文本单元格的 p 直接使用 IDocumentData。复杂文本可以走 DocumentDataModel → DocumentViewModel → DocumentSkeleton,复用 Docs 的测量、换行和布局;简单文本另有测量与缓存路径,并非每个格子都完整排版一份文档。

单元格编辑器和公式栏同样复用 Docs。用户完成输入后,编辑控制器把文档内容转回单元格数据,调用 SetRangeValuesCommand;后者执行 Mutation、收集自动行高等关联变更,并记录撤销操作。因此,表格中的“文字输入”与表格数据写入之间有明确的转换层。

源码:表格几何骨架、视口缓存绘制、单元格文档模型、编辑提交、设置范围命令。

公式引擎

The formula engine parses formulas into an AST and evaluates them through its own interpreter. engine-formula 的 Lexer、AST、依赖管理和函数实现均在本仓库;公式计算不依赖 Excel 进程、HyperFormula 或 JavaScript eval。其处理过程如下:

text
公式字符串
  → Lexer 与 LexerTreeBuilder 分析词法
  → AstTreeBuilder 构建语法树
  → 解析单元格与范围引用 建立依赖
  → 安排需要计算的节点
  → Interpreter 调用函数与运算实现
  → ValueObject 表示数字 字符串 错误 数组等结果

例如 A1 改变时,表格公式插件把变更范围记为 dirty range;计算触发服务合并待计算内容,依赖管理通过范围索引找到受影响的公式及其下游,再安排执行。这是按依赖传播的增量计算机制。

当前版本还用强连通分量把互相依赖的公式归为一组,为循环引用与迭代计算安排执行。不能把整个公式依赖结构一概描述为无环图。SUM 等函数本体也在仓库中实现,函数注册和扩展属于公式引擎的一部分。

公式引擎自研不意味着自动覆盖全部 Excel 行为。公式语义、数组公式、命名区域、跨表引用、数值精度以及 VBA 属于不同兼容维度,需按实际文件验证。

源码:计算管线、依赖传播、循环依赖分组、SUM 实现。

Docs 与 Word 类功能

文本流与结构模型

The Docs model stores a linear text stream and range-based metadata rather than using the DOM as its source of truth. IDocumentBody 的核心是 dataStream:文本字符与结构控制符组成的线性流;textRuns 按范围记录字符样式,paragraphs、sectionBreaks、tables、customRanges 等记录结构和元数据。

例如段落和分节具有专用 token,表格也有开始、行、单元格、结束等标记。操作位置以字符串偏移为基础,部分接口明确采用 UTF-16 偏移,不能简单等同于用户肉眼看到的字符个数。

IDocumentData 在正文之外还可以包含文档样式、页眉页脚、绘图、列表、脚注尾注和插件资源。模型保留部分 DOCX 原始 XML 与往返转换元数据;保留某段原始 XML,与该内容已能排版或编辑是两件事。

源码:文档数据结构、正文结构、结构 token。

从文档到页面坐标

DocumentViewModel 把线性文本流解析为分节、段落、表格等结构树。DocumentSkeleton 进行文本测量、断行、段落与页面布局,生成 Page → Section → Column → Line → Divide → Glyph 等几何结构。Documents 读取骨架,按页、行和字形组绘制,字体扩展最终调用 Canvas fillText。

浏览器仍负责底层字体测量与绘字;Univer 负责文档级的结构解释、换行和页面组织。这与直接交给浏览器 DOM 自动排版不同,也不意味着它从零实现了完整字体栅格化引擎。

当前 DocumentFlavor 包含 TRADITIONAL、MODERN、DRAWINGML:Traditional 面向纸张分页文档;Modern 使用连续布局;DrawingML 为形状和幻灯片文本提供宿主语义。代码还包含增量布局、旧任务取消和部分结果发布,因而不能把它描述为每次输入都必然全量排版。

源码:解析文本流、增量排版与连续模式、Canvas 文本绘制、DocumentFlavor。

输入法与选区

Canvas 本身不提供完整文本输入语义。当前实现创建了一个文本和原生光标透明、被 1px 容器裁剪的 contentEditable div,接收 input、键盘及 composition 事件,再由控制器转换成编辑命令。IME 有独立的输入控制器;这里使用的不是 textarea。

可见的光标和选区由项目维护,并借助布局骨架将屏幕坐标映射为文本偏移。必要时再与 DOM selection 同步。因此,用户看见的是 Canvas 文字,浏览器原生可编辑元素承担输入接入,两者相互配合。

源码:创建输入桥、输入与输入法事件。

编辑操作与 OT

flowchart TB
    Input[输入事件] --> Command[InsertTextCommand]
    Command --> Ops[TextX 与 JSONX 操作]
    Ops --> Mutation[RichTextEditingMutation]
    Mutation --> Model[DocumentDataModel]
    Model --> Layout[布局与 Canvas 更新]
    Ops --> History[逆操作与历史合并]

TextX 是仓库实现的富文本操作类型,提供 insert、retain、delete 以及操作应用、合并、变换和求逆。JSONX 则包装第三方 ot-json1,将 TextX 注册为子类型,用来处理整份文档 JSON 中的操作。

历史服务可把连续编辑合并,并保存正向与逆向 Mutation。OT 原语让操作具备相互变换的基础,但联网协同还需要会话、服务端权威版本、操作确认、广播和持久化,不能仅因存在 transform 就认定开源仓库附带完整协同系统。

源码:插入文本操作、实际应用到模型、JSONX 与 ot-json1、历史合并。

Slides 与 PPT 类功能

页面与元素

ISlideData 保存演示文稿标识、标题、页面大小等,正文用 pages 字典保存页面,用 pageOrder 保存顺序。每页再保存背景和 pageElements;元素具有位置、尺寸、旋转、缩放、翻转、层级,以及图形、图片、富文本、文档或表格等载荷。

渲染控制器为每页创建独立 Scene。ObjectProvider 通过适配器将数据变成引擎的 Rect、Circle、RichText、Documents、Spreadsheet 等对象,再挂到场景中。切页激活对应子场景,缩略图复用相同页面场景缩放绘制。

这是一条自有模型到自有渲染引擎的链路,不是调用 PptxGenJS 生成文件后再当作编辑器,也不是把 Reveal.js 网页幻灯片作为内核。

源码:Slides 数据模型、页面场景创建、对象适配注册表。

文本和图形编辑

双击文字时,Slides 启动内部的 Docs 编辑单元,复用文档输入、选区和排版,结束后把结果写回幻灯片元素。嵌入文档和表格的适配器,也分别调用自家的文档、表格模型和渲染组件;存在表格适配器不等于已经提供完整的嵌入式 Sheets 编辑体验。

图片和图形使用共享渲染引擎的对象变换能力,Transformer 提供选择及移动、缩放、旋转等交互。开源形状适配器当前明确处理矩形、圆角矩形和椭圆等基础类型。

源码:文本编辑桥、内部 Docs 单元、文档适配器、基础形状适配。

开源基础层的边界

当前 README 将 Slides 分为开源模型与 UI 包,以及 Pro 模型、UI、文件转换、图表和表格等增强层。但公开基础实现也不应等同于完整 PowerPoint:

  • master、layout 等 schema 字段说明模型表达了相关概念,不能单靠字段证明完整母版编辑、继承解析或 PowerPoint 动画兼容。
  • UpdateSlideElementOperation 直接合并并更新页面元素,这条路径不同于成熟的 Mutation 历史编排。
  • SlideDataModel.getRev() 恒返回 0,incrementRev、setRev 没有更新逻辑,源码明确保留尚未实现 Slides 协同的说明。这一结论针对该开源类,不代表 Pro 产品的全部能力。

源码:元素更新操作、revision 接口。

自研部分与第三方复用

能力实现判断可核实的复用
Office 编辑器主体模型、命令、场景与编辑流程在仓库内实现浏览器平台能力与通用库
Canvas 场景引擎自有 Engine、Scene、Layer、Viewport 及渲染组件原生 Canvas 2D、字体测量与绘制
UI 外壳自有工作台与设计组件React、ReactDOM、Radix UI、Floating UI 等
插件运行时自有插件管理、生命周期、服务约定redi 依赖注入、RxJS 状态流
表格公式自有 Lexer、AST、Interpreter、依赖与函数体系区间索引、decimal.js 等基础依赖
文档操作自有 TextX 与文档编辑语义JSONX 直接使用 ot-json1 的 JSON OT
文本断行与断词集成后服务于自有文档布局断行 fork 自 foliojs/linebreak,断词部分改自 ytiurin/hyphen
数字与日期格式仓库内有实现及扩展源码明确派生自 MIT 的 numfmt 3.2.6
Office 格式转换官方提供独立 Pro 模块商业转换内核的底层依赖不能由本仓库确认

单看 package.json 会漏掉直接纳入源码的代码;也不应把“有 decimal.js 依赖”理解为所有公式基本运算都交给它,当前 math-kit 的基础算术仍直接使用 JavaScript number。

来源:core 依赖、公式引擎依赖、断行代码来源、断词代码来源、numfmt 来源、基础算术实现。

Office 文件转换与商业能力

开源与 Pro 的分界

公开仓库使用 Apache 2.0,基础 SDK 可以独立使用;Pro 是可选扩展,产品家族中的商业能力仍适用各自的许可。

类型开源仓库的主要能力README 列出的部分 Pro 扩展
Sheets表格编辑、公式、格式、筛选排序、校验、条件格式、评论、表格与绘图集成协同、编辑历史、导入导出、打印、图表、透视表、增强计算等
Docs文档模型、编辑 UI、列表、链接、评论与绘图集成协同、导入导出、打印、增强表格列表、分栏与形状等
Slides演示文稿模型与 UI 包Pro 模型与 UI、导入导出、图表和表格等
运行时Node headless、Worker 与 RPC 基础协同服务、SSR、计算委托及变更重放等

表中是公开能力划分,不是逐个格式或函数的测试清单。例如排版基础中存在 Column 结构,并不能据此认定所有产品级分栏编辑功能都属于开源 UI。

依据:当前 README 的开源与 Pro 边界。

转换链路

flowchart TB
    File[Office 文件] --> Import[Pro Exchange 导入]
    Import --> Data[Univer 模型与 snapshot]
    Data --> Editor[浏览器编辑器]
    Editor --> Updated[更新后的 snapshot]
    Updated --> Export[Pro Exchange 导出]
    Export --> Result[Office 文件]

当前官方 Server SDK 使用 @univerjs-pro/exchange-node 提供 importFile()、exportToFile()。转换既可集成在自己的 Node.js 进程,也可由远程服务承接;单独文件转换不要求接入 AI SDK 或多人协同服务。浏览器侧转换 API 与后端转换模块也应分开理解。

官网 Node 转换页列出的支持范围如下,属于格式入口支持,不代表所有内容完全保真:

内容类型Node 模块导入Node 模块导出
电子表格XLS、XLSX、XLSM、CSV、TSVXLSX、CSV、TSV
文档DOC、DOCXDOCX
演示文稿PPT、PPTX、PPTM、PPSX、PPSM、POTXPPTX

浏览器 API 的接受范围更窄:Sheets 不接受 XLSM,Slides 接受 PPT、PPTX,而不是自动接受后端表中的其他演示文件扩展名。接受含宏格式作为输入,也不等于执行 VBA。官方 Web SDK 文档还说明,1.2.0 起移除了内建导入导出菜单交互,但保留服务和 Facade,业务应用需要组织自己的入口。

公开编辑内核可以追踪到本仓库的实现;商业 Exchange 内部使用何种 OOXML 解析器、是否依赖其他原生库,仅凭此仓库不能确认。文件能打开、内容能编辑、导出后保留原貌,是三个需要分别验证的目标。

官方资料:Server 架构、Node 文件转换与格式表、Sheets 浏览器转换、Docs 浏览器转换、Slides 浏览器转换。

Worker 服务端与协同

Worker 的两条路径

Sheets 支持把模型与公式引擎实例放进 Web Worker。主线程发送 snapshot 创建计算副本,之后通过 RPC 同步相关 Mutation,计算结果再回送。Worker preset 注册的是数据逻辑、公式和通信插件,不意味着整个编辑 UI 都被搬到 Worker。

Docs 则有独立的 layout worker:根据 snapshot 创建文档模型及布局会话,处理排版并返回结果,要求相应的 OffscreenCanvas 文本测量能力。这与 Sheets 的计算副本协议不同。是否启用这些路径取决于插件和配置,不能笼统表述为“所有编辑默认都在 Worker”。

源码:Sheets Worker preset、snapshot 与 Mutation 同步、Docs 排版 Worker。

Node headless 与多人协同

同构设计要求将模型、Command、Mutation 等逻辑与 React、Canvas、菜单等浏览器依赖分开。Facade 也随插件划分,使同一套数据处理 API 能用于浏览器与 Node。这为批量修改工作簿、公式计算和自动化提供了基础;浏览器画面、Office 文件转换和完整协同后端仍需各自的模块。

当前官方协同 Server SDK 采用模块化结构:Transport 承接 HTTP 与 WebSocket;Endpoint 管理会话、房间、确认与广播;Service 处理权威 Unit、OT、连续 revision 和持久化协调;数据库 Adapter 对接存储。业务应用接入自己的身份和权限逻辑。

典型过程是客户端加载 Unit、建立会话、提交 changeset,服务端进行操作变换并确认版本,再持久化和广播。它与本地撤销、Worker 内部副本同步属于不同场景,不能把三者都称作同一套“协同”,也不应把这里的 OT 机制写成 Yjs CRDT。

依据:仓库同构约束、协同模块职责、协同包结构。

架构判断与选型验证

Univer 让不同内容类型复用同一套运行时。Sheets、Docs 和 Slides 共享身份与插件机制、服务、渲染基础,以及部分文本和绘图能力。Docs/Sheets 的 Drawing 插件还共用基础绘图模型,再分别处理文档锚点和表格锚点;图形随行列或段落变化的行为,需要宿主自己的适配逻辑。

这种架构适合需要深度嵌入、扩展命令或建立结构化自动化接口的产品。代价是需要理解插件组合、内部模型和各模块的功能边界;自有 Canvas 文本系统还需要关注输入法、字体、无障碍与排版回归。不能用某一个模块的能力水平代表整个 SDK。

当前快照下,Sheets 的模型、网格、公式与变更管线可在源码中完整追踪;Docs 已包含分页、连续布局、表格及复杂文本机制;开源 Slides 提供基础页面和元素编辑,但 revision 与变更路径显示出更明确的限制。需要 PPT、Word、Excel 文件兼容时,还应将 Pro 转换和目标文件样本纳入评估。

进入实际项目之前,建议用真实材料验证以下内容:

  1. 表格计算:关键公式、循环引用、大范围依赖、格式和数值结果是否满足业务要求。
  2. 文档排版:中文输入、字体替换、分页、页眉页脚、表格及复杂段落是否稳定。
  3. 演示文稿:母版、图表、嵌入内容、动画与导出保真分别达到什么程度。
  4. 运行与集成:目标设备上的加载和编辑表现、Worker 配置,以及实际需要的 Pro 模块和部署方式。

继续读源码时,可依次从 core/src/univer.ts、core 的模型与命令系统、各类型的 skeleton 和渲染组件,再进入 UI 控制器。这个顺序能先建立数据和状态变化的理解,再解释屏幕上的编辑行为。

补充源码:Docs Drawing 依赖、Sheets Drawing 依赖、Drawing 状态到渲染对象。

博客助手

正在打开博客助手…