Headless Univer

Headless Univer — среда выполнения Univer для работы с содержимым без интерактивного интерфейса редактора. Она использует общие с браузерным редактором модели, команды, вычисления и Facade API. Обработка возможна в Node.js и в браузере без UI. Доступные возможности определяются зарегистрированными плагинами продуктов и функций.

В Node.js можно создавать и загружать книги, менять ячейки, форматы, строки и столбцы, вычислять формулы и сохранять снимки Univer. Фильтры, сортировка, проверка данных, сводные таблицы и диаграммы требуют своих плагинов содержимого. Изменение данных диаграммы не означает её отрисовку.

Пример использует режим плагинов. @univerjs/core и плагины содержимого продукта входят в Headless; @univerjs/ui, пакеты продукта *-ui и стандартные браузерные пресеты сюда не входят. @univerjs/engine-render остаётся общей зависимостью без создания Canvas и монтирования редактора. См. границы пакетов Headless.

Локальные снимки не требуют сервиса совместной работы. Совместный пример дополнительно требует клиента, сервера и аутентификации. Для конвертации Office и снимков экрана нужны соответственно Server SDK и среда рендеринга. Импорт /facade сам по себе не регистрирует плагины и не превращает UI API в Headless API.

Установка

Shell
pnpm add @univerjs/core @univerjs/engine-render @univerjs/engine-formula @univerjs/docs @univerjs/sheets @univerjs/sheets-formula @univerjs/sheets-numfmt

Использование

TypeScript
import { LocaleType, mergeLocales, Univer } from '@univerjs/core'import { FUniver } from '@univerjs/core/facade'import { UniverDocsPlugin } from '@univerjs/docs'import { UniverFormulaEnginePlugin } from '@univerjs/engine-formula'import { UniverRenderEnginePlugin } from '@univerjs/engine-render'import { UniverSheetsPlugin } from '@univerjs/sheets'import { UniverSheetsFormulaPlugin } from '@univerjs/sheets-formula'import { UniverSheetsNumfmtPlugin } from '@univerjs/sheets-numfmt'import SheetsRuRU from '@univerjs/sheets/locale/ru-RU'import '@univerjs/engine-formula/facade'import '@univerjs/sheets/facade'import '@univerjs/sheets-formula/facade'import '@univerjs/sheets-numfmt/facade'const univer = new Univer({  locale: LocaleType.RU_RU,  locales: {    [LocaleType.RU_RU]: mergeLocales(      SheetsRuRU,    ),  },})univer.registerPlugin(UniverRenderEnginePlugin)univer.registerPlugin(UniverFormulaEnginePlugin)univer.registerPlugin(UniverDocsPlugin)univer.registerPlugin(UniverSheetsPlugin)univer.registerPlugin(UniverSheetsFormulaPlugin)univer.registerPlugin(UniverSheetsNumfmtPlugin)const univerAPI = FUniver.newAPI(univer)univerAPI.createWorkbook({})

Пример совместного редактирования в Node.js

Node.js может работать как клиент совместного редактирования: загружать существующую книгу с сервера, изменять её содержимое через API Facade и синхронизировать изменения с другими клиентами, открывшими тот же документ. Перед запуском подготовьте сервис совместного редактирования, клиентскую лицензию и ID доступного документа.

Файлы примера

Расположите файлы следующим образом:

text
src/sheets/node/├── main.ts├── univer.ts└── worker.ts
  • univer.ts: создаёт Univer и регистрирует плагины функций, клиент совместного редактирования и необязательный RPC-плагин основного процесса.
  • worker.ts: регистрирует вычислительный движок формул, плагины удалённых формул, фильтров и сводных таблиц и обменивается данными с основным процессом через RPC.
  • main.ts: загружает совместный документ и демонстрирует API прав доступа, базовых операций с листами, условного форматирования, проверки данных, таблиц, сводных таблиц, спарклайнов, изображений, диаграмм, фигур, группировки и гиперссылок.

Замените your-license в обоих файлах одной и той же лицензией, а также укажите URL сервера, Cookie, ID документа и ID пользователей. Пример прав доступа в main.ts также настраивает соавторов и защищённые диапазоны: используйте реальных пользователей приложения и учётную запись с необходимыми правами управления.

