perf: externalize d3 and eliminate runtime hot-path waste - #115
Conversation
Remove noExternal d3 inlining and the bun-symlink-resolver esbuild plugin from core/engine/vanilla tsup configs so consumers' bundlers dedupe and tree-shake d3 modules. Line-only consumer bundle drops from 549.8KB min / 179.2KB gz to 508.5KB / 161.6KB, with one d3-format and one d3-array copy instead of duplicates, and sankey/geo/force tree-shaken to zero for non-users. The graph simulation worker stays self-contained. Claude-Session: https://claude.ai/code/session_017u5r9Db2Yw3HTeKqawiHqN
Hoist constant-spec d3-format instances and compiled d3-time-format formatters to module scope instead of re-parsing per call, and precompute the per-weight table objects and line-measure helper in estimateTextWidth, the hottest layout function. Output is unchanged; formatter caching assumes the default d3 locale (documented at the cache site). Claude-Session: https://claude.ai/code/session_017u5r9Db2Yw3HTeKqawiHqN
Fuse field extraction and parsing in scale building into single-pass helpers, replace Math.max spreads over category sums with loop accumulation (also avoids the >65k-argument RangeError), build color domains without intermediate arrays, pass lazily-iterated field values to format-context computation, and hoist the row-invariant encoded-fields set out of the per-row tooltip title path. Layout output is unchanged. Claude-Session: https://claude.ai/code/session_017u5r9Db2Yw3HTeKqawiHqN
…nsitions Build color interpolators once per tween, cache resolved rect/line/area/ rule/tick child elements at tween construction (shared by the per-frame step and the final snap), reuse point-interpolation buffers and the partial-corner scratch object across frames, and replace find() scans in gridline/tick matching with first-match-guarded Maps. Frames and final states are unchanged. Claude-Session: https://claude.ai/code/session_017u5r9Db2Yw3HTeKqawiHqN
Share one point-circle query and parsed coordinate pass between the voronoi and endpoint-label passes in the SVG renderer, replace find() scans in map/tilemap/sankey mounts with id-keyed Maps, drop per-mark split() allocations in series lookup, and skip ResizeObserver deliveries whose size is unchanged (first delivery always fires). Rendered DOM is unchanged. Claude-Session: https://claude.ai/code/session_017u5r9Db2Yw3HTeKqawiHqN
Move the four duplicated fieldIterable generators into layout/shared.ts, and replace the single-entry Map in observeResize with a plain variable since each observer watches exactly one element. Claude-Session: https://claude.ai/code/session_017u5r9Db2Yw3HTeKqawiHqN
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (24)
💤 Files with no reviewable changes (4)
Included review availability: Your plan provides up to 1 included review per hour; 0 remain after this review. 📝 WalkthroughWalkthroughThe change reduces repeated allocations and lookups across formatting, engine layout, tooltips, SVG rendering, and transitions. It also removes Bun-specific build resolution and external-bundling configuration. ChangesFormatting and engine data paths
Vanilla rendering and build paths
Estimated code review effort: 4 (Complex) | ~45 minutes Merge Risk: ⚪ Minimal · up to The PR reduces bundle size and runtime overhead while preserving rendered output; cached date formatters may retain the locale active at first use, so the compatibility documentation should explicitly cover that behavior. This is a bounded follow-up, and no actionable merge-blocking risk remains. Sequence Diagram(s)sequenceDiagram
participant TweenSetup
participant TransitionApply
participant SVGElements
TweenSetup->>TransitionApply: provide cached tween elements and buffers
TransitionApply->>SVGElements: update geometry and colors
TransitionApply->>SVGElements: apply final snapped state
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches 💡 1📝 Generate docstrings 💡
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Summary
Zero-behavior-change performance pass: bundle weight and runtime speed improve, rendered output is identical (all 80 visual baselines byte-identical, no snapshot updates).
Bundle: externalize d3 (backlog item 2)
Removed
noExternal: [/^d3-/]inlining and thebun-symlink-resolveresbuild plugin from core/engine/vanilla tsup configs, so d3 resolves from consumers' node_modules and their bundlers can dedupe and tree-shake it.formatDefaultLocale()calls are unsupported; d3-sankey's legacy d3-array@2/d3-shape@1 remain duplicated (different majors); core's dist is no longer standalone-loadable without an import mapRuntime (same-output optimizations)
estimateTextWidthnew Date(String(v))andNumber('')quirks);Math.max(...spread)→ loop (also removes a >65k-category RangeError); lazily-iterated format-context inputs; row-invariant tooltip set hoisted out of per-row paths.findscans → first-match-guarded MapsDeliberately NOT changed (verified as behavior-load-bearing): the JSON.stringify spec-diff guards in the React/Vue/Svelte wrappers (they detect same-object mutation and register deep reactivity), and getBoundingClientRect calls in mouse handlers.
Verification
bun run build && bun run test && bun run lint && bun run typecheckgreen (4419 tests), plustest:visual80/80 with zero pixel diffs,test:invariants120/120,test:perf2/2. Tarball smoke install (core+engine+vanilla packed, npm-installed with happy-dom) renders a static line chart and compiles sankey + geo specs against externalized d3. Reviewed by a code-review agent; both findings addressed in the final commit.https://claude.ai/code/session_017u5r9Db2Yw3HTeKqawiHqN
Summary by CodeRabbit
Performance
Interaction Improvements