Skip to content

docs(html-to-docx): browser ESM build + exports map (zero-config bundler imports) - #8

Draft
nicolasiscoding wants to merge 3 commits into
mainfrom
docs/html-to-docx-browser-esm
Draft

docs(html-to-docx): browser ESM build + exports map (zero-config bundler imports)#8
nicolasiscoding wants to merge 3 commits into
mainfrom
docs/html-to-docx-browser-esm

Conversation

@nicolasiscoding

Copy link
Copy Markdown
Member

What & why

Updates the turbodocx-html-to-docx skill to match the new browser packaging in @turbodocx/html-to-docx (see TurboDocx/html-to-docx#205): a browser-targeted ESM build (dist/html-to-docx.browser.esm.js) plus an exports map, so a plain import HTMLtoDOCX from "@turbodocx/html-to-docx" works in any bundler (Next.js/Turbopack, Vite, webpack) with zero config — no alias, no Buffer/process polyfills.

⚠️ Gated on TurboDocx/html-to-docx#205. The browser ESM build + exports map ship in that PR. Merge/publish this after #205 is released so the skill's stated resolution mechanism matches the live package. (The skill's guidance already produces working client-side code on current versions too — see testing — but the browser.esm.js/exports references only become literally accurate once #205 ships.)

Changes (skills/turbodocx-html-to-docx)

  • references/usage.md:
    • Build-outputs table now lists four files (adds dist/html-to-docx.browser.esm.js); corrected sizes (~1.6 MB across the board; the IIFE is ~1.6 MB minified, not ~2.4 MB).
    • Explains the exports map (browser/node/import/require conditions) so consumers don't pick a build manually.
    • Path 1 (bundler) rewritten: plain import, no config/polyfills; adds a Next.js Client Component + dynamic-import() code-splitting note and points to the runnable example/nextjs-example.
    • Version note: older versions mostly still work via the legacy fields, but strict-ESM bundlers (Vite 8+) can hit "does not provide an export named 'default'" on the IIFE — upgrading removes those edge cases.
    • Fixes the URL.revokeObjectURL timing (revoke on next tick) in both Path 1 and Path 2 snippets.
  • SKILL.md: intro + "prefer server-side" note updated (exports map, ~1.6 MB); browser-only path size corrected; skill version1.3.0.

Testing (skill-creator eval)

Ran the skill against fresh projects (with the updated skill vs the v1.2.0 snapshot):

Eval Result
Express server-side (POST /generate → .docx) ✅ helper + route + download headers; valid 20KB docx (PK magic); tsc clean. No regression from the browser-doc edits.
Vite + React client-side (new) ✅ dynamic import (code-split), Blob download, zero polyfills/alias, package added. A real vite build succeeded zero-config (1.69 MB split chunk, default export preserved).
Vite client-side on the old skill (baseline) top-level static import, no code-splitting, no client-side nuance — the new version is a clear improvement.

Note surfaced by the eval: on current v1.21.0 (pre-#205), Vite resolves the self-contained IIFE via the legacy browser field and still works — so the guidance is safe today and becomes mechanism-accurate once #205 ships.

Draft for review.

🤖 Generated with Claude Code

…-config bundler imports

Update the html-to-docx skill for the new packaging: four dist builds
(adds browser.esm.js), the exports map that auto-resolves the browser
condition, corrected ~1.6 MB sizes (was ~2.4 MB), and a zero-config Path 1
bundler import (no alias/polyfills). Also fix the download object-URL revoke
timing and add a Next.js dynamic-import note. Bumps skill to 1.3.0.
@nicolasiscoding

Copy link
Copy Markdown
Member Author

🔗 Depends on: TurboDocx/html-to-docx#205, which adds the browser ESM build (dist/html-to-docx.browser.esm.js) + exports map this skill documents.

Merge order: land and publish #205 first, then merge this. The guidance here already produces working client-side code on current versions, but the browser.esm.js / exports-condition references only become mechanism-accurate once #205 ships.

#205 makes `npm run build` emit four artifacts (adds browser.esm.js) and
changes `BUILD_TARGET=browser` to emit both browser builds, not the IIFE
only. Update the build-command notes to match (verified by building #205).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@K-Kumar-01
K-Kumar-01 marked this pull request as ready for review June 15, 2026 16:36
Signed-off-by: Kushal <kushalkumargupta4@gmail.com>
@yacineKahlerras

Copy link
Copy Markdown
Contributor

Code review

Two documentation-consistency observations. Both are below the confidence bar this review normally posts at, so treat them as judgement calls rather than blockers.

Three candidate issues were checked and dismissed as false positives: the browser.esm.js file does ship in v1.22.0, npm run build:browser does emit both browser builds (verified by building #205), and the ~2.4 MB → ~1.6 MB change corrects a longstanding doc error rather than claiming a new size reduction.

  1. SKILL.md presents the exports map as automatic resolution with no version qualifier, while the caveat that it only ships in newer versions lives in references/usage.md. A reader who stops at SKILL.md won't know the mechanism is version-gated.

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

The caveat it omits:

> 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.

  1. Evals 24-26 assert package-installed via file_contains on package.json, which matches the package name but not its version. Since the zero-config import these evals exercise depends on the exports map, an older pinned version would satisfy every assertion while failing in a real bundler — the same failure mode usage.md line 288 warns about (Vite 8+ does not provide an export named 'default').

quickstart/evals/evals.json

Lines 413 to 415 in 337f1ee

"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)"},

Worth noting this is arguably by design — no existing eval pins a version, and npm installs latest by default.

🤖 Generated with Claude Code

- If this code review was useful, please react with 👍. Otherwise, react with 👎.

@yacineKahlerras
yacineKahlerras self-requested a review July 17, 2026 10:14
@yacineKahlerras

Copy link
Copy Markdown
Contributor

🚫 Do not merge yet — blocked by a runtime bug in the library

Tested this PR's guidance end-to-end (ran the skill on a fresh Vite app, built it, drove it in a real browser). The docs are accurate and the skill generates good code — but it produces an app that crashes at runtime on Vite and Remix. The blocker is in the library, not this PR, but merging this PR ships guidance that walks users straight into it.

Filed as TurboDocx/html-to-docx#218.

What breaks

@turbodocx/html-to-docx's browser build throws ReferenceError: global is not defined when returning the finished document — index.js:50 uses a bare Node-only global. It surfaces per-bundler:

Bundler Result
Next.js / webpack ✅ works (webpack auto-shims global)
Vite / Rollup 💥 ReferenceError: global is not defined
Remix (Vite) 💥 same mechanism (not yet clicked, but identical)

The trap: the build passes green. The failure only appears when a user clicks the button in the browser. Verified in Chromium — no Blob, no download.

Why this PR specifically is affected

This PR documents zero-config browser imports and adds Vite (eval 24) and Remix (eval 26) client-side evals — the two bundlers that break. The evals pass anyway because they assert on the generated source text (plain-import, handles-blob, …), which is all correct; no text assertion can catch a library that throws at runtime. So we'd be greenlighting guidance that fails for real users on 2 of the 3 documented frameworks.

Their own example/nextjs-example (added in #205) genuinely works — I confirmed it downloads a valid 25 KB .docx. That's exactly why this slipped through: it was only ever exercised on webpack, the one bundler that hides the bug.

What needs to happen first

  1. Fix #218 — one line, globalglobalThis at index.js:50 and :53. Verified working in Node and browser.
  2. Release the library with the fix.
  3. Then merge this PR. Once #218 ships, the guidance is genuinely correct on all three bundlers and this is a good PR.

Not blockers, but worth a look while it's open

  • Phase 1 branch labels in SKILL.md: branch 2 is labeled "Browser-only project" but gated on no package.json. A browser SPA with a package.json matches neither branch cleanly — invites picking the wrong (<script>-tag) path. Cheap wording fix.
  • I retract my earlier eval version-pinning comment on this PR — I tested it and had it backwards. Old versions fail loudly at build time (exactly as usage.md:288 predicts); it's the new version that fails silently. Not an eval gap.
  • Add one eval that builds and clicks, not just greps source — that is the only kind of test that would have caught #218.

What's genuinely good here (for the record)

A fresh agent given only this skill produced: a plain import, no alias, no polyfills, vite.config.ts untouched (the PR's core promise, honored), correct dynamic-import code-splitting (chunk measured at 1,647.64 kB, independently confirming the ~1.6 MB claim), and the setTimeout(revokeObjectURL, 0) fix. The writing and the instructions are solid. The only thing standing between this PR and merge is #218.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants