Show HN: DOM-docx – 将 HTML 转换为原生、可编辑的 Word 文档 (MIT)
Show HN: DOM-docx – HTML to native, editable Word docs (MIT)

原始链接: https://github.com/floodtide/dom-docx

**dom-docx** 是一款高保真的库,旨在将语义化 HTML 片段转换为原生的、可编辑的 Microsoft Word (OOXML) 文档。与那些使用截图或布局技巧的工具不同,它通过段落、列表、表格和图像来生成结构化内容。 ### 主要特性 * **灵活的模式:** * **内联模式(默认):** 一条轻量级的纯 JavaScript 路径(无需浏览器),用于解析内联样式。 * **计算模式:** 使用 `getComputedStyle` 处理基于类名或样式表的 CSS。在 Node.js 环境中,此模式可选依赖 Playwright/Chromium;在浏览器中,则利用实时 DOM。 * **高级渲染:** 通过可选的 `rasterizeInPlace` 功能支持复杂的图表和 ``/SVG 元素,可在导出前将其转换为高清晰度的 PNG。 * **高度可定制:** 提供对页面布局、元数据、页眉/页脚、封面及目录生成的广泛控制。 * **注重质量:** 该引擎通过自动化的视觉回归循环进行优化,根据人工验证的指标对输出内容的结构保真度和布局进行评分。 ### 使用方法 * **Node.js:** 通过 API 或 CLI(`npx dom-docx input.html`)轻松转换 HTML。 * **浏览器:** 使用专用的浏览器包,无需服务器端依赖,直接从用户标签页导出文档。 更多信息请访问 [dom-docx.com](https://dom-docx.com)。

抱歉。
相关文章

原文

Convert semantic HTML fragments to native, editable Word documents (OOXML): paragraphs, runs, lists, tables, images. Not screenshots or layout hacks.

Live demo: dom-docx.com. Try the converter, browse showcases, read the learn guide.

Built with a visual regression loop: render HTML in Chromium, convert to docx, rasterize via LibreOffice, score layout + structural fidelity against a human-validated metric, iterate. Latest scores: TEST-SCORES.md · methodology: SCORING.md.

Requires Node.js ≥ 20. No browser or Playwright is needed for the default inline path.

When is Playwright needed?

Entry styleSource: "inline" styleSource: "computed" rasterizeInPlace
Node (dom-docx) Pure JS, no browser Playwright + Chromium Playwright + Chromium (same headless page)
Browser (dom-docx/browser) Pure JS, no live DOM Live page: native getComputedStyle Live page: canvas/SVG → PNG <img> in the tab

On Node, playwright is an optional peer dependency. npm install dom-docx pulls only docx, cheerio and fflate, nothing heavy. It is loaded lazily when you pass styleSource: "computed" or rasterizeInPlace. To use those paths, install Playwright and Chromium yourself, once:

npm install playwright
npx playwright install chromium

Playwright is also used by the dev test harness (not required to use the library). Contributors: npm run setup after clone.

LibreOffice is not needed to convert. It is only used for the visual test harness.

Convert a file without writing any code:

npx dom-docx input.html -o output.docx
npx dom-docx input.html                      # writes input.docx next to it
cat fragment.html | npx dom-docx - -o -      # stdin → binary stdout (pipelines)
npx dom-docx input.html -s computed          # stylesheet/class HTML (needs playwright installed)
npm install -g dom-docx                      # optional: install globally, then run "dom-docx" without npx

Input is a body HTML fragment, same as the API. --help for all options.

import { convertHtmlToDocx } from "dom-docx/browser";

const html = `
<h1 style="color:#1a1a2e">Quarterly Report</h1>
<p>Revenue grew <strong>12%</strong> year over year.</p>
<ul>
  <li>North America</li>
  <li>EMEA</li>
</ul>
`;

const blob = await convertHtmlToDocx(html);
// e.g. trigger a download in the browser
const a = document.createElement("a");
a.href = URL.createObjectURL(blob);
a.download = "output.docx";
a.click();

No Playwright, no Node. This runs entirely in the user's tab. See Browser bundle below.

import { writeFile } from "node:fs/promises";
import { convertHtmlToDocx } from "dom-docx";

const html = `
<h1 style="color:#1a1a2e">Quarterly Report</h1>
<p>Revenue grew <strong>12%</strong> year over year.</p>
<ul>
  <li>North America</li>
  <li>EMEA</li>
</ul>
`;

const docx = await convertHtmlToDocx(html);
await writeFile("output.docx", docx);

Pass a body fragment only (no <!DOCTYPE> / <html> / <body> required). Defaults: US Letter, 1″ margins, Arial 10.5pt body text.