TypeScript
import { UniverCollaborationPlugin } from '@univerjs-pro/collaboration'import { UniverCollaborationClientPlugin } from '@univerjs-pro/collaboration-client'import {  NodeCollaborationSocketService,  UniverCollaborationClientNodePlugin,} from '@univerjs-pro/collaboration-client-node'import { UniverProFormulaEnginePlugin } from '@univerjs-pro/engine-formula'import { UniverLicensePlugin } from '@univerjs-pro/license'import { UniverSheetsChartPlugin } from '@univerjs-pro/sheets-chart'import { UniverSheetsOutlinePlugin } from '@univerjs-pro/sheets-outline'import { UniverSheetsPivotTablePlugin } from '@univerjs-pro/sheets-pivot'import { UniverSheetsShapePlugin } from '@univerjs-pro/sheets-shape'import { UniverSheetSparklinePlugin } from '@univerjs-pro/sheets-sparkline'import { IAuthzIoService, IImageIoService, IUndoRedoService, LocaleType, Univer } from '@univerjs/core'import { UniverDataValidationPlugin } from '@univerjs/data-validation'import { UniverDocsPlugin } from '@univerjs/docs'import { UniverDocsDrawingPlugin } from '@univerjs/docs-drawing'import { UniverDrawingPlugin } from '@univerjs/drawing'import { UniverRenderEnginePlugin } from '@univerjs/engine-render'import { UniverNetworkPlugin } from '@univerjs/network'import { UniverRPCNodeMainPlugin } from '@univerjs/rpc-node'import { UniverSheetsPlugin } from '@univerjs/sheets'import { UniverSheetsConditionalFormattingPlugin } from '@univerjs/sheets-conditional-formatting'import { UniverSheetsDataValidationPlugin } from '@univerjs/sheets-data-validation'import { UniverSheetsDrawingPlugin } from '@univerjs/sheets-drawing'import { UniverSheetsFilterPlugin } from '@univerjs/sheets-filter'import { UniverSheetsFormulaPlugin } from '@univerjs/sheets-formula'import { UniverSheetsHyperLinkPlugin } from '@univerjs/sheets-hyper-link'import { UniverSheetsNotePlugin } from '@univerjs/sheets-note'import { UniverSheetsNumfmtPlugin } from '@univerjs/sheets-numfmt'import { UniverSheetsSortPlugin } from '@univerjs/sheets-sort'import { UniverSheetsTablePlugin } from '@univerjs/sheets-table'import { UniverThreadCommentPlugin } from '@univerjs/thread-comment'import path from 'node:path'// Используемые здесь API Facade входят в packages/facade/src/apis/everything.ts// Реализация некоторых плагинов неполная, поэтому их API здесь не включены.import '@univerjs/engine-formula/facade'import '@univerjs/sheets/facade'import '@univerjs/sheets-formula/facade'import '@univerjs/sheets-numfmt/facade'import '@univerjs/sheets-conditional-formatting/facade'import '@univerjs/sheets-data-validation/facade'import '@univerjs/sheets-filter/facade'import '@univerjs/sheets-hyper-link/facade'import '@univerjs/sheets-note/facade'import '@univerjs/sheets-table/facade'import '@univerjs/sheets-drawing/facade'import '@univerjs/sheets-thread-comment/facade'import '@univerjs-pro/engine-shape/facade'import '@univerjs-pro/sheets-pivot/facade'import '@univerjs-pro/sheets-sparkline/facade'import '@univerjs-pro/sheets-chart/facade'import '@univerjs-pro/sheets-shape/facade'import '@univerjs-pro/sheets-outline/facade'import '@univerjs-pro/collaboration-client/facade'export interface ICreateUniverOnNodeOptions {  useComputingWorker?: boolean}export function createUniverOnNode(options: ICreateUniverOnNodeOptions = {}): Univer {  const { useComputingWorker = false } = options  const univer = new Univer({    locale: LocaleType.RU_RU,    locales: {},    override: [      [IAuthzIoService, null],      [IUndoRedoService, null],    ],  })  univer.registerPlugin(UniverLicensePlugin, {    license: 'your-license',  })  registerBasicPlugins(univer, useComputingWorker)  registerSharedPlugins(univer)  if (useComputingWorker) {    registerRPCPlugin(univer)  }  registerDocPlugins(univer)  registerSheetPlugins(univer, useComputingWorker)  registerCollaborationPlugins(univer)  return univer}function registerBasicPlugins(univer: Univer, useComputingWorker: boolean): void {  univer.registerPlugin(UniverProFormulaEnginePlugin, { notExecuteFormula: useComputingWorker })}function registerSharedPlugins(univer: Univer): void {  univer.registerPlugin(UniverRenderEnginePlugin)  univer.registerPlugin(UniverThreadCommentPlugin)  univer.registerPlugin(UniverDrawingPlugin, {    override: [[IImageIoService, null]],  })}function registerDocPlugins(univer: Univer): void {  univer.registerPlugin(UniverDocsPlugin)  univer.registerPlugin(UniverDocsDrawingPlugin)}function registerSheetPlugins(univer: Univer, useComputingWorker: boolean): void {  univer.registerPlugin(UniverSheetsPlugin, { notExecuteFormula: useComputingWorker })  univer.registerPlugin(UniverSheetsFormulaPlugin, { notExecuteFormula: useComputingWorker })  univer.registerPlugin(UniverSheetsNumfmtPlugin)  univer.registerPlugin(UniverSheetsConditionalFormattingPlugin)  univer.registerPlugin(UniverDataValidationPlugin)  univer.registerPlugin(UniverSheetsDataValidationPlugin)  univer.registerPlugin(UniverSheetsFilterPlugin)  univer.registerPlugin(UniverSheetsHyperLinkPlugin)  univer.registerPlugin(UniverSheetsNotePlugin)  univer.registerPlugin(UniverSheetsDrawingPlugin)  univer.registerPlugin(UniverSheetsSortPlugin)  univer.registerPlugin(UniverSheetsTablePlugin)  univer.registerPlugin(UniverSheetsChartPlugin)  univer.registerPlugin(UniverSheetsShapePlugin)  univer.registerPlugin(UniverSheetsPivotTablePlugin, {    notExecuteFormula: useComputingWorker ? true : undefined,  })  univer.registerPlugin(UniverSheetSparklinePlugin)  univer.registerPlugin(UniverSheetsOutlinePlugin)}function registerRPCPlugin(univer: Univer): void {  // Укажите путь к worker в соответствии с расположением результата сборки.  const childPath = path.join(__dirname, 'worker.js')  univer.registerPlugin(UniverRPCNodeMainPlugin, { workerSrc: childPath })}function registerCollaborationPlugins(univer: Univer): void {  const serverUrl = 'https://your-univer-server.example.com'  univer.registerPlugin(UniverNetworkPlugin)  univer.registerPlugin(UniverCollaborationPlugin)  univer.registerPlugin(UniverCollaborationClientPlugin, {    socketService: NodeCollaborationSocketService,    enableOfflineEditing: false,    enableSingleActiveInstanceLock: false,    snapshotServerUrl: `${serverUrl}/universer-api/snapshot`,    collabSubmitChangesetUrl: `${serverUrl}/universer-api/comb`,    collabWebSocketUrl: `${serverUrl}/universer-api/comb/connect`,    wsSessionTicketUrl: `${serverUrl}/universer-api/user/session-ticket`,    uploadFileServerUrl: `${serverUrl}/universer-api/stream/file/upload`,    signUrlServerUrl: `${serverUrl}/universer-api/file/{fileID}/sign-url`,    downloadEndpointUrl: `${serverUrl}`,    authzUrl: `${serverUrl}/universer-api/authz`,    sendChangesetTimeout: 200,    retryConnectingInterval: 1000,    customHeaders: {      // Пример: если сервер Univer использует аутентификацию через Cookie, задайте здесь заголовок 'cookie'.      cookie: 'your-cookie',      // ... другие заголовки    },  })  univer.registerPlugin(UniverCollaborationClientNodePlugin)}

