@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
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var jsxRuntime = require('./jsx-runtime.cjs');
|
|
4
|
+
var h = require('./h-CSqpvOof.cjs');
|
|
5
|
+
|
|
6
|
+
function jsxDEV(type, props, key) {
|
|
7
|
+
return jsxRuntime.jsx(type, props, key);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
exports.jsx = jsxRuntime.jsx;
|
|
11
|
+
exports.jsxs = jsxRuntime.jsxs;
|
|
12
|
+
exports.Fragment = h.Fragment;
|
|
13
|
+
exports.jsxDEV = jsxDEV;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { jsx, JSX } from './jsx-runtime.js';
|
|
2
|
+
export { jsxs } from './jsx-runtime.js';
|
|
3
|
+
export { F as Fragment } from './vnode-zJOgBYFm.js';
|
|
4
|
+
|
|
5
|
+
declare function jsxDEV(type: Parameters<typeof jsx>[0], props: Parameters<typeof jsx>[1], key?: string): JSX.Element;
|
|
6
|
+
|
|
7
|
+
export { JSX, jsx, jsxDEV };
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var h = require('./h-CSqpvOof.cjs');
|
|
4
|
+
|
|
5
|
+
function jsx(type, props = null, key) {
|
|
6
|
+
return createJsxVNode(type, props, key);
|
|
7
|
+
}
|
|
8
|
+
function jsxs(type, props = null, key) {
|
|
9
|
+
return createJsxVNode(type, props, key);
|
|
10
|
+
}
|
|
11
|
+
function createJsxVNode(type, props, key) {
|
|
12
|
+
const { children, ...restProps } = props ?? {};
|
|
13
|
+
const vnodeProps = {
|
|
14
|
+
...restProps,
|
|
15
|
+
...(key !== undefined ? { key } : {}),
|
|
16
|
+
};
|
|
17
|
+
return h.h(type, vnodeProps, normalizeChildren(children));
|
|
18
|
+
}
|
|
19
|
+
function normalizeChildren(children) {
|
|
20
|
+
if (Array.isArray(children)) {
|
|
21
|
+
const normalized = children
|
|
22
|
+
.filter((child) => child !== null && child !== undefined && typeof child !== "boolean")
|
|
23
|
+
.map((child) => (typeof child === "number" ? String(child) : child));
|
|
24
|
+
if (normalized.every((child) => typeof child === "string")) {
|
|
25
|
+
return normalized.join("");
|
|
26
|
+
}
|
|
27
|
+
return normalized.map((child) => typeof child === "string" ? h.h("span", null, child) : child);
|
|
28
|
+
}
|
|
29
|
+
if (children === null || children === undefined || typeof children === "boolean") {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
if (typeof children === "number") {
|
|
33
|
+
return String(children);
|
|
34
|
+
}
|
|
35
|
+
return typeof children === "string" ? children : [children];
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
exports.Fragment = h.Fragment;
|
|
39
|
+
exports.jsx = jsx;
|
|
40
|
+
exports.jsxs = jsxs;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { V as VNode, a as VNodeProps, k as VNodeChild, d as VNodeType } from './vnode-zJOgBYFm.js';
|
|
2
|
+
export { F as Fragment } from './vnode-zJOgBYFm.js';
|
|
3
|
+
|
|
4
|
+
type JSXChild = VNodeChild | number | boolean | null | undefined;
|
|
5
|
+
type JSXChildren = JSXChild | JSXChild[];
|
|
6
|
+
type JSXProps = VNodeProps & {
|
|
7
|
+
children?: JSXChildren;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
declare function jsx(type: VNodeType, props?: JSXProps | null, key?: string): VNode;
|
|
11
|
+
declare function jsxs(type: VNodeType, props?: JSXProps | null, key?: string): VNode;
|
|
12
|
+
declare namespace JSX {
|
|
13
|
+
type Element = VNode;
|
|
14
|
+
interface IntrinsicElements {
|
|
15
|
+
[name: string]: VNodeProps & {
|
|
16
|
+
children?: JSXChildren;
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
interface ElementChildrenAttribute {
|
|
20
|
+
children: unknown;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export { JSX, jsx, jsxs };
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { h } from './h-B1loyNPC.js';
|
|
2
|
+
export { F as Fragment } from './h-B1loyNPC.js';
|
|
3
|
+
|
|
4
|
+
function jsx(type, props = null, key) {
|
|
5
|
+
return createJsxVNode(type, props, key);
|
|
6
|
+
}
|
|
7
|
+
function jsxs(type, props = null, key) {
|
|
8
|
+
return createJsxVNode(type, props, key);
|
|
9
|
+
}
|
|
10
|
+
function createJsxVNode(type, props, key) {
|
|
11
|
+
const { children, ...restProps } = props ?? {};
|
|
12
|
+
const vnodeProps = {
|
|
13
|
+
...restProps,
|
|
14
|
+
...(key !== undefined ? { key } : {}),
|
|
15
|
+
};
|
|
16
|
+
return h(type, vnodeProps, normalizeChildren(children));
|
|
17
|
+
}
|
|
18
|
+
function normalizeChildren(children) {
|
|
19
|
+
if (Array.isArray(children)) {
|
|
20
|
+
const normalized = children
|
|
21
|
+
.filter((child) => child !== null && child !== undefined && typeof child !== "boolean")
|
|
22
|
+
.map((child) => (typeof child === "number" ? String(child) : child));
|
|
23
|
+
if (normalized.every((child) => typeof child === "string")) {
|
|
24
|
+
return normalized.join("");
|
|
25
|
+
}
|
|
26
|
+
return normalized.map((child) => typeof child === "string" ? h("span", null, child) : child);
|
|
27
|
+
}
|
|
28
|
+
if (children === null || children === undefined || typeof children === "boolean") {
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
if (typeof children === "number") {
|
|
32
|
+
return String(children);
|
|
33
|
+
}
|
|
34
|
+
return typeof children === "string" ? children : [children];
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export { jsx, jsxs };
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
declare enum ShapeFlags {
|
|
2
|
+
ELEMENT = 1,
|
|
3
|
+
TEXT_CHILDREN = 2,
|
|
4
|
+
ARRAY_CHILDREN = 4,
|
|
5
|
+
COMPONENT = 8,
|
|
6
|
+
FRAGMENT = 16
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
type EmitFn = (event: string, ...args: unknown[]) => void;
|
|
10
|
+
type SlotProps = Record<string, unknown>;
|
|
11
|
+
type Slot = (props?: SlotProps) => VNodeChildren;
|
|
12
|
+
interface Slots {
|
|
13
|
+
default?: Slot;
|
|
14
|
+
[name: string]: Slot | undefined;
|
|
15
|
+
}
|
|
16
|
+
interface ComponentSetupContext {
|
|
17
|
+
emit: EmitFn;
|
|
18
|
+
slots: Slots;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
type ComponentProps = Record<string, unknown>;
|
|
22
|
+
type ComponentRender = () => VNode;
|
|
23
|
+
type ComponentType<Props extends object = ComponentProps> = (props: Props, context: ComponentSetupContext) => ComponentRender | VNode;
|
|
24
|
+
declare const Fragment: unique symbol;
|
|
25
|
+
type FragmentType = typeof Fragment;
|
|
26
|
+
type VNodeType = string | ComponentType<never> | FragmentType;
|
|
27
|
+
type VNodeProps = Record<string, unknown>;
|
|
28
|
+
type VNodeChild = string | VNode;
|
|
29
|
+
type VNodeChildren = string | VNode | VNode[] | null;
|
|
30
|
+
type VNodeSlots = Record<string, Slot>;
|
|
31
|
+
type ComponentVNodeChildren = VNodeChildren | VNodeSlots;
|
|
32
|
+
interface VNode {
|
|
33
|
+
type: VNodeType;
|
|
34
|
+
props: VNodeProps | null;
|
|
35
|
+
key: string | number | null;
|
|
36
|
+
children: ComponentVNodeChildren;
|
|
37
|
+
shapeFlag: ShapeFlags;
|
|
38
|
+
el: Element | Text | null;
|
|
39
|
+
component: unknown;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export { Fragment as F };
|
|
43
|
+
export type { ComponentType as C, EmitFn as E, Slot as S, VNode as V, VNodeProps as a, VNodeChildren as b, ComponentVNodeChildren as c, VNodeType as d, ComponentRender as e, ComponentSetupContext as f, ComponentProps as g, FragmentType as h, SlotProps as i, Slots as j, VNodeChild as k, VNodeSlots as l };
|
package/docs/api.md
ADDED
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
# API
|
|
2
|
+
|
|
3
|
+
## Public Root Export
|
|
4
|
+
|
|
5
|
+
The `solace` package root exposes the stable runtime surface:
|
|
6
|
+
|
|
7
|
+
- App: `createApp`
|
|
8
|
+
- Reactivity: `reactive`, `effect`, `computed`, `ref`, `watch`, `watchEffect`
|
|
9
|
+
- Rendering: `h`, `render`, `Fragment`
|
|
10
|
+
- Components: `defineComponent`, `defineAsyncComponent`, default slots
|
|
11
|
+
- Component context: `provide`, `inject`
|
|
12
|
+
- Lifecycle: `onMounted`, `onUpdated`, `onUnmounted`
|
|
13
|
+
- Scheduler: `nextTick`
|
|
14
|
+
- Store: `createStore`
|
|
15
|
+
|
|
16
|
+
Public TypeScript types include `App`, `AsyncComponentLoader`, `AsyncComponentOptions`, `AsyncComponentSource`, `ComponentSetupContext`, `EmitFn`, `Slot`, `Slots`, `Store`, `StoreContext`, `StoreGetterContext`, `StoreGetters`, `StoreOptions`, `VNode`, `VNodeProps`, and related component/VNode helper types.
|
|
17
|
+
|
|
18
|
+
Internal helpers such as scheduler queues, shape flags, component instance setup, and VNode factory internals are not exported from the package root.
|
|
19
|
+
|
|
20
|
+
## App
|
|
21
|
+
|
|
22
|
+
### `createApp(rootComponent)`
|
|
23
|
+
|
|
24
|
+
Creates an app wrapper around a root component or VNode.
|
|
25
|
+
|
|
26
|
+
```ts
|
|
27
|
+
import { createApp, h } from "solace";
|
|
28
|
+
|
|
29
|
+
const App = () => h("p", null, "hello");
|
|
30
|
+
|
|
31
|
+
createApp(App).mount(document.querySelector("#app") as Element);
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Returns:
|
|
35
|
+
|
|
36
|
+
- `mount(container: Element): void`
|
|
37
|
+
- `provide(key, value): App`
|
|
38
|
+
- `use(plugin, ...options): App`
|
|
39
|
+
|
|
40
|
+
Provide an app-level value before mounting. Components can read it with `inject()`, and component-level
|
|
41
|
+
providers override app-level values.
|
|
42
|
+
|
|
43
|
+
```ts
|
|
44
|
+
import { createApp } from "solace";
|
|
45
|
+
|
|
46
|
+
createApp(App)
|
|
47
|
+
.provide("theme", "dark")
|
|
48
|
+
.mount(document.querySelector("#app") as Element);
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Install a plugin before mounting an app. A plugin can be a function or an object with `install`.
|
|
52
|
+
Each plugin is installed once per app instance, and `use()` returns the app for chaining. Plugins can
|
|
53
|
+
also call `app.provide()` to expose values to the mounted component tree.
|
|
54
|
+
|
|
55
|
+
```ts
|
|
56
|
+
import { createApp } from "solace";
|
|
57
|
+
import type { App, Plugin } from "solace";
|
|
58
|
+
|
|
59
|
+
const plugin: Plugin = (app: App, option) => {
|
|
60
|
+
app.provide("theme", "dark");
|
|
61
|
+
console.log(app, option);
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
createApp(App)
|
|
65
|
+
.use(plugin, "enabled")
|
|
66
|
+
.mount(document.querySelector("#app") as Element);
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Reactivity
|
|
70
|
+
|
|
71
|
+
### `reactive(target)`
|
|
72
|
+
|
|
73
|
+
Wraps an object in a reactive proxy.
|
|
74
|
+
|
|
75
|
+
```ts
|
|
76
|
+
const state = reactive({ count: 0 });
|
|
77
|
+
state.count += 1;
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### `effect(fn)`
|
|
81
|
+
|
|
82
|
+
Runs `fn` immediately and reruns it when tracked dependencies change.
|
|
83
|
+
|
|
84
|
+
```ts
|
|
85
|
+
effect(() => {
|
|
86
|
+
console.log(state.count);
|
|
87
|
+
});
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### `computed(getter)`
|
|
91
|
+
|
|
92
|
+
Returns a cached ref-like object with readonly `.value`.
|
|
93
|
+
|
|
94
|
+
```ts
|
|
95
|
+
const double = computed(() => state.count * 2);
|
|
96
|
+
console.log(double.value);
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### `ref(value)`
|
|
100
|
+
|
|
101
|
+
Creates a reactive value holder.
|
|
102
|
+
|
|
103
|
+
```ts
|
|
104
|
+
const count = ref(0);
|
|
105
|
+
count.value += 1;
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### `watch(source, callback)`
|
|
109
|
+
|
|
110
|
+
Runs `callback` when the source result changes.
|
|
111
|
+
|
|
112
|
+
```ts
|
|
113
|
+
watch(
|
|
114
|
+
() => state.count,
|
|
115
|
+
(value, oldValue) => {
|
|
116
|
+
console.log(value, oldValue);
|
|
117
|
+
},
|
|
118
|
+
);
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### `watchEffect(effect)`
|
|
122
|
+
|
|
123
|
+
Runs `effect` immediately and reruns it when tracked dependencies change.
|
|
124
|
+
|
|
125
|
+
```ts
|
|
126
|
+
const stop = watchEffect(() => {
|
|
127
|
+
console.log(state.count);
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
stop();
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
## Rendering
|
|
134
|
+
|
|
135
|
+
### `h(type, props?, children?)`
|
|
136
|
+
|
|
137
|
+
Creates a VNode for an element, component, or Fragment.
|
|
138
|
+
|
|
139
|
+
```ts
|
|
140
|
+
h("button", { onClick: save }, "Save");
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
### `render(vnode, container)`
|
|
144
|
+
|
|
145
|
+
Mounts or patches a VNode into a DOM container.
|
|
146
|
+
|
|
147
|
+
```ts
|
|
148
|
+
render(h(App), document.querySelector("#app") as Element);
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
### `Fragment`
|
|
152
|
+
|
|
153
|
+
Groups multiple children without an extra DOM wrapper.
|
|
154
|
+
|
|
155
|
+
## Components
|
|
156
|
+
|
|
157
|
+
Function components receive props and a setup context with `emit` and `slots`.
|
|
158
|
+
|
|
159
|
+
```ts
|
|
160
|
+
import { h } from "solace";
|
|
161
|
+
import type { ComponentSetupContext } from "solace";
|
|
162
|
+
|
|
163
|
+
const Button = (props: { label: string }, { emit }: ComponentSetupContext) =>
|
|
164
|
+
h("button", { onClick: () => emit("change") }, props.label);
|
|
165
|
+
|
|
166
|
+
const Panel =
|
|
167
|
+
(_props: object, { slots }: ComponentSetupContext) =>
|
|
168
|
+
() =>
|
|
169
|
+
h("section", null, [
|
|
170
|
+
h("header", null, slots.header?.() ?? null),
|
|
171
|
+
h("main", null, slots.default?.({ text: "Body" }) ?? null),
|
|
172
|
+
]);
|
|
173
|
+
|
|
174
|
+
h(Panel, null, {
|
|
175
|
+
header: () => h("h1", null, "Title"),
|
|
176
|
+
default: (slotProps) => h("p", null, String(slotProps?.text)),
|
|
177
|
+
});
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
### `defineComponent(component)`
|
|
181
|
+
|
|
182
|
+
Declares a Solace component while preserving the existing function component contract.
|
|
183
|
+
|
|
184
|
+
```ts
|
|
185
|
+
import { defineComponent, h } from "solace";
|
|
186
|
+
|
|
187
|
+
const Button = defineComponent((props: { label: string }) => h("button", null, props.label));
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
### `defineAsyncComponent(loader | options)`
|
|
191
|
+
|
|
192
|
+
Declares a component that loads another component asynchronously. While the loader is pending,
|
|
193
|
+
the wrapper renders an empty Fragment or `loadingComponent`. If the loader rejects, it renders
|
|
194
|
+
`errorComponent` when provided. Resolved, loading, and error components receive the latest props
|
|
195
|
+
and default slot children. Use `delay` to postpone the loading component and `timeout` to enter
|
|
196
|
+
the error state when loading takes too long. Use `retry` to make additional attempts after loader
|
|
197
|
+
rejection or timeout, and `retryDelay` to wait before each retry.
|
|
198
|
+
|
|
199
|
+
```ts
|
|
200
|
+
import { defineAsyncComponent, h } from "solace";
|
|
201
|
+
|
|
202
|
+
const LazyMessage = defineAsyncComponent<{ text: string }>(() =>
|
|
203
|
+
Promise.resolve((props: { text: string }) => h("p", null, props.text)),
|
|
204
|
+
);
|
|
205
|
+
|
|
206
|
+
const LazyPanel = defineAsyncComponent<{ title: string }>({
|
|
207
|
+
loader: () => Promise.resolve((props: { title: string }) => h("section", null, props.title)),
|
|
208
|
+
loadingComponent: () => h("span", null, "loading"),
|
|
209
|
+
errorComponent: () => h("strong", null, "failed"),
|
|
210
|
+
delay: 200,
|
|
211
|
+
timeout: 3000,
|
|
212
|
+
retry: 2,
|
|
213
|
+
retryDelay: 100,
|
|
214
|
+
});
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
### `provide(key, value)` / `inject(key, defaultValue?)`
|
|
218
|
+
|
|
219
|
+
Passes values from an ancestor component setup to descendant component setup without prop drilling.
|
|
220
|
+
|
|
221
|
+
```ts
|
|
222
|
+
import { h, inject, provide } from "solace";
|
|
223
|
+
|
|
224
|
+
const ThemeKey = Symbol("theme");
|
|
225
|
+
|
|
226
|
+
const Child = () => {
|
|
227
|
+
const theme = inject(ThemeKey, "light");
|
|
228
|
+
|
|
229
|
+
return () => h("span", null, theme);
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
const Parent = () => {
|
|
233
|
+
provide(ThemeKey, "dark");
|
|
234
|
+
|
|
235
|
+
return () => h(Child);
|
|
236
|
+
};
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
### Lifecycle
|
|
240
|
+
|
|
241
|
+
- `onMounted(hook)`
|
|
242
|
+
- `onUpdated(hook)`
|
|
243
|
+
- `onUnmounted(hook)`
|
|
244
|
+
|
|
245
|
+
Lifecycle hooks register during component setup.
|
|
246
|
+
|
|
247
|
+
## Scheduler
|
|
248
|
+
|
|
249
|
+
### `nextTick()`
|
|
250
|
+
|
|
251
|
+
Resolves after queued component updates flush.
|
|
252
|
+
|
|
253
|
+
```ts
|
|
254
|
+
state.count += 1;
|
|
255
|
+
await nextTick();
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
## Store
|
|
259
|
+
|
|
260
|
+
### `createStore({ state, getters, actions })`
|
|
261
|
+
|
|
262
|
+
Creates a small centralized store.
|
|
263
|
+
|
|
264
|
+
```ts
|
|
265
|
+
import { createStore } from "solace";
|
|
266
|
+
import type { StoreContext, StoreGetterContext } from "solace";
|
|
267
|
+
|
|
268
|
+
const store = createStore({
|
|
269
|
+
state: () => ({ count: 0 }),
|
|
270
|
+
getters: {
|
|
271
|
+
double({ state }: StoreGetterContext<{ count: number }>) {
|
|
272
|
+
return state.count * 2;
|
|
273
|
+
},
|
|
274
|
+
},
|
|
275
|
+
actions: {
|
|
276
|
+
increment({ state }: StoreContext<{ count: number }, { double: number }>, amount: number) {
|
|
277
|
+
state.count += amount;
|
|
278
|
+
},
|
|
279
|
+
},
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
store.actions.increment(1);
|
|
283
|
+
console.log(store.getters.double);
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
## JSX
|
|
287
|
+
|
|
288
|
+
Use TypeScript automatic JSX runtime:
|
|
289
|
+
|
|
290
|
+
```json
|
|
291
|
+
{
|
|
292
|
+
"compilerOptions": {
|
|
293
|
+
"jsx": "react-jsx",
|
|
294
|
+
"jsxImportSource": "solace"
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
Public JSX entry points:
|
|
300
|
+
|
|
301
|
+
- `solace/jsx-runtime`
|
|
302
|
+
- `solace/jsx-dev-runtime`
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# Architecture
|
|
2
|
+
|
|
3
|
+
## Layers
|
|
4
|
+
|
|
5
|
+
Solace is organized as a small runtime pipeline:
|
|
6
|
+
|
|
7
|
+
```text
|
|
8
|
+
reactivity -> scheduler -> component -> vnode -> renderer -> DOM
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
- `reactivity`: tracks reads and triggers effects on writes.
|
|
12
|
+
- `scheduler`: batches component update jobs and exposes `nextTick`.
|
|
13
|
+
- `component`: creates component instances, owns props, render effects, emit, and lifecycle hooks.
|
|
14
|
+
- `vnode`: represents elements, components, Fragment, props, keys, and children.
|
|
15
|
+
- `renderer`: mounts, patches, diffs, and unmounts VNodes.
|
|
16
|
+
- `event`: patches DOM event listeners through invoker caching.
|
|
17
|
+
- `store`: composes `reactive` and `computed` into a centralized state container.
|
|
18
|
+
|
|
19
|
+
## Update Flow
|
|
20
|
+
|
|
21
|
+
1. A component render function reads reactive state.
|
|
22
|
+
2. The active component `ReactiveEffect` tracks those reads.
|
|
23
|
+
3. A state write calls `trigger`.
|
|
24
|
+
4. The component effect scheduler queues the component update with `queueJob`.
|
|
25
|
+
5. The queued job reruns the component render.
|
|
26
|
+
6. The renderer patches the previous subtree against the next subtree.
|
|
27
|
+
7. `nextTick` resolves after the scheduler queue is flushed.
|
|
28
|
+
|
|
29
|
+
## Component Flow
|
|
30
|
+
|
|
31
|
+
Mounting a component creates a `ComponentInstance` with:
|
|
32
|
+
|
|
33
|
+
- `vnode`, `type`, `props`, `subTree`
|
|
34
|
+
- `render`, `effect`, `update`
|
|
35
|
+
- `emit`
|
|
36
|
+
- lifecycle arrays for mounted, updated, and unmounted hooks
|
|
37
|
+
|
|
38
|
+
The first render mounts the component subtree. Later renders patch the old subtree against the new subtree. Unmounting stops the component effect, recursively unmounts the subtree, and calls unmounted hooks.
|
|
39
|
+
|
|
40
|
+
## DOM Patch Flow
|
|
41
|
+
|
|
42
|
+
The renderer compares VNode type and key:
|
|
43
|
+
|
|
44
|
+
- Different type or key: unmount old node, mount new node.
|
|
45
|
+
- Same element: patch props, then patch children.
|
|
46
|
+
- Same component: update props, then run the component update.
|
|
47
|
+
- Fragment: patch or unmount its children without adding a wrapper element.
|
|
48
|
+
|
|
49
|
+
Children diff favors correctness and DOM reuse. Keyed children support insert, delete, move, and patch, and the
|
|
50
|
+
keyed-middle path uses LIS optimization to avoid unnecessary DOM moves.
|
|
51
|
+
|
|
52
|
+
## Event Flow
|
|
53
|
+
|
|
54
|
+
DOM event props use the `onXxx` convention. Each DOM element caches event invokers:
|
|
55
|
+
|
|
56
|
+
- First listener: add one native listener.
|
|
57
|
+
- Handler update: replace `invoker.value`.
|
|
58
|
+
- Prop removal or element unmount: remove the cached native listener.
|
|
59
|
+
|
|
60
|
+
This avoids repeated add/remove work when only the handler function changes.
|
|
61
|
+
|
|
62
|
+
## Package Shape
|
|
63
|
+
|
|
64
|
+
The package exposes:
|
|
65
|
+
|
|
66
|
+
- `solace`: root runtime APIs.
|
|
67
|
+
- `solace/jsx-runtime`: automatic JSX runtime.
|
|
68
|
+
- `solace/jsx-dev-runtime`: development JSX runtime.
|
|
69
|
+
|
|
70
|
+
Rollup builds ESM, CJS, and `.d.ts` artifacts for each public entry point.
|
|
71
|
+
|
|
72
|
+
## Examples
|
|
73
|
+
|
|
74
|
+
The Vite examples in `examples/` exercise the package through local aliases:
|
|
75
|
+
|
|
76
|
+
- `basic-counter`: JSX runtime, reactive state, and DOM events.
|
|
77
|
+
- `todo-app`: form input, keyed list updates, checkbox state, and deletion.
|
|
78
|
+
- `large-list`: 10,000 keyed rows and targeted text/class patching.
|
|
79
|
+
|
|
80
|
+
See `docs/examples.md` for run commands and e2e coverage.
|