Dom-docx: HTML a Word Editable de Verdad (Ni Screenshot, Ni Tabla de 1×1)

dom-docx: HTML a Word Editable de Verdad (Ni Screenshot, Ni Tabla de 1×1)

Todo developer que alguna vez tuvo que generar un documento Word desde código se chocó con la misma pared: las librerías HTML-a-docx del ecosistema OSS no producen estructura Word real y editable. Conseguís algo que se ve bien cuando lo abrís, pero tratá de editarlo de verdad y descubrís que es un screenshot disfrazado de .docx, o una tabla con una sola celda de 1×1 haciéndose pasar por un layout de página.

dom-docx es una librería nueva, MIT, que se propuso arreglar exactamente ese problema — y la historia de cómo se construyó vale la pena por sí sola.

El problema aburrido que nadie resolvió bien

El framing del propio autor, del lanzamiento en Show HN: hace mucho trabajo de generación de documentos backend, y actualizar templates y código para eso es una de las partes menos favoritas del trabajo — errores crípticos, rebuild loops de más de un minuto. Quería construir reportes en HTML renderizado por JS (Vue, React) en lugar de eso, pero cada opción HTML-a-docx existente en el espacio OSS se quedaba corta produciendo output Word válido y editable.

Este es un problema “forma MarkItDown”: poco glamoroso, universal, y algo que casi todo producto de cara a developers eventualmente necesita — facturas, reportes, contratos, dashboards exportables. El pitch de dom-docx no es una categoría nueva de herramienta. Es la conversión aburrida hecha bien, en un npm install.

Cómo se construyó de verdad

Esta es la parte que lo hace más que “otro conversor más”. El autor aplicó lo que llama el patrón “Autoresearch” de Karpathy — un agente corre iteraciones contra un score objetivo, se queda con lo que mejora, descarta lo que no — al problema de fidelidad OOXML.

El loop funciona así: renderizá HTML y sacale un screenshot, convertilo a .docx con dom-docx, rasterizá ese .docx vía LibreOffice y sacale otro screenshot, puntuá los dos screenshots entre sí por fidelidad de layout, meté ese score de vuelta en el loop, repetí. El autor corrió esto contra 37 patrones de HTML del mundo real conocidos por romper conversores — listas anidadas, tablas, layouts flex, blockquotes — e iteró hasta que el output se sostuvo.

Vale la pena marcarlo claramente: los números resultantes son el benchmark propio del autor, puntuado con su propio harness, no un resultado auditado independientemente. La metodología publicada reporta 85.6% de concordancia con evaluaciones humanas ciegas en fidelidad de layout (usando una comparación de estructura por proyección de tinta), más guardrails para mal contraste, marcadores de lista faltantes, texto incorrecto y bloques sombreados desbalanceados. El “engine score” general está ponderado 50% fidelidad visual, 35% editabilidad (estructura de documento real, no tablas de layout), 15% velocidad de compilación. La metodología completa y la comparación cabeza a cabeza contra html-to-docx y @turbodocx/html-to-docx están publicadas en BENCHMARK.md y SCORING.md del repo, por si querés revisar la tarea del autor vos mismo.

Qué hace en realidad

Por debajo, es un pipeline de tres etapas:

  1. Resolución de estilos — ya sea atributos inline style="" (el default, JS puro) o estilos computados del navegador vía getComputedStyle.
  2. Visitor — un recorrido con Cheerio que convierte el HTML en párrafos, tablas, numeración e hyperlinks de docx.
  3. Pack + patch — genera el OOXML y parchea la numeración de listas y la alineación de bloques sombreados para que tanto LibreOffice como Word lo rendericen correctamente, incluyendo exportación a PDF.

El path por defecto no necesita nada más que docx, cheerio y fflate — sin navegador headless, sin Playwright. Eso solo entra en juego si optás por estilos computados o rasterización de gráficos.

Instalación y uso

npm install dom-docx

Requiere Node.js ≥ 20.

CLI, sin escribir código:

npx dom-docx input.html -o output.docx
npx dom-docx input.html                   # escribe input.docx al lado
cat fragment.html | npx dom-docx - -o -   # stdin → stdout binario, para pipelines
npx dom-docx input.html -s computed       # HTML con stylesheet/clases, necesita Playwright instalado

Node:

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);

Browser, sin Playwright, sin Node — corre enteramente en la pestaña del usuario:

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

const blob = await convertHtmlToDocx(html);
const a = document.createElement("a");
a.href = URL.createObjectURL(blob);
a.download = "output.docx";
a.click();

El input es un fragmento de HTML del body — no necesitás <!DOCTYPE>, <html> ni wrapper de <body>. Los defaults son US Letter, márgenes de 1", Arial 10.5pt.

Qué convierte bien (y qué todavía no)

Excelente Bueno Evitar (o usar rasterizeInPlace)
Headings, listas, tablas simples Banners sombreados, filas flex (≤4 ítems) Librerías de gráficos en vivo (Highcharts, canvas) sin rasterizar
strong/em/links inline Backgrounds de filas/celdas de tabla SVG complejo con paths/gradientes sin rasterizar
Saltos de página explícitos Blockquotes, <hr> CSS grid, floats, layout absoluto
Barras SVG simples (rect + texto) Imágenes data: Forms, web fonts, stylesheets externos en el path inline

Para gráficos y SVG complejo, rasterizeInPlace: { scale: 2 } los rasteriza a PNG antes de la conversión — recomendado para cualquier cosa construida con Highcharts o <canvas>.

La API también soporta configuración de página (pageSize, orientation, margins), metadata, HTML de header/footer, números de página, una portada (coverHtml), y un slot de tabla de contenidos (tocHtml) con links internos funcionales. La referencia completa de opciones está en API.md.

Licenciamiento

MIT, así de simple. Sin tier pago, sin API hosteada todavía (aunque el sitio del proyecto menciona que hay una en el roadmap).

¿Vale la pena usarlo?

Si alguna vez shippeaste un feature de “exportar a Word” sacándole un screenshot a una página y convirtiéndolo en imagen, o fingiendo un layout con una tabla, esto vale la pena probarlo. Es temprano — v0.1.x, lanzamiento de hace 2 días, primer proyecto open source de este autor — así que esperá los gaps mencionados arriba (todavía sin web fonts, sin CSS grid, sin rowspan) en lugar de un producto terminado. Pero el claim central — OOXML real y editable en vez de un screenshot fingiendo ser un documento — es exactamente el problema aburrido que vale la pena resolver una vez y olvidarse.


¿Alguien ya lo probó generando reportes desde React o Vue? Contanos cómo te fue en los comentarios. :speech_balloon: