快速开始

先运行双浏览器协同示例,再把最小 Client、Transport、Endpoint、Service 与 Adapter 组合迁入自己的应用。

本页分为两条路径:先运行官方示例,在两个浏览器中验证完整协同链路;然后阅读并复制最小的 Server 与 Web 组装,把 Collaboration SDK 接入自己的 Univer 应用。

开始前,请先完成环境与版本要求。所有 @univerjs/*@univerjs-pro/* package 必须固定在同一个匹配的 release cohort,不要混用版本。

路径一:运行官方示例

运行前准备

该仓库要求 Node.js 24+,并声明使用 pnpm 10.32.1。先检查 node --versionpnpm --version,保留仓库锁文件。所有 pnpm example:* 命令均从仓库根目录执行:命令会构建前端、服务端并检查类型,然后启动服务,并非热更新开发服务器。修改源码后,需要停止进程并重新运行命令。

如果已有客户端许可证,请在构建前通过终端设置 UNIVER_LICENSE。示例的 vite.config.ts 会把它写入浏览器产物;只在启动已构建的服务时设置变量,不会更新客户端许可证。配置说明见客户端许可证

SQLite 示例会在安装时编译 better-sqlite3,需要可用的 node-gyp、Python 和 C/C++ 编译工具链。如果出现 node-gyp: command not found,先在开发环境中安装 node-gyp,再使用与运行示例相同的 Node.js 版本重新安装依赖。

Bash
git clone https://github.com/dream-num/univer-collaboration-examples.gitcd univer-collaboration-examplespnpm install --frozen-lockfilepnpm example:quick-start

打开:

text
http://127.0.0.1:3010/?unit=quick-start-sheet&type=2

验证实时协同

  1. 在一个浏览器窗口中打开完整 URL。
  2. 使用另一个浏览器 profile 或无痕窗口打开同一 URL。
  3. 在任一窗口的 Sheet 中修改单元格。
  4. 确认另一个窗口实时出现相同修改。

必须使用两个独立浏览器 Context,才能建立两个不同的在线 Session。只在同一个页面里观察自身修改, 不足以验证 Room、ACK 与广播。

这次成功证明了什么

text
Univer Collaboration Client  → Node Transport  → UniverCollabEndpoint  → UniverCollabService  → MemoryDatabaseAdapter

双浏览器同步意味着浏览器已经通过 HTTP 加载 Unit,通过一次性 Session Ticket 建立 WebSocket Session 并 JOIN 同一个 Room。修改被 Service 执行 OT、确认新 revision、保存到 Adapter,再由 Endpoint 广播给其他成员。

路径二:迁入自己的应用

下面展示最小的 Server 与 Web 组装。完整应用结构可参考官方 Quick Start:

如果已有 Univer Web 应用,可以保留自己的 Runtime、Preset 和 UI,只增加下面的 Collaboration Client 配置与 Server。若从空项目开始,直接复制 Quick Start 目录中的 index.htmlvite.config.ts 和样式文件作为外壳。

按可运行文件迁入应用

examples/quick-start 复制应用骨架,保留 package.jsonindex.htmlvite.config.tsweb/styles.cssweb/main.tsserver/main.ts。在复制后的示例目录内执行 build 和 start:Express 按工作目录读取 dist/web。保留 Vite 中的许可证注入,并确保 #app 有非零高度。下面的代码用于解释组装关系,完整可运行项目以这些文件为起点。

示例页面和 /universer-api 使用同一源。迁到独立前端服务时,需要代理 HTTP 与 WebSocket Upgrade,或一起修改客户端的五个协议地址。URL 中的 unit 必须与后端创建的文档 ID 一致,type=2 表示 Sheets;仅填写 URL 参数不会创建文档。

1. 安装同一发布批次的 Package

服务端需要 Transport、Endpoint、Service 和一个 Database Adapter;浏览器需要 Collaboration Client。下面的命令安装所需 package,应用应再把所有 Univer package 固定为同一个精确版本:

Bash
pnpm add \  @univerjs-pro/collaboration@1.0.0-rc.0 \  @univerjs-pro/collaboration-client@1.0.0-rc.0 \  @univerjs-pro/collaboration-client-ui@1.0.0-rc.0 \  @univerjs-pro/collaboration-database-memory@1.0.0-rc.0 \  @univerjs-pro/collaboration-endpoint@1.0.0-rc.0 \  @univerjs-pro/collaboration-service@1.0.0-rc.0 \  @univerjs-pro/collaboration-transport-node@1.0.0-rc.0 \  @univerjs-pro/license@1.0.0-rc.0 \  @univerjs/core@1.0.0-rc.0 \  @univerjs/preset-sheets-core@1.0.0-rc.0 \  @univerjs/presets@1.0.0-rc.0 \  @univerjs/protocol@1.0.0-rc.0 \  express react react-dom rxjs

官方示例 manifest 是当前 package 组合与精确版本的可运行来源。

2. 创建 Service、Endpoint 与初始 Unit

UniverCollabService 是权威协同核心。Endpoint 把浏览器协议映射到 Service,Memory Adapter 暂时保存 snapshot、changeset 与 revision:

TypeScript
import { createServer } from "node:http";import express from "express";import type { IWorkbookData } from "@univerjs/core";import { LocaleType } from "@univerjs/core";import { MemoryDatabaseAdapter } from "@univerjs-pro/collaboration-database-memory";import { UniverCollabEndpoint } from "@univerjs-pro/collaboration-endpoint";import { UniverCollabService } from "@univerjs-pro/collaboration-service";import { createNodeTransport } from "@univerjs-pro/collaboration-transport-node";import { ErrorCode, UniverType } from "@univerjs/protocol";const UNIT_ID = "quick-start-sheet";const unitData: IWorkbookData = {  id: UNIT_ID,  rev: 1,  name: "Quick Start Sheet",  appVersion: "",  locale: LocaleType.EN_US,  sheetOrder: ["sheet-1"],  sheets: {    "sheet-1": {      id: "sheet-1",      name: "Sheet 1",      rowCount: 100,      columnCount: 26,      cellData: {},    },  },  styles: {},  resources: [],};const database = new MemoryDatabaseAdapter();const service = new UniverCollabService({ dbAdapter: database });const endpoint = new UniverCollabEndpoint(service);const transport = createNodeTransport();transport.use(async (context, next) => {  context.userID = "demo-user";  await next();});transport.register(endpoint);await service.createUnitFromData(  { type: UniverType.UNIVER_SHEET, data: unitData },  { userID: "demo-user" },);

这里固定 demo-user 是为了展示可信身份从 Transport 进入 Endpoint 和 Service 的路径。生产应用 必须在 transport.use() 中验证自己的 Cookie、Bearer token 或 Session,再设置稳定业务 userID

3. 挂载授权、HTTP 与 WebSocket 入口

当前 Collaboration Client 会查询授权协议。Quick Start 为该接口返回固定 allowed: true, 并将 Transport 挂载到应用的 HTTP Server:

TypeScript
const app = express();app.post("/universer-api/authz/-/object/-/batch_allowed", express.json(), (request, response) => {  const body = request.body as {    requests: Array<{ unitID: string; objectID: string; actions: unknown[] }>;  };  response.json({    error: { code: ErrorCode.OK, message: "" },    objectActions: body.requests.map((item) => ({      unitID: item.unitID,      objectID: item.objectID,      actions: item.actions.map((action) => ({ action, allowed: true })),    })),  });});app.use(express.static("dist/web"));const server = createServer(app);transport.attach(server);server.listen(3010, "127.0.0.1");

固定允许只负责让教学示例可运行,不是安全边界。正式应用必须同时保护 HTTP read、实时 JOIN、 changeset submit 和 Unit 生命周期;完整覆盖见身份与权限

4. 在浏览器 Runtime 中注册 Collaboration Client

浏览器端继续使用正常的 Web SDK 与 Preset,并增加 Collaboration Plugin、Client、UI 和 协议地址:

TypeScript
import { LocaleType, LogLevel } from "@univerjs/core";import { UniverCollaborationPlugin } from "@univerjs-pro/collaboration";import { UniverCollaborationClientPlugin } from "@univerjs-pro/collaboration-client";import CollaborationClientEnUS from "@univerjs-pro/collaboration-client/locale/en-US";import {  BrowserCollaborationSocketService,  UniverCollaborationClientUIPlugin,} from "@univerjs-pro/collaboration-client-ui";import CollaborationClientUIEnUS from "@univerjs-pro/collaboration-client-ui/locale/en-US";import { UniverLicensePlugin } from "@univerjs-pro/license";import { UniverSheetsCorePreset } from "@univerjs/preset-sheets-core";import UniverPresetSheetsCoreEnUS from "@univerjs/preset-sheets-core/locales/en-US";import { createUniver, defaultTheme, mergeLocales } from "@univerjs/presets";import "@univerjs/preset-sheets-core/lib/index.css";import "@univerjs-pro/collaboration-client-ui/lib/index.css";const httpProtocol = location.protocol === "https:" ? "https" : "http";const wsProtocol = location.protocol === "https:" ? "wss" : "ws";const baseURL = `${httpProtocol}://${location.host}/universer-api`;createUniver({  locale: LocaleType.EN_US,  locales: {    [LocaleType.EN_US]: mergeLocales(      UniverPresetSheetsCoreEnUS,      CollaborationClientEnUS,      CollaborationClientUIEnUS,    ),  },  theme: defaultTheme,  logLevel: LogLevel.WARN,  collaboration: true,  presets: [UniverSheetsCorePreset({ container: "app" })],  plugins: [    [UniverLicensePlugin, { license: import.meta.env.UNIVER_LICENSE || undefined }],    UniverCollaborationPlugin,    [      UniverCollaborationClientPlugin,      {        socketService: BrowserCollaborationSocketService,        sendChangesetTimeout: 200,        authzUrl: `${baseURL}/authz`,        snapshotServerUrl: `${baseURL}/snapshot`,        collabSubmitChangesetUrl: `${baseURL}/comb`,        collabWebSocketUrl: `${wsProtocol}://${location.host}/universer-api/comb/connect`,        wsSessionTicketUrl: `${baseURL}/user/session-ticket`,      },    ],    UniverCollaborationClientUIPlugin,  ],});

页面需要一个 id="app" 的容器,并通过查询参数指定 Unit:

text
/?unit=quick-start-sheet&type=2

5. 用生产能力替换教学配置

教学配置接入应用时替换为
固定 demo-user应用自己的认证 Middleware 与稳定用户 ID
权限默认允许覆盖 read、JOIN、submit 和生命周期的服务端 ACL
固定 Unit调用 createUnitFromData() 的应用创建 API 与产品记录
Memory AdapterSQLite 或经过合同测试的自定义持久化 Adapter

下一步通常先完成 Database Adapter身份与权限, 再按需增加 History、Thread Comment、Worktree 或 Exchange。所有可运行组合见 示例索引

首次运行排查

现象检查项
安装或构建失败Node.js 24+、仓库声明的 pnpm 版本、Univer 包版本一致
3010 端口被占用停止前一个示例,或设置 PORT=3011,两个浏览器都使用新端口
白屏或静态资源缺失构建成功、从示例目录启动、#app 有高度
页面能打开但修改不同步两个窗口的完整 URL 相同;snapshot 与 session-ticket 请求成功;/universer-api/comb/connect 建立 WebSocket
重启后数据丢失Quick Start 使用内存,继续运行 SQLite 示例

你觉得这篇文档如何?

© 2026 DreamNum Co., Ltd.