Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 59 additions & 0 deletions evals/evals.json
Original file line number Diff line number Diff line change
Expand Up @@ -403,6 +403,65 @@
{"name": "has-example", "type": "file_contains", "description": "Script includes example HTML content to demonstrate usage"},
{"name": "uses-async", "type": "file_contains", "description": "Script uses async/await (HTMLtoDOCX is async)"}
]
},
{
"id": 24,
"skill_name": "turbodocx-html-to-docx",
"prompt": "Add a 'Download as Word' button to my Vite + React app that converts an HTML string to a DOCX file entirely in the browser and downloads it. This is a static SPA with no backend.",
"expected_output": "Installs @turbodocx/html-to-docx, adds client-side generation inside a React component click handler using a plain import (zero bundler config), coerces the result to a Blob, and triggers a download via URL.createObjectURL + an anchor click. No vite.config polyfills/aliases, no server code.",
"files": ["package.json", "vite.config.ts", "tsconfig.json", "index.html", "src/main.tsx", "src/App.tsx"],
"assertions": [
{"name": "package-installed", "type": "file_contains", "description": "package.json contains @turbodocx/html-to-docx as a dependency"},
{"name": "plain-import", "type": "file_contains", "description": "Code imports HTMLtoDOCX from '@turbodocx/html-to-docx' with a plain default import (no deep path into dist/, no alias)"},
{"name": "no-bundler-polyfills", "type": "file_not_contains", "description": "vite.config.ts is NOT modified to add Node polyfills or a package alias (no vite-plugin-node-polyfills, no resolve.alias for @turbodocx/html-to-docx, no global/process/Buffer define shims) — the browser ESM build resolves zero-config"},
{"name": "in-react-component", "type": "file_contains", "description": "Generation is wired into a React component (e.g. src/App.tsx) via an onClick handler — not a standalone Node script"},
{"name": "handles-blob", "type": "file_contains", "description": "Code treats the result as a Blob (e.g. `result instanceof Blob` or wraps it in `new Blob([...])`) — does NOT call fs / writeFileSync / Buffer"},
{"name": "creates-object-url", "type": "file_contains", "description": "Code calls URL.createObjectURL on the Blob to make it downloadable"},
{"name": "triggers-download", "type": "file_contains", "description": "Code creates an anchor element, sets its download attribute, and calls .click() to start the download"},
{"name": "revokes-object-url", "type": "file_contains", "description": "Code calls URL.revokeObjectURL after triggering the download (ideally on the next tick via setTimeout, per the skill's timing fix)"},
{"name": "no-server-code", "type": "file_not_contains", "description": "No server/API code is created (no Express route, no fetch to a backend, no Node-only APIs) — generation happens fully client-side"},
{"name": "no-fs-usage", "type": "file_not_contains", "description": "Generated browser code does NOT import 'fs' or call writeFileSync (that only works in Node)"},
{"name": "uses-typescript", "type": "file_exists", "description": "Generated files use .ts/.tsx extension (Vite + TS project)"}
]
},
{
"id": 25,
"skill_name": "turbodocx-html-to-docx",
"prompt": "I have a Next.js App Router app. Add a 'Download as Word' button to a page that converts HTML to DOCX entirely in the browser — generate it on the client, no API route.",
"expected_output": "Adds a Client Component (\"use client\") with a button whose handler dynamically imports @turbodocx/html-to-docx (so the ~1.6 MB build is code-split out of first-load JS and never runs during SSR), coerces the result to a Blob, and downloads it via URL.createObjectURL. Does NOT create an app/api route.",
"files": ["package.json", "next.config.js", "tsconfig.json", "app/layout.tsx", "app/page.tsx"],
"assertions": [
{"name": "package-installed", "type": "file_contains", "description": "package.json contains @turbodocx/html-to-docx as a dependency"},
{"name": "use-client-directive", "type": "file_contains", "description": "The component that runs generation starts with the \"use client\" directive (it uses browser APIs and event handlers)"},
{"name": "dynamic-import", "type": "file_contains", "description": "The click handler dynamically imports the library via `await import('@turbodocx/html-to-docx')` so it is code-split and never bundled into server/first-load JS (per the skill's Next.js guidance)"},
{"name": "no-api-route", "type": "file_not_contains", "description": "No API route is created under app/api/ (e.g. app/api/.../route.ts) — the user explicitly wants client-side generation only"},
{"name": "no-server-generation", "type": "file_not_contains", "description": "HTMLtoDOCX is NOT called in a Server Component, route handler, or server action — generation runs only in the browser click handler"},
{"name": "handles-blob", "type": "file_contains", "description": "Code treats the result as a Blob (e.g. `result instanceof Blob` or `new Blob([...])`)"},
{"name": "creates-object-url", "type": "file_contains", "description": "Code calls URL.createObjectURL on the Blob"},
{"name": "triggers-download", "type": "file_contains", "description": "Code creates an anchor, sets the download attribute, and calls .click()"},
{"name": "revokes-object-url", "type": "file_contains", "description": "Code calls URL.revokeObjectURL after the download (ideally on the next tick)"},
{"name": "no-fs-usage", "type": "file_not_contains", "description": "Generated browser code does NOT import 'fs' or call writeFileSync"},
{"name": "uses-typescript", "type": "file_exists", "description": "Generated files use .ts/.tsx extension"}
]
},
{
"id": 26,
"skill_name": "turbodocx-html-to-docx",
"prompt": "Add client-side HTML-to-DOCX download to my Remix app. When a user clicks a button on a route, generate the Word document in the browser and download it — don't do it on the server.",
"expected_output": "Installs @turbodocx/html-to-docx and adds generation inside a route component's client-side click handler using a plain import, coercing the result to a Blob and downloading via URL.createObjectURL. Does NOT run generation inside a Remix loader or action (those execute on the server).",
"files": ["package.json", "vite.config.ts", "tsconfig.json", "app/root.tsx", "app/routes/_index.tsx"],
"assertions": [
{"name": "package-installed", "type": "file_contains", "description": "package.json contains @turbodocx/html-to-docx as a dependency"},
{"name": "plain-import", "type": "file_contains", "description": "Code imports HTMLtoDOCX from '@turbodocx/html-to-docx' (plain default import, no deep dist/ path, no alias)"},
{"name": "client-side-handler", "type": "file_contains", "description": "Generation runs inside a browser event handler (e.g. an onClick) in a route component — using browser APIs"},
{"name": "not-in-loader-or-action", "type": "file_not_contains", "description": "HTMLtoDOCX is NOT called inside a Remix `loader` or `action` (those run on the server, defeating the client-side requirement)"},
{"name": "handles-blob", "type": "file_contains", "description": "Code treats the result as a Blob (e.g. `result instanceof Blob` or `new Blob([...])`)"},
{"name": "creates-object-url", "type": "file_contains", "description": "Code calls URL.createObjectURL on the Blob"},
{"name": "triggers-download", "type": "file_contains", "description": "Code creates an anchor, sets the download attribute, and calls .click()"},
{"name": "revokes-object-url", "type": "file_contains", "description": "Code calls URL.revokeObjectURL after the download (ideally on the next tick)"},
{"name": "no-fs-usage", "type": "file_not_contains", "description": "Generated browser code does NOT import 'fs' or call writeFileSync"},
{"name": "uses-typescript", "type": "file_exists", "description": "Generated files use .ts/.tsx extension"}
]
}
]
}
8 changes: 4 additions & 4 deletions skills/turbodocx-html-to-docx/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,15 @@ name: turbodocx-html-to-docx
description: Set up @turbodocx/html-to-docx to convert HTML to Microsoft Word (.docx) documents in a Node.js project. Use this skill when the user wants to generate DOCX files from HTML, add document generation to their app, convert HTML templates to Word documents, or integrate TurboDocx html-to-docx into their project. Also trigger for mentions of HTML-to-Word, HTML-to-DOCX, document generation, report generation from HTML, or any request involving the @turbodocx/html-to-docx package.
metadata:
author: TurboDocx
version: "1.2.0"
version: "1.3.0"
license: MIT
---

