solid-tag-runtime 0.0.7 → 0.0.9

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 CHANGED
@@ -42,7 +42,9 @@ The runtime should allow applications to:
42
42
  9. observe runtime lifecycle transitions for tooling/tracing
43
43
  10. inspect dependencies and compiled source
44
44
  11. support normal JavaScript modules in the same graph
45
- 12. keep the JSX compiler replaceable behind a narrow adapter
45
+ 12. declaratively mount runtime component exports through the HTML adapter
46
+ 13. reuse existing runtime modules through a light-DOM `solid-render` custom element
47
+ 14. keep the JSX compiler replaceable behind a narrow adapter
46
48
 
47
49
  ## 3. Current non-goals
48
50
 
@@ -101,6 +103,7 @@ const runtime = createRuntime(options);
101
103
 
102
104
  ```ts
103
105
  runtime.define(id, source, options?);
106
+ runtime.defineMany(definitions);
104
107
  runtime.update(id, source, options?);
105
108
  ```
106
109
 
@@ -138,9 +141,18 @@ runtime.getModuleInfo(id);
138
141
 
139
142
  ```ts
140
143
  runtime.invalidate(id);
144
+ runtime.remove(id, options?);
145
+ runtime.clear(options?);
146
+
147
+ runtime.subscribe(listener);
148
+ runtime.subscribe(type, listener);
149
+ runtime.subscribe(types, listener);
150
+
141
151
  runtime.dispose();
142
152
  ```
143
153
 
154
+ Lifecycle subscriptions are synchronous observational notifications. They do not intercept or modify runtime behavior. Resolution behavior remains customizable through `resolve()`, compilation through the compiler adapter, and executable-module creation through `ModuleUrlBackend`.
155
+
144
156
  ## 6. Module kinds
145
157
 
146
158
  The runtime currently has three module record kinds.
@@ -232,6 +244,10 @@ await html.observe({ registerExisting: false });
232
244
  The controller exposes:
233
245
 
