@gantfdt/office-sdk
v1.3.0
Published
GantOffice 一体化 Office SDK:Univer 文档/表格/演示引擎 + OOXML 基线保留读写 + PDF 解析与查看器 + 可装配工作台 UI。react/react-dom 由宿主提供(peerDependencies);@univerjs/* 与 rxjs 为精确锁定的运行时依赖,其余第三方引擎已全部打进 dist,安装即用。
Readme
@gantfdt/office-sdk
GantOffice 一体化 Office SDK:文档(DOCX)/ 表格(XLSX)/ 演示(PPTX)/ PDF 的解析、编辑、预览与可装配工作台 UI,单一 npm 包、多入口按需引用。
- 运行时策略:
react/react-dom由宿主提供(peerDependencies,避免双 React 实例);@univerjs/*与rxjs为精确锁版运行时依赖(与构建时同版本,请勿单独升级);其余第三方引擎(pdfjs-dist、jszip、xlsx、pdf-lib 等)已全部打进dist/,安装即用。 - 样式策略:见下文「样式」一节,含 Mako 类 CSS 解析器宿主的运行时注入逃生路。
安装
npm i @gantfdt/office-sdk # 宿主需已有 react 18 / react-dom 18要求 Node ≥ 20(构建侧);浏览器产物 target es2020。
入口一览(exports 子路径)
| 子路径 | 内容 | 说明 |
| ----------------- | ------------------------------------------------------------ | ------------------------------------------------- |
| . | createOffice / 会话 / 能力矩阵 / 日志与错误模型 / 下载工具 | 无头一体化门面 |
| ./react | useOfficeSession、usePresentationEditor 等 React 绑定 | |
| ./univer | UniverWorkbench / createUniverEngine / disposeEngine | 文档/表格/演示引擎与工作台画布 |
| ./pdf | GPdf / convertPdf | PDF 解析与转换(无头) |
| ./pdf/ui | GPdfViewer | PDF canvas 查看器(样式内联在 JS,无需外部 css) |
| ./pdf/worker | pdf.worker.min.mjs 文件路径 | 与内联 pdfjs同版本配对,仅供自托管该文件时使用 |
| ./pdf/worker-text | pdfWorkerText 字符串导出 | 随包 worker 源码,SDK 内部用它造 blob worker;宿主一般不直接用 |
| ./workspace | WorkspaceShell / Ribbon / 配置 hooks / 功能开关 | 编辑器工作台 UI |
| ./workspace/pdf | PdfWorkspace / PdfRibbon / usePdfWorkspace | PDF 工作台 |
| ./styles.css | GantOffice 工作台自有样式 | 静态 css 子路径 |
| ./univer.css | Univer 引擎样式 | 用 ./univer、./workspace 时需宿主显式引入 |
| ./styles-text | stylesCss / univerCss / allCssText 字符串导出 | Mako 类宿主运行时注入用 |
快速上手
Excel/DOCX/PPTX 预览或编辑(保留宿主自己的弹窗/页面外壳,SDK 无头渲染):
import { UniverWorkbench } from '@gantfdt/office-sdk/univer';
import '@gantfdt/office-sdk/univer.css'; // 引擎样式(Mako 类宿主改用 ./styles-text 注入)
import '@gantfdt/office-sdk/styles.css'; // GantOffice 工作台样式
<UniverWorkbench file={arrayBuffer} fileName="book.xlsx" readOnly />PDF 预览(worker 随包交付,无需任何 worker 配置):
import { GPdf } from '@gantfdt/office-sdk/pdf';
import { GPdfViewer } from '@gantfdt/office-sdk/pdf/ui';
const pdf = await GPdf.open(uint8Array, { name: 'contract.pdf' });
const viewer = new GPdfViewer({ container, pdf }); // container: HTMLElement
await viewer.mount();页面有 CSP 且不放行 blob: worker 时,钉一个同源 worker 文件即可(详见下节):
import { setWorkerSrc } from '@gantfdt/office-sdk/pdf';
setWorkerSrc('/js/pdf.worker.min.mjs'); // 文件取自 ./pdf/worker,拷进宿主静态目录需要工作台外壳(Ribbon/面板/功能开关)时用 ./workspace 的 WorkspaceShell,preset="viewer" | "import" | "full",或 features={{ save: false }} 精细裁剪。
样式(务必读一次)
正常宿主(webpack / rspack / vite / esbuild):显式引两份 css——
import '@gantfdt/office-sdk/univer.css'(Univer 引擎样式)与import '@gantfdt/office-sdk/styles.css'(工作台)。入口 JS 刻意不带 css 副作用 import:一旦带上,下面第 2 类宿主就没有逃生路了。univer.css是并集:SDK 自有样式 + 构建期从@univerjs/preset-sheets-core、@univerjs/ui等依赖收编进来的引擎样式(引擎规则在前、SDK 规则在后,保持与宿主现有层叠一致),bundle 内不残留任何@univerjs/**.css导入。post-build 末尾有完整性守卫——任何一条源规则在最终univer.css里消失就直接exit 1,防止「收编变成覆盖写」把 SDK 自有规则(.dark:/.rtl:/ 任意值宽度那一类)整片抹掉。因此宿主侧那些「安装后剥掉 Tailwind 转义规则」的后处理脚本(例如某些仓库的fix-univer-csspostinstall)碰不到它,也不会再引发 Univer 布局坍塌。Mako 类 Rust CSS 解析器宿主(解析不了 Tailwind 任意值转义选择器
\[ \] \",会直接报错):不要静态引 css 子路径,改用运行时字符串注入——import { allCssText } from '@gantfdt/office-sdk/styles-text'; const el = document.createElement('style'); el.textContent = allCssText; document.head.appendChild(el);严禁对 SDK 样式做规则删减/“瘦身”:Tailwind 转义选择器是 Univer 主布局(grid 列定义等)的一部分,剥规则 = 表格布局坍塌(实测事故)。样式原文零删减随包分发。
PDF worker:随包交付(1.3.0 起零配置)
打开 PDF 时 worker 按四级优先级解析,命中即停:
- 本次调用的
workerSrc(GPdf.open/GPdfViewer/PdfWorkspace/printPdfBytes都有); setWorkerSrc(url)注册的进程级默认值;- 用随包 worker 文本(
./pdf/worker-text)现造的blob:module worker——默认就走这一级,宿主既不用拷文件也不用配 bundler 资源规则; - 空串:不去动
GlobalWorkerOptions.workerSrc。这一级只在 Node(批量转换、构建期预转换)有意义,浏览器里它是硬失败,见下。
随包文本那条 import('@gantfdt/office-sdk/pdf/worker-text') 刻意保持字面量动态 import 且不在构建期解析,宿主打包器(webpack / rspack / Mako / vite)把它切成异步 chunk:只有真的打开 PDF 才下载。实测 12 页样例页:走 blob 时该 chunk 传输 418KB(原文 1.42MB,服务端 gzip),走 setWorkerSrc 时传输 0B。post-build 有字节守卫——dist/worker-text.js 与 dist/pdf.worker.min.mjs 逐字节一致才允许出包,所以它永远与 dist 里内联的 pdfjs 主线程同版本配对(本批 pdfjs 4.10.38),不要混用宿主 node_modules 里其他版本的 worker。
worker 的这份状态放在 globalThis 的 Symbol.for('@gant/pdf-sdk.worker') 袋子里,不是模块变量:三个 pdf 入口各自内联一份 SDK,模块作用域互不相通,只有共享袋子才能保证「在 ./pdf 里钉的地址,./pdf/ui 与 ./workspace/pdf 同样认」。
CSP 实测矩阵
12 页样例 PDF,五种配置下渲染结果完全一致(12 页 / 12 canvas / 同一 26731 个着墨像素),差别只在 worker 落在哪:
| CSP 现场 | 宿主配置 | 实际用的 worker | 结果 |
| -------- | -------- | --------------- | ---- |
| 无 | 不配置 | 2× blob: module worker | 正常,open 78ms / mount 215ms |
| worker-src 'self'; script-src 'self' blob: | 不配置 | worker 被拦,pdf.js 改 import() 同源文本 | 正常,解析退回主线程(小文件无感,大文件变慢) |
| worker-src 'self'; script-src 'self' | 不配置 | worker 与兜底 import() 双双被拦 | 失败:Setting up fake worker failed: "Failed to fetch dynamically imported module: blob:…" |
| worker-src 'self'; script-src 'self' | setWorkerSrc('/js/pdf.worker.min.mjs') | 2× 同源 module worker | 正常,open 68ms / mount 201ms,且不下载 worker 文本 chunk |
| 同上 | 每次调用传 workerSrc | 2× 同源 module worker | 正常,open 72ms / mount 229ms |
要点:
worker-src 'self'不覆盖blob:,default-src同理。所以只要现场 CSP 卡了blob:,就必须走同源文件(setWorkerSrc或逐次workerSrc)——这条就是「A 方案」的老做法,随时可用,优先级还更高。new Worker(blob:)被 CSP 拦时不会同步抛错(违规是异步上报的),SDK 无法提前探测,只能交给 pdf.js 退主线程;因此「能不能兜住」取决于script-src放不放行blob:,上表第三行就是两者都不放行的死局。- 别混着配。
GlobalWorkerOptions.workerSrc是全局唯一值,一处传了同源地址、另一处不传,不传那处会把全局覆盖成blob:,严格 CSP 下连原本能用的文档也一起失败——用setWorkerSrc一次钉死就不会有这种漂移。 - 完全取消随包 worker(
setWorkerTextLoader(null)且不传workerSrc)在浏览器里是硬失败,pdf.js 报No "GlobalWorkerOptions.workerSrc" specified.;主线程兜底只对 Node 成立。SDK 此时会console.warn一次点名这条错误。 - 长时间运行、反复开合预览的页面可以调
disposeWorkerSrc()回收已造的 blob URL。
体积与按需引入
多入口各自完整自包含(tsup splitting: false,无共享 chunk)——这是「宿主只装一个包、引一个入口即可用」的代价与取舍:
| 入口 | 体积(min 后) |
| ------------------------------------------- | -------------- |
| index.js / univer.js | ~12 MB |
| workspace.js / workspace-pdf.js | ~6–7 MB |
| pdf.js | ~0.9 MB |
| react.js / pdf-ui.js / styles-text.js | <0.5 MB |
| worker-text.js(PDF 异步 chunk) | ~1.4 MB(不打开 PDF 就不下载) |
| 全包 tarball | ~9.8 MB |
- 同一宿主建议只引一个主入口:文档/表格/演示场景选
./workspace(或无头./ 画布./univer),PDF 场景选./pdf+./pdf/ui。多入口同时静态引入不会报错(@univerjs/*为 external 共享同一份,无双实例风险),但打进各自 dist 的 pdfjs / xlsx / jszip 等引擎与内部装配代码会被重复下载与执行,浪费带宽与内存,且各入口的 Univer preset 装配代码会重复注册,控制台出现大量Identifier … already exists提示(Univer 幂等降级,不影响功能)。 - 需要多个能力域时优先动态
import()(路由级按需加载),或按官方 dist sizes 打印评估取舍。 - 精确数字以
npm run build -w @gantfdt/office-sdk结束打印的 dist sizes 为准。
版本与升级
- 本包与内部
@gant-office/*系列同仓同批发布(统一版本号)。 @univerjs/*精确锁版是刻意的:bundle 与类型图按该版本构建,semver 范围会引入不可复现的类型/运行时漂移。- 升级 = 升级本包一个动作;无需单独动 react(peer)或 pdfjs(已内联)。
- 宿主
tsconfig请保持skipLibCheck: true(社区默认)。Univer 1.0.4 自身 d.ts 在skipLibCheck: false下会报其内部类型错(缺@types/async-lock等),与本包无关;本包 d.ts 自身在两种模式下均无悬空引用。
来源
源码仓库:https://codeup.aliyun.com/gant/GantLabs/gant-office(sdk/office-sdk,其余能力实现见同仓库 sdk/* 内部包,构建时全部内联进本包 dist)。