Supported (default styleSource: "inline"):

  • Headings, paragraphs, lists (<ul>/<ol> including list-style-type), tables, links, inline formatting
  • Block backgrounds, blockquotes, <hr>, simple flex rows (≤4 items)
  • data: images; remote images via your imageResolver
  • Page size/orientation/margins, default font, metadata, header/footer HTML, page numbers, table of contents, lang/direction
  • Low-complexity inline SVG (bars + text)
  • CSS bar divs in table cells (background + height/width → native shaded bands)

Advanced (optional styleSource: "computed"):

  • Resolves <style> blocks and class/#id selectors via getComputedStyle
  • Node: requires playwright (optional peer dependency, installed separately) + Chromium. The library launches headless Chromium to render the fragment
  • Browser bundle: uses the live DOM in the user's tab. No Playwright, no extra install
  • SPA fragment export: pass root (browser) or rootSelector (Node + live page) when converting element.innerHTML so computed-style paths match the fragment tree
  • Inline is the supported default for npm installs; computed is for stylesheets/classes or when you already have a rendered page

Charts & complex SVG (optional rasterizeInPlace):

  • Rasterizes <canvas> and complex <svg> (e.g. Highcharts) to PNG <img> before conversion
  • Recommended for charts: rasterizeInPlace: { scale: 2 } — supersamples at 2× density for sharper images in Word (default scale: 1; max 4)
  • Browser: requires root. Clones off-screen by default so the live page is not mutated
  • Node: uses the same Playwright/Chromium context as computed styles; ephemeral spawn pages mutate in place by default
  • Simple inline SVG (rect + text bar charts) still converts natively without rasterization

Not supported in v0.1.x:

  • External stylesheets on the inline path (use computed or inline all styles)
  • Web fonts, CSS grid/float layout, forms, <pre> polish, <dl>, table rowspan
  • Header/footer first/even page variants; guaranteed multi-page layout fidelity
  • Complex SVG (paths, gradients, <use>) unless rasterizeInPlace is used on a live rendered page

See AGENTS.md for HTML authoring tiers and API.md for full options.

convertHtmlToDocx(html, options?)

Returns Promise<Buffer> (Node) with a valid .docx file.

import { convertHtmlToDocx, type ConvertOptions } from "dom-docx";

const docx = await convertHtmlToDocx(html, {
  pageSize: "a4",
  orientation: "landscape",
  margins: { top: 0.75, bottom: 0.75 }, // inches; omitted sides default to 1
  defaultFont: { family: "Georgia", sizePt: 11 },
  metadata: { title: "Q3 Report", creator: "Finance" },
  headerHtml: "<p style='font-size:12px;color:#666'>Confidential</p>",
  footerHtml: "<p style='font-size:12px'>© 2026 ACME</p>",
  pageNumber: true,
  lang: "en-US",
  direction: "ltr",
  coverHtml: "<h1 style='text-align:center'>Quarterly Review</h1>", // page 1, before the TOC
  tocHtml: "<ol><li><a href='#intro'>Introduction</a></li></ol>", // your TOC; links jump to id="intro"
});
Option Default Description
styleSource "inline" "inline" parses style="" only (pure JS, fast). "computed" uses getComputedStyle. On Node this requires Playwright + Chromium; in the browser bundle it reads from the live DOM (no Playwright).
browser / page Node only. Reuse an open Playwright browser or page (computed styles and/or rasterizeInPlace). Not used by dom-docx/browser.
rootSelector Node only. CSS selector for the export root when converting element.innerHTML from a live Playwright page. Must match the node whose HTML you pass.
rasterizeInPlace Rasterize <canvas> / chart <svg> to PNG <img> before conversion. Recommended: { scale: 2 } for chart exports. Node: Playwright page; browser: requires root. See API.md.
imageResolver Hook to fetch non-data: <img src> (library never fetches on its own).
pageSize "letter" "letter", "a4" or { width, height } in inches.
orientation "portrait" "landscape" swaps dimensions.
margins 1 inch each Per-side overrides in inches.
defaultFont Arial 10.5pt { family, sizePt } for body text without explicit CSS.
metadata title, subject, creator, keywords[], descriptiondocProps/core.xml.
headerHtml / footerHtml HTML fragments for page header/footer.
pageNumber false Appends centered Page N field to footer.
lang / direction Spell-check locale; "rtl" for right-to-left.
coverHtml HTML fragment rendered as a cover page — the first content, before the TOC, followed by an automatic page break. Inline styles + data: images (e.g. a logo). Header/footer/page number are suppressed on the cover page.
tocHtml HTML fragment rendered as a table-of-contents "slot" — placed after the cover, before the body. You control the markup/styling (numbered, boxed, columns…); in-page links (<a href="#id">) jump to the matching id in the body. Add a trailing <div style="break-after:page"></div> to put it on its own page.