234
246
  ```ts
247
+ html.subscribe(listener);
248
+ html.subscribe(type, listener);
249
+ html.subscribe(types, listener);
250
+
235
251
  html.register(options?);
236
252
  html.observe(options?);
237
253
  html.flush(options?);
@@ -1031,3 +1047,152 @@ Invariants:
1031
1047
  8. `removeElement()` is the canonical operation for releasing shared WeakMap ownership.
1032
1048
 
1033
1049
  Regression coverage: package tests cover batch definition and duplicate rejection, dependency removal/invalidation, clear-with-host-preservation, lifecycle event delivery/unsubscribe, owned element updates, identity-change rejection, ownership release, DOM removal, and preserving a runtime module while releasing its HTML element.
1050
+
1051
+ ### 0.0.8 — selective lifecycle subscriptions and HTML observability
1052
+
1053
+ Decision: expand lifecycle observation into typed, independently removable subscriptions on both the core runtime and the HTML adapter. Keep lifecycle subscriptions observational and separate from behavioral hooks/interceptors.
1054
+
1055
+ Core subscription forms:
1056
+
1057
+ ```ts
1058
+ runtime.subscribe(listener);
1059
+ runtime.subscribe("module-error", listener);
1060
+ runtime.subscribe(["module-evaluating", "module-evaluated"], listener);
1061
+ ```
1062
+
1063
+ HTML controllers expose the same shape:
1064
+
1065
+ ```ts
1066
+ html.subscribe(listener);
1067
+ html.subscribe("element-registered", listener);
1068
+ html.subscribe(["observer-batch", "html-error"], listener);
1069
+ ```
1070
+
1071
+ Each call creates an independent subscription and returns its own disposer. Disposing one subscription never affects another registration of the same callback or subscriptions to other event groups. Subscriber exceptions are reported but cannot alter runtime/HTML operations.
1072
+
1073
+ Core event additions:
1074
+
1075
+ ```text
1076
+ module-updated
1077
+ modules-defined
1078
+ module-resolving
1079
+ module-resolved
1080
+ module-linking
1081
+ module-linked
1082
+ ```
1083
+
1084
+ The evaluation pipeline is now observed in semantic order:
1085
+
1086
+ ```text
1087
+ module-linking
1088
+ ↓
1089
+ module-resolving / module-resolved
1090
+ ↓
1091
+ module-linked
1092
+ ↓
1093
+ module-compiled
1094
+ ↓
1095
+ module-evaluating
1096
+ ↓
1097
+ module-evaluated
1098
+ ```
1099
+
1100
+ `module-resolved` exposes the logical resolution result and resolution kind, while `module-linked` exposes source specifier → resolved module-ID dependencies without exposing Blob/data URL implementation details. `modules-defined` marks the completion of a `defineMany()` batch after buffered per-module events become observable. `module-updated` supplements the backward-compatible `module-defined { operation: "update" }` event with an explicit semantic update notification.
1101
+
1102
+ `module-error` remains one event with a structured pipeline phase instead of creating separate error event types. Supported phases are `resolve`, `analyze`, `compile`, `link`, `url-create`, `evaluate`, and `host-bridge`.
1103
+
1104
+ HTML controller event additions:
1105
+
1106
+ ```text
1107
+ element-discovered
1108
+ element-claimed
1109
+ element-loading
1110
+ element-loaded
1111
+ element-registered
1112
+ element-updating
1113
+ element-updated
1114
+ element-removing
1115
+ element-released
1116
+ element-removed
1117
+ observer-connected
1118
+ observer-disconnected
1119
+ observer-batch
1120
+ root-changing
1121
+ root-changed
1122
+ append-target-changed
1123
+ entry-executing
1124
+ entry-executed
1125
+ html-error
1126
+ ```
1127
+
1128
+ HTML ownership events include an origin describing the ingestion path (`initial-scan`, `observer`, `register-element`, `append`, `add-module`, etc.). The observer emits summarized `observer-batch` events instead of exposing raw `MutationRecord` values. Root/append-target events belong only to the HTML controller and are deliberately not mixed into core runtime events.
1129
+
1130
+ Architectural boundary:
1131
+
1132
+ ```text
1133
+ Observation
1134
+ runtime.subscribe()
1135
+ html.subscribe()
1136
+
1137
+ Behavior customization
1138
+ resolve()
1139
+ compiler adapter
1140
+ ModuleUrlBackend
1141
+ ```
1142
+
1143
+ Lifecycle listeners are not interceptors: they cannot cancel evaluation, rewrite source, change resolution, or alter cache behavior. If source/evaluation interception is needed later, it must be designed as a separate hook contract with explicit ordering, async, cancellation, and caching semantics.
1144
+
1145
+ Implementation note: core and HTML event streams share a small internal dispatcher so all/selective subscription behavior, independent disposers, and listener-error isolation have identical semantics while keeping separate event unions.
1146
+
1147
+ Regression coverage: package tests cover catch-all, single-type, and multi-type subscriptions; independent unsubscribe; listener exception isolation; update/batch events; resolution/link/evaluation ordering and payloads; structured resolution errors; HTML ownership origins; observer connect/disconnect/batch events; root and append-target events; external source loading; entry execution; update/removal events; HTML error reporting; and HTML subscriber exception isolation.
1148
+
1149
+
1150
+
1151
+ ### 0.0.9 — shared declarative rendering and `solid-render`
1152
+
1153
+ Decision: rendering remains entirely inside `solid-tag-runtime/html`. The core runtime still owns module identity, resolution, linking, execution, caching, invalidation, and host-module injection; it has no DOM or component-mounting responsibility.
1154
+
1155
+ Two public rendering forms now share one internal rendering core:
1156
+
1157
+ ```text
1158
+ <script module render>
1159
+ define source + mount one component instance
1160
+
1161
+ <solid-render module>
1162
+ reference an existing module + mount one component instance
1163
+ ```
1164
+
1165
+ Declarative script rendering follows these invariants:
1166
+
1167
+ - `render="#selector"` imports the module, selects `default` unless `component="Name"` is present, and uses normal Solid container rendering.
1168
+ - bare `render` replaces the declaration with start/end comment markers and mounts with positional insertion before the end marker; no wrapper element is introduced.
1169
+ - every discovered module in a batch is defined before any `entry` or render action executes.
1170
+ - `entry` and `render` on the same declaration are rejected as ambiguous.
1171
+ - anonymous `render` declarations receive internal runtime IDs just like anonymous entries.
1172
+ - render ownership/disposal is stored on the HTML declaration record and remains separate from module lifetime.
1173
+ - `updateElement()` remounts a rendered declaration after source/component/render metadata changes while preserving the same logical module identity.
1174
+ - `removeElement()` disposes mounted UI before releasing declaration/module ownership.
1175
+ - observer-discovered render declarations use the same batch/ownership pipeline as ordinary declarations.
1176
+
1177
+ The shared render adapter lazily resolves the host application's own `solid-js` and `@solidjs/web` modules through the runtime (with `solid-js/web` as a compatibility fallback). This preserves the no-duplicate-Solid invariant and keeps the HTML package free of a direct Solid dependency. Tests/special hosts may inject a render adapter.
1178
+
1179
+ `solid-render` invariants:
1180
+
1181
+ 1. It references an existing runtime module; it never defines source.
1182
+ 2. Missing `component` means `namespace.default`; `component="Name"` selects a named export.
1183
+ 3. `data-solid-runtime` selects the HTML runtime through the shared controller registry. With no attribute, a single unambiguous controller may be used.
1184
+ 4. The element remains in the DOM and is the light-DOM mount container. Wrapperless rendering belongs to bare `<script render>`.
1185
+ 5. Renderer configuration (`module`, `component`, `data-solid-runtime`) is never forwarded as component props.
1186
+ 6. Declarative props use `prop:*`; kebab names normalize to camelCase. Empty/presence values are boolean `true`, other values remain strings.
1187
+ 7. `.props` carries arbitrary JavaScript references and overrides declarative props.
1188
+ 8. Prop changes update a reactive prop facade and do not remount the component, preserving local state.
1189
+ 9. Module/component/runtime identity changes increment a generation token, dispose the current owner, and remount. Stale async import successes and failures are ignored; they cannot mount or report an error against a newer identity.
1190
+ 10. Initial light-DOM child nodes are captured once and exposed as reusable `props.children`; named slots and dynamic child recapture are not part of this phase.
1191
+ 11. Multiple elements may share one evaluated module namespace while owning independent Solid component roots.
1192
+ 12. Disconnect disposes the mounted root; reconnect mounts a fresh component instance from the retained declaration inputs.
1193
+ 13. Custom-element registration is global/idempotent per `CustomElementRegistry`. `createHTMLRuntime()` registers it after installing the controller in the shared registry; registration/observation paths remain idempotent. `registerSolidRenderElement()` remains available for explicit/custom-registry use.
1194
+ 14. Runtime/controller disposal tears down both script-owned mounts and connected `solid-render` instances so no Solid owners are orphaned.
1195
+
1196
+ Rendering lifecycle joins the existing HTML event stream through `render-mounting`, `render-mounted`, `render-disposing`, and `render-disposed`; failures are also reported through `html-error` with `render`/`solid-render` operations. Lifecycle subscriptions remain observational and cannot alter rendering.
1197
+
1198
+ The first `solid-render` phase intentionally excludes named slots, Shadow DOM, automatic numeric/JSON coercion, arbitrary unprefixed prop forwarding, loading/fallback templates, and expression evaluation inside attributes.
package/README.md CHANGED
@@ -107,8 +107,8 @@ Browser import maps must map the subpath explicitly as well as the package root:
107
107
  ```json
