Worker 与流式
Worker 多线程(≥ 20,000 行)
浏览器中数据量 ≥ 20,000 行时,auto 会选择 Worker 路径:主线程只做一次结构化克隆(10 万行约 94ms),其余工作都在 Worker 内执行。其中 20,000–49,999 行在 Worker 内加载 WASM 并构建(Workbook 路径);≥ 50,000 行切换为不依赖 WASM 的 Fast stream(见下)。
worker 资产(export.worker.js,自包含单文件 ESM)默认自动定位,无需任何配置;仅自托管副本场景需要 configureWasm({ workerUrl }) 覆盖。
Worker 路径行为:
- Worker 失败会优雅降级——Worker 路由失败时(例如
workerUrl覆盖配置指向 404),导出会先回退到主线程重试(modern-xlsx 保留样式;≥ 50,000 行的 Fast stream 本身不依赖 WASM)。在 Worker + Workbook 路由上,重试再失败才继续降级到无样式的流式兜底(mode: "stream"且result.error非空,success仍为true);而在 Worker + stream 路由上(浏览器 ≥ 50,000 行),主线程重试本身就是对同一份输入跑同一个 fast stream,因此再失败即终局——导出以success: false结束,不再尝试第三次构建。无论哪条路由,调用方的 Promise 都正常 resolve(不会 reject),每一级降级都会在 console 打印[excel-exporter]前缀警告; - Worker 实例复用,请求按
requestId并发分发,多次导出互不串扰; - 函数形式的 format 会被剥离(结构化克隆无法传递函数)——worker 路径请使用 FormatSpec;
onProgress/onPhase会从 Worker 转发回主线程:build在 Worker 内构建完成时上报;init仅在 Worker + Workbook 路由、且 Worker 确实执行了 WASM 初始化时才上报——Worker + stream 路由不使用 WASM,完全不上报init。
流式写入(≥ 50,000 行)
fast-xlsx.ts 使用 fflate 生成 minimal OOXML,10 万行约 0.8s(对比 Workbook 路径 17.5s)。auto 在 ≥ 5 万行时自动选择它。
Stream 路径的已知限制:
| 特性 | Stream 路径 |
|---|---|
多行表头(children) | 支持(表头自动合并) |
单元格合并(merges) | 支持(数据区) |
单元格样式(style) | 不支持 |
表头样式(headerStyle) | 不支持 |
列宽(width) | 不支持 |
| 冻结行 / 自动筛选 | 不支持 |
| 自定义数字格式 | 不支持(decimals 烧入存储值) |
| 日期格式 | 按 pattern 输出可读字符串 |
| 进度回调 | 每 1000 行上报一次 |
被跳过的特性(单元格样式、表头样式、列宽、冻结等)会在 console 打印 [excel-exporter] stream mode: features not supported (...) 警告。
什么时候该用哪个
| 需求 | 推荐路径 |
|---|---|
| < 5 万行且需要完整样式 | auto(main / worker + Workbook) |
| ≥ 5 万行,样式可接受降级 | auto(worker + Fast stream) |
| Node 服务端大批量 | auto(main → ≥ 5 万行 stream) |
| 对主线程零阻塞有强要求 | 显式 mode: "worker" |
直接使用底层 API
库同时导出 WorkbookBuilder 与 exportAsStream,可在复杂场景下精细控制:
ts
import { WorkbookBuilder, exportAsStream } from "@marcusok/excel-exporter";
// 批量化构建
const builder = await WorkbookBuilder.create();
builder.addSheet(sheetA).addSheet(sheetB);
const bytes = await builder.toBuffer();
// 流式导出
const { bytes, rowCount } = await exportAsStream(sheets, onProgress);WARNING
这两个底层入口只识别已展开的 __index__ 列——与 exportExcel 不同, 它们不会自行展开 SheetConfig.indexColumn,直接传入时该配置会被静默忽略。 若在这些路径上需要序号列,须先调用 applyIndexColumn(sheet)(即 exportExcel 内部使用的同一工具)。详见 API 参考中的 applyIndexColumn。