docx-preview vs docxtemplater vs jszip vs mammoth vs officegen
JavaScript DOCX 处理方案全解析:渲染、生成与转换
docx-previewdocxtemplaterjszipmammothofficegen类似的npm包:

JavaScript DOCX 处理方案全解析:渲染、生成与转换

docx-previewdocxtemplaterjszipmammothofficegen 构成了 JavaScript 生态中处理 Microsoft Word (DOCX) 文件的核心工具链。由于 DOCX 本质上是基于 XML 的 ZIP 压缩包,这些库分别解决了文件生命周期的不同阶段:docx-preview 专注于在浏览器端直接渲染 DOCX 预览;docxtemplater 用于基于模板动态生成文档(如发票、报告);mammoth 侧重于将 DOCX 转换为 HTML 或纯文本以提取内容;officegen 是早期的 Node.js 服务端生成方案;而 jszip 则是底层处理 ZIP 结构的基础工具,常被其他库作为依赖使用。理解它们的边界对于构建文档密集型应用至关重要。

npm下载趋势

3 年

GitHub Stars 排名

统计详情

npm包名称
下载量
Stars
大小
Issues
发布时间
License
docx-preview01,964964 kB557 个月前Apache-2.0
docxtemplater03,5681.31 MB67 天前MIT
jszip010,347762 kB410-(MIT OR GPL-3.0-or-later)
mammoth06,1972.17 MB632 个月前BSD-2-Clause
officegen02,7132.73 MB200-MIT

JavaScript DOCX 处理方案全解析:渲染、生成与转换

在 Web 开发中处理 Microsoft Word 文档一直是个棘手的问题。DOCX 格式本质上是多个 XML 文件打包成的 ZIP 压缩包,这意味着处理它既需要理解 Office Open XML 标准,又要处理二进制流。docx-previewdocxtemplaterjszipmammothofficegen 是目前生态中最常见的五个解决方案,但它们各自解决的问题域完全不同。本文将从架构师视角,深入对比它们在渲染、生成、转换和底层操作上的技术差异。

🖼️ 文档渲染:浏览器直接预览 vs 后端转换

docx-preview 允许你直接在浏览器中渲染 DOCX 文件,无需后端介入转换为 PDF。

  • 它解析 DOCX 内部的 XML 并将其映射为 HTML/CSS。
  • 适合文档管理系统(DMS)的预览场景。
  • 注意:复杂排版(如分栏、特定页眉页脚)可能无法完美还原。
// docx-preview: 浏览器端渲染
import { renderAsync } from 'docx-preview';

const data = await fetch('/document.docx').then(res => res.arrayBuffer());
const container = document.getElementById('container');

// 将 DOCX 二进制数据渲染到 DOM 节点
await renderAsync(data, container, null, {
  className: 'docx-wrapper',
  inWrapper: true
});

officegen 主要用于生成,不具备渲染能力。如果需要预览,通常配合后端转换为 PDF。

  • 老旧方案,缺乏现代浏览器渲染支持。
  • 如果必须预览,通常需结合 libreoffice 或云服务。
// officegen: 无直接渲染 API,仅生成
// 此处展示其局限性,无法像 docx-preview 那样直接显示
const docx = officegen('docx');
// 只能保存文件,不能预览

mammoth 也不用于视觉预览,而是用于内容提取。

  • 它将 DOCX 转换为 HTML,丢失了精确的页面布局。
  • 适合将文档内容展示在网页文章中,而非作为“文件预览”。
// mammoth: 转换为 HTML 用于内容展示
import mammoth from 'mammoth';

const result = await mammoth.convertToHtml({ arrayBuffer: data });
document.getElementById('content').innerHTML = result.value; 
// 警告:messages 数组可能包含样式丢失的警告

📝 文档生成:模板填充 vs 编程式构建

docxtemplater 采用“模板填充”模式。

  • 你准备好一个标准的 DOCX 文件,在里面写 {username} 这样的占位符。
  • 代码负责将数据注入这些占位符。
  • 适合发票、证书、合同等格式固定的文档。
// docxtemplater: 模板数据绑定
import Docxtemplater from 'docxtemplater';
import PizZip from 'pizzip'; // 常用 ZIP 库

const zip = new PizZip(content);
const doc = new Docxtemplater(zip, {
  paragraphDelimiter: { delimiter: '\n' }
});

doc.render({ username: '张三', date: '2023-10-01' });
const outBuffer = doc.getZip().generate({ type: 'blob' });

officegen 采用“编程式构建”模式。

  • 你通过代码 API 一行行定义段落、样式。
  • 适合动态结构文档,但代码量大,维护困难。
  • 目前社区活跃度低,新项目慎用。
// officegen: 编程式定义文档结构
const docx = officegen('docx');
docx.on('finalize', function (written) { console.log('Done'); });

docx.createList({
  type: 'number',
  data: ['项目一', '项目二']
});
// 通过流式写入,适合服务端大批量生成

jszip 是底层工具,不直接生成 DOCX 内容。

  • docxtemplater 等库依赖它来打包 XML。
  • 如果你需要修改 DOCX 内部的特定 XML 文件(如自定义属性),可能需要直接操作 JSZip。
// jszip: 底层 ZIP 操作
import JSZip from 'jszip';

const zip = new JSZip();
zip.file('hello.txt', 'Hello World\n');
// 手动构建 DOCX 需要遵循 Open XML 规范,极其复杂
const content = await zip.generateAsync({ type: 'blob' });

📥 内容提取:保留样式 vs 纯文本

mammoth 的设计哲学是“内容大于样式”。

  • 它会将 Word 的样式转换为语义化的 HTML 标签。
  • 如果 Word 里用了奇怪的样式,它会忽略并警告。
  • 适合将旧文档迁移到 CMS 系统。
// mammoth: 提取内容并转换样式
const options = {
  styleMap: [
    "p[style-name='Heading 1'] => h1:fresh",
    "r[style-name='Strong'] => strong"
  ]
};
const result = await mammoth.convertToHtml({ path: "input.docx" }, options);

docx-preview 试图保留视觉样式。

  • 它努力还原 Word 的视觉效果。
  • 不适合提取数据,适合给人看。
// docx-preview: 视觉还原
// 见上文渲染示例,它关注的是视觉一致性

⚙️ 底层依赖与架构风险

jszip 是许多 DOCX 库的基石。

  • DOCX = ZIP。任何处理 DOCX 的库几乎都在用 JSZip 或类似逻辑。
  • 如果你发现现有库无法满足需求(例如需要修改 DOCX 内部某个特定 XML 节点),你可以直接用 JSZip 解压 DOCX,修改 XML,再压缩回去。
// jszip: 修改 DOCX 内部 XML
const zip = await JSZip.loadAsync(docxBlob);
const coreXml = await zip.file('docProps/core.xml').async('text');
// 修改 XML 字符串...
zip.file('docProps/core.xml', modifiedXml);
const newDocx = await zip.generateAsync({ type: 'blob' });

officegen 的架构风险。

  • 它不依赖 JSZip,而是自己实现 ZIP 生成逻辑。
  • 这导致它在处理复杂 DOCX 结构时容易出错,且维护成本高。
  • 社区普遍建议迁移到基于 JSZip 的现代方案。

🌱 共同点:生态重叠与互补

尽管功能不同,这些库在底层技术栈上有显著重叠。

1. 🗂️ 基于 ZIP 的文件结构

  • 所有库都理解 DOCX 是 ZIP 容器。
  • jszipdocxtemplaterdocx-preview 的常见依赖。
// 所有库处理的本质都是二进制流
const buffer = await file.arrayBuffer(); // 通用输入格式

2. 🌐 前端与服务端界限模糊

  • docx-previewmammoth 主要运行在浏览器。
  • docxtemplaterofficegen 主要运行在 Node.js,但也可通过 Bundle 在浏览器运行(需注意性能)。
// docxtemplater 可在两端运行
// 浏览器:适合即时生成预览
// Node.js:适合批量生成存储