108
108
  {
109
109
  "imports": {
110
- "solid-tag-runtime": "https://esm.sh/solid-tag-runtime@0.0.7",
111
- "solid-tag-runtime/html": "https://esm.sh/solid-tag-runtime@0.0.7/html"
110
+ "solid-tag-runtime": "https://esm.sh/solid-tag-runtime@0.0.9",
111
+ "solid-tag-runtime/html": "https://esm.sh/solid-tag-runtime@0.0.9/html"
112
112
  }
113
113
  }
114
114
  ```
@@ -138,6 +138,155 @@ await html.register();
138
138
  await html.observe({ registerExisting: false });
139
139
  ```
140
140
 
141
+
142
+ ## Render runtime UI
143
+
144
+ `solid-tag-runtime/html` can now mount component exports as an HTML-adapter concern while the core runtime remains DOM-independent.
145
+
146
+ There are two complementary forms:
147
+
148
+ ```text
149
+ <script module>
150
+ define a runtime module
151
+
152
+ <script module render>
153
+ define a module + mount one instance
154
+
155
+ <solid-render module>
156
+ reference an existing module + mount one instance
157
+ ```
158
+
159
+ ### Define and render in one declaration
160
+
161
+ Render the default export into a normal Solid container:
162
+
163
+ ```html
164
+ <div id="app"></div>
165
+
166
+ <script
167
+ type="solid-jsx"
168
+ data-solid-runtime="main"
169
+ module="/App.jsx"
170
+ render="#app"
171
+ >
172
+ export default function App() {
173
+ return <h1>Hello from runtime JSX</h1>;
174
+ }
175
+ </script>
176
+ ```
177
+
178
+ `render` defaults to `module.default`. Select a named export with `component`:
179
+
180
+ ```html
181
+ <script
182
+ type="solid-jsx"
183
+ module="/widgets.jsx"
184
+ render="#app"
185
+ component="Counter"
186
+ >
187
+ export function Counter() {
188
+ return <button>Counter</button>;
189
+ }
190
+ </script>
191
+ ```
192
+
193
+ Bare `render` mounts at the declaration's exact sibling position without adding a wrapper:
194
+
195
+ ```html
196
+ <p>Before</p>
197
+
198
+ <script type="solid-jsx" module="/Message.jsx" render>
199
+ export default function Message() {
200
+ return <strong>Hello</strong>;
201
+ }
202
+ </script>
203
+
204
+ <p>After</p>
205
+ ```
206
+
207
+ The adapter replaces the declaration with an owned start/end marker range and inserts the component between the markers. The module definition and mount lifecycle remain separate: removing the declaration through `html.removeElement()` disposes the Solid owner while module removal follows the existing lifecycle options.
208
+
209
+ `entry` and `render` are intentionally mutually exclusive on one declaration. Use separate declarations when a side-effect entry and a mounted component are both needed.
210
+
211
+ All matching modules in one scan/observer batch are defined before any `entry` or `render` action executes, so a render declaration may import a dependency declared later in the same batch.
212
+
213
+ ### Reuse an existing module with `<solid-render>`
214
+
215
+ `<solid-render>` references an already-defined module and remains as the light-DOM mount container:
216
+
217
+ ```html
218
+ <solid-render
219
+ data-solid-runtime="main"
220
+ module="/Counter.jsx"
221
+ ></solid-render>
222
+ ```
223
+
224
+ Named export:
225
+
226
+ ```html
227
+ <solid-render
228
+ data-solid-runtime="main"
229
+ module="/widgets.jsx"
230
+ component="Counter"
231
+ ></solid-render>
232
+ ```
233
+
234
+ Component props use an explicit namespace so renderer configuration can never collide with component prop names:
235
+
236
+ ```html
237
+ <solid-render
238
+ module="/UserCard.jsx"
239
+ prop:name="Alice"
240
+ prop:module="billing"
241
+ prop:compact
242
+ ></solid-render>
243
+ ```
244
+
245
+ Declarative values are conservative:
246
+
247
+ ```text
248
+ prop:name="Alice" → "Alice"
249
+ prop:compact → true
250
+ prop:count="42" → "42"
251
+ ```
252
+
253
+ Kebab-case prop names normalize to camelCase (`prop:user-id` → `userId`). Arbitrary JavaScript references use the `.props` property:
254
+
255
+ ```ts
256
+ const renderer = document.querySelector("solid-render");
257
+
258
+ renderer.props = {
259
+ user,
260
+ onSave,
261
+ service,
262
+ };
263
+ ```
264
+
265
+ Programmatic props override `prop:*` attributes. Prop changes update the existing component instance reactively and preserve local Solid state. Changes to `module`, `component`, or `data-solid-runtime` dispose the current component and mount a new identity.
266
+
267
+ Initial light-DOM children become `props.children`:
268
+
269
+ ```html
270
+ <solid-render module="/Card.jsx" prop:title="Profile">
271
+ <p>Hello from HTML.</p>
272
+ <solid-render module="/SaveButton.jsx"></solid-render>
273
+ </solid-render>
274
+ ```
275
+
276
+ The first implementation deliberately has no named-slot system and no Shadow DOM. Nested `<solid-render>` elements work through normal custom-element connection when captured children are instantiated.
277
+
278
+ `createHTMLRuntime()` ensures the custom element is registered globally once after the controller has entered the shared runtime registry. `registerHTML()` / `observeHTML()` keep the same idempotent guarantee. Most applications therefore do not need to call a registration helper directly.
279
+
280
+ For custom registries, tests, or explicit registration, the helper remains available:
281
+
282
+ ```ts
283
+ import { registerSolidRenderElement } from "solid-tag-runtime/html";
284
+
285
+ registerSolidRenderElement();
286
+ ```
287
+
288
+ Multiple `<solid-render>` elements may share one cached runtime module namespace while each mounted component owns independent Solid state. Async module changes are generation-guarded so a stale import can never replace a newer `module`/`component`/runtime selection.
289
+
141
290
  A scope is written to owned script elements as:
