Skip to content

Commit 2c21908

Browse files
techniqCopilot
andauthored
Reduce bundle size: Layer-specific components (#848)
* split Circle.svelte into 3 layer-specific components (Circle.svg.svelte, etc) along with CircleState (Circle.shared.svelte.ts). Keep Circle.svelte and delegate to underlying type * split Text.svelte into 3 layer-specific components * Organize into component directories * split Rect, Line, and Path into 3 layer-specific components * split Ellipse, Polygon, Group, Image, ClipPath, Pattern, LinearGradient, and RadialGradient into 3 layer-specific components * Add changeset for per-layer primitive * split Axis into 3 layer-specific components * Add full-chart layer examples (to monitor progress) * split Grid and Rule into 3 layer-specific components * split Chart and ChartChildren into 3 layer-specific components, and alias Layer based on layer type (Layer within layerchart/svg => Svg) * refine bundle scenarios * refine bundle scenarios * Add more foundation examples Co-authored-by: Copilot <copilot@github.com> * improve bundle comment * split Highlight, ChartClipPath, RectClipPath into 3 layer-specific components * split Arc, Area, and Spline into 3 layer-specific components * split ArcLabel, Bar, Bars, Labels, Pie, and Points into 3 layer-specific components * split Frame, Cell, Threshold, AnnotationLine, AnnotationPoint, and Trail into 3 layer-specific components * split AnnotationRange, CircleClipPath, Vector, Link, Hull, Density, and Calendar into 3 layer-specific components * split BoxPlot, Violin, Raster, Month, Contour, and Voronoi into 3 layer-specific components * split geo components (GeoPath, GeoSpline, etc) into 3 layer-specific components * split Ribbon into 3 layer-specific components. Re-export all layout/helper components * split high-level charts (BarChart, LineChart, etc) into 3 layer-specific components. Re-export all layout/helper components * update changeset and docs * Update bundle size PR comment with collapsible sections * Remove `Svg` from core (just Chart) * Split `Foundation` scenarios into `Core (agnostic)` and `Core (layer-specific)` * update bundle report * Lazy load transform context (and only when used) similar to brush context * Add ChartCore. Fix TransformContext test * update bundle sizes in docs * move core section above base * Fix primitive fill/stroke for canvas primitives * fix(Text): Render on `<Svg>` layer when only one of `x`/`y` is set The static-mode render guard required both `x` and `y` to be explicit, so `<Text y={-6}>` inside a positioned `<Group>` (e.g. tooltip labels in the GeoPoint world-capitals example) was skipped on the Svg layer — Canvas worked because it doesn't gate on coordinate validity. Change `&&` to `||` so Text renders when either coordinate is set; the missing one falls through to the existing `motionX`/`motionY` default of 0, matching SVG's natural "missing coord = 0" behavior and the Canvas variant. * fix components missing html impls (Calendar, Bars, Annotation*, etc) * Fix Text within group --------- Co-authored-by: Copilot <copilot@github.com>
1 parent 9c074ba commit 2c21908

461 files changed

Lines changed: 24167 additions & 15391 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
---
2+
'layerchart': minor
3+
---
4+
5+
feat: Per-layer variants for primitives, compound marks, and high-level charts (`layerchart/svg`, `layerchart/canvas`, `layerchart/html`)
6+
7+
Layer-agnostic components auto-detect the surrounding `<Svg>`, `<Canvas>`, or `<Html>` layer and bundle every render path. The new sub-path exports expose layer-specific variants so consumers committed to a single rendering layer can opt into a smaller bundle.
8+
9+
```ts
10+
// Default: agnostic, dispatches at runtime — works in any layer
11+
import { Rect, Circle, Text, Path, LineChart } from 'layerchart';
12+
13+
// SVG-only — skips canvas + html branches
14+
import { Rect, Circle, Text, Path, LineChart } from 'layerchart/svg';
15+
16+
// Canvas-only
17+
import { Rect, Circle, Text, LineChart } from 'layerchart/canvas';
18+
19+
// HTML-only — drops canvas + svg overhead (some primitives are ~95% smaller)
20+
import { Rect, Circle, Text, Pattern, LinearGradient } from 'layerchart/html';
21+
```
22+
23+
Each agnostic component (e.g. `Rect.svelte`) now dispatches to the corresponding per-layer variant under the hood (`Rect.svg.svelte`, `Rect.canvas.svelte`, `Rect.html.svelte`) — no breaking change for existing consumers.
24+
25+
### What's split
26+
27+
**Primitives (13)** — the basic graphics building blocks
28+
`Circle`, `Text`, `Rect`, `Line`, `Path`, `Ellipse`, `Polygon`, `Group`, `Image`, `ClipPath`, `Pattern`, `LinearGradient`, `RadialGradient`
29+
30+
**Compound marks (~30)** — chart axes, marks, annotations, and chart-relative shapes
31+
`Axis`, `Grid`, `Rule`, `Highlight`, `Layer`, `ChartChildren`, `ChartClipPath`, `CircleClipPath`, `Bars`, `Bar`, `Spline`, `Area`, `Pie`, `Arc`, `ArcLabel`, `Points`, `Cell`, `Frame`, `Threshold`, `Trail`, `Vector`, `Link`, `Labels`, `AnnotationLine`, `AnnotationPoint`, `AnnotationRange`, `Hull`, `Density`, `Voronoi`, `Contour`, `Raster`, `Violin`, `BoxPlot`, `Calendar`, `Month`
32+
33+
**Geo components (`layerchart/geo`)**
34+
`GeoPath`, `GeoSpline`, `GeoPoint`, `GeoCircle`, `GeoTile`, `TileImage`, `Graticule`, `GeoClipPath`, `GeoEdgeFade`
35+
36+
**Graph components (`layerchart/graph`)**
37+
`Ribbon`
38+
39+
**High-level chart wrappers** — pre-composed charts with built-in tooltips, highlights, and series handling
40+
`LineChart`, `AreaChart`, `BarChart`, `ScatterChart`, `PieChart`, `ArcChart`
41+
42+
The geo, graph, hierarchy, and force sub-paths also re-export every layer-agnostic helper they previously included, so a single `from 'layerchart/svg'` import covers a typical SVG chart end-to-end without falling back to `'layerchart'`.
43+
44+
### Standout per-layer wins (gz, vs agnostic baseline)
45+
46+
**Primitives where the per-layer rendering is dramatically simpler:**
47+
- `Pattern` html: 14.81 → 0.92 KB (-94%) — HTML implementation is just CSS-string generation
48+
- `LinearGradient` html: 14.38 → 0.53 KB (-96%)
49+
- `Image` canvas: 14.95 → 3.73 KB (-75%)
50+
- `Text` svg/html: 29.13 → ~16 KB (-45%)
51+
- `Circle` / `Rect` / `Ellipse` / `Line` / `Path`: ~22–27% smaller per-layer
52+
53+
**Compound marks:** typically 8–15% gz savings per-layer; outliers like `Highlight` (-30% canvas) and `Cell` (-22% svg) are larger because their HTML/canvas vs. SVG paths diverge significantly.
54+
55+
**High-level charts:** ~5–12% gz savings (~5–11 KB) when imported from `layerchart/svg` or `layerchart/canvas`. A single-layer LineChart drops from 89.6 KB → 79.0 KB gz on the SVG path.
56+
57+
For a consumer who migrates all imports to a single layer, cumulative savings across primitives and compound marks are 60–80 KB gz.
58+
59+
### Bundle reductions on the default `<Chart>` path
60+
61+
In addition to opt-in per-layer variants, this release also makes a few previously-eager features lazy:
62+
63+
- **`<TransformContext>`** is now dynamically imported when `<Chart transform={...}>` is set — saves ~2.8 KB gz on every chart that doesn't pan/zoom.
64+
- **`<BrushContext>`** was already lazy; nothing changes there.
65+
66+
### `<ChartCore>` for non-cartesian charts (new)
67+
68+
A new `<ChartCore>` component is exported alongside `<Chart>` from each layer sub-path (`layerchart`, `layerchart/svg`, `layerchart/canvas`, `layerchart/html`). It provides the chart context, sizing, brush, transform, and tooltip plumbing — but skips `<ChartChildren>` and the `Layer` / `Axis` / `Grid` / `Rule` / `Highlight` / `ChartClipPath` import chain it pulls in.
69+
70+
Use it for geo maps, custom layouts, or any chart that renders its own primitives directly via the `children` snippet:
71+
72+
```svelte
73+
<script>
74+
import { ChartCore, Svg, GeoProjection, GeoPath } from 'layerchart/svg';
75+
</script>
76+
77+
<ChartCore data={countries}>
78+
{#snippet children({ context })}
79+
<Svg>
80+
<GeoProjection projection={geoMercator} fitGeojson={countries}>
81+
<GeoPath geojson={countries} fill="steelblue" />
82+
</GeoProjection>
83+
</Svg>
84+
{/snippet}
85+
</ChartCore>
86+
```
87+
88+
Measured savings (bundle scenarios):
89+
- `base` (`<Chart>`) → `core` (`<ChartCore>`): 83.42 → 50.93 KB gz (**−39%**)
90+
- `geo` (`<Chart>` + `GeoPath`/`GeoPoint`) → `core-geo` (`<ChartCore>` + `GeoProjection` + `GeoPath`): 87.23 → 54.67 KB gz (**−37%**)
91+
- `base-svg` (per-layer) → `core-svg` (per-layer): 77.37 → 50.88 KB gz (**−34%**)
92+
93+
### Behavior
94+
95+
Identical to the agnostic versions: visual output, props, types, and bindable refs all match. The dispatcher pattern adds ~0.2 KB per primitive to `core` for users on the agnostic API (transitive cost from `Highlight` / `Axis` / `Chart`) — a worthwhile tradeoff for the opt-in per-layer savings.
96+
97+
See the updated ["Bundle Size" guide](https://layerchart.com/docs/guides/bundle-size) for the full table, tradeoffs, and when to opt into per-layer imports.

bundle-analyzer/README.md

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,18 +28,22 @@ Svelte runtime is excluded from measurements since it's shared across all compon
2828

2929
## Scenarios
3030

31-
Scenarios are defined in [`define-scenarios.ts`](./define-scenarios.ts) and represent real-world usage patterns:
31+
Scenarios are defined in [`bundle-scenarios.ts`](./bundle-scenarios.ts) and represent real-world usage patterns:
3232

3333
| Scenario | Description |
3434
|----------|-------------|
35-
| `core` | Bare minimum: `Chart` + `Svg` |
35+
| `base` | Full `Chart` (with the cartesian frame: Axis, Grid, Rule, Highlight) |
36+
| `base-svg` / `base-canvas` / `base-html` | `Chart` from `layerchart/svg`, etc. |
37+
| `core` | Bare-bones `ChartCore` (no Axis/Grid/Rule/Highlight) |
38+
| `core-svg` / `core-canvas` / `core-html` | `ChartCore` from `layerchart/svg`, etc. |
39+
| `core-geo` / `core-line` / `core-scatter` | `ChartCore` + manual primitives (geo / spline / points) |
3640
| `line-chart` | Line chart with axes and grid |
3741
| `line-chart-interactive` | Line chart with tooltip and highlight |
3842
| `area-chart` | Area chart with axes |
3943
| `bar-chart` | Bar chart with axes |
4044
| `scatter-chart` | Scatter plot with points |
4145
| `pie-chart` | Pie/donut chart with arcs |
42-
| `high-level-charts` | All high-level chart components |
46+
| `LineChart` / `AreaChart` / `BarChart` / etc. | High-level chart wrappers |
4347
| `geo` | Geographic map with paths |
4448
| `geo-tiles` | Geographic map with tile layer |
4549
| `geo-full` | All geo components |
@@ -50,7 +54,6 @@ Scenarios are defined in [`define-scenarios.ts`](./define-scenarios.ts) and repr
5054
| `dagre` | Dagre directed graph |
5155
| `sankey` | Sankey flow diagram |
5256
| `chord` | Chord diagram |
53-
| `canvas` | Canvas-based rendering |
5457
| `all` | Everything from layerchart |
5558

5659
## CLI options
@@ -83,7 +86,7 @@ Two GitHub Actions workflows automate bundle tracking:
8386

8487
## Adding scenarios
8588

86-
Edit [`define-scenarios.ts`](./define-scenarios.ts) to add new scenarios to the `scenarios` array:
89+
Edit [`bundle-scenarios.ts`](./bundle-scenarios.ts) to add new scenarios to the `scenarios` array:
8790

8891
```ts
8992
{

bundle-analyzer/bundle-analyzer.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import {
1616
getScenarios,
1717
getComponentScenarios,
1818
type Scenario,
19-
} from "./define-scenarios.js";
19+
} from "./bundle-scenarios.js";
2020

2121
const __filename = fileURLToPath(import.meta.url);
2222
const __dirname = dirname(__filename);
@@ -190,9 +190,12 @@ import * as LayerChartGraph from "layerchart/graph";
190190
`;
191191
} else {
192192
// Group imports by source module (root vs each sub-path).
193+
// Per-scenario `layers` win over the default mapping —
194+
// used to test layer-specific variants like `layerchart/svg`.
193195
const groups = new Map<string, string[]>([["layerchart", []]]);
194196
for (const name of scenario.imports) {
195-
const sub = SUBPATH_FOR_COMPONENT[name];
197+
const overrideSub = scenario.layers?.[name];
198+
const sub = overrideSub ?? SUBPATH_FOR_COMPONENT[name];
196199
const mod = sub ? `layerchart/${sub}` : "layerchart";
197200
const list = groups.get(mod) ?? [];
198201
list.push(name);

0 commit comments

Comments
 (0)