Skip to content

feat(exporters): real PDF/PPTX/ZIP via system Chrome + pptxgenjs + zip-lib (lazy-loaded)#8

Merged
hqhq1025 merged 1 commit intomainfrom
wt/exporters-v2
Apr 18, 2026
Merged

feat(exporters): real PDF/PPTX/ZIP via system Chrome + pptxgenjs + zip-lib (lazy-loaded)#8
hqhq1025 merged 1 commit intomainfrom
wt/exporters-v2

Conversation

@hqhq1025
Copy link
Copy Markdown
Collaborator

Goal

Replace the EXPORTER_NOT_READY stubs in packages/exporters/src/{pdf,pptx,zip}.ts with working tier-1 implementations. All three formats ship as real exporters; the heavy runtime deps load lazily so the Electron cold-start bundle is unchanged (PRINCIPLES §1, §6).

Files in / out

Modified

  • packages/exporters/src/pdf.ts — puppeteer-core driving the user's installed Chrome
  • packages/exporters/src/pptx.ts — pptxgenjs, one slide per <section> (or one for the whole doc)
  • packages/exporters/src/zip.ts — zip-lib bundling index.html + README.md + optional assets
  • packages/exporters/src/index.tsisExporterReady(...) returns true for all four formats; exportArtifact dispatches with payload
  • packages/exporters/package.json — adds three prod deps (see below)
  • apps/desktop/src/main/exporter-ipc.ts — comment update only (channel/payload unchanged)
  • apps/desktop/src/renderer/src/components/PreviewToolbar.tsx — flips dropdown items from "Coming in Phase 2" to per-format hover hints

Created

  • packages/exporters/src/chrome-discovery.ts — Mac/Win/Linux Chrome resolver + CODESIGN_CHROME_PATH override
  • packages/exporters/src/chrome-discovery.test.ts — 6 tests (per-OS path, override, EXPORTER_NO_CHROME)
  • packages/exporters/src/pdf.test.ts — 3 tests, puppeteer-core mocked
  • packages/exporters/src/pptx.test.ts — 6 tests including '你好世界' CJK round-trip with the real library
  • packages/exporters/src/zip.test.ts — 3 tests, real zip-lib extract round-trip

Untouched (boundary respected)

  • apps/desktop/src/main/index.ts, apps/desktop/src/renderer/src/App.tsx, apps/desktop/src/renderer/src/store.ts, apps/desktop/src/preload/index.ts

New dependencies

Package License Install size Lazy-loaded?
puppeteer-core@^24.10.0 Apache-2.0 ~3 MB (no Chromium download) yes — await import('puppeteer-core') inside exportPdf
pptxgenjs@^3.12.0 MIT ~2.5 MB yes — await import('pptxgenjs') inside exportPptx
zip-lib@^1.0.4 MIT ~80 KB yes — await import('zip-lib') inside exportZip

All three are Apache-2.0-compatible. Lazy-load proof: grep -E "^import .* from ['\"](puppeteer-core\|pptxgenjs\|zip-lib)['\"]" packages/exporters/src/*.ts returns nothing — every reference is inside an async function via dynamic import(). Total impact stays well inside the 80 MB installer budget; no Chromium is bundled (we discover the system Chrome at runtime and throw EXPORTER_NO_CHROME with the install link if missing).

Acceptance test outcomes

$ pnpm -r typecheck     # all 10 workspaces green
$ pnpm lint             # only one pre-existing complexity warning in PasteKey.tsx (unrelated)
$ pnpm -r test          # 18/18 exporter tests pass
   ✓ src/chrome-discovery.test.ts (6 tests)
   ✓ src/pdf.test.ts (3 tests)              — puppeteer-core mocked
   ✓ src/pptx.test.ts (6 tests, 582ms)      — real pptxgenjs, CJK '你好世界' round-trip, valid PK zip header
   ✓ src/zip.test.ts (3 tests)              — real zip-lib extract round-trip

No silent fallbacks: each exporter throws CodesignError with a structured code (EXPORTER_NO_CHROME / EXPORTER_PDF_FAILED / EXPORTER_PPTX_FAILED / EXPORTER_ZIP_FAILED).

§5b checklist

  • CompatibleExporterFormat, ExportResult, IPC channel codesign:export, and the renderer's ExportItem shape are unchanged. Every error carries a versioned code string callers can pattern-match on.
  • Upgradeable — tier-1 boundary intact: pptx.ts already routes through extractSlides(), so swapping in dom-to-pptx (tier 2) or a Chromium screenshot fallback only touches that one helper. chrome-discovery.ts accepts a deps injection point so a future "managed Chrome download" path can plug in without touching exporters.
  • No bloat — three prod deps added (still well under the 30-prod-dep cap), all lazy-loaded so the cold-start bundle is unchanged. No Chromium ships with the installer.
  • ElegantPreviewToolbar keeps existing tokens / spacing / easing; only the per-item hint copy changed. Each exporter is a single small function with a single throw site.

How to test manually

  1. pnpm i && pnpm dev
  2. Generate any HTML preview, click Export, pick PDF → save dialog → confirm Chrome launches headless and a real PDF lands on disk.
  3. Pick PPTX with a deck containing <section> blocks → opens in PowerPoint / Keynote with one slide per section.
  4. Pick ZIP → archive contains index.html, README.md, and any assets.
  5. With Chrome uninstalled (or set CODESIGN_CHROME_PATH=/nonexistent), PDF export shows the EXPORTER_NO_CHROME toast with the install link.

…p-lib

Replaces the EXPORTER_NOT_READY stubs with working tier-1 implementations
for all three formats. Heavy runtime deps load lazily via dynamic import()
inside each exporter function so the Electron cold-start bundle remains
unchanged (PRINCIPLES §1).

- pdf.ts: puppeteer-core driving the user's installed Chrome (discovered
  via chrome-discovery.ts on Mac/Win/Linux). Refuses to bundle Chromium —
  a ~150 MB download would blow the 80 MB installer budget. Throws
  EXPORTER_NO_CHROME (loud, with install link) when no Chromium-based
  browser is present.
- pptx.ts: pptxgenjs with one slide per top-level <section> (or one for
  the whole document if none exist). Sidesteps the dom-to-pptx CJK
  word-wrap bug by keeping wrap=square + fit:'shrink' (research/04).
  dom-to-pptx itself is deferred to tier 2.
- zip.ts: zip-lib bundling index.html + README.md + optional assets.
- index.ts: isExporterReady('pdf'|'pptx'|'zip') now true; exportArtifact
  forwards htmlContent + destinationPath to the per-format modules.
- PreviewToolbar: flips the dropdown items from "Coming in Phase 2" to
  per-format hover hints describing what each produces.

Hard rules respected:
- No silent fallbacks; each path throws CodesignError with a structured
  code (EXPORTER_NO_CHROME / EXPORTER_PDF_FAILED / EXPORTER_PPTX_FAILED
  / EXPORTER_ZIP_FAILED).
- Lazy-load: index.ts imports zero heavy deps statically; verified
  with `grep "^import .* from 'puppeteer-core|pptxgenjs|zip-lib'"`.
- Tests: chrome-discovery (6), pdf mocked via puppeteer-core (3), pptx
  CJK round-trip with the real library (6), zip extract round-trip (3).

Signed-off-by: hqhq1025 <1506751656@qq.com>
@hqhq1025 hqhq1025 merged commit 0caca07 into main Apr 18, 2026
6 of 7 checks passed
@hqhq1025 hqhq1025 deleted the wt/exporters-v2 branch April 18, 2026 07:42
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.

1 participant