142
291
 
143
292
  ```html
@@ -572,28 +721,136 @@ removes everything. The returned array contains the IDs that were removed.
572
721
 
573
722
  ### Lifecycle events
574
723
 
724
+ `0.0.8` expands lifecycle subscriptions into a typed event stream with independent join/leave semantics.
725
+
726
+ Subscribe to everything:
727
+
575
728
  ```ts
576
729
  const unsubscribe = runtime.subscribe(event => {
577
730
  console.log(event.type, event);
578
731
  });
732
+ ```
733
+
734
+ Subscribe to one event type:
735
+
736
+ ```ts
737
+ const leaveErrors = runtime.subscribe(
738
+ "module-error",
739
+ event => {
740
+ console.error(event.phase, event.error);
741
+ },
742
+ );
743
+ ```
744
+
745
+ Subscribe to several event types:
746
+
747
+ ```ts
748
+ const leaveEvaluation = runtime.subscribe(
749
+ ["module-evaluating", "module-evaluated"],
750
+ event => {
751
+ console.log(event.type, event.id);
752
+ },
753
+ );
754
+ ```
755
+
756
+ Each call owns an independent subscription:
757
+
758
+ ```ts
759
+ leaveEvaluation();
760
+ // the error subscription remains active
761
+ ```
762
+
763
+ Core events include:
764
+
765
+ ```text
766
+ module-defined
767
+ module-updated
768
+ modules-defined
769
+
770
+ module-resolving
771
+ module-resolved
772
+
773
+ module-linking
774
+ module-linked
775
+
776
+ module-invalidated
777
+ module-compiled
778
+ module-evaluating
779
+ module-evaluated
780
+
781
+ module-removed
782
+ module-error
783
+
784
+ runtime-cleared
785
+ runtime-disposed
786
+ ```
787
+
788
+ `module-resolved` reports how a specifier was resolved (`relative`, `registered`, `custom`, `native`, and so on). `module-linked` exposes the logical dependency mapping used to link a source module. `modules-defined` marks the completion of a `defineMany()` batch after all definitions have been installed.
789
+
790
+ `module-error.phase` is one of the runtime pipeline phases such as `resolve`, `analyze`, `compile`, `link`, `url-create`, `evaluate`, or `host-bridge`.
791
+
792
+ Subscribers are observational only: they cannot cancel or modify runtime operations, and subscriber exceptions are isolated from runtime execution. Behavioral extension remains separate through the resolver, compiler adapter, and module URL backend.
793
+
794
+ ### HTML lifecycle events
795
+
796
+ The HTML controller has its own event stream because DOM ownership/observation is intentionally separate from the core module engine:
797
+
798
+ ```ts
799
+ const leaveHTML = html.subscribe(event => {
800
+ console.log(event.type, event);
801
+ });
802
+ ```
803
+
804
+ The same selective forms are supported:
805
+
806
+ ```ts
807
+ const leaveOwnership = html.subscribe(
808
+ ["element-claimed", "element-registered", "element-removed"],
809
+ event => {
810
+ console.log(event.type, event.moduleId);
811
+ },
812
+ );
813
+ ```
814
+
815
+ HTML events include:
816
+
817
+ ```text
818
+ element-discovered
819
+ element-claimed
820
+ element-loading
821
+ element-loaded
822
+ element-registered
823
+
824
+ element-updating
825
+ element-updated
826
+ element-removing
827
+ element-released
828
+ element-removed
829
+
830
+ observer-connected
831
+ observer-disconnected
832
+ observer-batch
833
+
834
+ root-changing
835
+ root-changed
836
+ append-target-changed
837
+
838
+ entry-executing
839
+ entry-executed
840
+
841
+ render-mounting
842
+ render-mounted
843
+ render-disposing
844
+ render-disposed
579
845
 
580
- // later
581
- unsubscribe();
846
+ html-error
582
847
  ```
583
848
 
584
- Events currently include:
849
+ Ownership-related events include an `origin` describing how the element entered the controller (`observer`, `append`, `register-element`, `add-module`, and related internal scan origins). This is useful for tracing observer/manual-registration races and proving that an explicit `append()` was not processed a second time by the observer.
585
850
 
586
- - `module-defined`
587
- - `module-invalidated`
588
- - `module-compiled`
589
- - `module-evaluating`
590
- - `module-evaluated`
591
- - `module-removed`
592
- - `module-error`
593
- - `runtime-cleared`
594
- - `runtime-disposed`
851
+ `observer-batch` summarizes a DOM discovery pass instead of exposing noisy raw `MutationRecord` objects. `element-loading` / `element-loaded` are emitted for `src`-backed modules, and entry events distinguish evaluation caused by an HTML `entry` declaration from an ordinary `runtime.import()`. Render events cover both `<script render>` and `<solid-render>`; `source` identifies `script-render` versus `solid-render`, and `mode` identifies selector, in-place, or container mounting.
595
852
 
596
- Subscriber exceptions are isolated from runtime execution. This makes `subscribe()` suitable for tracing, developer tools, and editor diagnostics.
853
+ Like core runtime subscribers, HTML subscribers are observational and exception-isolated.
597
854
 
598
855
  ## HTML-owned module updates and removal
599
856