Conversation
- 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.
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).
🦋 Changeset detectedLatest commit: 8379d86 The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
Contributor
Bundle Size AnalysisUse-Case ScenariosFoundation
Cartesian charts
Geo
Hierarchy
Graph / network
Worst case
Understanding this report
|
commit: |
Contributor
built with Refined Cloudflare Pages Action⚡ Cloudflare Pages Deployment
|
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.
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.
# Conflicts: # bundle-analyzer/bundle-reports/latest.json
…oltip
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.
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%).
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%).
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%).
… 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.
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).
…erchart 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/`.
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.
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.
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.
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.
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.
This was referenced Apr 27, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.