Across 812 analyzed functions in slidevjs/slidev, 61 land in the critical band and 61 sit in the fire quadrant — complex code that’s also under active edit. The top hotspot by raw score, runJavaScript in code-runners.ts, is structural debt sitting untouched for 59 days with a nesting depth of 8, but the more urgent story is right behind it: createSlidesLoader (activity-weighted risk 16.4, fan-out 61, touched once in the last 30 days) and exportSlides (activity-weighted risk 15.86, fan-out 118, touched twice, last changed the same day as this analysis). I’d start review there — a fan-out of 118 means a single function is coupled to more than a hundred other functions, and it’s actively changing.
The table below ranks functions by activity-weighted risk — a score that multiplies structural complexity by recent commit frequency. A function that is both hard to understand (high cyclomatic complexity) and actively changing is a higher priority than one that is complex but untouched. CC = cyclomatic complexity (independent execution paths); ND = max nesting depth; FO = fan-out (distinct callees).
Top 5 Hotspots
| Function | File | Risk | CC | ND | FO |
|---|---|---|---|---|---|
runJavaScript | packages/client/setup/code-runners.ts | 16.9 | 10 | 8 | 23 |
createSlidesLoader | packages/slidev/node/vite/loaders.ts | 16.4 | 17 | 5 | 61 |
exportSlides | packages/slidev/node/commands/export.ts | 15.9 | 12 | 4 | 118 |
parseTimeString | packages/parser/src/timesplit/timestring.ts | 15.4 | 21 | 5 | 12 |
render | packages/client/builtin/VClicks.ts | 15.4 | 18 | 5 | 18 |
812 functions analyzed
Calls an unusually large number of distinct functions (high fan-out), making it the structural centre of gravity for a subsystem.Complex Branching×8Complex Branching
High cyclomatic complexity — many independent execution paths, each a potential bug surface and required test case.Long Function×8Long Function
Function body is too long to review in a single pass; likely contains multiple distinct responsibilities.Deeply Nested×7Deeply Nested
Control structures nested 4+ levels deep, making it hard to reason about the full execution state at inner branches.Exit Heavy×7Exit Heavy
Multiple return or throw paths dispersed through the body — each exit needs separate test coverage.
The quadrant split is worth sitting with before getting into individual functions: 61 fire, 114 debt, 83 watch, 554 ok. Structural debt (high complexity, low recent activity) outnumbers live fire by almost 2 to 1. Most of slidev’s risk is dormant rather than actively churning — but the five functions below are the exceptions that matter most right now, plus one piece of debt that deserves attention before it’s next touched.
runJavaScript — packages/client/setup/code-runners.ts
This is the top-ranked hotspot in the whole repo, and it’s a debt-quadrant function — zero touches in 30 days, last changed 59 days ago. The nesting depth of 8 is the standout number: the source shows a new Function(...) construction wrapping user-authored code in a try/catch, with a nested printObject/objectToText pair handling type-by-type serialization (strings, Errors, Arrays, Sets, Maps, RegExp, plain objects) via a long else if chain. That chain is where the nesting stacks up — each branch adds another indent level, and objectToText itself shows up separately in the context data at fan-out 13 and nesting depth 8 with zero touches in 113 days. Because this function isn’t being edited right now, the risk isn’t regression — it’s blast radius the next time someone has to touch the sandboxed code runner. I’d extract the type-dispatch logic in objectToText into a lookup table keyed by constructor before adding any new supported type.
createSlidesLoader — packages/slidev/node/vite/loaders.ts
This is the fire-quadrant function I’d prioritize first in practice, because it’s both complex and live. Fan-out of 61 means this single Vite plugin factory calls out to 61 distinct functions — it owns HMR watcher wiring, a middleware handler that branches on GET vs POST for the slide-request path, frontmatter merging, and Markdown-It setup with conditional KaTeX registration. The source excerpt shows a middleware function with nested method checks and JSON body parsing sitting inside the returned plugin object — that’s the cyclomatic complexity of 17 concentrated in request routing logic. One touch in the last 30 days plus a fan-out this high means any change here has to be reasoned about against a wide surface. The related handleHotUpdate function in the same file (fan-out 28, cyclomatic complexity 29, but zero touches in 113 days) is debt sitting next to this fire — worth reviewing together since they likely share HMR state.
exportSlides — packages/slidev/node/commands/export.ts
Fan-out of 118 is the highest number in this entire dataset, and this function was last changed the same day as this snapshot. The source shows why: a long parameter list (20 named options with defaults) drives an if/else if chain across four export formats — pdf, png, md, pptx — each delegating to a genPagePdf/genPagePng/genPageMd/genPagePptx helper, plus an inner go(no, clicks) closure that builds Playwright navigation URLs with query params for range, clicks, and print mode. That’s a god-function pattern by name: one entry point orchestrating browser launch, page navigation, and format-specific rendering. Two touches in 30 days on a function this coupled is the definition of live regression risk — a change to one export format’s branch can affect the shared browser/page/progress lifecycle used by all four. I’d split the per-format generation (genPagePdf, genPagePng, genPageMd, genPagePptx) out from the browser lifecycle management so the format switch isn’t sharing a function body with resource setup/teardown.
parseTimeString — packages/parser/src/timesplit/timestring.ts
Cyclomatic complexity of 21 is the highest raw complexity among the fire-quadrant functions here, and it comes from parsing timestamps in multiple formats — numeric seconds, h:m:s colon notation with a length-based branch (3 parts, 2 parts, 1 part, else throw), and a unit-suffix format resolved through an 18-entry unitMap (s/sec/secs, m/min/mins, h/hr/hrs/hour/hours, day/days, week/weeks, month/months, year/years) walked via regex matchAll. Each format branch also throws its own TypeError on invalid input, which is where the exit-heavy pattern comes from — multiple distinct failure paths that all need test coverage. One touch in the last 30 days on a function this branchy in a parsing library is worth watching closely, since silent unit-mapping mistakes here would misplace slide timing rather than crash loudly. I’d pull the colon-notation parsing and the unit-suffix parsing into two separate functions — they don’t share logic beyond the final seconds accumulation.
render — packages/client/builtin/VClicks.ts
This is Vue render-function logic for the v-click building block, and the source shows recursive helpers (openAllTopLevelSlots, mapSubList, mapChildren) walking VNode trees with depth tracking and index bookkeeping (globalIdx, execIdx) to assign click-reveal order across nested lists. Nesting depth of 5 combined with two mutually recursive closures (mapSubList calling mapChildren and vice versa, per the no-use-before-define eslint-disable comment) is a legitimate source of subtle bugs in TypeScript — recursive VNode traversal with running counters is hard to unit test in isolation because the state threads through every call. Two touches in 30 days plus cyclomatic complexity of 18 makes this a function I’d write characterization tests for before the next feature request touches click-animation behavior.
One function just outside the top five deserves a mention for contrast: handler in packages/slidev/node/vite/contextInjection.ts sits at an activity-weighted risk of 15.29, cyclomatic complexity 19, nesting depth 5, and is also fire-quadrant with one touch in 30 days — it’s effectively tied with parseTimeString and belongs in the same review pass even though it didn’t make the top five cutoff. And resolveShikiOptions in shiki-options.ts is worth flagging as the highest raw complexity in the debt quadrant — cyclomatic complexity 31, nesting depth 7, 113 days since last touched — dormant now but expensive whenever syntax highlighting options next need to change.
Patterns Found
Antipatterns detected across the top functions in this snapshot:
| Pattern | Occurrences |
|---|---|
god_function | 10 |
complex_branching | 8 |
long_function | 8 |
deeply_nested | 7 |
exit_heavy | 7 |
These labels belong to two tiers — Tier 1 (structural): complex_branching, deeply_nested, exit_heavy, long_function, god_function. Tier 2 (relational/temporal): hub_function, cyclic_hub, middle_man, neighbor_risk, stale_complex, churn_magnet, shotgun_target, volatile_god.
See more analyses with these patterns: complex_branching, deeply_nested, exit_heavy, god_function, long_function.
Reproduce This Analysis
git clone https://github.com/slidevjs/slidev
cd slidev
git checkout 36f8a896618cc243be82577b77658d7ed1b122a5
hotspots analyze . --mode snapshot --explain-patterns --force
To run the same analysis on your own codebase, run hotspots analyze . --mode snapshot in any local git repo — no configuration required.
I use Hotspots to highlight structural and activity risk — not “bad code.” I treat these findings as a prioritization aid, not a bug predictor. Editorial policy →