3. ⚡ 异步处理模型

  • 由于涉及文件 IO 和解析,所有库都基于 Promise/Async。
  • 大文件处理需要注意内存占用,避免阻塞主线程。
// 建议使用 Worker 处理大文件解析
const worker = new Worker('doc-parser.js');

📊 总结对比表

特性docx-previewdocxtemplatermammothofficegenjszip
核心用途浏览器预览模板生成内容提取/转换服务端生成 (旧)底层 ZIP 操作
渲染 fidelity高 (视觉还原)N/A (生成文件)低 (转为 HTML)N/A (生成文件)N/A
数据绑定❌ 不支持✅ 强大 (循环/条件)❌ 不支持❌ 手动构建❌ 手动
运行环境浏览器Node/BrowserNode/BrowserNode.js通用
维护状态✅ 活跃✅ 活跃✅ 活跃⚠️ 低维护✅ 活跃
依赖关系依赖 JSZip依赖 JSZip独立独立

💡 架构师建议

docx-preview 是前端预览的最佳选择 🖼️。如果你需要在 SaaS 应用中让用户不下载文件就能查看 DOCX,它是首选。但请记住,它不是 PDF 阅读器,复杂排版可能会有落差。

docxtemplater 是文档生成的标准答案 📝。对于报表、合同生成,不要尝试用代码去画表格,而是让业务人员准备好 Word 模板,然后用它填充数据。这能极大降低维护成本。

mammoth 是内容迁移的工具 📥。当你需要把用户上传的 Word 文档存入数据库供网页展示时,用它转为 HTML。不要试图保留原始样式,那是不可维护的。

officegen 是遗留系统的负担 ⚠️。除非你被锁定在旧项目中,否则新开发请避免使用。现代方案(如 docxdocxtemplater)更安全、更灵活。

jszip 是最后的杀手锏 🛠️。当所有上层库都无法满足你的特殊需求(如修改文档元数据、修复损坏的 DOCX)时,直接使用 JSZip 操作底层 XML 是可行的,但需要深厚的 Open XML 知识。

最终思考:DOCX 处理没有银弹。大多数成熟架构会组合使用:用 mammoth 导入内容,用 docxtemplater 生成导出,用 docx-preview 在线查看。理解每个工具的边界,比寻找一个全能的库更重要。

如何选择: docx-preview vs docxtemplater vs jszip vs mammoth vs officegen

  • docx-preview:

    选择 docx-preview 如果你需要在浏览器端直接展示 DOCX 文件内容,且不想依赖后端转换为 PDF 或图片。它适合用于文档管理系统的预览功能,但要注意它主要还原内容结构,复杂排版可能与 Word 原生显示有差异。

  • docxtemplater:

    选择 docxtemplater 如果你需要基于现有 DOCX 模板填充数据生成新文档。它适合生成合同、发票等结构化文档,支持循环和条件逻辑,是前端或 Node.js 服务端生成文档的首选方案。

  • jszip:

    选择 jszip 如果你需要底层操作 ZIP 文件结构,或者你需要为 docxtemplater 等库提供 ZIP 实例。通常不建议直接用它操作 DOCX 除非你需要修改底层的 XML 关系文件,否则应优先使用上层封装库。

  • mammoth:

    选择 mammoth 如果你需要将 DOCX 内容导入到 Web 编辑器或数据库中,而不需要保留精确的视觉样式。它专注于内容提取(文本、标题、列表),忽略复杂布局,适合“内容优先”的场景。

  • officegen:

    选择 officegen 仅当你维护遗留的 Node.js 项目且无法迁移。该库目前已处于低维护状态,新项目建议使用 docxdocxtemplater 替代,以避免潜在的安全隐患和兼容性问题。

docx-preview的README

npm version Support Ukraine

docxjs

Docx rendering library

Demo - https://volodymyrbaydalka.github.io/docxjs/

Goal

Goal of this project is to render/convert DOCX document into HTML document with keeping HTML semantic as much as possible. That means library is limited by HTML capabilities (for example Google Docs renders *.docx document on canvas as an image).

Installation