Only data: URLs embed automatically. For http(s): or file paths, supply a resolver. You control fetch policy and security:

const docx = await convertHtmlToDocx(html, {
  imageResolver: async (src) => {
    const res = await fetch(src); // your allowlist / SSRF checks
    if (!res.ok) return null;
    return { data: new Uint8Array(await res.arrayBuffer()), type: "png" };
  },
});

For client-side conversion (returns a Blob). No Playwright. The bundle runs entirely in the user's browser.

import { convertHtmlToDocx } from "dom-docx/browser";

// Inline styles: pass HTML string directly
const blob = await convertHtmlToDocx(htmlFragment, { styleSource: "inline" });

// Computed styles: render the fragment in the live DOM first, then convert
document.body.innerHTML = htmlFragment;
const blob2 = await convertHtmlToDocx(htmlFragment, { styleSource: "computed" });

// SPA export: pass the live root so computed paths match element.innerHTML
const root = document.querySelector(".page-body")!;
const blob3 = await convertHtmlToDocx(root.innerHTML, {
  styleSource: "computed",
  root,
});

// Charts (Highcharts, canvas): rasterize to PNG <img> on a clone, then convert
const blob4 = await convertHtmlToDocx(root.innerHTML, {
  styleSource: "computed",
  root,
  rasterizeInPlace: { scale: 2 }, // recommended for charts; default scale is 1
});

Browser-only options: root, document, rasterizeInPlace. Build: npm run build:browserdist/browser/dom-docx.browser.js.

For advanced usage (buildDocxBuffer, custom StyleResolver, engine architecture), see API.md.


Optimized for Word-friendly semantic HTML: headings, paragraphs, lists, data tables, inline formatting, shaded callouts, simple flex rows.

Excellent Good Avoid (or use rasterizeInPlace)
Headings, lists, simple tables Shaded banners, flex (≤4 items) Live chart libraries (Highcharts, canvas) unless rasterized
Inline strong / em / links Table row/cell backgrounds Complex SVG paths/gradients unless rasterized
Explicit page breaks (break-before: page, break-after: page) CSS grid, floats, absolute layout
Short span highlights Blockquotes, <hr> External stylesheets (inline path)
Simple SVG bars (rect + text) data: images Forms, web fonts, CSS grid/float layout

Full authoring guide for agents: AGENTS.md.


dom-docx maps a practical HTML subset to native OOXML through a three-stage pipeline:

  1. Style resolution: inline style="" (default) or browser computed styles
  2. Visitor: Cheerio walk → docx paragraphs, tables, numbering, hyperlinks
  3. Pack + patch: generate OOXML, patch list numbering and shaded-block alignment for LibreOffice/Word PDF export

Quality is driven by an autonomous loop rather than one-off visual checks:

  • 30+ regression cases (defined in tools/generator.ts, run via npm run score:suite): human-validated layout fidelity (ink-projection structure comparison, 85.6% concordance with blind human quality ratings), plus guards for bad contrast, missing list markers, wrong text and imbalanced shaded blocks; raw pixel match is recorded as a regression tripwire
  • Engine score: 50% visual (layout-based) + 35% editability (native structure, not 1×1 layout tables) + 15% compile speed
  • OSS benchmark: same harness scores html-to-docx and @turbodocx/html-to-docx for ongoing comparison (BENCHMARK.md)

The default inline path is pure JavaScript (docx + cheerio + fflate) with no browser required. Playwright is Node-only: for styleSource: "computed" on the server and for the dev harness. The dom-docx/browser bundle never uses Playwright; computed styles come from the live page's getComputedStyle.

Full scoring formulas, subscores, calibration and the agent iteration workflow: SCORING.md.


For contributors and harness runs (not required to use the library):

git clone … && npm install && npm run setup
npm run build              # dist/ for npm pack
npm run typecheck
npm run score:suite          # full visual + XML regression suite (cases: tools/generator.ts)
npm run score:suite:priority # fast subset of the same cases
# Run one case: SUITE_ONLY=rasterize-in-place-chart npm run score:suite
npm run score:benchmark     # vs html-to-docx + TurboDocx
npm run guard:config        # ConvertOptions OOXML checks

Prerequisites for the harness: LibreOffice (soffice) for PDF rasterization, Playwright Chromium (npm run setup).


Doc Contents
API.md Full API reference, engine architecture, usage patterns
AGENTS.md HTML authoring guide for AI agents
SCORING.md Validation methodology and engine score
TEST-SCORES.md Latest suite metrics and per-case scores
BENCHMARK.md Comparison vs OSS html-to-docx libraries
examples/ Sample HTML, DOCX output and side-by-side previews
SHOWCASE.md How to run and extend showcase examples
CONTRIBUTING.md Library vs harness layout, dev setup

MIT

联系我们 contact @ memedata.com