@kahwee/sf-map-svg 4.0.0 → 4.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +5 -0
- package/README.md +2 -0
- package/dist/src/controller-types.d.ts +1 -0
- package/dist/src/explorer-core.js +77 -30
- package/dist/src/frame-scheduler.d.ts +11 -0
- package/dist/src/frame-scheduler.js +64 -0
- package/dist/src/marker-layer.d.ts +3 -0
- package/dist/src/marker-layer.js +27 -5
- package/dist/src/types.d.ts +1 -0
- package/docs/api-audit.md +2 -0
- package/docs/guide-bundle-report.md +2 -2
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,11 @@ User-visible changes are recorded here. Unreleased entries describe changes on `
|
|
|
4
4
|
|
|
5
5
|
## Unreleased
|
|
6
6
|
|
|
7
|
+
## 4.0.1 — 2026-09-28
|
|
8
|
+
|
|
9
|
+
- Reconcile marker updates by ID, preserving retained SVG nodes, picker options, keyboard focus, and entrance animations. Identical ordered updates no longer mutate the DOM; only new IDs animate in and removed markers cancel their entrances.
|
|
10
|
+
- Advance animated cameras and dependent label, marker, and cluster layout in one shared browser frame, with regression coverage for coalescing, cancellation, reentrant updates, and destruction.
|
|
11
|
+
|
|
7
12
|
## 4.0.0 — 2026-09-28
|
|
8
13
|
|
|
9
14
|
- Breaking: require Node 24 or newer for server rendering and development. Validate the minimum supported major alongside Node 26 in CI; browser runtime requirements are unchanged.
|
package/README.md
CHANGED
|
@@ -58,6 +58,8 @@ Construction options include `mode` (`basemap`, `neighborhoods`, or `districts`)
|
|
|
58
58
|
|
|
59
59
|
The controller also supports marker, neighborhood, and district selection; district year and style changes; source and mode changes; labels and touch navigation; screen projection; and typed `markerchange`, `neighborhoodchange`, `districtchange`, `districthover`, `districtactivate`, `districtyearchange`, `overlayactivate`, `clusteractivate`, `viewportchange`, and `mapresize` events. `destroy()` releases browser resources; operations after destruction throw.
|
|
60
60
|
|
|
61
|
+
`setMarkers()` reconciles by stable marker `id`: retained markers keep their DOM nodes, focus, and in-progress entrance animations; only new IDs animate in. Identical ordered marker updates are a visual no-op. Camera state and `viewportchange` events remain synchronous, while animated camera steps and their dependent label/marker layout commit in the same browser frame.
|
|
62
|
+
|
|
61
63
|
For a small guide, import `guideMapData` from `/guide/data` and pass it to `createMap`. The optional `/guide` entry also provides `createGuideMap`, `mountGuideMap`, and detailed-data loading for existing guide layouts. `/transit` provides the standalone schematic transit animation. See [examples](docs/EXAMPLES.md) and [consumer integration](docs/consumer-integration.md).
|
|
62
64
|
|
|
63
65
|
For the guide preset with the same controller API, use `createGuideController(options)` from `/guide` (or `/guide/map`). It accepts grouped `MapOptions` and returns `MapController`; `mountGuideController(shell, options)` enhances a `createGuideShell()` container. Both use the lightweight guide geography. Existing `createGuideMap` and `mountGuideMap` calls retain their element-based API.
|
|
@@ -75,6 +75,7 @@ export interface MapController {
|
|
|
75
75
|
configure(patch: MapConfiguration): void;
|
|
76
76
|
getConfiguration(): MapConfigurationSnapshot;
|
|
77
77
|
on<K extends keyof MapEvents>(type: K, listener: (detail: MapEvents[K]) => void): () => void;
|
|
78
|
+
/** Reconcile stable IDs; retained markers preserve nodes, focus, and entrance animations. */
|
|
78
79
|
setMarkers(markers: readonly MapMarker[]): void;
|
|
79
80
|
setOverlays(overlays: readonly MapOverlay[]): void;
|
|
80
81
|
selectMarker(id: string | null, options?: CameraOptions & {
|
|
@@ -6,6 +6,7 @@ import { createDistrictTransition } from './district-transition.js';
|
|
|
6
6
|
import { element, svgElement } from './dom.js';
|
|
7
7
|
import { fitBounds, interiorAnchor, projectedBounds } from './explorer-layout.js';
|
|
8
8
|
import { controlKeys, layerKeys, normalizeFeatures, validateSwitchPatch } from './features.js';
|
|
9
|
+
import { createFrameScheduler } from './frame-scheduler.js';
|
|
9
10
|
import { geometryPath } from './geometry.js';
|
|
10
11
|
import { createLabelRenderer } from './label-renderer.js';
|
|
11
12
|
import { createSFMapWithData, districtColors, getLayerPathsWithData } from './map-core.js';
|
|
@@ -504,7 +505,11 @@ export function createNeighborhoodExplorerCore(options = {}, data) {
|
|
|
504
505
|
let items = [];
|
|
505
506
|
let destroyed = false;
|
|
506
507
|
let initialized = false;
|
|
507
|
-
|
|
508
|
+
const frames = createFrameScheduler({
|
|
509
|
+
request: (callback) => requestAnimationFrame(callback),
|
|
510
|
+
cancel: (id) => cancelAnimationFrame(id),
|
|
511
|
+
render: drawLabels,
|
|
512
|
+
});
|
|
508
513
|
const markerRenderer = createMarkerLayer(markerLayer);
|
|
509
514
|
const cancelEntrances = markerRenderer.cancelEntrances;
|
|
510
515
|
const camera = createCamera({
|
|
@@ -512,8 +517,8 @@ export function createNeighborhoodExplorerCore(options = {}, data) {
|
|
|
512
517
|
write: setView,
|
|
513
518
|
duration: () => (initialized && features.motion ? features.motion.duration : 0),
|
|
514
519
|
reduced: () => reducedMotion.matches,
|
|
515
|
-
request:
|
|
516
|
-
cancel:
|
|
520
|
+
request: frames.request,
|
|
521
|
+
cancel: frames.cancel,
|
|
517
522
|
now: () => performance.now(),
|
|
518
523
|
});
|
|
519
524
|
const stopAnimation = camera.stop;
|
|
@@ -798,8 +803,7 @@ export function createNeighborhoodExplorerCore(options = {}, data) {
|
|
|
798
803
|
function scheduleLabels() {
|
|
799
804
|
if (destroyed)
|
|
800
805
|
return;
|
|
801
|
-
|
|
802
|
-
frame = requestAnimationFrame(drawLabels);
|
|
806
|
+
frames.invalidate();
|
|
803
807
|
}
|
|
804
808
|
function drawLabels() {
|
|
805
809
|
if (destroyed)
|
|
@@ -1274,10 +1278,16 @@ export function createNeighborhoodExplorerCore(options = {}, data) {
|
|
|
1274
1278
|
markerSelect.value = id ?? '';
|
|
1275
1279
|
for (const entry of markerItems) {
|
|
1276
1280
|
const active = entry.marker.id === id;
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
if (
|
|
1281
|
+
const pressed = String(active);
|
|
1282
|
+
const ring = active && features.selectedMarkerRing ? 'inline' : 'none';
|
|
1283
|
+
const fill = active ? selectedMarkerColor : (entry.marker.color ?? markerColor);
|
|
1284
|
+
if (entry.node.getAttribute('aria-pressed') !== pressed)
|
|
1285
|
+
entry.node.setAttribute('aria-pressed', pressed);
|
|
1286
|
+
if (entry.ring.getAttribute('display') !== ring)
|
|
1287
|
+
entry.ring.setAttribute('display', ring);
|
|
1288
|
+
if (entry.dot.getAttribute('fill') !== fill)
|
|
1289
|
+
entry.dot.setAttribute('fill', fill);
|
|
1290
|
+
if (active && markerLayer.lastChild !== entry.node)
|
|
1281
1291
|
markerLayer.append(entry.node);
|
|
1282
1292
|
}
|
|
1283
1293
|
scheduleLabels();
|
|
@@ -1294,41 +1304,78 @@ export function createNeighborhoodExplorerCore(options = {}, data) {
|
|
|
1294
1304
|
if (destroyed)
|
|
1295
1305
|
return;
|
|
1296
1306
|
validateMarkers(markers);
|
|
1307
|
+
const existing = new Map(markerItems.map((item) => [item.marker.id, item]));
|
|
1297
1308
|
const ids = new Set();
|
|
1298
1309
|
const next = markers.map((marker) => {
|
|
1299
1310
|
ids.add(marker.id);
|
|
1300
|
-
|
|
1311
|
+
const previous = existing.get(marker.id);
|
|
1312
|
+
const point = previous?.marker.lng === marker.lng && previous.marker.lat === marker.lat
|
|
1313
|
+
? previous.point
|
|
1314
|
+
: map.project([marker.lng, marker.lat]);
|
|
1315
|
+
return { marker: { ...marker }, point };
|
|
1301
1316
|
});
|
|
1317
|
+
const nextSelection = selectedMarker && ids.has(selectedMarker)
|
|
1318
|
+
? selectedMarker
|
|
1319
|
+
: (markers.find((marker) => marker.selected)?.id ?? null);
|
|
1320
|
+
const keys = ['id', 'lng', 'lat', 'label', 'selected', 'color', 'radius'];
|
|
1321
|
+
if (markerSelect.options.length > 0 &&
|
|
1322
|
+
nextSelection === selectedMarker &&
|
|
1323
|
+
next.length === markerItems.length &&
|
|
1324
|
+
next.every(({ marker }, index) => keys.every((key) => marker[key] === markerItems[index].marker[key])))
|
|
1325
|
+
return;
|
|
1302
1326
|
const focusedMarker = markerItems.find((item) => item.node === document.activeElement)?.marker
|
|
1303
1327
|
.id;
|
|
1304
1328
|
invalidateClusters();
|
|
1305
|
-
const
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1329
|
+
const options = new Map([...markerSelect.options].map((option) => [option.value, option]));
|
|
1330
|
+
for (const item of markerItems) {
|
|
1331
|
+
if (!ids.has(item.marker.id)) {
|
|
1332
|
+
markerRenderer.remove(item);
|
|
1333
|
+
options.get(item.marker.id)?.remove();
|
|
1334
|
+
}
|
|
1335
|
+
}
|
|
1336
|
+
if (!options.has('')) {
|
|
1337
|
+
const empty = element('option', 'No marker selected');
|
|
1338
|
+
empty.value = '';
|
|
1339
|
+
markerSelect.prepend(empty);
|
|
1340
|
+
}
|
|
1310
1341
|
markerItems = next.map(({ marker, point }, index) => {
|
|
1311
|
-
const
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1342
|
+
const previous = existing.get(marker.id);
|
|
1343
|
+
const item = previous ??
|
|
1344
|
+
markerRenderer.add(marker, point, index, {
|
|
1345
|
+
features,
|
|
1346
|
+
markerColor,
|
|
1347
|
+
selectedMarkerColor,
|
|
1348
|
+
reducedMotion: reducedMotion.matches,
|
|
1349
|
+
enter: true,
|
|
1350
|
+
});
|
|
1351
|
+
if (previous)
|
|
1352
|
+
markerRenderer.update(item, marker, point, index);
|
|
1353
|
+
const option = options.get(marker.id) ?? element('option');
|
|
1354
|
+
const label = marker.label ?? marker.id;
|
|
1355
|
+
if (option.textContent !== label)
|
|
1356
|
+
option.textContent = label;
|
|
1357
|
+
if (option.value !== marker.id)
|
|
1358
|
+
option.value = marker.id;
|
|
1359
|
+
if (markerSelect.children[index + 1] !== option)
|
|
1360
|
+
markerSelect.insertBefore(option, markerSelect.children[index + 1] ?? null);
|
|
1321
1361
|
return item;
|
|
1322
1362
|
});
|
|
1363
|
+
// Match input order without detaching unchanged nodes; selected markers remain on top.
|
|
1364
|
+
const ordered = markerItems.filter((item) => item.marker.id !== nextSelection);
|
|
1365
|
+
const selectedItem = markerItems.find((item) => item.marker.id === nextSelection);
|
|
1366
|
+
if (selectedItem)
|
|
1367
|
+
ordered.push(selectedItem);
|
|
1368
|
+
ordered.forEach((item, index) => {
|
|
1369
|
+
if (markerLayer.children[index] !== item.node)
|
|
1370
|
+
markerLayer.insertBefore(item.node, markerLayer.children[index] ?? null);
|
|
1371
|
+
});
|
|
1323
1372
|
markerLabel.hidden = !markerItems.length || controls.markerPicker === false;
|
|
1324
1373
|
if (markerLabel.firstChild)
|
|
1325
1374
|
markerLabel.firstChild.textContent = `${strings.chooseMarker ?? 'Choose marker'} (${markerItems.length})`;
|
|
1326
1375
|
syncFeatureControls();
|
|
1327
|
-
const
|
|
1328
|
-
? previous
|
|
1329
|
-
: (markers.find((marker) => marker.selected)?.id ?? null);
|
|
1376
|
+
const revision = markerRevision + 1;
|
|
1330
1377
|
selectMarker(nextSelection, { fit: false });
|
|
1331
|
-
if (focusedMarker)
|
|
1378
|
+
if (!destroyed && markerRevision === revision && focusedMarker)
|
|
1332
1379
|
(markerItems.find((item) => item.marker.id === focusedMarker)?.node ?? svg).focus({
|
|
1333
1380
|
preventScroll: true,
|
|
1334
1381
|
});
|
|
@@ -1598,7 +1645,7 @@ export function createNeighborhoodExplorerCore(options = {}, data) {
|
|
|
1598
1645
|
clusterEvents.abort();
|
|
1599
1646
|
canvas.style.touchAction = 'pan-y pinch-zoom';
|
|
1600
1647
|
observer?.disconnect();
|
|
1601
|
-
|
|
1648
|
+
frames.destroy();
|
|
1602
1649
|
labelRenderer.clear();
|
|
1603
1650
|
releaseDownloads();
|
|
1604
1651
|
},
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/** One browser frame for camera advancement and the dependent screen-space rendering. */
|
|
2
|
+
export declare function createFrameScheduler({ request, cancel, render, }: {
|
|
3
|
+
request: (callback: FrameRequestCallback) => number;
|
|
4
|
+
cancel: (id: number) => void;
|
|
5
|
+
render: () => void;
|
|
6
|
+
}): {
|
|
7
|
+
request(callback: FrameRequestCallback): number;
|
|
8
|
+
cancel(id: number): void;
|
|
9
|
+
invalidate(): void;
|
|
10
|
+
destroy(): void;
|
|
11
|
+
};
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/** One browser frame for camera advancement and the dependent screen-space rendering. */
|
|
2
|
+
export function createFrameScheduler({ request, cancel, render, }) {
|
|
3
|
+
let frame;
|
|
4
|
+
let pending;
|
|
5
|
+
let sequence = 0;
|
|
6
|
+
let dirty = false;
|
|
7
|
+
let flushing = false;
|
|
8
|
+
let destroyed = false;
|
|
9
|
+
function schedule() {
|
|
10
|
+
if (!destroyed && !flushing && frame === undefined && (pending || dirty))
|
|
11
|
+
frame = request(flush);
|
|
12
|
+
}
|
|
13
|
+
function flush(time) {
|
|
14
|
+
frame = undefined;
|
|
15
|
+
if (destroyed)
|
|
16
|
+
return;
|
|
17
|
+
flushing = true;
|
|
18
|
+
const next = pending;
|
|
19
|
+
pending = undefined;
|
|
20
|
+
try {
|
|
21
|
+
next?.callback(time);
|
|
22
|
+
if (!destroyed && dirty) {
|
|
23
|
+
dirty = false;
|
|
24
|
+
render();
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
finally {
|
|
28
|
+
flushing = false;
|
|
29
|
+
schedule();
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return {
|
|
33
|
+
request(callback) {
|
|
34
|
+
const id = ++sequence;
|
|
35
|
+
if (!destroyed) {
|
|
36
|
+
pending = { id, callback };
|
|
37
|
+
schedule();
|
|
38
|
+
}
|
|
39
|
+
return id;
|
|
40
|
+
},
|
|
41
|
+
cancel(id) {
|
|
42
|
+
if (pending?.id === id)
|
|
43
|
+
pending = undefined;
|
|
44
|
+
if (!pending && !dirty && frame !== undefined) {
|
|
45
|
+
cancel(frame);
|
|
46
|
+
frame = undefined;
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
invalidate() {
|
|
50
|
+
if (destroyed)
|
|
51
|
+
return;
|
|
52
|
+
dirty = true;
|
|
53
|
+
schedule();
|
|
54
|
+
},
|
|
55
|
+
destroy() {
|
|
56
|
+
destroyed = true;
|
|
57
|
+
if (frame !== undefined)
|
|
58
|
+
cancel(frame);
|
|
59
|
+
frame = undefined;
|
|
60
|
+
pending = undefined;
|
|
61
|
+
dirty = false;
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
}
|
|
@@ -7,11 +7,14 @@ export interface MarkerItem {
|
|
|
7
7
|
dot: SVGCircleElement;
|
|
8
8
|
hit: SVGCircleElement;
|
|
9
9
|
ring: SVGCircleElement;
|
|
10
|
+
title: SVGTitleElement;
|
|
10
11
|
}
|
|
11
12
|
/** Own marker visuals and entrances; selection/events remain with the controller. */
|
|
12
13
|
export declare function createMarkerLayer(layer: SVGGElement): {
|
|
13
14
|
cancelEntrances: () => void;
|
|
14
15
|
clear(): void;
|
|
16
|
+
remove(item: MarkerItem): void;
|
|
17
|
+
update(item: MarkerItem, marker: MapMarker, point: [number, number], index: number): void;
|
|
15
18
|
add(marker: MapMarker, point: [number, number], index: number, { features, markerColor, selectedMarkerColor, reducedMotion, enter, }: {
|
|
16
19
|
features: ReturnType<typeof normalizeFeatures>;
|
|
17
20
|
markerColor: string;
|
package/dist/src/marker-layer.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { svgElement } from './dom.js';
|
|
2
2
|
/** Own marker visuals and entrances; selection/events remain with the controller. */
|
|
3
3
|
export function createMarkerLayer(layer) {
|
|
4
|
-
const animations = new
|
|
4
|
+
const animations = new Map();
|
|
5
5
|
function cancelEntrances() {
|
|
6
|
-
for (const animation of animations)
|
|
6
|
+
for (const animation of animations.values())
|
|
7
7
|
animation.cancel();
|
|
8
8
|
animations.clear();
|
|
9
9
|
}
|
|
@@ -13,6 +13,24 @@ export function createMarkerLayer(layer) {
|
|
|
13
13
|
cancelEntrances();
|
|
14
14
|
layer.replaceChildren();
|
|
15
15
|
},
|
|
16
|
+
remove(item) {
|
|
17
|
+
animations.get(item.node)?.cancel();
|
|
18
|
+
animations.delete(item.node);
|
|
19
|
+
item.node.remove();
|
|
20
|
+
},
|
|
21
|
+
update(item, marker, point, index) {
|
|
22
|
+
if (item.point[0] !== point[0] || item.point[1] !== point[1])
|
|
23
|
+
item.node.setAttribute('transform', `translate(${point[0]},${point[1]})`);
|
|
24
|
+
const label = marker.label ?? marker.id;
|
|
25
|
+
if (item.title.textContent !== label) {
|
|
26
|
+
item.title.textContent = label;
|
|
27
|
+
item.node.setAttribute('aria-label', label);
|
|
28
|
+
}
|
|
29
|
+
if (item.node.style.getPropertyValue('--sf-marker-index') !== String(index))
|
|
30
|
+
item.node.style.setProperty('--sf-marker-index', String(index));
|
|
31
|
+
item.marker = marker;
|
|
32
|
+
item.point = point;
|
|
33
|
+
},
|
|
16
34
|
add(marker, point, index, { features, markerColor, selectedMarkerColor, reducedMotion, enter, }) {
|
|
17
35
|
const node = svgElement('g', {
|
|
18
36
|
transform: `translate(${point[0]},${point[1]})`,
|
|
@@ -54,11 +72,15 @@ export function createMarkerLayer(layer) {
|
|
|
54
72
|
easing: 'cubic-bezier(.2,.8,.2,1)',
|
|
55
73
|
fill: 'backwards',
|
|
56
74
|
});
|
|
57
|
-
animations.
|
|
58
|
-
|
|
75
|
+
animations.set(node, animation);
|
|
76
|
+
const release = () => {
|
|
77
|
+
if (animations.get(node) === animation)
|
|
78
|
+
animations.delete(node);
|
|
79
|
+
};
|
|
80
|
+
animation.finished.then(release, release);
|
|
59
81
|
}
|
|
60
82
|
layer.append(node);
|
|
61
|
-
return { marker, point, node, dot, hit, ring };
|
|
83
|
+
return { marker, point, node, dot, hit, ring, title };
|
|
62
84
|
},
|
|
63
85
|
};
|
|
64
86
|
}
|
package/dist/src/types.d.ts
CHANGED
|
@@ -207,6 +207,7 @@ export interface NeighborhoodExplorerElement extends HTMLElement {
|
|
|
207
207
|
fitGeometry(geometry: Geometry, padding?: number | MapPadding, options?: CameraOptions): void;
|
|
208
208
|
/** Explicitly engage map touch gestures; false restores page gestures. */
|
|
209
209
|
setTouchNavigation(enabled: boolean): void;
|
|
210
|
+
/** Reconcile stable IDs; retained markers preserve nodes, focus, and entrance animations. */
|
|
210
211
|
setMarkers(markers: readonly MapMarker[]): void;
|
|
211
212
|
setOverlays(overlays: readonly MapOverlay[]): void;
|
|
212
213
|
selectMarker(id: string | null, options?: {
|
package/docs/api-audit.md
CHANGED
|
@@ -24,6 +24,8 @@ unbounded geographic data can never fail.
|
|
|
24
24
|
| District style callbacks | Prepare and copy every style before committing. Hover/selection reuse the results; call `setDistrictStyle()` to refresh changed external data. Reentrant updates or destruction supersede pending work. |
|
|
25
25
|
| Rapid district year changes | Both outgoing layers are tracked, inert, and removed on interruption, completion, reduced-motion changes, or destruction. Zero duration creates no transition copies. |
|
|
26
26
|
| Label metrics and camera movement | Reuse screen-space text measurements and nodes across frames. Font loading invalidates measurements; hidden or removed candidates do not remain in the visible layer. |
|
|
27
|
+
| Marker replacement | Reconcile stable IDs, preserve retained nodes/picker options and entrance animations, cancel removed entrances, and restore focus after reordering. Identical ordered values do not mutate the DOM or restart work. |
|
|
28
|
+
| Camera and label scheduling | A shared frame advances the camera before rendering dependent labels, marker sizes, and clusters. State/events remain synchronous; cancellation preserves pending layout, and destruction cancels both kinds of work. |
|
|
27
29
|
| Guide controllers and compatibility factories | `createGuideController`/`mountGuideController` share grouped options and lifecycle with `createMap`. Existing element factories retain flat options and their return types. |
|
|
28
30
|
| Layer hidden and bundle cost | Visibility never unloads imported geography. Use narrow entrypoints or data injection to save bytes. |
|
|
29
31
|
|
|
@@ -4,9 +4,9 @@ Generated 2026-09-28 by `pnpm report:guide` with Vite production minification an
|
|
|
4
4
|
|
|
5
5
|
| Entry | Initial JS, raw | Initial JS, gzip | Explicit detail JS, gzip |
|
|
6
6
|
| --- | ---: | ---: | ---: |
|
|
7
|
-
| v3 root (explicit data) |
|
|
7
|
+
| v3 root (explicit data) | 89.7 KB | 26.3 KB | — |
|
|
8
8
|
| v3 static renderer | 15.3 KB | 5.0 KB | — |
|
|
9
|
-
| Guide preset |
|
|
9
|
+
| Guide preset | 479.5 KB | 114.9 KB | 473.7 KB |
|
|
10
10
|
|
|
11
11
|
**500 KB target:** met.
|
|
12
12
|
|
package/package.json
CHANGED