npm install docx-preview

Usage

<!--lib uses jszip-->
<script src="https://unpkg.com/jszip/dist/jszip.min.js"></script>
<script src="docx-preview.min.js"></script>
<script>
    var docData = <document Blob>;

    docx.renderAsync(docData, document.getElementById("container"))
        .then(x => console.log("docx: finished"));
</script>
<body>
    ...
    <div id="container"></div>
    ...
</body>

API

// renders document into specified element
renderAsync(
    document: Blob | ArrayBuffer | Uint8Array, // could be any type that supported by JSZip.loadAsync
    bodyContainer: HTMLElement, //element to render document content,
    styleContainer: HTMLElement, //element to render document styles, numbeings, fonts. If null, bodyContainer will be used.
    options: {
        className: string = "docx", //class name/prefix for default and document style classes
        inWrapper: boolean = true, //enables rendering of wrapper around document content
        hideWrapperOnPrint: boolean = false, //disable wrapper styles on print
        ignoreWidth: boolean = false, //disables rendering width of page
        ignoreHeight: boolean = false, //disables rendering height of page
        ignoreFonts: boolean = false, //disables fonts rendering
        breakPages: boolean = true, //enables page breaking on page breaks
        ignoreLastRenderedPageBreak: boolean = true, //disables page breaking on lastRenderedPageBreak elements
        experimental: boolean = false, //enables experimental features (tab stops calculation)
        trimXmlDeclaration: boolean = true, //if true, xml declaration will be removed from xml documents before parsing
        useBase64URL: boolean = false, //if true, images, fonts, etc. will be converted to base 64 URL, otherwise URL.createObjectURL is used
        renderChanges: false, //enables experimental rendering of document changes (inserions/deletions)
        renderHeaders: true, //enables headers rendering
        renderFooters: true, //enables footers rendering
        renderFootnotes: true, //enables footnotes rendering
        renderEndnotes: true, //enables endnotes rendering
        renderComments: false, //enables experimental comments rendering
        renderAltChunks: true, //enables altChunks (html parts) rendering
        debug: boolean = false, //enables additional logging
    }): Promise<WordDocument>

/// ==== experimental / internal API ===
// this API could be used to modify document before rendering
// renderAsync = parseAsync + renderDocument

// parse document and return internal document object
parseAsync(
    document: Blob | ArrayBuffer | Uint8Array,
    options: Options
): Promise<WordDocument>

// render internal document object into specified container
renderDocument(
    wordDocument: WordDocument,
    bodyContainer: HTMLElement,
    styleContainer: HTMLElement,
    options: Options
): Promise<void>

Thumbnails, TOC and etc.

Thumbnails is added only for example and it's not part of library. Library renders DOCX into HTML, so it can't be efficiently used for thumbnails.

Table of contents is built using the TOC fields and there is no efficient way to get table of contents at this point, since fields is not supported yet (http://officeopenxml.com/WPtableOfContents.php)

Breaks

Currently library does break pages:

  • if user/manual page break <w:br w:type="page"/> is inserted - when user insert page break
  • if application page break <w:lastRenderedPageBreak/> is inserted - could be inserted by editor application like MS word (ignoreLastRenderedPageBreak should be set to false)
  • if page settings for paragraph is changed - ex: user change settings from portrait to landscape page

Realtime page breaking is not implemented because it's requires re-calculation of sizes on each insertion and that could affect performance a lot.

If page breaking is crutual for you, I would recommend:

  • try to insert manual break point as much as you could
  • try use editors like MS Word, that inserts <w:lastRenderedPageBreak/> break points

NOTE: by default ignoreLastRenderedPageBreak is set to true. You may need to set it to false, to make library break by <w:lastRenderedPageBreak/> break points

Status and stability

So far I can't come up with final approach of parsing documents and final structure of API. Only renderAsync function is stable and definition shouldn't be changed in future. Inner implementation of parsing and rendering may be changed at any point of time.

Contributing

Please do not include contents of ./dist folder in your PR's. Otherwise I most likely will reject it due to stability and security concerns.