Включение вычислительного worker

createUniverOnNode() по умолчанию использует useComputingWorker: false и вычисляет формулы в текущем процессе. В приведённом выше main.ts явно передаётся useComputingWorker: true, чтобы включить вычислительный worker:

  1. В основном процессе плагины UniverProFormulaEnginePlugin, UniverSheetsPlugin, UniverSheetsFormulaPlugin и UniverSheetsPivotTablePlugin получают notExecuteFormula: true и передают вычисления дочернему процессу через RPC.
  2. UniverRPCNodeMainPlugin запускает собранный файл worker.js, указанный в workerSrc.
  3. Дочерний процесс получает данные и выполняет вычисления через UniverRPCNodeWorkerPlugin и UniverRemoteSheetsFormulaPlugin.

UniverSheetsPivotTablePlugin различает три режима: true в основном процессе при использовании worker, false в worker и отсутствие параметра или значение undefined без worker, чтобы зарегистрировать и сервисы сводных таблиц, и вычислительную логику в текущем процессе. Поэтому основной процесс использует useComputingWorker ? true : undefined, а не передаёт useComputingWorker напрямую.

Node.js запускает этот worker через child_process.fork(). Используйте инструмент сборки проекта, чтобы собрать main.ts и worker.ts как отдельные точки входа, и задайте в workerSrc путь к собранному файлу worker. В примере предполагается, что worker.js расположен в одном каталоге с выходным файлом основной точки входа; путь, расширение и формат модулей должны соответствовать результату сборки. Для поиска файла используется __dirname из CommonJS; в проектах ESM нужен соответствующий способ разрешения пути.

Примечания по запуску

loadSheetAsync() загружает существующий документ с сервера, поэтому вызывать createWorkbook() не требуется. Сервис совместного редактирования определяет пользователя и проверяет права по данным аутентификации, например Cookie в customHeaders. Сервер возвращает memberID соединения.

В примере отключены офлайн-кеш браузера и блокировка экземпляра между вкладками, поэтому вызывать setCurrentUser() для разделения кеша по учётным записям не нужно. Если позже понадобятся функции, зависящие от локального текущего пользователя, например комментарии, его можно задать через UserManagerService. Это не заменяет серверную аутентификацию.

После выполнения пример сохраняет соединение совместного редактирования. Откройте тот же документ в браузере, чтобы увидеть изменения. flush() ожидает подтверждения сервера для изменений в очереди. Для одноразового скрипта вызовите univer.dispose() после завершения всех асинхронных операций и синхронизации.

Насколько полезно это руководство?

© 2026 DreamNum Co., Ltd.