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.
@@ -1,20 +1,21 @@
1
1
  # `<solid-render>`
2
2
 
3
- `<solid-render>` references an already-defined runtime module and mounts a component instance into its own light DOM.
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>Counter</button>;
7
+ export default function Counter(props) {
8
+ return <button>{props.initial}</button>;
9
9
  }
10
10
  </script>
11
11
 
12
- <solid-render module="/Counter.jsx"></solid-render>
12
+ <solid-render
13
+ module="/Counter.jsx"
14
+ props="{ initial: 100 }"
15
+ ></solid-render>
13
16
  ```
14
17
 
15
- ## Important HTML syntax rule
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>`. Because initial child content is captured as `props.children`, this can make later page content appear to disappear.
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
- ## Declarative props
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
- prop:name="Alice"
59
- prop:user-id="42"
60
- prop:compact
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
- Rules:
135
+ The built-in parser accepts safe JSON5-style data conveniences:
65
136
 
66
- - `prop:user-id` becomes `userId`
67
- - a present empty prop becomes `true`
68
- - other attribute values remain strings
69
- - renderer configuration (`module`, `component`, `data-solid-runtime`) is not forwarded
70
- - `prop:module` is a normal component prop and does not collide with renderer `module`
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
- ## Programmatic props
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
- ```ts
75
- const element = document.querySelector("solid-render");
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
- element.props = {
78
- user,
225
+ Then:
226
+
227
+ ```ts
228
+ renderer.props = {
229
+ step: 20,
79
230
  onSave,
80
- service,
81
231
  };
82
232
  ```
83
233
 
84
- Programmatic values may contain arbitrary JavaScript references and override declarative `prop:*` values.
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
- ## Reactive updates
254
+ renderer.setAttribute("prop:step", "{20}");
255
+ ```
87
256
 
88
- Changing `prop:*` or `.props` updates the existing component instance without remounting.
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
- Initial children are captured once. Named slots and dynamic child recapture are not part of the current API.
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` and `0.0.15`, the tested fallback line is Solid `2.0.0-rc.13`.
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
+ }
@@ -37,14 +37,19 @@
37
37
  <solid-render
38
38
  data-solid-runtime="main"
39
39
  module="/Counter.jsx"
40
- prop:label="Counter A"
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
- ></solid-render>
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.15",
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",