Commit 888c990
authored
Reduce bundle size (#845)
* Initialize bundle analyzer package
* update bundle baseline
* Prepare ChartChildren and Chart for tree-shaking
- Add `"sideEffects": ["**/*.css"]` to layerchart/package.json so downstream bundlers can prune unused barrel re-exports
- Convert ChartChildren's value imports of components only referenced in `ComponentProps<typeof X>` to `import type` (Area, Arc, Bars, BrushContext, Group, Line, Pie, Spline, TooltipContext)
- Inline `geoFitObjectTransform` into Chart.svelte to drop the static import edge through `$lib/utils/geo.js` (which transitively imports d3-geo)
No visible change in the bundle analyzer (it already does aggressive treeshaking), but unlocks the dynamic-import refactor in the next commit and protects consumers whose bundlers tree-shake less aggressively.
* Lazy-load conditionally-rendered components in Chart
Convert statically-imported components to `{#await import(...)}` so they only ship to users who opt in via the corresponding prop:
- ChartChildren: ChartAnnotations (when annotations.length > 0), DefaultTooltip (tooltipContext truthy), Labels, Legend, Points
- TooltipContext: Voronoi (mode === 'voronoi'), Arc (radial bounds/band)
Voronoi alone removes d3-geo-voronoi and its transitive d3-geo from the always-loaded bundle.
Update the bundle analyzer to sum the entry chunk plus all chunks reachable via static imports (lazy chunks excluded), so the reported size reflects up-front cost rather than total feature surface.
Result: `core` (`Chart` + `Svg`) drops 154.94 → 109.95 KB gz (-29%). Comparable savings on every scenario except `all` (which exercises every lazy path).
* Add `pnpm bundle:visualize` to generate treemaps using rollup-plugin-visualizer
* Fix BarChart legend toggle test for lazy-loaded Legend
The test queried `.lc-legend-swatch-button` synchronously, but `Legend` is now dynamically imported inside `ChartChildren` and isn't in the DOM until the chunk resolves. Wrap the query in `vi.waitFor` so the test waits for the buttons to mount before clicking.
* Fix analyzer footgun: don't overwrite latest.json on filtered runs
A filtered run (e.g. `pnpm bundle:visualize -- core`) was overwriting `bundle-reports/latest.json` with just the filtered scenarios, causing the PR comparison comment to show "0 KB" for every scenario the filtered run didn't cover. Now `latest.json` is only updated when no `--components`, scenario, or component filters are passed; filtered runs still get a timestamped report. Also regenerate the full baseline against the current lazy-loaded code.
* Fix CI test failure by avoiding tooltip namespace barrel in DefaultTooltip
DefaultTooltip is dynamically imported from ChartChildren. It was using `import * as Tooltip from '../tooltip/index.js'`, which dragged the entire tooltip barrel — including TooltipContext.svelte (already in the static graph via Chart.svelte) — into its lazy chunk. Under CI's resource-constrained dev server this broke the DefaultTooltip browser test ("Failed to fetch dynamically imported module"). Replace the namespace import with explicit named imports of just the 5 components actually used (no Context). The local `const Tooltip = { Root, Header, List, Item, Separator }` keeps the existing template syntax (`<Tooltip.Root>`, etc.) unchanged. Bonus: tightens tree-shaking — `all` scenario drops ~3 KB gz.
* Revert DefaultTooltip lazy-load to fix vitest-browser CI test
The dynamic import of `DefaultTooltip` from `ChartChildren` caused a CI-only "Failed to fetch dynamically imported module" failure in `DefaultTooltip.svelte.test.ts`. Local tests passed; only the Linux/playwright runner reproduced. Switching the inner `import * as Tooltip` namespace to named imports (commit 7e5d6e7) didn't help. The savings were small (~5 KB gz on `core`) and not worth the test instability — the other lazy-loads (Voronoi, Arc, ChartAnnotations, Labels, Legend, Points) remain. Net Phase 2 gain on `core` is now -39 KB gz (-25%) instead of -45 KB gz (-29%).
* Revert ChartChildren lazy-loads; keep TooltipContext lazy-loads
The DefaultTooltip vitest-browser test still failed in CI ("Failed to fetch dynamically imported module") even after reverting just DefaultTooltip's lazy-load and after switching the inner namespace import to named imports. Rather than continue narrowing, revert all 4 ChartChildren lazy-loads (Labels, Legend, Points, ChartAnnotations). The TooltipContext lazy-loads (Voronoi, Arc) stay — they're the biggest win (~17 KB gz on core from removing d3-geo-voronoi + d3-geo from the static graph) and aren't in the test failure path. Net Phase 2 gain on `core` is now -17 KB gz (-11%).
* Switch ChartChildren lazy-loads from `{#await}` to `$effect`
The `{#await import('./X.svelte')}` template pattern broke `DefaultTooltip.svelte.test.ts` in CI ("Failed to fetch dynamically imported module" on the test file). Local tests passed; only the Linux/playwright runner reproduced. Move the dynamic imports back into `$effect` blocks (script-side `import()`), which Vite/vitest-browser appears to handle differently. Same chunks, same bundle savings, but the dynamic imports live in regular JS rather than Svelte template syntax. TooltipContext keeps `{#await}` for Voronoi/Arc — those weren't in the test failure path. Net Phase 2 gain on `core` recovered to -40 KB gz (-25%).
* Refactor lazy-loading to a `<Lazy>` wrapper with prop-spread + `then` snippet
Replace per-component `$state`/`$effect`/conditional-render boilerplate in ChartChildren with a generic `<Lazy>` component that takes a `load` factory and either spreads remaining props to the loaded component (single-render case: ChartAnnotations, Legend) or passes it via a `then` snippet (loop case: Points, Labels). Conditional gating uses standard `{#if}` outside `<Lazy>` rather than a `when` prop. The snippet is named `then` (not `children`) to avoid collisions with loaded components that have their own `children` prop. Same bundle behavior and CI-friendly `$effect`-under-the-hood as the previous explicit pattern; ~half the lines per lazy-load.
* Use `{#await import(...)}` for lazy-loads; remove `<Lazy>` wrapper
Now that `optimizeDeps.include: ['d3-interpolate']` prevents the mid-test Vite reload (the actual cause of the CI flake), the cleaner inline `{#await import('./X.svelte') then { default: X }}` pattern works fine in CI. Revert ChartChildren back to that pattern, matching what TooltipContext already does. Remove `Lazy.svelte` since it's no longer needed. Same chunks, same ~25% savings on `core` (115.60 KB gz).
* Add `build` script alias so CI's `build:packages` actually builds layerchart
The bundle analysis CI workflow runs `pnpm build:packages` (which calls `pnpm --filter './packages/*' build`) before `pnpm bundle:analyze`. layerchart only had a `package` script (svelte-package convention), no `build`, so the filter call did nothing in CI — the analyzer ran against an empty `dist/` and produced 0-byte sizes for every scenario. That's why the PR comment showed "0.00 KB" for "New". Add a `build` alias for `svelte-package` (kept alongside existing `package` for back-compat) so CI's existing build step now actually rebuilds layerchart's `dist/`.
* Add percentage change to bundle analysis PR comment
The script already computed `sizePercent` and `gzipSizePercent`; now display them inline in the Change column alongside the raw KB deltas (e.g. `-160.00 KB (-25.1%)`). Easier to scan relative impact across scenarios than raw byte counts alone. Same for the Individual Components table.
* Group bundle analysis scenarios in PR comment by category
Add a `group` field to scenarios (Foundation, Cartesian charts, Geo, Hierarchy, Graph / network, Worst case) and render the PR comment with one sub-table per group instead of one alphabetized list. Reorder `define-scenarios.ts` to put `core` first and scenarios within their category. Remove the alphabetical sort in `analyzeChanges` so the comment preserves the natural order.
* Move heavy-dep components into sub-path exports
Remove from root `layerchart`: `Geo*` + `Graticule` + `TileImage`, `Tree`/`Treemap`/`Pack`/`Partition`, `ForceSimulation`, `Dagre`/`Sankey`/`Chord`/`Ribbon`. Each group now lives in its own folder + sub-path entry: `layerchart/geo`, `layerchart/hierarchy`, `layerchart/force`, `layerchart/graph`.
Defends against bundlers that don't tree-shake the root barrel cleanly — `@dagrejs/dagre` (~22 KB), `d3-geo` (~15 KB), `d3-force` (~7 KB), `d3-hierarchy` (~6 KB), `d3-sankey` (~6 KB), and `d3-chord` (~2 KB) are now reachable only via opt-in imports. Per-scenario bundle sizes are unchanged for already-good consumers; the worst-case `all` scenario drops 241.8 → 235.5 KB gz.
`Voronoi`/`Hull` stay at root (already lazy via `TooltipContext`). `Contour`/`Density`/`Raster`/`BoxPlot`/`Violin`/`Threshold` also stay (not category-specific). High-level charts (`LineChart`, `BarChart`, etc.) remain at root.
Breaking: imports for the moved components must move to the new sub-paths.
* Lazy-load opt-in features in core path
Three components that everyone pays for in `core` today, but only some users actually need:
- `Spline` in `Grid` (radial linear grid lines only — non-radial users never render it)
- `Bar` in `Highlight` (only when user sets `bar` prop, default `false`)
- `BrushContext` in `Chart` (only when user sets `brush` prop, default `undefined`) — required splitting the inner `<TooltipContext>` tree across the brush/no-brush branches; brush tests now wait for the lazy chunk via a new `awaitBrushReady` helper
Saves ~4 KB gz on `core` (115.60 → 111.31 KB) and similar on every cartesian/geo/graph/hierarchy scenario. ~28% total reduction on `core` vs the pre-Phase-1 baseline.
Also switch `@layerstack/svelte-actions` imports from the barrel to sub-paths (`/styles`, `/portal`). No bundle effect since tree-shaking already stripped the unused `popover.js`, but it stops the Svelte REPL/CDN from eagerly fetching `@floating-ui/dom` (popover's transitive dep) when users load `layerchart` from a CDN.
* add changeset for lazy loading opt-in features (brush, radial spline, etc)
* lazy load d3-quadtree and DefaultTooltip based on usage
* Render chart subtree during BrushContext lazy load
The previous structure put `<TooltipContext>` + `<ChartChildren>` *inside* the `{#await import('./BrushContext.svelte')}` block, so on slow networks the entire chart was blocked on the chunk fetch (~300-1000ms on Fast 4G). Move the same subtree into the `{#await}`'s pending branch as well so the chart paints immediately; the `{:then}` branch then re-mounts it inside `BrushContext` once the chunk arrives.
Trade-off: brief one-time re-mount of `TooltipContext` + `ChartChildren` (~50ms) when the chunk lands. Acceptable because it happens before any user interaction (no tooltip/series state to lose) and brush is opt-in. Bundle savings preserved (core +0.1 KB from the duplicated template, since the actual modules are deduped).
The other lazy-load sites (`Voronoi`/`Arc` in `TooltipContext`, `DefaultTooltip`, `Bar`, `Points`/`Labels`/`Legend`/`ChartAnnotations` in `ChartChildren`, `Spline` in `Grid`) don't need the same treatment — none of them block visible chart content from rendering.1 parent 2cdf99d commit 888c990
355 files changed
Lines changed: 1537 additions & 1161 deletions
File tree
- .changeset
- bundle-analyzer
- bundle-reports
- docs
- src
- content
- components
- guides
- migrations
- utils
- examples
- components
- AnnotationLine
- AnnotationPoint
- AnnotationRange
- Arc
- AreaChart
- Area
- Axis
- BarChart
- BoxPlot
- BrushContext
- Cell
- Chord
- Circle
- Contour
- Dagre
- Density
- Ellipse
- ForceSimulation
- GeoCircle
- GeoClipPath
- GeoContext
- GeoLegend
- GeoPath
- GeoPoint
- GeoProjection
- GeoRaster
- GeoSpline
- GeoTile
- Graticule
- Group
- Hull
- Image
- LineChart
- Line
- Link
- Marker
- Pack
- Partition
- PieChart
- Polygon
- Raster
- Rect
- Rule
- Sankey
- Spline
- Text
- Treemap
- Tree
- Vector
- Violin
- utils/stats
- lib
- components
- controls
- llms
- routes
- api/charts
- geo
- sankey
- treemap
- tree
- docs
- components/[name]
- llms.txt
- examples
- search
- showcase
- examples/shadcn-svelte-1/src/lib/components/ui/chart
- packages/layerchart
- src/lib
- bench
- components
- __screenshots__/BrushContext.svelte.test.ts
- charts
- __screenshots__
- BarChart.svelte.test.ts
- DefaultTooltip.svelte.test.ts
- LineChart.svelte.test.ts
- force
- geo
- graph
- hierarchy
- tooltip
- utils
- graph
Some content is hidden
Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
24 | 24 | | |
25 | 25 | | |
26 | 26 | | |
| 27 | + | |
27 | 28 | | |
28 | 29 | | |
29 | 30 | | |
| |||
34 | 35 | | |
35 | 36 | | |
36 | 37 | | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
37 | 72 | | |
38 | 73 | | |
39 | 74 | | |
| |||
81 | 116 | | |
82 | 117 | | |
83 | 118 | | |
84 | | - | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
85 | 127 | | |
86 | 128 | | |
87 | 129 | | |
| |||
127 | 169 | | |
128 | 170 | | |
129 | 171 | | |
| 172 | + | |
130 | 173 | | |
131 | 174 | | |
132 | 175 | | |
| |||
137 | 180 | | |
138 | 181 | | |
139 | 182 | | |
140 | | - | |
| 183 | + | |
141 | 184 | | |
142 | | - | |
| 185 | + | |
| 186 | + | |
| 187 | + | |
| 188 | + | |
| 189 | + | |
143 | 190 | | |
144 | 191 | | |
145 | | - | |
146 | | - | |
| 192 | + | |
| 193 | + | |
| 194 | + | |
| 195 | + | |
| 196 | + | |
| 197 | + | |
| 198 | + | |
| 199 | + | |
| 200 | + | |
| 201 | + | |
| 202 | + | |
| 203 | + | |
| 204 | + | |
| 205 | + | |
| 206 | + | |
| 207 | + | |
| 208 | + | |
| 209 | + | |
| 210 | + | |
147 | 211 | | |
148 | 212 | | |
149 | 213 | | |
| |||
302 | 366 | | |
303 | 367 | | |
304 | 368 | | |
305 | | - | |
| 369 | + | |
306 | 370 | | |
307 | 371 | | |
308 | 372 | | |
309 | 373 | | |
310 | 374 | | |
311 | | - | |
312 | | - | |
| 375 | + | |
| 376 | + | |
| 377 | + | |
| 378 | + | |
313 | 379 | | |
314 | 380 | | |
| 381 | + | |
| 382 | + | |
| 383 | + | |
| 384 | + | |
| 385 | + | |
315 | 386 | | |
316 | 387 | | |
317 | 388 | | |
| |||
0 commit comments