@italone/solace 0.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/dist/devtools.cjs +8 -0
- package/dist/devtools.d.ts +54 -0
- package/dist/devtools.js +1 -0
- package/dist/h-B1loyNPC.js +53 -0
- package/dist/h-CSqpvOof.cjs +56 -0
- package/dist/index-CkECgTuy.js +91 -0
- package/dist/index-rtpmWox-.cjs +96 -0
- package/dist/index.cjs +1414 -0
- package/dist/index.d.ts +115 -0
- package/dist/index.js +1394 -0
- package/dist/jsx-dev-runtime.cjs +13 -0
- package/dist/jsx-dev-runtime.d.ts +7 -0
- package/dist/jsx-dev-runtime.js +9 -0
- package/dist/jsx-runtime.cjs +40 -0
- package/dist/jsx-runtime.d.ts +24 -0
- package/dist/jsx-runtime.js +37 -0
- package/dist/vnode-zJOgBYFm.d.ts +43 -0
- package/docs/api.md +302 -0
- package/docs/architecture.md +80 -0
- package/docs/devtools.md +145 -0
- package/docs/examples.md +78 -0
- package/docs/package-usage.md +96 -0
- package/docs/performance.md +230 -0
- package/docs/release.md +67 -0
- package/package.json +87 -0
- package/readme.md +632 -0
package/docs/devtools.md
ADDED
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
# DevTools
|
|
2
|
+
|
|
3
|
+
Solace exposes a narrow public DevTools integration surface through `solace/devtools`. This document records the public
|
|
4
|
+
lifecycle, private runtime boundary, and safe constraints for future instrumentation.
|
|
5
|
+
|
|
6
|
+
## Goals
|
|
7
|
+
|
|
8
|
+
- Help developers inspect component, reactivity, scheduler, renderer, and store behavior.
|
|
9
|
+
- Keep instrumentation opt-in.
|
|
10
|
+
- Avoid stabilizing internal runtime objects as public API.
|
|
11
|
+
- Avoid adding measurable overhead to production builds or benchmarks.
|
|
12
|
+
|
|
13
|
+
## Non-Goals
|
|
14
|
+
|
|
15
|
+
- No browser extension, custom panel, network transport, storage persistence, or automatic telemetry in the current phase.
|
|
16
|
+
|
|
17
|
+
## Public API
|
|
18
|
+
|
|
19
|
+
DevTools integrations should import from the `solace/devtools` subpath:
|
|
20
|
+
|
|
21
|
+
```ts
|
|
22
|
+
import { createDevtoolsRecorder, onDevtoolsEvent } from "solace/devtools";
|
|
23
|
+
import type { DevtoolsEvent } from "solace/devtools";
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
The public subpath exports listener and recorder APIs only. It does not export emit helpers, listener-state helpers,
|
|
27
|
+
global cleanup helpers, serializers, DOM nodes, VNode trees, component instances, props, reactive targets, store state,
|
|
28
|
+
action arguments, or action results.
|
|
29
|
+
|
|
30
|
+
## Public API Lifecycle
|
|
31
|
+
|
|
32
|
+
`solace/devtools` is the only supported public DevTools entry point. New runtime exports require package boundary tests,
|
|
33
|
+
packed consumer smoke coverage, documentation, and a project log entry before they are treated as supported API.
|
|
34
|
+
|
|
35
|
+
Event payload additions must remain small serializable summaries and must update payload stability coverage. They should
|
|
36
|
+
not include raw props, state, DOM nodes, VNodes, reactive targets, action arguments, action results, stack traces, or
|
|
37
|
+
user content.
|
|
38
|
+
|
|
39
|
+
Renames or removals require an intentional breaking-change plan. Internal helpers remain private even when public APIs
|
|
40
|
+
reuse them internally, and incidental runtime cleanup must not change the public subpath shape.
|
|
41
|
+
|
|
42
|
+
## Candidate Capabilities
|
|
43
|
+
|
|
44
|
+
| Area | Useful Signals | Notes |
|
|
45
|
+
| ---------- | ------------------------------------------------------ | ---------------------------------------------------------------------------- |
|
|
46
|
+
| Components | mount, update, unmount, props, emits, lifecycle hooks | Component lifecycle and emit summaries are emitted by the internal event bus |
|
|
47
|
+
| Reactivity | effect creation, dependency tracking, triggers, stops | Trigger summaries are emitted without raw targets, keys, or values |
|
|
48
|
+
| Scheduler | queued jobs, flush duration, skipped stale jobs | `scheduler:flush` summary is emitted by the internal event bus |
|
|
49
|
+
| Renderer | element mount, prop patch, child diff, unmount | Element summaries are emitted without DOM nodes or VNode trees |
|
|
50
|
+
| Store | action calls, narrow state paths, getter recomputation | Action summaries are emitted without args, results, or state |
|
|
51
|
+
|
|
52
|
+
## Hook Boundary
|
|
53
|
+
|
|
54
|
+
Solace has an internal event bus in `src/devtools/events.ts`. Runtime modules emit through that internal bus, while
|
|
55
|
+
public integrations subscribe through `solace/devtools`. The package root intentionally does not export DevTools APIs.
|
|
56
|
+
|
|
57
|
+
```ts
|
|
58
|
+
type DevtoolsEvent =
|
|
59
|
+
| { type: "component:mount"; id: number; name: string }
|
|
60
|
+
| { type: "component:update"; id: number; name: string }
|
|
61
|
+
| { type: "component:unmount"; id: number; name: string }
|
|
62
|
+
| { type: "component:emit"; id: number; name: string; event: string; handlerCount: number }
|
|
63
|
+
| { type: "scheduler:flush"; queuedJobs: number; dedupedJobs: number; durationMs: number }
|
|
64
|
+
| {
|
|
65
|
+
type: "reactivity:trigger";
|
|
66
|
+
targetType: string;
|
|
67
|
+
keyType: string;
|
|
68
|
+
effectCount: number;
|
|
69
|
+
scheduledEffects: number;
|
|
70
|
+
runEffects: number;
|
|
71
|
+
}
|
|
72
|
+
| {
|
|
73
|
+
type: "renderer:element";
|
|
74
|
+
operation: "mount" | "update" | "unmount";
|
|
75
|
+
tag: string;
|
|
76
|
+
}
|
|
77
|
+
| {
|
|
78
|
+
type: "store:action";
|
|
79
|
+
name: string;
|
|
80
|
+
status: "success" | "error";
|
|
81
|
+
durationMs: number;
|
|
82
|
+
};
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
`component:emit` summaries include the component id, component name, emitted event name, and callable handler count only.
|
|
86
|
+
They do not include emitted arguments, raw props, handler functions, component instances, VNodes, DOM nodes, or user
|
|
87
|
+
content.
|
|
88
|
+
|
|
89
|
+
`scheduler:flush` summaries include executed job count, deduped queue attempt count, and flush duration only. They do
|
|
90
|
+
not include scheduler job functions, function names, stack traces, component instances, reactive effects, VNodes, DOM
|
|
91
|
+
nodes, or user data.
|
|
92
|
+
|
|
93
|
+
Future runtime modules should emit small serializable events only when a listener is registered. If no listener is registered, the runtime should do no meaningful extra work.
|
|
94
|
+
|
|
95
|
+
`serializeDevtoolsEvent(event)` is available only from the internal event bus module. It returns an explicit plain-object
|
|
96
|
+
copy for the current event union and is used by integration tests to lock the payload boundary. It is not exported from
|
|
97
|
+
the package root.
|
|
98
|
+
|
|
99
|
+
`createDevtoolsRecorder()` is public through `solace/devtools`. It installs a listener, stores serialized events in
|
|
100
|
+
memory, exposes `snapshot()` for a copy of collected events, exposes `clear()` to reset the current capture window, and
|
|
101
|
+
exposes `stop()` to remove the listener. Pass `{ limit }` to keep only the latest N events in memory. It does not
|
|
102
|
+
persist data, send data over the network, write to storage, or install third-party scripts.
|
|
103
|
+
|
|
104
|
+
Production package builds do not publish JavaScript sourcemaps. This keeps internal DevTools wiring visible in source
|
|
105
|
+
control but out of package artifacts, so consumers do not accidentally couple to private helper names or module layout.
|
|
106
|
+
|
|
107
|
+
## Privacy And Safety
|
|
108
|
+
|
|
109
|
+
- Do not emit full props, state, DOM nodes, or reactive targets by default.
|
|
110
|
+
- Redact or summarize values before exposing them to tooling.
|
|
111
|
+
- Keep hooks disabled unless a dev-only listener is installed.
|
|
112
|
+
- Do not send data over the network.
|
|
113
|
+
|
|
114
|
+
## Performance Constraints
|
|
115
|
+
|
|
116
|
+
- Production builds should not pay for DevTools instrumentation.
|
|
117
|
+
- Benchmark commands should run with DevTools disabled.
|
|
118
|
+
- Hook payload construction should be lazy or guarded by a listener check.
|
|
119
|
+
- Component tree and dependency graph snapshots should be explicit actions, not automatic on every update.
|
|
120
|
+
|
|
121
|
+
## Phased Roadmap
|
|
122
|
+
|
|
123
|
+
1. **Event model design**: completed for initial component and scheduler summary events.
|
|
124
|
+
2. **Development-only event bus**: internal event bus exists in `src/devtools/events.ts`.
|
|
125
|
+
3. **Scheduler flush and dedupe summary**: `scheduler:flush` reports executed jobs, deduped queue attempts, and duration.
|
|
126
|
+
4. **Component lifecycle summaries**: component mount/update/unmount summaries emit id and name only.
|
|
127
|
+
5. **Component emit summaries**: `component:emit` is emitted with event name and callable handler count only.
|
|
128
|
+
6. **Store action summaries**: `store:action` is emitted after action success or error without raw values.
|
|
129
|
+
7. **Reactivity trigger summaries**: `reactivity:trigger` is emitted without raw targets, keys, or values.
|
|
130
|
+
8. **Renderer element summaries**: `renderer:element` is emitted for element mount/update/unmount without DOM nodes or VNode trees.
|
|
131
|
+
9. **Payload stability smoke**: integrated runtime events serialize to JSON-safe payloads with allowed fields only.
|
|
132
|
+
10. **Internal recorder boundary**: `createDevtoolsRecorder()` captures serialized event snapshots for examples and experiments.
|
|
133
|
+
11. **Example-oriented recorder smoke**: a todo-style interaction validates recorder capture after clearing initial mount noise.
|
|
134
|
+
12. **Bounded recorder captures**: `createDevtoolsRecorder({ limit })` keeps recorder memory bounded for examples and experiments.
|
|
135
|
+
13. **Large-list recorder smoke**: a 10,000-row keyed update validates public recorder snapshots remain serialized summaries without DOM, VNode, raw state, or row data.
|
|
136
|
+
14. **Public package boundary guard**: package exports tests verify DevTools internals are not available from the package root.
|
|
137
|
+
15. **Public DevTools subpath**: `solace/devtools` exposes listener and recorder APIs without internal emit helpers.
|
|
138
|
+
16. **Production artifact boundary**: package builds do not publish JavaScript sourcemaps that expose internal wiring.
|
|
139
|
+
17. **Inspector UI or browser extension**: build only after event payloads prove stable in examples.
|
|
140
|
+
|
|
141
|
+
## Recommendation
|
|
142
|
+
|
|
143
|
+
Do not implement a DevTools UI yet. The public `solace/devtools` subpath is a low-level integration surface for examples,
|
|
144
|
+
tests, and future inspector tooling. Build a browser extension or custom panel only after more payloads prove stable in
|
|
145
|
+
real examples.
|
package/docs/examples.md
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# Examples
|
|
2
|
+
|
|
3
|
+
Solace includes three Vite examples that exercise the runtime from small state updates to larger keyed lists.
|
|
4
|
+
|
|
5
|
+
## Basic Counter
|
|
6
|
+
|
|
7
|
+
Run:
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
pnpm dev
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Location: `examples/basic-counter`
|
|
14
|
+
|
|
15
|
+
Coverage:
|
|
16
|
+
|
|
17
|
+
- JSX automatic runtime.
|
|
18
|
+
- `reactive` state.
|
|
19
|
+
- DOM event patching through `onClick`.
|
|
20
|
+
- Reactive re-render after a button click.
|
|
21
|
+
|
|
22
|
+
The Playwright test `tests/e2e/basic-counter.spec.ts` verifies that the counter starts at `count: 0` and increments to `count: 1`.
|
|
23
|
+
|
|
24
|
+
## Todo App
|
|
25
|
+
|
|
26
|
+
Run:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
pnpm dev:todo
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Location: `examples/todo-app`
|
|
33
|
+
|
|
34
|
+
Coverage:
|
|
35
|
+
|
|
36
|
+
- Form submit handling.
|
|
37
|
+
- Controlled input updates with `onInput`.
|
|
38
|
+
- Keyed list rendering.
|
|
39
|
+
- Checkbox state toggles.
|
|
40
|
+
- Item deletion.
|
|
41
|
+
|
|
42
|
+
The Playwright test `tests/e2e/todo-app.spec.ts` verifies add, toggle, and delete flows.
|
|
43
|
+
|
|
44
|
+
## Large List
|
|
45
|
+
|
|
46
|
+
Run:
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
pnpm dev:large
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Location: `examples/large-list`
|
|
53
|
+
|
|
54
|
+
Coverage:
|
|
55
|
+
|
|
56
|
+
- 10,000 keyed rows.
|
|
57
|
+
- Class and text patching.
|
|
58
|
+
- A targeted state update from row 1 to row 5000.
|
|
59
|
+
|
|
60
|
+
The Playwright test `tests/e2e/large-list.spec.ts` verifies the list renders 10,000 rows and updates the selected row marker.
|
|
61
|
+
|
|
62
|
+
## E2E Validation
|
|
63
|
+
|
|
64
|
+
Run all browser examples through Playwright:
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
pnpm test:e2e
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
The Playwright config starts each example on a fixed localhost port:
|
|
71
|
+
|
|
72
|
+
| Example | Port |
|
|
73
|
+
| ------------- | ------ |
|
|
74
|
+
| Basic counter | `5174` |
|
|
75
|
+
| Todo app | `5175` |
|
|
76
|
+
| Large list | `5176` |
|
|
77
|
+
|
|
78
|
+
`pnpm release:check` also runs these e2e tests after quality checks, coverage, package smoke, jsdom benchmark smoke, and the Chromium production browser benchmark.
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# Package Usage
|
|
2
|
+
|
|
3
|
+
## Install
|
|
4
|
+
|
|
5
|
+
The package is publishable, so registry install is valid after release:
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pnpm add @italone/solace
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Before that release decision, run `pnpm release:readiness` to check local release metadata and validate package consumption with the packed-consumer smoke test described below.
|
|
12
|
+
|
|
13
|
+
## Import Runtime APIs
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
import { createApp, h, reactive } from "@italone/solace";
|
|
17
|
+
|
|
18
|
+
const state = reactive({ count: 0 });
|
|
19
|
+
|
|
20
|
+
const App = () =>
|
|
21
|
+
h(
|
|
22
|
+
"button",
|
|
23
|
+
{
|
|
24
|
+
onClick: () => {
|
|
25
|
+
state.count += 1;
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
`count: ${state.count}`,
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
createApp(App).mount(document.querySelector("#app") as Element);
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Use JSX
|
|
35
|
+
|
|
36
|
+
Configure TypeScript with the Solace JSX runtime:
|
|
37
|
+
|
|
38
|
+
```json
|
|
39
|
+
{
|
|
40
|
+
"compilerOptions": {
|
|
41
|
+
"jsx": "react-jsx",
|
|
42
|
+
"jsxImportSource": "@italone/solace"
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Then write components with JSX:
|
|
48
|
+
|
|
49
|
+
```tsx
|
|
50
|
+
import { createApp, reactive } from "@italone/solace";
|
|
51
|
+
|
|
52
|
+
const state = reactive({ count: 0 });
|
|
53
|
+
|
|
54
|
+
const App = () => (
|
|
55
|
+
<button
|
|
56
|
+
onClick={() => {
|
|
57
|
+
state.count += 1;
|
|
58
|
+
}}
|
|
59
|
+
>
|
|
60
|
+
count: {state.count}
|
|
61
|
+
</button>
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
createApp(App).mount(document.querySelector("#app") as Element);
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Public Entry Points
|
|
68
|
+
|
|
69
|
+
- `@italone/solace`: core runtime APIs.
|
|
70
|
+
- `@italone/solace/jsx-runtime`: TypeScript automatic JSX runtime.
|
|
71
|
+
- `@italone/solace/jsx-dev-runtime`: development JSX runtime used by Vite.
|
|
72
|
+
- `@italone/solace/devtools`: low-level DevTools listener and recorder APIs.
|
|
73
|
+
|
|
74
|
+
Do not import from `src/**`, `dist/**`, or internal runtime modules directly. Those paths are implementation details and are not part of the package compatibility contract.
|
|
75
|
+
|
|
76
|
+
## Verify A Packed Consumer
|
|
77
|
+
|
|
78
|
+
Before release, run the package consumer smoke test:
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
pnpm package:smoke
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
The smoke test builds Solace, packs the current package, installs the tarball into a temporary consumer project, typechecks a JSX entry file, and verifies ESM and CJS imports for all public entry points.
|
|
85
|
+
|
|
86
|
+
For the full local release gate, run:
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
pnpm release:check
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
That command runs quality checks including format check, coverage thresholds, package consumer smoke, jsdom benchmark smoke, Chromium production browser benchmark, and browser e2e tests.
|
|
93
|
+
|
|
94
|
+
See `docs/release.md` for versioning and publish steps.
|
|
95
|
+
|
|
96
|
+
See `docs/examples.md` for runnable Vite examples and their e2e coverage.
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
# Performance
|
|
2
|
+
|
|
3
|
+
This document defines how Solace performance should be measured. It does not claim unverified results.
|
|
4
|
+
|
|
5
|
+
## Current Validation
|
|
6
|
+
|
|
7
|
+
The repository currently validates behavior with:
|
|
8
|
+
|
|
9
|
+
- Unit tests for reactivity, scheduler, renderer, components, events, JSX runtime, store, and package exports.
|
|
10
|
+
- Playwright e2e tests for basic counter, todo app, and a 10,000-row large list example.
|
|
11
|
+
- Rollup production build checks.
|
|
12
|
+
- Tinybench smoke benchmarks for initial render, list diff, keyed insert/remove/move/reorder,
|
|
13
|
+
Fragment rendering, batched component updates, and mount/unmount loops.
|
|
14
|
+
- Chromium browser production benchmark for large-list initial render, reactive update, and unmount
|
|
15
|
+
through `pnpm benchmark:browser`.
|
|
16
|
+
|
|
17
|
+
The large-list e2e test confirms that 10,000 rows can render and one selected row can update in a browser smoke test. It is not a benchmark result.
|
|
18
|
+
|
|
19
|
+
## Latest Local Benchmark Run
|
|
20
|
+
|
|
21
|
+
Date: 2026-07-20
|
|
22
|
+
|
|
23
|
+
Environment:
|
|
24
|
+
|
|
25
|
+
| Item | Value |
|
|
26
|
+
| ------------ | ----------------------- |
|
|
27
|
+
| OS | Darwin 25.4.0 arm64 arm |
|
|
28
|
+
| Node | v22.22.2 |
|
|
29
|
+
| Runtime | darwin arm64 |
|
|
30
|
+
| Vitest | 4.1.10 |
|
|
31
|
+
| CPU / memory | Recorded by metadata |
|
|
32
|
+
|
|
33
|
+
Command:
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
SOLACE_BENCHMARK_HISTORY_PATH=.benchmark-history/jsdom.jsonl SOLACE_BENCHMARK_SAMPLE_SIZE=3 pnpm benchmark
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
The command logs a `benchmark metadata` JSON line before running the jsdom benchmark suite. The metadata includes package name/version, Node version, OS platform/release/architecture, CPU model, logical CPU count, total memory, benchmark runner, benchmark environment, sample size, and an ISO timestamp.
|
|
40
|
+
|
|
41
|
+
`sampleSize` defaults to `1` so `pnpm benchmark` remains a smoke benchmark run. Set
|
|
42
|
+
`SOLACE_BENCHMARK_SAMPLE_SIZE=3 pnpm benchmark` to run three independent Vitest benchmark samples.
|
|
43
|
+
The command reports the configured sample size in metadata, but it does not yet aggregate medians.
|
|
44
|
+
|
|
45
|
+
Set `SOLACE_BENCHMARK_HISTORY_PATH=.benchmark-history/jsdom.jsonl pnpm benchmark` to append one
|
|
46
|
+
JSONL record after a successful jsdom benchmark run. History recording is opt-in and records
|
|
47
|
+
metadata plus run status. Records created by the current benchmark runner also include
|
|
48
|
+
task-level Tinybench metrics under `summary.tasks`.
|
|
49
|
+
|
|
50
|
+
The local ignored jsdom history currently contains records with task-level Tinybench metrics when created by the current
|
|
51
|
+
benchmark runner. `pnpm benchmark:history -- --json` summarizes those jsdom task metrics with count, median, p95, and
|
|
52
|
+
variance while still accepting older metadata-only records.
|
|
53
|
+
|
|
54
|
+
Result summary:
|
|
55
|
+
|
|
56
|
+
| Scenario | File | Status | Notes |
|
|
57
|
+
| ------------------------------------------------ | --------------------------------------------- | ------ | ------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
58
|
+
| 1,000 component initial render | `tests/performance/render.bench.ts` | Passed | Uses jsdom and Tinybench, intended for trend tracking |
|
|
59
|
+
| 10,000 row create/update/delete/reorder | `tests/performance/list-diff.bench.ts` | Passed | Covers list creation, text-to-list mount, initial element child mount, unkeyed append/remove, local text update, delete, and keyed reorder |
|
|
60
|
+
| 10,000 row keyed local insert/remove/move | `tests/performance/list-diff.bench.ts` | Passed | Covers focused middle insert, middle remove, tail-to-head move, mixed insert/move, adjacent insert/move, and adjacent remove/move |
|
|
61
|
+
| 5,000 Fragment child initial render/patch/insert | `tests/performance/fragment.bench.ts` | Passed | Covers Fragment child mount, keyed text patch, and keyed middle insert |
|
|
62
|
+
| 1,000 component batched reactive update | `tests/performance/component-update.bench.ts` | Passed | Covers scheduler batching across many component consumers |
|
|
63
|
+
| Component mount/unmount loop | `tests/performance/memory.bench.ts` | Passed | Observes repeated cleanup path and records heap delta during the run |
|
|
64
|
+
|
|
65
|
+
Conclusion:
|
|
66
|
+
|
|
67
|
+
- The benchmark command is reproducible and currently passes.
|
|
68
|
+
- These runs are smoke benchmarks in jsdom, not browser production benchmarks.
|
|
69
|
+
- No claim is made that Solace meets or exceeds a specific framework performance target yet.
|
|
70
|
+
- The latest renderer follow-ups batch all-element Fragment initial mounts through a `DocumentFragment`,
|
|
71
|
+
batch all-element array children during element initial mount and text-to-array child replacement, skip
|
|
72
|
+
stable child component updates when parent rerenders do not change child props or children, skip
|
|
73
|
+
unchanged keyed element sibling patches during local list updates, and avoid prop patching plus `Object.keys`
|
|
74
|
+
props scans for keyed child-only updates. Unkeyed appended all-element suffixes
|
|
75
|
+
batch through a `DocumentFragment` after index patching, and safe removed leaf suffixes detach through
|
|
76
|
+
a temporary `DocumentFragment`, including adjacent old keyed runs removed during mixed placement. Keyed
|
|
77
|
+
mixed insert/move patches now mount new children directly at their final anchor instead of appending and
|
|
78
|
+
moving them. Contiguous all-element keyed insert segments also batch through a `DocumentFragment` before
|
|
79
|
+
one parent insert, including adjacent new runs discovered during mixed keyed placement. The component update path
|
|
80
|
+
also avoids repeated enqueue attempts while a component update is already pending. The component initial mount path
|
|
81
|
+
also batches child inserts through a `DocumentFragment`. The initial element mount path now uses a conservative props
|
|
82
|
+
fast path for ordinary attributes, avoiding `Object.entries()` scans and redundant attribute removals on fresh
|
|
83
|
+
elements. It also uses a direct HTML `className` fast path for `class` props while keeping the existing attribute
|
|
84
|
+
fallback for non-HTML nodes. Next optimization work should focus on additional browser trend samples.
|
|
85
|
+
|
|
86
|
+
## Browser Production Benchmark
|
|
87
|
+
|
|
88
|
+
Command:
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
pnpm benchmark:browser
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
This command builds `examples/performance-benchmark`, serves the production output through Vite
|
|
95
|
+
preview, and runs `tests/e2e/browser-benchmark.spec.ts` in Chromium.
|
|
96
|
+
|
|
97
|
+
Measured scenarios:
|
|
98
|
+
|
|
99
|
+
| Scenario | Scale | Assertion |
|
|
100
|
+
| --------------------------- | ----------- | -------------------------------------- |
|
|
101
|
+
| Initial large-list render | 10,000 rows | selected row 1 is rendered |
|
|
102
|
+
| Reactive selected-row patch | 10,000 rows | selected row 5000 reflects final state |
|
|
103
|
+
| Large-list unmount | 10,000 rows | row nodes are removed |
|
|
104
|
+
|
|
105
|
+
The command logs a `browser benchmark summary` JSON line. It intentionally does not enforce absolute
|
|
106
|
+
timing thresholds because browser, CPU, power mode, and background process variance can dominate
|
|
107
|
+
individual runs.
|
|
108
|
+
|
|
109
|
+
The summary also includes reproducibility metadata:
|
|
110
|
+
|
|
111
|
+
| Field | Source |
|
|
112
|
+
| ---------------------------------------------------------------------------- | ------------------------------- |
|
|
113
|
+
| `metadata.packageName` / `metadata.packageVersion` | `package.json` |
|
|
114
|
+
| `metadata.node` | `process.version` |
|
|
115
|
+
| `metadata.platform`, `metadata.release`, `metadata.arch` | Node `os` module |
|
|
116
|
+
| `metadata.cpuModel`, `metadata.logicalCpuCount`, `metadata.totalMemoryBytes` | Node `os` module |
|
|
117
|
+
| `metadata.browserName`, `metadata.browserVersion`, `metadata.projectName` | Playwright |
|
|
118
|
+
| `metadata.sampleSize` | Current benchmark harness |
|
|
119
|
+
| `metadata.runAt` | ISO timestamp for the local run |
|
|
120
|
+
|
|
121
|
+
`metadata.sampleSize` defaults to `1` so `pnpm benchmark:browser` remains a smoke benchmark run. Set
|
|
122
|
+
`SOLACE_BROWSER_BENCHMARK_SAMPLE_SIZE=3 pnpm benchmark:browser` to run three independent browser benchmark samples in
|
|
123
|
+
one Playwright run. The command logs one `browser benchmark summary` line per sample.
|
|
124
|
+
|
|
125
|
+
Set `SOLACE_BROWSER_BENCHMARK_HISTORY_PATH=.benchmark-history/browser.jsonl pnpm benchmark:browser`
|
|
126
|
+
to append one JSONL record after each successful Chromium production benchmark sample. Browser history
|
|
127
|
+
records persist the existing summary object; they do not add timing thresholds or statistical aggregation.
|
|
128
|
+
|
|
129
|
+
Run `pnpm benchmark:history` to summarize local JSONL history from `.benchmark-history/jsdom.jsonl`
|
|
130
|
+
and `.benchmark-history/browser.jsonl`. Use `pnpm benchmark:history -- --json <path>` for
|
|
131
|
+
machine-readable output. The summary reports record counts plus median, p95, and variance for
|
|
132
|
+
numeric browser timing metrics and jsdom task metrics; it does not enforce thresholds.
|
|
133
|
+
|
|
134
|
+
Use `pnpm benchmark:history -- --min-browser-count 5` to require each browser benchmark scenario
|
|
135
|
+
to have at least five local history records. This is an opt-in trend quality gate for local or CI
|
|
136
|
+
checks; it is not a timing threshold and does not compare measured performance against a target.
|
|
137
|
+
Use `pnpm benchmark:history -- --latest-browser-count 5` to summarize only the latest five browser
|
|
138
|
+
records per scenario while leaving jsdom record counts in the summary. This is useful when older
|
|
139
|
+
slow samples dominate full-history p95 and the next runtime hotspot needs a fresher trend window.
|
|
140
|
+
Run `pnpm benchmark:history -- --help` to list the supported summary options.
|
|
141
|
+
|
|
142
|
+
### Latest Local Browser History Summary
|
|
143
|
+
|
|
144
|
+
Date: 2026-07-20
|
|
145
|
+
|
|
146
|
+
Local history command:
|
|
147
|
+
|
|
148
|
+
```bash
|
|
149
|
+
pnpm benchmark:history -- --min-browser-count 20 --json
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
The local ignored history currently contains forty-five Chromium `large-list` production benchmark records. The
|
|
153
|
+
`--min-browser-count 20` trend gate passes locally. p95 still reflects the slowest observed samples and should be
|
|
154
|
+
treated as trend context only, not a release threshold.
|
|
155
|
+
|
|
156
|
+
| Metric | Count | Median | p95 | Variance |
|
|
157
|
+
| ----------------- | ----- | ------ | ---- | -------- |
|
|
158
|
+
| `initialRenderMs` | 45 | 7.7 | 15.7 | 18.98 |
|
|
159
|
+
| `updateMs` | 45 | 3.6 | 6.1 | 4.23 |
|
|
160
|
+
| `unmountMs` | 45 | 1.2 | 1.4 | 0.14 |
|
|
161
|
+
|
|
162
|
+
Latest-window command:
|
|
163
|
+
|
|
164
|
+
```bash
|
|
165
|
+
pnpm benchmark:history -- --latest-browser-count 5 --min-browser-count 5 --json
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
The latest five Chromium `large-list` records still show a slow-sample ceiling on initial render, but the median
|
|
169
|
+
remains lower than the preceding full-history window.
|
|
170
|
+
|
|
171
|
+
| Metric | Count | Median | p95 | Variance |
|
|
172
|
+
| ----------------- | ----- | ------ | ---- | -------- |
|
|
173
|
+
| `initialRenderMs` | 5 | 7.7 | 16.2 | 13.46 |
|
|
174
|
+
| `updateMs` | 5 | 4.0 | 6.1 | 0.93 |
|
|
175
|
+
| `unmountMs` | 5 | 1.2 | 1.3 | 0.01 |
|
|
176
|
+
|
|
177
|
+
## Benchmark Principles
|
|
178
|
+
|
|
179
|
+
Benchmarks should:
|
|
180
|
+
|
|
181
|
+
- Run in production mode where possible.
|
|
182
|
+
- Separate initial render, update, and unmount costs.
|
|
183
|
+
- Record browser, OS, Node, package version, and commit.
|
|
184
|
+
- Avoid comparing development builds against production builds.
|
|
185
|
+
- Report medians and variance, not a single best run.
|
|
186
|
+
- Keep benchmark fixtures in source control.
|
|
187
|
+
|
|
188
|
+
## Suggested Benchmarks
|
|
189
|
+
|
|
190
|
+
### Reactivity
|
|
191
|
+
|
|
192
|
+
- Create many reactive objects.
|
|
193
|
+
- Track many effects.
|
|
194
|
+
- Trigger narrow and broad dependency sets.
|
|
195
|
+
- Measure computed cache hits and invalidations.
|
|
196
|
+
|
|
197
|
+
### Renderer
|
|
198
|
+
|
|
199
|
+
- Mount 1,000 and 10,000 simple elements.
|
|
200
|
+
- Patch text props across a large list.
|
|
201
|
+
- Insert, remove, and move keyed children.
|
|
202
|
+
- Unmount nested component trees.
|
|
203
|
+
|
|
204
|
+
### Components
|
|
205
|
+
|
|
206
|
+
- Mount many small components.
|
|
207
|
+
- Batch repeated state writes in one tick.
|
|
208
|
+
- Repeatedly mount and unmount component subtrees.
|
|
209
|
+
- Verify no retained effects after unmount.
|
|
210
|
+
|
|
211
|
+
### Store
|
|
212
|
+
|
|
213
|
+
- Read state directly in components.
|
|
214
|
+
- Read computed getters in components.
|
|
215
|
+
- Dispatch actions that update narrow state paths.
|
|
216
|
+
|
|
217
|
+
## Reporting Template
|
|
218
|
+
|
|
219
|
+
```text
|
|
220
|
+
Scenario:
|
|
221
|
+
Build mode:
|
|
222
|
+
Browser / Node:
|
|
223
|
+
Machine:
|
|
224
|
+
Sample size:
|
|
225
|
+
Median:
|
|
226
|
+
p95:
|
|
227
|
+
Notes:
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
Performance claims should only be added after this data exists.
|
package/docs/release.md
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# Release
|
|
2
|
+
|
|
3
|
+
Solace uses Changesets for version notes and package version updates.
|
|
4
|
+
|
|
5
|
+
## Local Release Gate
|
|
6
|
+
|
|
7
|
+
Run the full local gate before preparing a release:
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
pnpm release:check
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
This runs format check, typecheck, JSX dev typecheck, lint, default tests, package exports tests, coverage thresholds, package consumer smoke, jsdom benchmark smoke, Chromium production browser benchmark, and browser e2e tests.
|
|
14
|
+
|
|
15
|
+
The GitHub Actions CI workflow keeps these checks split into named steps and also runs both benchmark commands: `pnpm benchmark` and `pnpm benchmark:browser`.
|
|
16
|
+
|
|
17
|
+
CI also runs `pnpm release:readiness` before the longer checks so package metadata and release script drift fail early.
|
|
18
|
+
|
|
19
|
+
## Release Readiness
|
|
20
|
+
|
|
21
|
+
Run the local metadata readiness check before changing publishability:
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
pnpm release:readiness
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
This command checks local package metadata, package entry points, release scripts, and Changesets public access configuration. It does not contact npm and does not publish.
|
|
28
|
+
|
|
29
|
+
The package is now publishable. To verify the stricter publishable mode before publishing, run:
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
pnpm release:readiness -- --publishable
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Prepare A Version
|
|
36
|
+
|
|
37
|
+
Create a changeset for user-visible changes:
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
pnpm changeset
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Apply pending changesets to `package.json` and changelog files:
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
pnpm release:version
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Publish
|
|
50
|
+
|
|
51
|
+
Before publishing, explicitly confirm:
|
|
52
|
+
|
|
53
|
+
- the npm package name `@italone/solace` is available or controlled by the maintainer,
|
|
54
|
+
- npm authentication and organization access are configured,
|
|
55
|
+
- public access is intended,
|
|
56
|
+
- `pnpm release:readiness -- --publishable` passes,
|
|
57
|
+
- `pnpm release:check` passes,
|
|
58
|
+
- `pnpm package:smoke` passes after the final version update,
|
|
59
|
+
- Changesets versioning has been run for user-visible changes.
|
|
60
|
+
|
|
61
|
+
After that decision, run:
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
pnpm release:publish
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
`release:publish` runs the full local release gate before `changeset publish`.
|