plans/023-decompose-god-functions.md
export.ts and cli.tsExecutor instructions: This is a behavior-preserving refactor of two critical, high-churn paths. Do it in small, verifiable steps; never change what the code does, only how it's organized. Run the full test suite after each step. Honor the STOP conditions. When done, update the status row in
plans/README.md.Drift check (run first):
git diff --stat c63cb120..HEAD -- packages/slidev/node/commands/export.ts packages/slidev/node/cli.tsOn a mismatch with the excerpts below, treat it as a STOP condition.
c63cb120, 2026-07-10Two functions concentrate risk and resist testing:
exportSlides (export.ts:167-572) is a ~400-line function wrapping 13 nested
closures (go, getSlidesIndex, genPageWithClicks, genPagePdf*,
genPagePng*, genPageMd, genPagePptx, addPdfMetadata, addTocToPdf) that
share mutable output/page/progress via closure. The per-format exporters
can't be unit-tested or reused independently.cli.ts:114-340) is a ~226-line closure
holding initServer, restartServer, tunnel/QR/open helpers, the SHORTCUTS
table, bindShortcut, and the chokidar watcher — mixing CLI wiring, server
lifecycle, TTY shortcuts, and file-watching.Both are frequently edited (git churn), so the coupling compounds maintenance cost and risk.
export.ts: exportSlides(options) opens a browser/context/page, dispatches on
format, and all gen* helpers are nested functions closing over page,
output, progress, pages, width, height, etc. (see export.ts:167-572).cli.ts: the default yargs command handler (:114-340) defines server
lifecycle + shortcuts + watcher inline.export.ts has characterization tests for
getExportOptions + outline/range helpers. There are no tests for the serve
handler (it's interactive), so its refactor must be especially conservative.| Purpose | Command | Expected |
|---|---|---|
| Install | pnpm install | exit 0 |
| Build | pnpm build | exit 0 |
| Test | pnpm test | all pass (incl. plan 022's) |
| Typecheck | pnpm typecheck | exit 0 |
| Lint | pnpm lint | exit 0 |
In scope:
packages/slidev/node/commands/export.ts (extract per-format exporters behind
an explicit context object)packages/slidev/node/commands/export/ for the
extracted exporterspackages/slidev/node/cli.ts (lift serve-handler helpers into a module with
injected deps) — only if it can be done without behavior changeOut of scope:
exportSlides/exportNotes/getExportOptions (keep
them stable — they're imported by build.ts and cli.ts).refactor/decompose-export-serve.refactor(export): extract PngExporter, etc.Do the export decomposition first (it has tests). Treat the serve handler as a second, optional phase and STOP for confirmation before starting it.
Define an ExportContext object holding what the closures currently capture
(page, output, progress, pages, width, height, withClicks, range,
flags). Change exportSlides to build it once and pass it to the (still-nested,
for now) helpers as a parameter instead of relying on closure capture.
Verify: pnpm build && pnpm test → green; export snapshots unchanged.
Move genPagePdf*/genPagePng*/genPageMd/genPagePptx into standalone
functions (e.g. PdfExporter(ctx), PngExporter(ctx), …) that take the
ExportContext. exportSlides becomes: build browser/context/page → build ctx →
dispatch to the chosen exporter → (try/finally close, from plan 007). Keep
addPdfMetadata/addTocToPdf/makeOutline/getSlidesIndex as helpers the
exporters call.
Verify after each extraction: pnpm build && pnpm test && pnpm typecheck →
green; no export snapshot/behavior change.
Only after Step 2 and operator confirmation: extract initServer/restartServer/
tunnel-QR-open/bindShortcut/watcher into a SlidevDevServerController module
with injected dependencies, leaving cli.ts to wire args → controller. Because
there are no automated tests here, do this in the smallest possible commits and
verify manually with pnpm demo:dev (server starts, restart on config change,
shortcuts o/e/q, watcher reload).
Verify: pnpm build && pnpm typecheck && pnpm lint green; manual serve smoke
passes.
pnpm test after every extraction.exportSlides dispatches to standalone per-format exporters via an explicit context (no shared-closure mutable state for the format logic)exportSlides/exportNotes/getExportOptions unchangedpnpm build && pnpm typecheck && pnpm lint && pnpm test passplans/README.md status row updatedStop and report if: