solid-tag-runtime 0.0.12 → 0.0.14

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.
@@ -0,0 +1,181 @@
1
+ # Solid runtime setup and provider resolution
2
+
3
+ `solid-tag-runtime/solid` is an **optional additive integration layer**. The core `createRuntime()` API remains synchronous and provider-agnostic.
4
+
5
+ ## Standard Solid family
6
+
7
+ The helper treats these packages as one coordinated runtime family:
8
+
9
+ ```text
10
+ solid-js
11
+ @solidjs/web
12
+ @solidjs/html
13
+ @solidjs/signals
14
+ ```
15
+
16
+ The family is resolved coherently rather than as four unrelated imports.
17
+
18
+ ## High-level setup
19
+
20
+ ```ts
21
+ import { createSolidRuntime } from "solid-tag-runtime/solid";
22
+
23
+ const runtime = await createSolidRuntime({
24
+ modules: {
25
+ "@app/state": state,
26
+ },
27
+ });
28
+ ```
29
+
30
+ `createSolidRuntime()`:
31
+
32
+ 1. resolves/imports the standard Solid family;
33
+ 2. merges those namespaces with `modules`;
34
+ 3. gives caller-supplied modules precedence;
35
+ 4. calls the normal runtime engine;
36
+ 5. installs a lazy Solid integration capability used by wrapperless DOM rendering.
37
+
38
+ The returned value is the normal `SolidTagRuntime` API.
39
+
40
+ ## Low-level setup
41
+
42
+ Nothing changes for applications that already own their exact Solid namespaces:
43
+
44
+ ```ts
45
+ import * as Solid from "solid-js";
46
+ import * as SolidWeb from "@solidjs/web";
47
+ import html from "@solidjs/html";
48
+ import { createRuntime } from "solid-tag-runtime";
49
+
50
+ const runtime = createRuntime({
51
+ modules: {
52
+ "solid-js": Solid,
53
+ "@solidjs/web": SolidWeb,
54
+ "@solidjs/html": { default: html },
55
+ },
56
+ });
57
+ ```
58
+
59
+ This path performs no provider detection and installs no implicit DOM delegation behavior.
60
+
61
+ ## Composable loader
62
+
63
+ ```ts
64
+ import { loadSolidModules } from "solid-tag-runtime/solid";
65
+
66
+ const solidModules = await loadSolidModules();
67
+
68
+ const runtime = createRuntime({
69
+ modules: {
70
+ ...solidModules,
71
+ "@app/state": state,
72
+ },
73
+ });
74
+ ```
75
+
76
+ The returned object is directly compatible with the existing `modules` option.
77
+
78
+ ## Resolution precedence
79
+
80
+ For the standard family the loader uses this precedence:
81
+
82
+ ```text
83
+ 1. already supplied namespace
84
+ 2. explicit specifier override
85
+ 3. existing browser import-map/effective host resolution
86
+ 4. provider inferred from an existing Solid-family mapping
87
+ 5. provider inferred weakly from other page mappings
88
+ 6. configured fallback provider
89
+ 7. actionable failure
90
+ ```
91
+
92
+ Existing mappings are authoritative. The helper never silently replaces them.
93
+
94
+ ## Source modes
95
+
96
+ ```ts
97
+ await loadSolidModules({ source: "auto" });
98
+ await loadSolidModules({ source: "host" });
99
+ await loadSolidModules({ source: "esm.sh" });
100
+ await loadSolidModules({ source: "jsdelivr" });
101
+ ```
102
+
103
+ ### `auto`
104
+
105
+ Use host/browser resolution first. Fill missing packages only when a coherent provider path can be determined.
106
+
107
+ If only part of the Solid family resolves from an opaque host/bundler identity and the loader cannot identify the provider/version behind that runtime, `auto` fails instead of filling the rest from a CDN. This prevents a hidden second Solid runtime. Supply the missing namespaces explicitly or provide a coherent mapping/provider configuration.
108
+
109
+ ### `host`
110
+
111
+ Use only already-resolvable host/bundler/browser modules. No CDN fallback is allowed.
112
+
113
+ ### `esm.sh`
114
+
115
+ Use explicit/existing mappings where present and generate missing package URLs through the esm.sh adapter.
116
+
117
+ ### `jsdelivr`
118
+
119
+ Use explicit/existing mappings where present and generate missing package URLs through the jsDelivr adapter. Cases that cannot safely preserve a pre-existing Solid identity fail rather than mixing providers silently.
120
+
121
+ ## Tested fallback version
122
+
123
+ When provider fallback is needed and there is no versioned Solid anchor, this release uses a documented tested family version rather than CDN `latest`:
124
+
125
+ ```ts
126
+ import { TESTED_SOLID_VERSION } from "solid-tag-runtime/solid";
127
+ ```
128
+
129
+ For `0.0.14`, the tested fallback line is Solid `2.0.0-rc.13`.
130
+
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
+
133
+ ## esm.sh identity handling
134
+
135
+ When an authoritative page mapping exists, generated esm.sh siblings externalize that dependency so the browser resolves it through the existing mapping.
136
+
137
+ When there is no existing core anchor, generated sibling package URLs pin coordinated Solid dependencies through provider-specific `deps` parameters. Solid 2's family direction is modeled with `@solidjs/signals` as the reactive core consumed by `solid-js`, then `@solidjs/web`, then `@solidjs/html`; the generated URLs pin/externalize that family closure rather than treating `@solidjs/signals` as depending back on `solid-js`.
138
+
139
+ Provider query details remain isolated in the provider adapter; the core runtime does not know they exist.
140
+
141
+ ## Import maps are read-only
142
+
143
+ The helpers may inspect import-map script contents and use the browser's effective resolver, but they never insert or rewrite import maps.
144
+
145
+ ```text
146
+ loadSolidModules()
147
+ createSolidRuntime()
148
+ ↓
149
+ read/detect only
150
+ ↓
151
+ no global import-map mutation
152
+ ```
153
+
154
+ If an application wants to install an import map, it should do so before module evaluation using application-owned tooling.
155
+
156
+ ## User overrides
157
+
158
+ Caller modules win:
159
+
160
+ ```ts
161
+ const runtime = await createSolidRuntime({
162
+ modules: {
163
+ "solid-js": CustomSolid,
164
+ "@app/state": state,
165
+ },
166
+ });
167
+ ```
168
+
169
+ This is the escape hatch for custom builds, embedded runtimes, tests, and applications that already control dependency identity.
170
+
171
+ ## Wrapperless delegation capability
172
+
173
+ `createSolidRuntime()` installs one private capability used by the HTML adapter:
174
+
175
+ ```text
176
+ ensureDelegation(document)
177
+ ```
178
+
179
+ Normal users do not call it. It is invoked lazily only when wrapperless `<script render>` mounting requires Solid's delegated-event infrastructure.
180
+
181
+ See [Wrapperless delegation](./wrapperless-delegation.md).
@@ -0,0 +1,81 @@
1
+ # Wrapperless Solid 2 event delegation
2
+
3
+ Bare declarative rendering uses marker ranges instead of a container element:
4
+
5
+ ```html
6
+ <script type="solid-jsx" render>
7
+ export default function App() {
8
+ return <button onClick={() => console.log("clicked")}>Click</button>;
9
+ }
10
+ </script>
11
+ ```
12
+
13
+ Each declaration remains an **independent Solid reactive root**.
14
+
15
+ ## Ownership model
16
+
17
+ ```text
18
+ Document
19
+ │
20
+ │ lazily, once per Document × @solidjs/web namespace:
21
+ ├── delegation host
22
+ │
23
+ ├── wrapperless declaration A
24
+ │ └── createRoot A + insert(range A)
25
+ │
26
+ └── wrapperless declaration B
27
+ └── createRoot B + insert(range B)
28
+ ```
29
+
30
+ The delegation host exists only for DOM/event infrastructure. It is not the reactive parent of the wrapperless roots.
31
+
32
+ Disposing A does not dispose B and does not tear down the document host.
33
+
34
+ ## When it is initialized
35
+
36
+ The high-level factory:
37
+
38
+ ```ts
39
+ await createSolidRuntime(...)
40
+ ```
41
+
42
+ installs the capability but performs no DOM work immediately.
43
+
44
+ The first wrapperless range mount does:
45
+
46
+ ```text
47
+ ensure document delegation host
48
+ ensure delegated event types
49
+ create independent root
50
+ insert component into marker range
51
+ ```
52
+
53
+ If the application never performs wrapperless rendering, no document delegation host is created by this feature.
54
+
55
+ ## Selector rendering is unchanged
56
+
57
+ ```html
58
+ <script type="solid-jsx" render="#app">
59
+ ```
60
+
61
+ continues to use normal Solid container rendering.
62
+
63
+ ## `<solid-render>` is unchanged
64
+
65
+ ```html
66
+ <solid-render module="/Counter.jsx"></solid-render>
67
+ ```
68
+
69
+ continues to use its own persistent light-DOM container. This feature does not change its ownership model.
70
+
71
+ ## Low-level `createRuntime()`
72
+
73
+ The core runtime deliberately does not install Solid-specific DOM behavior.
74
+
75
+ If wrapperless delegated-event setup should be automatic, use `createSolidRuntime()` from `solid-tag-runtime/solid`.
76
+
77
+ Advanced low-level users remain free to supply their own render adapter or host DOM setup.
78
+
79
+ ## Delegated events
80
+
81
+ The integration prefers the renderer's exported delegated-event set when available and otherwise uses the standard Solid/dom-expressions delegated UI event family. Registration is idempotent per renderer/document pair.
@@ -0,0 +1,41 @@
1
+ import { createRuntime } from "solid-tag-runtime";
2
+ import * as Solid from "solid-js";
3
+ import * as SolidWeb from "@solidjs/web";
4
+ import html from "@solidjs/html";
5
+
6
+ const runtime = createRuntime({
7
+ modules: {
8
+ "solid-js": Solid,
9
+ "@solidjs/web": SolidWeb,
10
+ "@solidjs/html": { default: html },
11
+ },
12
+ });
13
+
14
+ runtime.define(
15
+ "/Button.jsx",
16
+ `
17
+ export function Button(props) {
18
+ return <button onClick={props.onClick}>{props.children}</button>;
19
+ }
20
+ `,
21
+ );
22
+
23
+ runtime.define(
24
+ "/Counter.jsx",
25
+ `
26
+ import { createSignal } from "solid-js";
27
+ import { Button } from "./Button.jsx";
28
+
29
+ export function Counter() {
30
+ const [count, setCount] = createSignal(0);
31
+ return (
32
+ <Button onClick={() => setCount(value => value + 1)}>
33
+ Count: {count()}
34
+ </Button>
35
+ );
36
+ }
37
+ `,
38
+ );
39
+
40
+ const { Counter } = await runtime.import("/Counter.jsx");
41
+ console.log(Counter);
@@ -0,0 +1,35 @@
1
+ import {
2
+ createRuntime,
3
+ createIndexedDBCompileCache,
4
+ } from "solid-tag-runtime";
5
+
6
+ const store = createIndexedDBCompileCache({
7
+ database: "my-app-runtime",
8
+ });
9
+
10
+ function createAppRuntime() {
11
+ return createRuntime({
12
+ compileCache: {
13
+ store,
14
+ namespace: "main",
15
+ version: "1",
16
+ maxEntries: 500,
17
+ maxBytes: 50 * 1024 * 1024,
18
+ },
19
+ });
20
+ }
21
+
22
+ // First runtime: compile miss + persistent write.
23
+ const first = createAppRuntime();
24
+ first.define("/config.js", `export const value = 42;`, { format: "js" });
25
+ await first.import("/config.js");
26
+ console.log("first", first.getModuleInfo("/config.js")?.compileCache);
27
+ first.dispose();
28
+
29
+ // New runtime, same source and cache namespace/version: compile hit.
30
+ // Linking, module URL creation, and native evaluation are still fresh.
31
+ const second = createAppRuntime();
32
+ second.define("/config.js", `export const value = 42;`, { format: "js" });
33
+ await second.import("/config.js");
34
+ console.log("second", second.getModuleInfo("/config.js")?.compileCache);
35
+ second.dispose();
@@ -0,0 +1,318 @@
1
+ import { render } from "@solidjs/web";
2
+ import * as Solid from "solid-js";
3
+ import * as SolidWeb from "@solidjs/web";
4
+ import html from "@solidjs/html";
5
+ import { createRuntime } from "solid-tag-runtime";
6
+ import { createHTMLRuntime } from "solid-tag-runtime/html";
7
+
8
+ // A tiny type helper for module exports returned by solid-tag-runtime.
9
+ type Component<P = Record<string, unknown>> = (props: P) => any;
10
+
11
+ async function main() {
12
+ const root = document.getElementById("root");
13
+ if (!root) throw new Error("Missing #root element");
14
+
15
+ // ---------------------------------------------------------------------------
16
+ // 1. Host application state
17
+ // ---------------------------------------------------------------------------
18
+ // These signals are created by the Solid runtime already bundled with the app.
19
+ // Runtime-created modules receive the exact same signal/accessor references.
20
+
21
+ const [hostCount, setHostCount] = Solid.createSignal(100);
22
+ const [hostMessage, setHostMessage] = Solid.createSignal(
23
+ "Hello from the host application",
24
+ );
25
+
26
+ // ---------------------------------------------------------------------------
27
+ // 2. Create the runtime and reuse this application's Solid modules
28
+ // ---------------------------------------------------------------------------
29
+ // This is important: dynamic components do NOT load another Solid runtime.
30
+
31
+ const runtime = createRuntime({
32
+ modules: {
33
+ "solid-js": Solid,
34
+ "@solidjs/web": SolidWeb,
35
+ "@solidjs/html": { default: html },
36
+ },
37
+ });
38
+
39
+ // ---------------------------------------------------------------------------
40
+ // 3. Expose host-owned state/services as a normal runtime module
41
+ // ---------------------------------------------------------------------------
42
+ // Runtime source can now use:
43
+ //
44
+ // import { hostCount, setHostCount } from "@app/state";
45
+ //
46
+ // No serialization or global-variable rewriting is involved.
47
+
48
+ runtime.defineModule("@app/state", {
49
+ hostCount,
50
+ setHostCount,
51
+ hostMessage,
52
+ setHostMessage,
53
+ });
54
+
55
+ // ---------------------------------------------------------------------------
56
+ // 4. Observe runtime module scripts added to the document
57
+ // ---------------------------------------------------------------------------
58
+ // We already own registration in this demo, so only watch scripts added from
59
+ // this point forward. Each observed batch is fully defined before any entry
60
+ // in that batch executes.
61
+
62
+ const htmlRuntime = createHTMLRuntime(runtime, {
63
+ scope: "main",
64
+ root: document,
65
+ appendTo: document.body,
66
+ acceptUnscoped: false,
67
+ });
68
+
69
+ await htmlRuntime.observe({
70
+ registerExisting: false,
71
+ onError(error) {
72
+ console.error("Observed runtime module failed", error);
73
+ },
74
+ });
75
+
76
+ // This script is inert to the browser because its type is solid-jsx, but the
77
+ // solid-tag-runtime HTML adapter discovers it through MutationObserver.
78
+ const observedModuleScript = document.createElement("script");
79
+ observedModuleScript.type = "solid-jsx";
80
+ observedModuleScript.setAttribute("data-solid-runtime", "main");
81
+ observedModuleScript.setAttribute("module", "/observed/Badge.jsx");
82
+ observedModuleScript.textContent = `
83
+ export function ObservedBadge(props) {
84
+ return (
85
+ <small
86
+ style={{
87
+ display: "inline-block",
88
+ padding: "4px 8px",
89
+ border: "1px dashed #777",
90
+ "border-radius": "999px"
91
+ }}
92
+ >
93
+ Observed module: {props.children}
94
+ </small>
95
+ );
96
+ }
97
+ `;
98
+ document.body.append(observedModuleScript);
99
+
100
+ // MutationObserver is automatic; flush() is useful when the application wants
101
+ // to explicitly wait until discovered modules are ready before importing them.
102
+ await htmlRuntime.flush();
103
+
104
+ // ---------------------------------------------------------------------------
105
+ // 5. Define a reusable runtime JSX module
106
+ // ---------------------------------------------------------------------------
107
+
108
+ runtime.define(
109
+ "/ui/Button.jsx",
110
+ `
111
+ export function Button(props) {
112
+ return (
113
+ <button
114
+ type="button"
115
+ onClick={props.onClick}
116
+ style={{
117
+ padding: "8px 12px",
118
+ border: "1px solid #777",
119
+ "border-radius": "6px",
120
+ cursor: "pointer"
121
+ }}
122
+ >
123
+ {props.children}
124
+ </button>
125
+ );
126
+ }
127
+ `,
128
+ );
129
+
130
+ // ---------------------------------------------------------------------------
131
+ // 6. Define another runtime module that imports Button + host state + Solid
132
+ // ---------------------------------------------------------------------------
133
+
134
+ runtime.define(
135
+ "/features/Counter.jsx",
136
+ `
137
+ import { createSignal } from "solid-js";
138
+ import { Button } from "../ui/Button.jsx";
139
+ import { hostCount, setHostCount } from "@app/state";
140
+
141
+ export function Counter() {
142
+ const [localCount, setLocalCount] = createSignal(0);
143
+
144
+ return (
145
+ <section
146
+ style={{
147
+ display: "grid",
148
+ gap: "10px",
149
+ padding: "16px",
150
+ border: "1px solid #555",
151
+ "border-radius": "8px"
152
+ }}
153
+ >
154
+ <strong>Runtime Counter module</strong>
155
+
156
+ <div>Local runtime signal: {localCount()}</div>
157
+ <div>Host application signal: {hostCount()}</div>
158
+
159
+ <div style={{ display: "flex", gap: "8px", "flex-wrap": "wrap" }}>
160
+ <Button onClick={() => setLocalCount(value => value + 1)}>
161
+ Increment local
162
+ </Button>
163
+
164
+ <Button onClick={() => setHostCount(value => value + 1)}>
165
+ Increment host
166
+ </Button>
167
+ </div>
168
+ </section>
169
+ );
170
+ }
171
+ `,
172
+ );
173
+
174
+ // ---------------------------------------------------------------------------
175
+ // 7. toModule(): create an anonymous runtime module and get its exports
176
+ // ---------------------------------------------------------------------------
177
+
178
+ const greetingModule = await runtime.toModule(`
179
+ export function Greeting(props) {
180
+ return (
181
+ <p style={{ margin: 0, opacity: 0.8 }}>
182
+ Runtime module says: Hello {props.name}!
183
+ </p>
184
+ );
185
+ }
186
+ `);
187
+
188
+ // A compiled module namespace can itself be exposed as a named host module.
189
+ // That lets other runtime modules import it normally.
190
+ runtime.defineModule("@runtime/greeting", greetingModule);
191
+
192
+ // ---------------------------------------------------------------------------
193
+ // 8. Define the runtime application module
194
+ // ---------------------------------------------------------------------------
195
+ // It imports another runtime JSX module, an anonymous module namespace that we
196
+ // registered above, and host state.
197
+
198
+ runtime.define(
199
+ "/App.jsx",
200
+ `
201
+ import { Counter } from "./features/Counter.jsx";
202
+ import { Greeting } from "@runtime/greeting";
203
+ import { ObservedBadge } from "./observed/Badge.jsx";
204
+ import { hostMessage, setHostMessage } from "@app/state";
205
+
206
+ export function App(props) {
207
+ return (
208
+ <main
209
+ style={{
210
+ display: "grid",
211
+ gap: "16px",
212
+ width: "min(680px, calc(100% - 32px))",
213
+ margin: "40px auto",
214
+ "font-family": "system-ui, sans-serif"
215
+ }}
216
+ >
217
+ <h1 style={{ margin: 0 }}>{props.title}</h1>
218
+
219
+ <Greeting name={props.name} />
220
+
221
+ <ObservedBadge>
222
+ discovered through MutationObserver
223
+ </ObservedBadge>
224
+
225
+ <p style={{ margin: 0 }}>
226
+ Host message: {hostMessage()}
227
+ </p>
228
+
229
+ <button
230
+ type="button"
231
+ onClick={() =>
232
+ setHostMessage("The runtime App changed host-owned state")
233
+ }
234
+ >
235
+ Change host message from runtime code
236
+ </button>
237
+
238
+ <Counter />
239
+ </main>
240
+ );
241
+ }
242
+ `,
243
+ );
244
+
245
+ // ---------------------------------------------------------------------------
246
+ // 9. Import the runtime module and use its exported component in host JSX
247
+ // ---------------------------------------------------------------------------
248
+
249
+ const appModule = await runtime.import("/App.jsx");
250
+ const RuntimeApp = appModule.App as Component<{
251
+ title: string;
252
+ name: string;
253
+ }>;
254
+
255
+ const disposeRender = render(
256
+ () => (
257
+ <RuntimeApp
258
+ title="solid-tag-runtime demo"
259
+ name="Solid"
260
+ />
261
+ ),
262
+ root,
263
+ );
264
+
265
+ // ---------------------------------------------------------------------------
266
+ // 10. Runtime module graph introspection
267
+ // ---------------------------------------------------------------------------
268
+
269
+ console.table(runtime.modules());
270
+ console.log("/App.jsx dependencies:", runtime.dependencies("/App.jsx"));
271
+ console.log(
272
+ "/features/Counter.jsx dependencies:",
273
+ runtime.dependencies("/features/Counter.jsx"),
274
+ );
275
+ console.log(
276
+ "/ui/Button.jsx dependents:",
277
+ runtime.dependents("/ui/Button.jsx"),
278
+ );
279
+
280
+ // You can inspect the transformed module source too.
281
+ const compiledCounter = await runtime.compile("/features/Counter.jsx");
282
+ console.log("Compiled Counter module:\n", compiledCounter.code);
283
+
284
+ // Optional demo/debug handle from the browser console.
285
+ Object.assign(globalThis, {
286
+ solidTagDemo: {
287
+ runtime,
288
+ hostCount,
289
+ setHostCount,
290
+ hostMessage,
291
+ setHostMessage,
292
+ htmlRuntime,
293
+ dispose() {
294
+ htmlRuntime.disconnect();
295
+ observedModuleScript.remove();
296
+ disposeRender();
297
+ runtime.dispose();
298
+ },
299
+ },
300
+ });
301
+ }
302
+
303
+ main().catch(error => {
304
+ console.error("solid-tag-runtime demo failed", error);
305
+
306
+ const root = document.getElementById("root");
307
+ if (root) {
308
+ const pre = document.createElement("pre");
309
+ pre.style.whiteSpace = "pre-wrap";
310
+ pre.style.color = "crimson";
311
+ pre.textContent =
312
+ error instanceof Error
313
+ ? error.stack ?? error.message
314
+ : String(error);
315
+
316
+ root.replaceChildren(pre);
317
+ }
318
+ });