solid-tag-runtime 0.0.15 → 0.0.18
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/ARCHITECTURE.md +131 -4
- package/README.md +47 -3
- package/bench/observer.mjs +103 -0
- package/docs/api/html.md +33 -2
- package/docs/getting-started.md +18 -3
- package/docs/html-runtime.md +41 -5
- package/docs/lifecycle-events.md +6 -1
- package/docs/rendering.md +4 -0
- package/docs/solid-render.md +227 -29
- package/docs/solid-runtime-setup.md +1 -1
- package/examples/observer-strategies.js +19 -0
- package/examples/render.html +7 -2
- package/html.d.ts +29 -0
- package/package.json +5 -3
- package/src/html/data-literal.js +270 -0
- package/src/html.js +546 -97
package/docs/solid-render.md
CHANGED
|
@@ -1,20 +1,21 @@
|
|
|
1
1
|
# `<solid-render>`
|
|
2
2
|
|
|
3
|
-
`<solid-render>` references an already-defined runtime module and mounts
|
|
3
|
+
`<solid-render>` references an already-defined runtime module and mounts one component instance into its own light DOM.
|
|
4
4
|
|
|
5
5
|
```html
|
|
6
6
|
<script type="solid-jsx" module="/Counter.jsx">
|
|
7
|
-
export default function Counter() {
|
|
8
|
-
return <button>
|
|
7
|
+
export default function Counter(props) {
|
|
8
|
+
return <button>{props.initial}</button>;
|
|
9
9
|
}
|
|
10
10
|
</script>
|
|
11
11
|
|
|
12
|
-
<solid-render
|
|
12
|
+
<solid-render
|
|
13
|
+
module="/Counter.jsx"
|
|
14
|
+
props="{ initial: 100 }"
|
|
15
|
+
></solid-render>
|
|
13
16
|
```
|
|
14
17
|
|
|
15
|
-
##
|
|
16
|
-
|
|
17
|
-
**Always use an explicit closing tag in HTML.**
|
|
18
|
+
## Always use an explicit closing tag
|
|
18
19
|
|
|
19
20
|
Correct:
|
|
20
21
|
|
|
@@ -28,7 +29,68 @@ Do not write:
|
|
|
28
29
|
<solid-render module="/Counter.jsx" />
|
|
29
30
|
```
|
|
30
31
|
|
|
31
|
-
Custom elements are not HTML void elements. The HTML parser ignores the XML-style self-closing slash, so following siblings may become children of `<solid-render>`.
|
|
32
|
+
Custom elements are not HTML void elements. The HTML parser ignores the XML-style self-closing slash, so following siblings may accidentally become children of `<solid-render>`.
|
|
33
|
+
|
|
34
|
+
## Hidden until ready by default
|
|
35
|
+
|
|
36
|
+
`0.0.18` prevents initial light-DOM content from flashing before the requested component is ready.
|
|
37
|
+
|
|
38
|
+
```html
|
|
39
|
+
<solid-render module="/Counter.jsx">
|
|
40
|
+
this text is captured, but does not flash before the component mounts
|
|
41
|
+
</solid-render>
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
The runtime installs the required visibility rule automatically; application CSS is not required.
|
|
45
|
+
|
|
46
|
+
The element exposes its current state:
|
|
47
|
+
|
|
48
|
+
```text
|
|
49
|
+
data-solid-render-state="pending"
|
|
50
|
+
data-solid-render-state="ready"
|
|
51
|
+
data-solid-render-state="error"
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
The normal lifecycle is:
|
|
55
|
+
|
|
56
|
+
```text
|
|
57
|
+
pending
|
|
58
|
+
↓
|
|
59
|
+
module import / component mount
|
|
60
|
+
↓
|
|
61
|
+
ready
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
On failure:
|
|
65
|
+
|
|
66
|
+
```text
|
|
67
|
+
pending → error
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Error state is visible, and captured initial content is restored as fallback content.
|
|
71
|
+
|
|
72
|
+
The visibility rule is installed as soon as the HTML runtime is created. For pages that need to avoid any paint before the runtime bootstrap itself executes, load/bootstrap the HTML runtime early in the document.
|
|
73
|
+
|
|
74
|
+
### Show fallback content while pending
|
|
75
|
+
|
|
76
|
+
If initial children are intentional loading content, opt out of hiding:
|
|
77
|
+
|
|
78
|
+
```html
|
|
79
|
+
<solid-render
|
|
80
|
+
module="/Account.jsx"
|
|
81
|
+
show-until-ready
|
|
82
|
+
>
|
|
83
|
+
Loading account…
|
|
84
|
+
</solid-render>
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Programmatically:
|
|
88
|
+
|
|
89
|
+
```ts
|
|
90
|
+
renderer.hideUntilReady = false;
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
The state attribute still transitions through `pending`, `ready`, and `error`; only the visibility policy changes.
|
|
32
94
|
|
|
33
95
|
## Named component
|
|
34
96
|
|
|
@@ -39,6 +101,8 @@ Custom elements are not HTML void elements. The HTML parser ignores the XML-styl
|
|
|
39
101
|
></solid-render>
|
|
40
102
|
```
|
|
41
103
|
|
|
104
|
+
Without `component`, `module.default` is used.
|
|
105
|
+
|
|
42
106
|
## Runtime selection
|
|
43
107
|
|
|
44
108
|
```html
|
|
@@ -50,48 +114,180 @@ Custom elements are not HTML void elements. The HTML parser ignores the XML-styl
|
|
|
50
114
|
|
|
51
115
|
The selected controller must match scope rules and contain the element within its configured root.
|
|
52
116
|
|
|
53
|
-
##
|
|
117
|
+
## Structured declarative props
|
|
118
|
+
|
|
119
|
+
Use `props` for a structured object:
|
|
54
120
|
|
|
55
121
|
```html
|
|
56
122
|
<solid-render
|
|
57
123
|
module="/UserCard.jsx"
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
124
|
+
props="{
|
|
125
|
+
user: {
|
|
126
|
+
name: 'Alice',
|
|
127
|
+
age: 32,
|
|
128
|
+
},
|
|
129
|
+
compact: true,
|
|
130
|
+
tags: ['admin', 'active'],
|
|
131
|
+
}"
|
|
61
132
|
></solid-render>
|
|
62
133
|
```
|
|
63
134
|
|
|
64
|
-
|
|
135
|
+
The built-in parser accepts safe JSON5-style data conveniences:
|
|
65
136
|
|
|
66
|
-
-
|
|
67
|
-
-
|
|
68
|
-
-
|
|
69
|
-
-
|
|
70
|
-
-
|
|
137
|
+
- unquoted object keys
|
|
138
|
+
- single- or double-quoted strings
|
|
139
|
+
- arrays and nested objects
|
|
140
|
+
- numbers, booleans, and `null`
|
|
141
|
+
- trailing commas
|
|
142
|
+
- line/block comments
|
|
71
143
|
|
|
72
|
-
|
|
144
|
+
Declarative props are **data only**. The parser never uses `eval()` or `new Function()` and does not resolve JavaScript variables, member access, calls, functions, or constructors.
|
|
73
145
|
|
|
74
|
-
|
|
75
|
-
|
|
146
|
+
This is rejected rather than executed:
|
|
147
|
+
|
|
148
|
+
```html
|
|
149
|
+
<solid-render props="{ value: getValue() }"></solid-render>
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
The top-level `props` value must be an object.
|
|
153
|
+
|
|
154
|
+
## Individual `prop:*` values
|
|
155
|
+
|
|
156
|
+
Plain HTML attribute values remain strings:
|
|
157
|
+
|
|
158
|
+
```html
|
|
159
|
+
<solid-render
|
|
160
|
+
prop:first="123"
|
|
161
|
+
prop:second=123
|
|
162
|
+
></solid-render>
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
Both values are the string `"123"`.
|
|
166
|
+
|
|
167
|
+
A present empty prop remains boolean `true`:
|
|
168
|
+
|
|
169
|
+
```html
|
|
170
|
+
<solid-render prop:compact></solid-render>
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
To opt one prop into typed data parsing, wrap it in one outer `{...}` pair:
|
|
174
|
+
|
|
175
|
+
```html
|
|
176
|
+
<solid-render
|
|
177
|
+
prop:count="{3}"
|
|
178
|
+
prop:enabled="{true}"
|
|
179
|
+
prop:missing="{null}"
|
|
180
|
+
prop:label="{'3'}"
|
|
181
|
+
prop:items="{[1, 2, 3]}"
|
|
182
|
+
prop:options="{{ theme: 'dark', step: 5 }}"
|
|
183
|
+
></solid-render>
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
Effective values:
|
|
187
|
+
|
|
188
|
+
```js
|
|
189
|
+
{
|
|
190
|
+
count: 3,
|
|
191
|
+
enabled: true,
|
|
192
|
+
missing: null,
|
|
193
|
+
label: "3",
|
|
194
|
+
items: [1, 2, 3],
|
|
195
|
+
options: { theme: "dark", step: 5 },
|
|
196
|
+
}
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
The outer braces are a typed-data marker, not JavaScript expression syntax. Object values naturally use double braces because the inner braces belong to the object literal.
|
|
200
|
+
|
|
201
|
+
`prop:user-id` still normalizes to `userId`.
|
|
202
|
+
|
|
203
|
+
## Prop precedence
|
|
204
|
+
|
|
205
|
+
The effective prop layers are:
|
|
206
|
+
|
|
207
|
+
```text
|
|
208
|
+
props="..."
|
|
209
|
+
↓ overridden by
|
|
210
|
+
prop:*
|
|
211
|
+
↓ overridden by
|
|
212
|
+
element.props
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
Example:
|
|
216
|
+
|
|
217
|
+
```html
|
|
218
|
+
<solid-render
|
|
219
|
+
module="/Counter.jsx"
|
|
220
|
+
props="{ initial: 100, step: 5 }"
|
|
221
|
+
prop:step="{10}"
|
|
222
|
+
></solid-render>
|
|
223
|
+
```
|
|
76
224
|
|
|
77
|
-
|
|
78
|
-
|
|
225
|
+
Then:
|
|
226
|
+
|
|
227
|
+
```ts
|
|
228
|
+
renderer.props = {
|
|
229
|
+
step: 20,
|
|
79
230
|
onSave,
|
|
80
|
-
service,
|
|
81
231
|
};
|
|
82
232
|
```
|
|
83
233
|
|
|
84
|
-
Programmatic
|
|
234
|
+
The effective `step` is `20`. Programmatic `.props` can contain arbitrary JavaScript values such as functions, signals, services, class instances, Maps/Sets, or identity-sensitive objects.
|
|
235
|
+
|
|
236
|
+
## Reactive prop updates
|
|
237
|
+
|
|
238
|
+
Changing any of these updates the existing mounted component without remounting:
|
|
239
|
+
|
|
240
|
+
```text
|
|
241
|
+
props attribute
|
|
242
|
+
prop:* attributes
|
|
243
|
+
element.props
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
For example:
|
|
247
|
+
|
|
248
|
+
```ts
|
|
249
|
+
renderer.setAttribute(
|
|
250
|
+
"props",
|
|
251
|
+
"{ initial: 200, options: { theme: 'light' } }",
|
|
252
|
+
);
|
|
85
253
|
|
|
86
|
-
|
|
254
|
+
renderer.setAttribute("prop:step", "{20}");
|
|
255
|
+
```
|
|
87
256
|
|
|
88
|
-
|
|
257
|
+
Local component state is preserved.
|
|
89
258
|
|
|
90
259
|
Changing `module`, `component`, or `data-solid-runtime` changes render identity and therefore disposes/remounts.
|
|
91
260
|
|
|
261
|
+
Invalid declarative data produces a `solid-render` error; it is never partially executed as JavaScript.
|
|
262
|
+
|
|
263
|
+
## Custom declarative parser
|
|
264
|
+
|
|
265
|
+
Applications with a domain-specific data syntax may override declarative parsing at the HTML-controller boundary:
|
|
266
|
+
|
|
267
|
+
```ts
|
|
268
|
+
const html = createHTMLRuntime(runtime, {
|
|
269
|
+
parseProps(source, context) {
|
|
270
|
+
if (context.kind === "props") {
|
|
271
|
+
return myObjectParser(source);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
return myValueParser(source);
|
|
275
|
+
},
|
|
276
|
+
});
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
Context distinguishes:
|
|
280
|
+
|
|
281
|
+
```text
|
|
282
|
+
kind: "props" bulk props attribute
|
|
283
|
+
kind: "prop" typed prop:* value
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
A custom bulk parser must still return an object. Parser customization changes data decoding only; it does not change runtime selection, module resolution, or component ownership.
|
|
287
|
+
|
|
92
288
|
## Children
|
|
93
289
|
|
|
94
|
-
Initial child DOM becomes `props.children`:
|
|
290
|
+
Initial child DOM is captured once and becomes `props.children`:
|
|
95
291
|
|
|
96
292
|
```html
|
|
97
293
|
<solid-render module="/Card.jsx">
|
|
@@ -99,7 +295,9 @@ Initial child DOM becomes `props.children`:
|
|
|
99
295
|
</solid-render>
|
|
100
296
|
```
|
|
101
297
|
|
|
102
|
-
|
|
298
|
+
By default those children are hidden while the element is pending, then instantiated through `props.children` when the component mounts. With `show-until-ready`, a clone is also displayed as fallback content while pending.
|
|
299
|
+
|
|
300
|
+
Named slots and dynamic child recapture are not part of the current API.
|
|
103
301
|
|
|
104
302
|
## Multiple instances
|
|
105
303
|
|
|
@@ -126,7 +126,7 @@ When provider fallback is needed and there is no versioned Solid anchor, this re
|
|
|
126
126
|
import { TESTED_SOLID_VERSION } from "solid-tag-runtime/solid";
|
|
127
127
|
```
|
|
128
128
|
|
|
129
|
-
For `0.0.14`
|
|
129
|
+
For `0.0.14` through `0.0.18`, the tested fallback line is Solid `2.0.0-rc.13`.
|
|
130
130
|
|
|
131
131
|
If an existing Solid mapping is explicitly versioned, that version becomes the family anchor for generated siblings. An explicitly unversioned existing mapping remains unversioned; the loader does not pretend it is pinned.
|
|
132
132
|
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { createHTMLRuntime } from "solid-tag-runtime/html";
|
|
2
|
+
|
|
3
|
+
export async function manual(runtime, root) {
|
|
4
|
+
const html = createHTMLRuntime(runtime, { root });
|
|
5
|
+
await html.register();
|
|
6
|
+
return html;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export async function bootstrap(runtime, root) {
|
|
10
|
+
const html = createHTMLRuntime(runtime, { root });
|
|
11
|
+
await html.observe({ mode: "bootstrap", idleMs: 50 });
|
|
12
|
+
return html; // disconnected after startup settles
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export async function continuous(runtime, root) {
|
|
16
|
+
const html = createHTMLRuntime(runtime, { root });
|
|
17
|
+
await html.observe({ mode: "continuous" });
|
|
18
|
+
return html;
|
|
19
|
+
}
|
package/examples/render.html
CHANGED
|
@@ -37,14 +37,19 @@
|
|
|
37
37
|
<solid-render
|
|
38
38
|
data-solid-runtime="main"
|
|
39
39
|
module="/Counter.jsx"
|
|
40
|
-
|
|
40
|
+
props="{ label: 'Counter A' }"
|
|
41
|
+
prop:initial="{100}"
|
|
41
42
|
></solid-render>
|
|
42
43
|
|
|
43
44
|
<solid-render
|
|
44
45
|
data-solid-runtime="main"
|
|
45
46
|
module="/Counter.jsx"
|
|
46
47
|
prop:label="Counter B"
|
|
47
|
-
|
|
48
|
+
prop:initial="{200}"
|
|
49
|
+
show-until-ready
|
|
50
|
+
>
|
|
51
|
+
Loading Counter B…
|
|
52
|
+
</solid-render>
|
|
48
53
|
|
|
49
54
|
<!--
|
|
50
55
|
This bootstrap is ordinary JavaScript. The surrounding application/import
|
package/html.d.ts
CHANGED
|
@@ -18,8 +18,13 @@ export interface HTMLAttributeElementLike {
|
|
|
18
18
|
|
|
19
19
|
export interface HTMLModuleScriptElement extends HTMLAttributeElementLike {}
|
|
20
20
|
|
|
21
|
+
export type SolidRenderState = "pending" | "ready" | "error";
|
|
22
|
+
|
|
21
23
|
export interface SolidRenderElement extends HTMLAttributeElementLike, HTMLAppendTarget {
|
|
22
24
|
props: Record<string, unknown>;
|
|
25
|
+
readonly renderState: SolidRenderState;
|
|
26
|
+
/** Default true. Set false to expose captured fallback children while pending. */
|
|
27
|
+
hideUntilReady: boolean;
|
|
23
28
|
childNodes?: ArrayLike<any> | Iterable<any>;
|
|
24
29
|
attributes?: ArrayLike<{ name: string; value: string }> | Iterable<{ name: string; value: string }>;
|
|
25
30
|
replaceChildren?(...nodes: any[]): void;
|
|
@@ -32,6 +37,7 @@ export interface HTMLAppendTarget {
|
|
|
32
37
|
}
|
|
33
38
|
|
|
34
39
|
export interface HTMLDocumentLike extends HTMLModuleRoot, HTMLAppendTarget {
|
|
40
|
+
head?: HTMLAppendTarget;
|
|
35
41
|
body?: HTMLAppendTarget;
|
|
36
42
|
documentElement?: HTMLAppendTarget;
|
|
37
43
|
createElement?(tagName: string): any;
|
|
@@ -159,6 +165,8 @@ export interface RemoveHTMLElementResult {
|
|
|
159
165
|
|
|
160
166
|
export interface RegisterHTMLOptions extends DefineScriptOptions {
|
|
161
167
|
selector?: string;
|
|
168
|
+
/** Override safe declarative data parsing for `<solid-render props>` and typed `prop:*="{...}"`. */
|
|
169
|
+
parseProps?: HTMLDeclarativePropsParser;
|
|
162
170
|
executeEntries?: boolean;
|
|
163
171
|
/** Execute declarative render actions. Default: true. */
|
|
164
172
|
executeRenders?: boolean;
|
|
@@ -223,7 +231,13 @@ export type HTMLRuntimeEventType = HTMLRuntimeEvent["type"];
|
|
|
223
231
|
export type HTMLRuntimeEventOfType<T extends HTMLRuntimeEventType> =
|
|
224
232
|
Extract<HTMLRuntimeEvent, { type: T }>;
|
|
225
233
|
|
|
234
|
+
export type HTMLObservationMode = "continuous" | "bootstrap";
|
|
235
|
+
|
|
226
236
|
export interface ObserveHTMLOptions extends RegisterHTMLOptions {
|
|
237
|
+
/** Observation lifecycle. Default: "continuous". */
|
|
238
|
+
mode?: HTMLObservationMode;
|
|
239
|
+
/** Bootstrap-mode quiet period after DOM readiness and the last relevant mutation. Default: 50 ms. */
|
|
240
|
+
idleMs?: number;
|
|
227
241
|
/** Register scripts already present when observation starts. Default: true. */
|
|
228
242
|
registerExisting?: boolean;
|
|
229
243
|
/** Observe descendant additions as well as direct children. Default: true. */
|
|
@@ -251,6 +265,19 @@ export interface AddHTMLModuleOptions extends DefineScriptOptions {
|
|
|
251
265
|
createElement?: (tagName: string) => HTMLModuleScriptElement;
|
|
252
266
|
}
|
|
253
267
|
|
|
268
|
+
export interface HTMLDeclarativePropsParseContext {
|
|
269
|
+
element: SolidRenderElement;
|
|
270
|
+
/** `props` parses a top-level object; `prop` parses one typed prop value. */
|
|
271
|
+
kind: "props" | "prop";
|
|
272
|
+
attribute: string;
|
|
273
|
+
key?: string;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
export type HTMLDeclarativePropsParser = (
|
|
277
|
+
source: string,
|
|
278
|
+
context: HTMLDeclarativePropsParseContext,
|
|
279
|
+
) => unknown;
|
|
280
|
+
|
|
254
281
|
export interface HTMLRuntimeOptions extends ObserveHTMLOptions {
|
|
255
282
|
appendTo?: HTMLAppendTarget;
|
|
256
283
|
createElement?: (tagName: string) => HTMLModuleScriptElement;
|
|
@@ -317,6 +344,8 @@ export interface CustomElementRegistryLike {
|
|
|
317
344
|
export interface RegisterSolidRenderElementOptions {
|
|
318
345
|
customElements?: CustomElementRegistryLike;
|
|
319
346
|
HTMLElement?: new (...args: any[]) => any;
|
|
347
|
+
/** Document that receives the built-in pending-visibility rule. Defaults to global document. */
|
|
348
|
+
document?: HTMLDocumentLike;
|
|
320
349
|
}
|
|
321
350
|
|
|
322
351
|
export type HTMLObserverController = HTMLRuntimeController;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "solid-tag-runtime",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.18",
|
|
4
4
|
"description": "Runtime module system for JSX modules compiled with solid-tag and executed through @solidjs/html",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -41,7 +41,8 @@
|
|
|
41
41
|
"README.md",
|
|
42
42
|
"ARCHITECTURE.md",
|
|
43
43
|
"docs",
|
|
44
|
-
"examples"
|
|
44
|
+
"examples",
|
|
45
|
+
"bench"
|
|
45
46
|
],
|
|
46
47
|
"sideEffects": false,
|
|
47
48
|
"dependencies": {
|
|
@@ -49,7 +50,8 @@
|
|
|
49
50
|
},
|
|
50
51
|
"scripts": {
|
|
51
52
|
"test": "node ./test/run.mjs",
|
|
52
|
-
"pack:check": "npm pack --dry-run"
|
|
53
|
+
"pack:check": "npm pack --dry-run",
|
|
54
|
+
"bench:observer": "node ./bench/observer.mjs"
|
|
53
55
|
},
|
|
54
56
|
"keywords": [
|
|
55
57
|
"solid",
|