# HTML to DOCX Setup

This skill adds `@turbodocx/html-to-docx` to a JavaScript/TypeScript project — a zero-dependency library that converts HTML strings to Word documents without Puppeteer, Chrome, or LibreOffice. It runs in Node.js **and** in the browser (via the bundled IIFE/ESM builds).
This skill adds `@turbodocx/html-to-docx` to a JavaScript/TypeScript project — a zero-dependency library that converts HTML strings to Word documents without Puppeteer, Chrome, or LibreOffice. It runs in Node.js **and** in the browser (via the bundled browser ESM / IIFE builds, resolved automatically through the package `exports` map).

> **Prefer server-side when possible.** Running this server-side is faster, avoids the ~2.4 MB browser bundle, sidesteps polyfill requirements, and keeps `sharp` (for SVG → PNG conversion) available. Default to a server-side integration whenever the project has any backend (Express/Fastify/Next.js API route/etc.) and only fall back to the browser bundle when the project is genuinely static or the user explicitly asks for client-side generation.
> **Prefer server-side when possible.** Running this server-side is faster, keeps the ~1.6 MB browser build out of your client bundle, and keeps `sharp` (for SVG → PNG conversion) available. Default to a server-side integration whenever the project has any backend (Express/Fastify/Next.js API route/etc.) and only fall back to the browser build when the project is genuinely static or the user explicitly asks for client-side generation.

## Phase 1: Detect the Project Type

Expand All @@ -29,7 +29,7 @@ Use Glob to check what kind of project this is:
- Proceed to **Phase 2 (npm install path)**.

2. **Browser-only project** — no `package.json`, but HTML files exist (`*.html`) or the user has explicitly said they want to use this in a static page / CDN setup. Before committing to this path, briefly confirm with the user that they don't have a backend they'd rather run this in — server-side is preferred. If they confirm browser is required:
- Skip npm install entirely. The library ships a self-contained browser bundle (`dist/html-to-docx.browser.js`, ~2.4 MB IIFE) with all dependencies inlined.
- Skip npm install entirely. The library ships a self-contained browser bundle (`dist/html-to-docx.browser.js`, ~1.6 MB IIFE) with all dependencies inlined.
- Read the **Browser Usage** section in `references/usage.md` for the polyfill snippet, the `HTMLToDOCX(...)` global, and the limitations (no `sharp`, CORS-restricted remote images, no filesystem).
- Drop in a `<script src="...">` referencing either a hosted copy or a CDN build, and generate a minimal working example tailored to the user's page.
- Skip Phases 2-4 below; the browser path is install-less.
Expand Down
33 changes: 20 additions & 13 deletions skills/turbodocx-html-to-docx/references/usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -272,28 +272,32 @@ const options = {

## Browser Usage

> **Prefer server-side if possible.** Server-side generation is faster, has no ~2.4 MB bundle, requires no polyfills, and keeps `sharp` available for SVG → PNG conversion. Only use the browser path if the project is truly static or the user has explicitly asked for client-side generation.
> **Prefer server-side if possible.** Server-side generation is faster, keeps the browser build out of your client bundle, and keeps `sharp` available for SVG → PNG conversion. Only use the browser path if the project is truly static or the user has explicitly asked for client-side generation.

This library runs in browsers via the bundled standalone build. It is not server-side only. There are three distribution files produced by `npm run build`:
This library runs in browsers via a self-contained, polyfilled build — it is not server-side only. `npm run build` produces four distribution files:

| File | Format | Size | Use case |
|------|--------|------|----------|
| `dist/html-to-docx.esm.js` | ES Module | ~1.6 MB | Modern bundlers (Webpack, Vite, Rollup) — deps external |
| `dist/html-to-docx.umd.js` | UMD | ~1.6 MB | Node.js, AMD, manual dep management |
| `dist/html-to-docx.browser.js` | IIFE | ~2.4 MB | Direct `<script>` / CDN — **all deps bundled** |
| `dist/html-to-docx.esm.js` | ES Module | ~1.6 MB | Node.js ESM / server-side bundling — deps external |
| `dist/html-to-docx.umd.js` | UMD | ~1.6 MB | Node.js `require`, AMD |
| `dist/html-to-docx.browser.esm.js` | ES Module | ~1.6 MB | Browser bundlers — **all deps bundled + polyfilled** |
| `dist/html-to-docx.browser.js` | IIFE | ~1.6 MB | Direct `<script>` / CDN — **all deps bundled** |

`package.json` already wires these up as `main` / `module` / `browser`, so bundlers pick the right one automatically.
The package's `exports` map points each environment at the right file automatically: browser bundlers resolve the `browser` condition to `html-to-docx.browser.esm.js`, Node resolves `import`/`require` to the esm/umd builds, and the `<script>` path uses the IIFE directly. So consumers never choose manually.

> The browser ESM build + `exports` map ship in newer versions of `@turbodocx/html-to-docx`. Older versions expose only the legacy `main`/`module`/`browser` fields; most bundlers still resolve a working build from those (the IIFE is self-contained), but strict-ESM toolchains (e.g. Vite 8+) can fail to load the IIFE with *"does not provide an export named 'default'"*, and a bundler that picks the externalized `esm` build may hit missing Node globals. The browser ESM build + `exports` map remove those edge cases so a plain `import` works everywhere with no alias or polyfill. If a user is pinned to an older version and hits one of these, upgrading is the fix.

### Path 1 — Bundler (Vite, Webpack, Rollup, Next.js client component, etc.)

Install normally and import. The bundler picks the ESM build:
Install normally and import — no bundler config, no polyfills. The `browser` export condition resolves a self-contained ESM build with Node polyfills already inlined:

```typescript
import HTMLtoDOCX from '@turbodocx/html-to-docx';

async function downloadDocx(html: string) {
const result = await HTMLtoDOCX(html);
// In browser: result is a Blob. In Node: Buffer/ArrayBuffer.
// In the browser the library returns a Blob. (Coerce defensively in case a
// future build returns an ArrayBuffer/Buffer.)
const blob = result instanceof Blob
? result
: new Blob([result], { type: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' });
Expand All @@ -303,10 +307,13 @@ async function downloadDocx(html: string) {
a.href = url;
a.download = 'document.docx';
a.click();
URL.revokeObjectURL(url);
// Revoke on the next tick so the browser has started the download first.
setTimeout(() => URL.revokeObjectURL(url), 0);
}
```

In Next.js / SSR frameworks, do this inside a Client Component (`"use client"`) and trigger it on a user action — ideally via a dynamic `await import('@turbodocx/html-to-docx')` in the handler so the ~1.6 MB build is code-split out of your first-load JS and never runs during server rendering. See the runnable `example/nextjs-example` in the html-to-docx repo for a complete app.

### Path 2 — Static HTML page (no bundler, `<script>` tag)

Use the IIFE bundle. **Polyfills for `global`, `process`, and `Buffer` must be set before the script loads** — some dependencies check for them synchronously during init:
Expand Down Expand Up @@ -357,7 +364,7 @@ Use the IIFE bundle. **Polyfills for `global`, `process`, and `Buffer` must be s
a.href = url;
a.download = 'document.docx';
a.click();
URL.revokeObjectURL(url);
setTimeout(() => URL.revokeObjectURL(url), 0);
}
</script>

Expand All @@ -371,9 +378,9 @@ Use the IIFE bundle. **Polyfills for `global`, `process`, and `Buffer` must be s
The browser bundle ships in the npm package's `dist/` directory, so most users don't need to build it. If you're working from a cloned repo or want a custom build:

```bash
npm run build # all three outputs (ESM + UMD + Browser)
npm run build:browser # browser IIFE only (dev)
npm run build:browser:prod # browser IIFE only (minified, production)
npm run build # all four outputs (ESM + UMD + Browser ESM + Browser IIFE)
npm run build:browser # both browser builds (ESM + IIFE), dev
npm run build:browser:prod # both browser builds (ESM + IIFE), minified, production
```

### Browser limitations
Expand Down