solid-tag-runtime 0.0.1 → 0.0.2
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 +161 -30
- package/README.md +144 -0
- package/html.d.ts +114 -0
- package/package.json +7 -1
- package/src/html.js +362 -0
package/ARCHITECTURE.md
CHANGED
|
@@ -42,7 +42,7 @@ The runtime should allow applications to:
|
|
|
42
42
|
9. support normal JavaScript modules in the same graph
|
|
43
43
|
10. keep the JSX compiler replaceable behind a narrow adapter
|
|
44
44
|
|
|
45
|
-
## 3.
|
|
45
|
+
## 3. Current non-goals
|
|
46
46
|
|
|
47
47
|
The initial package does not attempt to provide:
|
|
48
48
|
|
|
@@ -54,9 +54,8 @@ The initial package does not attempt to provide:
|
|
|
54
54
|
- circular runtime source-module support
|
|
55
55
|
- transparent live replacement of already-held component references
|
|
56
56
|
- full HMR propagation semantics
|
|
57
|
-
- script-tag discovery/registration
|
|
58
57
|
|
|
59
|
-
These may be added later without changing the module-first public model.
|
|
58
|
+
These may be added later without changing the module-first public model. HTML script discovery was added in `0.0.2` as a separate browser adapter rather than as a responsibility of the core module engine.
|
|
60
59
|
|
|
61
60
|
## 4. Package relationship
|
|
62
61
|
|
|
@@ -193,7 +192,119 @@ No JSX transformation. Imports are still resolved and rewritten through the runt
|
|
|
193
192
|
|
|
194
193
|
This permits ordinary JavaScript and JSX modules to coexist in one graph.
|
|
195
194
|
|
|
196
|
-
|
|
195
|
+
|
|
196
|
+
## 8. HTML/browser adapter
|
|
197
|
+
|
|
198
|
+
`0.0.2` adds `solid-tag-runtime/html` as an opt-in browser-facing adapter.
|
|
199
|
+
|
|
200
|
+
Important architectural boundary:
|
|
201
|
+
|
|
202
|
+
```text
|
|
203
|
+
DOM / HTML document
|
|
204
|
+
↓
|
|
205
|
+
solid-tag-runtime/html
|
|
206
|
+
↓
|
|
207
|
+
runtime.define(...)
|
|
208
|
+
↓
|
|
209
|
+
core module graph
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
The core runtime remains DOM-independent. It does not scan documents, fetch `<script>` elements, or know about HTML attributes.
|
|
213
|
+
|
|
214
|
+
### Public HTML API
|
|
215
|
+
|
|
216
|
+
```ts
|
|
217
|
+
await registerHTML(runtime, options?);
|
|
218
|
+
await defineScript(runtime, element, options?);
|
|
219
|
+
const observer = await observeHTML(runtime, options?);
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
`registerHTML()` discovers matching script elements, defines the complete discovered graph, and only then executes modules marked `entry` unless `executeEntries: false` is supplied.
|
|
223
|
+
|
|
224
|
+
`observeHTML()` is the live-discovery adapter. It uses `MutationObserver` to discover scripts added after registration. Existing scripts are registered by default; `registerExisting: false` supports the pattern where `registerHTML()` already performed the initial scan.
|
|
225
|
+
|
|
226
|
+
Every observer scan preserves the same graph invariant as `registerHTML()`: all newly discovered modules in that batch are defined before any newly discovered entry is executed. The observer tracks element identity so an already-seen element is not repeatedly redefined on unrelated DOM mutations.
|
|
227
|
+
|
|
228
|
+
The returned controller exposes:
|
|
229
|
+
|
|
230
|
+
```ts
|
|
231
|
+
observer.initial;
|
|
232
|
+
observer.connected;
|
|
233
|
+
await observer.flush();
|
|
234
|
+
observer.disconnect();
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
`flush()` waits for queued observer work and performs another scan, making it useful to synchronize application code with asynchronous `src` fetching and module evaluation. Background observer failures may be handled with `onError`.
|
|
238
|
+
|
|
239
|
+
Observation in `0.0.2` is intentionally addition-only: changes to attributes/text of an already registered script and removal of that script do not implicitly update or remove its runtime module. Those semantics require explicit runtime updates today and may become a separate future feature.
|
|
240
|
+
|
|
241
|
+
This ordering is an invariant: document order must not require dependencies to appear before entries.
|
|
242
|
+
|
|
243
|
+
### Supported script declarations
|
|
244
|
+
|
|
245
|
+
```html
|
|
246
|
+
<script type="solid-jsx" module="/App.jsx">...</script>
|
|
247
|
+
<script type="solid-js" module="/utils.js">...</script>
|
|
248
|
+
<script type="solid-module" module="/App.jsx">...</script>
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
`text/solid-jsx`, `text/solid-js`, and `text/solid-module` aliases are also accepted.
|
|
252
|
+
|
|
253
|
+
Format rules:
|
|
254
|
+
|
|
255
|
+
- `solid-jsx` → `jsx`
|
|
256
|
+
- `solid-js` → `js`
|
|
257
|
+
- `solid-module` → infer from `module`/`src` extension
|
|
258
|
+
- `language="js"` or `language="jsx"` overrides inference
|
|
259
|
+
|
|
260
|
+
### `src` versus `module`
|
|
261
|
+
|
|
262
|
+
These attributes deliberately have different roles:
|
|
263
|
+
|
|
264
|
+
```text
|
|
265
|
+
src = source provider / fetch location
|
|
266
|
+
module = identity in the runtime module graph
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
Example:
|
|
270
|
+
|
|
271
|
+
```html
|
|
272
|
+
<script
|
|
273
|
+
type="solid-jsx"
|
|
274
|
+
src="./source/Button.jsx"
|
|
275
|
+
module="/ui/Button.jsx">
|
|
276
|
+
</script>
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
The source may move without changing imports of `/ui/Button.jsx`.
|
|
280
|
+
|
|
281
|
+
If a `src` script omits `module`, its resolved source URL becomes the module ID. This allows multiple URL-backed source scripts to use normal relative imports.
|
|
282
|
+
|
|
283
|
+
Inline non-entry scripts require an explicit `module` identity. Inline entries may omit it and receive an anonymous runtime ID.
|
|
284
|
+
|
|
285
|
+
### Entry execution
|
|
286
|
+
|
|
287
|
+
`entry` is an adapter concern, not a new runtime module kind.
|
|
288
|
+
|
|
289
|
+
```html
|
|
290
|
+
<script type="solid-jsx" module="/App.jsx" entry>...</script>
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
After all discovered modules are defined, the adapter performs:
|
|
294
|
+
|
|
295
|
+
```ts
|
|
296
|
+
await runtime.import("/App.jsx");
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
The entry module is expected to perform whatever side effect starts the application, such as calling `render()`.
|
|
300
|
+
|
|
301
|
+
### Explicitly not HTML-as-JSX
|
|
302
|
+
|
|
303
|
+
The adapter does not transform arbitrary document HTML into JSX and does not currently assign semantics to `<template>` elements. JSX remains inside JavaScript module source.
|
|
304
|
+
|
|
305
|
+
This avoids introducing a second template language, implicit lexical scope rules, or ambiguous ownership of already-parsed DOM.
|
|
306
|
+
|
|
307
|
+
## 9. Compiler adapter boundary
|
|
197
308
|
|
|
198
309
|
The runtime engine itself does not depend on Acorn or the `solid-tag` AST.
|
|
199
310
|
|
|
@@ -233,7 +344,7 @@ Possible future adapters include:
|
|
|
233
344
|
|
|
234
345
|
The module graph must remain independent of parser details.
|
|
235
346
|
|
|
236
|
-
##
|
|
347
|
+
## 10. Linking pipeline
|
|
237
348
|
|
|
238
349
|
For a source module `/features/Counter.jsx`:
|
|
239
350
|
|
|
@@ -266,7 +377,7 @@ Example graph:
|
|
|
266
377
|
└── ../ui/Button.jsx → compiled virtual source URL
|
|
267
378
|
```
|
|
268
379
|
|
|
269
|
-
##
|
|
380
|
+
## 11. Resolution rules
|
|
270
381
|
|
|
271
382
|
Resolution order is conceptually:
|
|
272
383
|
|
|
@@ -282,7 +393,7 @@ Relative virtual resolution uses POSIX-style paths regardless of the host operat
|
|
|
282
393
|
|
|
283
394
|
This keeps module IDs stable in browsers and on Windows hosts.
|
|
284
395
|
|
|
285
|
-
##
|
|
396
|
+
## 12. Native ESM execution backend
|
|
286
397
|
|
|
287
398
|
The first execution backend deliberately relies on the JavaScript engine's native ESM evaluator rather than `new Function()`.
|
|
288
399
|
|
|
@@ -320,9 +431,9 @@ Benefits:
|
|
|
320
431
|
- no `eval` / `new Function()` runtime wrapper
|
|
321
432
|
- clean `toModule()` semantics
|
|
322
433
|
|
|
323
|
-
##
|
|
434
|
+
## 13. Circular dependency limitation
|
|
324
435
|
|
|
325
|
-
Virtual source module cycles are explicitly rejected
|
|
436
|
+
Virtual source module cycles are explicitly rejected through v0.0.2.
|
|
326
437
|
|
|
327
438
|
Reason: the current linker must know the final URL of dependencies before it can create a module's immutable Blob/data URL. A cycle requires module identities to exist before final source URLs can be constructed.
|
|
328
439
|
|
|
@@ -341,7 +452,7 @@ Future options:
|
|
|
341
452
|
|
|
342
453
|
Do not add ad-hoc cycle handling that breaks module semantics; change the backend deliberately.
|
|
343
454
|
|
|
344
|
-
##
|
|
455
|
+
## 14. Host module bridges
|
|
345
456
|
|
|
346
457
|
`defineModule()` stores the namespace in a runtime-specific registry:
|
|
347
458
|
|
|
@@ -376,7 +487,7 @@ The ES module bridge captures each exported property value when the bridge modul
|
|
|
376
487
|
|
|
377
488
|
To replace exports, call `defineModule()` again. This invalidates dependent runtime modules.
|
|
378
489
|
|
|
379
|
-
##
|
|
490
|
+
## 15. Scope injection design choice
|
|
380
491
|
|
|
381
492
|
The runtime does not automatically turn unknown identifiers into scope lookups.
|
|
382
493
|
|
|
@@ -413,7 +524,7 @@ Reasons:
|
|
|
413
524
|
|
|
414
525
|
A future convenience API may create scope modules automatically, but it should still compile down to module semantics.
|
|
415
526
|
|
|
416
|
-
##
|
|
527
|
+
## 16. Dependency graph
|
|
417
528
|
|
|
418
529
|
Each record tracks:
|
|
419
530
|
|
|
@@ -431,7 +542,7 @@ When linking `/App.jsx` imports `/Button.jsx`:
|
|
|
431
542
|
|
|
432
543
|
These edges power invalidation and tooling.
|
|
433
544
|
|
|
434
|
-
##
|
|
545
|
+
## 17. Caching
|
|
435
546
|
|
|
436
547
|
A source module caches:
|
|
437
548
|
|
|
@@ -444,7 +555,7 @@ Repeated imports use the same module URL/import promise until invalidated.
|
|
|
444
555
|
|
|
445
556
|
Native ESM also caches imports by URL.
|
|
446
557
|
|
|
447
|
-
##
|
|
558
|
+
## 18. Invalidation and updates
|
|
448
559
|
|
|
449
560
|
Updating a module invalidates:
|
|
450
561
|
|
|
@@ -469,7 +580,7 @@ const fresh = await runtime.import("/App.jsx");
|
|
|
469
580
|
|
|
470
581
|
This is deliberate and matches the immutable identity of native evaluated modules.
|
|
471
582
|
|
|
472
|
-
##
|
|
583
|
+
## 19. `toModule()` semantics
|
|
473
584
|
|
|
474
585
|
`toModule(source)` is sugar for:
|
|
475
586
|
|
|
@@ -485,7 +596,7 @@ return module namespace
|
|
|
485
596
|
|
|
486
597
|
It is asynchronous by design.
|
|
487
598
|
|
|
488
|
-
##
|
|
599
|
+
## 20. `toComponent()` semantics
|
|
489
600
|
|
|
490
601
|
`toComponent()` calls `toModule()` and returns a selected callable export.
|
|
491
602
|
|
|
@@ -499,7 +610,7 @@ Named exports require an explicit `exportName`.
|
|
|
499
610
|
|
|
500
611
|
The runtime does not guess between multiple component exports.
|
|
501
612
|
|
|
502
|
-
##
|
|
613
|
+
## 21. Security model
|
|
503
614
|
|
|
504
615
|
`solid-tag-runtime` is **not a sandbox**.
|
|
505
616
|
|
|
@@ -507,7 +618,7 @@ Dynamic source executes as JavaScript in the page/module environment and has wha
|
|
|
507
618
|
|
|
508
619
|
Do not use this package to execute untrusted hostile code without a separate isolation mechanism such as a suitably sandboxed iframe/worker/process boundary.
|
|
509
620
|
|
|
510
|
-
##
|
|
621
|
+
## 22. CSP considerations
|
|
511
622
|
|
|
512
623
|
The browser backend currently uses Blob module URLs by default.
|
|
513
624
|
|
|
@@ -519,7 +630,7 @@ Applications with strict Content Security Policy may need:
|
|
|
519
630
|
|
|
520
631
|
The public module API should not depend on Blob URLs so this backend can change later.
|
|
521
632
|
|
|
522
|
-
##
|
|
633
|
+
## 23. Future directions
|
|
523
634
|
|
|
524
635
|
Potential additions, in rough architectural order:
|
|
525
636
|
|
|
@@ -537,16 +648,6 @@ await runtime.load("/App.jsx");
|
|
|
537
648
|
|
|
538
649
|
with configurable fetch/source providers.
|
|
539
650
|
|
|
540
|
-
### Script-tag module registration
|
|
541
|
-
|
|
542
|
-
Possible declarative zero-build API:
|
|
543
|
-
|
|
544
|
-
```html
|
|
545
|
-
<script type="solid-jsx" module="/Button.jsx">...</script>
|
|
546
|
-
```
|
|
547
|
-
|
|
548
|
-
implemented as a thin source-provider layer over the same runtime graph.
|
|
549
|
-
|
|
550
651
|
### Runtime update/HMR helpers
|
|
551
652
|
|
|
552
653
|
Graph invalidation already exists. Future APIs can add lifecycle hooks and entry re-rendering without changing module identity rules.
|
|
@@ -563,7 +664,7 @@ The runtime compiler boundary allows a future TSX-capable transformer without co
|
|
|
563
664
|
|
|
564
665
|
If `solid-tag` later gains tagged-template → JSX transformation, runtime/editor tooling can expose source views without changing module execution semantics.
|
|
565
666
|
|
|
566
|
-
##
|
|
667
|
+
## 24. Decision log
|
|
567
668
|
|
|
568
669
|
### 2026-10-02 — Separate runtime package
|
|
569
670
|
|
|
@@ -606,3 +707,33 @@ Reason: the current immutable URL linker cannot assign stable module identities
|
|
|
606
707
|
Decision: the runtime engine consumes `analyze()` and `transform()` rather than importing parser internals directly.
|
|
607
708
|
|
|
608
709
|
Reason: preserve the option to add TSX, tagged-source, or alternative transformer backends later.
|
|
710
|
+
### 2026-10-02 — HTML integration is an adapter subpath
|
|
711
|
+
|
|
712
|
+
Decision: add declarative script registration in `0.0.2` as `solid-tag-runtime/html`, not as methods on the core runtime and not as a separate npm package.
|
|
713
|
+
|
|
714
|
+
Reason: HTML discovery, DOM access, and fetching are browser concerns. Keeping them behind an export subpath preserves a DOM-free core while avoiding premature package fragmentation.
|
|
715
|
+
|
|
716
|
+
### 2026-10-02 — HTML declares modules; it is not itself JSX
|
|
717
|
+
|
|
718
|
+
Decision: support JSX/JS source inside runtime `<script>` modules, but do not reinterpret arbitrary document HTML or `<template>` content as JSX in `0.0.2`.
|
|
719
|
+
|
|
720
|
+
Reason: module source already has clear lexical/import semantics. Treating document HTML as executable JSX would introduce separate scope, parsing, hydration, and DOM-ownership semantics.
|
|
721
|
+
|
|
722
|
+
### 2026-10-02 — `src` and `module` are separate concepts
|
|
723
|
+
|
|
724
|
+
Decision: `src` identifies where source is fetched, while `module` identifies the module inside the runtime graph. When `module` is omitted for an external script, the resolved source URL is used as its identity.
|
|
725
|
+
|
|
726
|
+
Reason: source location and logical module identity should be independently controllable while still making zero-config relative URL module graphs ergonomic.
|
|
727
|
+
|
|
728
|
+
### 2026-10-02 — Define all HTML modules before running entries
|
|
729
|
+
|
|
730
|
+
Decision: `registerHTML()` completes discovery/fetch/definition for all matching scripts before importing any module marked `entry`.
|
|
731
|
+
|
|
732
|
+
Reason: dependency availability must not depend on HTML document order.
|
|
733
|
+
|
|
734
|
+
### 2026-10-02 — Live HTML discovery uses an addition-only observer
|
|
735
|
+
|
|
736
|
+
Decision: add `observeHTML()` to the existing `solid-tag-runtime/html` subpath in `0.0.2`. It uses `MutationObserver`, registers existing scripts by default, tracks seen element identities, and applies the same define-before-entry invariant to every newly discovered batch.
|
|
737
|
+
|
|
738
|
+
Reason: applications that dynamically insert runtime modules should not need to manually rescan the document, while keeping DOM observation separate from the core runtime module engine. Addition-only semantics avoid silently redefining modules because of text/attribute churn or trying to infer deletion semantics before the runtime has an explicit module-removal API.
|
|
739
|
+
|
package/README.md
CHANGED
|
@@ -97,6 +97,150 @@ counterModule.Counter;
|
|
|
97
97
|
|
|
98
98
|
The runtime compiles and links the dependency graph automatically.
|
|
99
99
|
|
|
100
|
+
|
|
101
|
+
## Declarative HTML modules
|
|
102
|
+
|
|
103
|
+
`0.0.2` adds a browser adapter at `solid-tag-runtime/html`. The core runtime remains DOM-independent; the HTML adapter only discovers source modules and feeds them into the same runtime module graph.
|
|
104
|
+
|
|
105
|
+
```ts
|
|
106
|
+
import { registerHTML, observeHTML } from "solid-tag-runtime/html";
|
|
107
|
+
|
|
108
|
+
await registerHTML(runtime);
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
The adapter recognizes runtime script modules such as:
|
|
112
|
+
|
|
113
|
+
```html
|
|
114
|
+
<script type="solid-jsx" module="/ui/Button.jsx">
|
|
115
|
+
export function Button(props) {
|
|
116
|
+
return (
|
|
117
|
+
<button onClick={props.onClick}>
|
|
118
|
+
{props.children}
|
|
119
|
+
</button>
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
</script>
|
|
123
|
+
|
|
124
|
+
<script type="solid-jsx" module="/App.jsx" entry>
|
|
125
|
+
import { render } from "@solidjs/web";
|
|
126
|
+
import { Button } from "./ui/Button.jsx";
|
|
127
|
+
|
|
128
|
+
function App() {
|
|
129
|
+
return <Button>Hello from runtime JSX</Button>;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
render(() => <App />, document.getElementById("app"));
|
|
133
|
+
</script>
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
All matching scripts are **defined before any `entry` module is executed**. This means an entry may import a module that appears later in the HTML document.
|
|
137
|
+
|
|
138
|
+
External source is supported as well:
|
|
139
|
+
|
|
140
|
+
```html
|
|
141
|
+
<script
|
|
142
|
+
type="solid-jsx"
|
|
143
|
+
module="/ui/Button.jsx"
|
|
144
|
+
src="./components/Button.jsx">
|
|
145
|
+
</script>
|
|
146
|
+
|
|
147
|
+
<script
|
|
148
|
+
type="solid-jsx"
|
|
149
|
+
module="/App.jsx"
|
|
150
|
+
src="./App.jsx"
|
|
151
|
+
entry>
|
|
152
|
+
</script>
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
`src` answers **where the source is fetched from** while `module` answers **what its identity is in the runtime graph**. They are intentionally separate.
|
|
156
|
+
|
|
157
|
+
If `module` is omitted for a `src` script, the resolved source URL becomes the module ID. This makes relative imports between externally loaded modules work naturally. Inline non-entry scripts require a `module` attribute; an inline `entry` may omit it and receives an anonymous runtime module ID.
|
|
158
|
+
|
|
159
|
+
### HTML script formats
|
|
160
|
+
|
|
161
|
+
Explicit script types:
|
|
162
|
+
|
|
163
|
+
```text
|
|
164
|
+
solid-jsx / text/solid-jsx → JSX transformation
|
|
165
|
+
solid-js / text/solid-js → plain JavaScript module
|
|
166
|
+
solid-module → infer from module/src extension
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
For `solid-module`, `.js`/`.mjs` are treated as JavaScript and other extensions currently default to JSX. A `language="js"` or `language="jsx"` attribute can override inference.
|
|
170
|
+
|
|
171
|
+
### Register without executing entries
|
|
172
|
+
|
|
173
|
+
```ts
|
|
174
|
+
const registration = await registerHTML(runtime, {
|
|
175
|
+
executeEntries: false,
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
console.log(registration.modules);
|
|
179
|
+
console.log(registration.entries);
|
|
180
|
+
|
|
181
|
+
await runtime.import(registration.entries[0]);
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
### Register one script manually
|
|
185
|
+
|
|
186
|
+
```ts
|
|
187
|
+
import { defineScript } from "solid-tag-runtime/html";
|
|
188
|
+
|
|
189
|
+
const definition = await defineScript(
|
|
190
|
+
runtime,
|
|
191
|
+
document.querySelector("#runtime-component"),
|
|
192
|
+
);
|
|
193
|
+
|
|
194
|
+
const namespace = await runtime.import(definition.id);
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
### Observe scripts added later
|
|
198
|
+
|
|
199
|
+
`observeHTML()` uses `MutationObserver` to register runtime module scripts added after startup. It performs an initial scan by default and preserves the same batch invariant as `registerHTML()`: all newly discovered modules are defined before any newly discovered `entry` executes.
|
|
200
|
+
|
|
201
|
+
```ts
|
|
202
|
+
import { observeHTML } from "solid-tag-runtime/html";
|
|
203
|
+
|
|
204
|
+
const observer = await observeHTML(runtime, {
|
|
205
|
+
onError(error) {
|
|
206
|
+
console.error("runtime module registration failed", error);
|
|
207
|
+
},
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
// Later, code may append a new runtime module script.
|
|
211
|
+
const script = document.createElement("script");
|
|
212
|
+
script.type = "solid-jsx";
|
|
213
|
+
script.setAttribute("module", "/dynamic/Greeting.jsx");
|
|
214
|
+
script.textContent = `
|
|
215
|
+
export function Greeting(props) {
|
|
216
|
+
return <p>Hello {props.name}</p>;
|
|
217
|
+
}
|
|
218
|
+
`;
|
|
219
|
+
document.body.append(script);
|
|
220
|
+
|
|
221
|
+
// Normally MutationObserver handles this automatically. flush() is useful when
|
|
222
|
+
// code needs to wait until queued observation work has completed.
|
|
223
|
+
await observer.flush();
|
|
224
|
+
|
|
225
|
+
const { Greeting } = await runtime.import("/dynamic/Greeting.jsx");
|
|
226
|
+
|
|
227
|
+
observer.disconnect();
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
If existing scripts were already registered separately, start observation without re-registering them:
|
|
231
|
+
|
|
232
|
+
```ts
|
|
233
|
+
await registerHTML(runtime);
|
|
234
|
+
|
|
235
|
+
const observer = await observeHTML(runtime, {
|
|
236
|
+
registerExisting: false,
|
|
237
|
+
});
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
In `0.0.2`, observation is intentionally **addition-only**. Editing attributes/text of an already registered script or removing that script does not automatically update/remove the corresponding runtime module. Use `runtime.update()` / `runtime.invalidate()` explicitly when needed.
|
|
241
|
+
|
|
242
|
+
The HTML adapter does **not** reinterpret ordinary document HTML as JSX and does not currently add `<template>` component semantics. JSX remains source-code syntax inside runtime modules.
|
|
243
|
+
|
|
100
244
|
## `toModule()`
|
|
101
245
|
|
|
102
246
|
For one-off source strings:
|
package/html.d.ts
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { SolidTagRuntimeError } from "./index.js";
|
|
2
|
+
import type { ModuleFormat, ModuleNamespaceLike, SolidTagRuntime } from "./index.js";
|
|
3
|
+
|
|
4
|
+
export interface HTMLModuleScriptElement {
|
|
5
|
+
textContent?: string | null;
|
|
6
|
+
baseURI?: string;
|
|
7
|
+
getAttribute(name: string): string | null;
|
|
8
|
+
hasAttribute?(name: string): boolean;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface HTMLModuleRoot {
|
|
12
|
+
baseURI?: string;
|
|
13
|
+
querySelectorAll(selector: string): Iterable<HTMLModuleScriptElement> | ArrayLike<HTMLModuleScriptElement>;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface HTMLFetchResponse {
|
|
17
|
+
ok?: boolean;
|
|
18
|
+
status?: number;
|
|
19
|
+
statusText?: string;
|
|
20
|
+
text(): Promise<string>;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export type HTMLFetch = (url: string) => Promise<HTMLFetchResponse>;
|
|
24
|
+
|
|
25
|
+
export interface HTMLMutationObserverLike {
|
|
26
|
+
observe(
|
|
27
|
+
target: unknown,
|
|
28
|
+
options: {
|
|
29
|
+
childList: boolean;
|
|
30
|
+
subtree: boolean;
|
|
31
|
+
},
|
|
32
|
+
): void;
|
|
33
|
+
disconnect(): void;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface HTMLMutationObserverConstructor {
|
|
37
|
+
new (
|
|
38
|
+
callback: (...args: unknown[]) => void,
|
|
39
|
+
): HTMLMutationObserverLike;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface DefineScriptOptions {
|
|
43
|
+
root?: HTMLModuleRoot;
|
|
44
|
+
baseUrl?: string;
|
|
45
|
+
format?: ModuleFormat | "javascript";
|
|
46
|
+
fetch?: HTMLFetch;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export interface HTMLModuleDefinition {
|
|
50
|
+
id: string;
|
|
51
|
+
format: ModuleFormat;
|
|
52
|
+
entry: boolean;
|
|
53
|
+
sourceUrl?: string;
|
|
54
|
+
inline: boolean;
|
|
55
|
+
element: HTMLModuleScriptElement;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export interface RegisterHTMLOptions extends DefineScriptOptions {
|
|
59
|
+
selector?: string;
|
|
60
|
+
executeEntries?: boolean;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export interface RegisterHTMLResult {
|
|
64
|
+
modules: HTMLModuleDefinition[];
|
|
65
|
+
entries: string[];
|
|
66
|
+
executed: Array<{
|
|
67
|
+
id: string;
|
|
68
|
+
namespace: ModuleNamespaceLike;
|
|
69
|
+
}>;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export interface ObserveHTMLOptions extends RegisterHTMLOptions {
|
|
73
|
+
/** Register scripts already present when observation starts. Default: true. */
|
|
74
|
+
registerExisting?: boolean;
|
|
75
|
+
/** Observe descendant additions as well as direct children. Default: true. */
|
|
76
|
+
subtree?: boolean;
|
|
77
|
+
/** Override the platform MutationObserver constructor, useful for DOM-like hosts/tests. */
|
|
78
|
+
MutationObserver?: HTMLMutationObserverConstructor;
|
|
79
|
+
/** Called for asynchronous observer registration/import failures. */
|
|
80
|
+
onError?: (error: unknown) => void;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export interface HTMLObserverController {
|
|
84
|
+
/** Result of the initial scan. Empty when registerExisting is false. */
|
|
85
|
+
initial: RegisterHTMLResult;
|
|
86
|
+
readonly connected: boolean;
|
|
87
|
+
/** Wait for queued observer work and synchronously scan for any unseen scripts. */
|
|
88
|
+
flush(): Promise<RegisterHTMLResult>;
|
|
89
|
+
disconnect(): void;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export declare class HTMLModuleError extends SolidTagRuntimeError {
|
|
93
|
+
element?: HTMLModuleScriptElement;
|
|
94
|
+
moduleId?: string;
|
|
95
|
+
sourceUrl?: string;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export declare const htmlModuleSelector: string;
|
|
99
|
+
|
|
100
|
+
export declare function defineScript(
|
|
101
|
+
runtime: SolidTagRuntime,
|
|
102
|
+
element: HTMLModuleScriptElement,
|
|
103
|
+
options?: DefineScriptOptions,
|
|
104
|
+
): Promise<HTMLModuleDefinition>;
|
|
105
|
+
|
|
106
|
+
export declare function registerHTML(
|
|
107
|
+
runtime: SolidTagRuntime,
|
|
108
|
+
options?: RegisterHTMLOptions,
|
|
109
|
+
): Promise<RegisterHTMLResult>;
|
|
110
|
+
|
|
111
|
+
export declare function observeHTML(
|
|
112
|
+
runtime: SolidTagRuntime,
|
|
113
|
+
options?: ObserveHTMLOptions,
|
|
114
|
+
): Promise<HTMLObserverController>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "solid-tag-runtime",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.2",
|
|
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": {
|
|
@@ -18,6 +18,11 @@
|
|
|
18
18
|
"types": "./index.d.ts",
|
|
19
19
|
"import": "./src/runtime.js",
|
|
20
20
|
"default": "./src/runtime.js"
|
|
21
|
+
},
|
|
22
|
+
"./html": {
|
|
23
|
+
"types": "./html.d.ts",
|
|
24
|
+
"import": "./src/html.js",
|
|
25
|
+
"default": "./src/html.js"
|
|
21
26
|
}
|
|
22
27
|
},
|
|
23
28
|
"main": "./src/index.js",
|
|
@@ -26,6 +31,7 @@
|
|
|
26
31
|
"files": [
|
|
27
32
|
"src",
|
|
28
33
|
"index.d.ts",
|
|
34
|
+
"html.d.ts",
|
|
29
35
|
"README.md",
|
|
30
36
|
"ARCHITECTURE.md"
|
|
31
37
|
],
|
package/src/html.js
ADDED
|
@@ -0,0 +1,362 @@
|
|
|
1
|
+
import { SolidTagRuntimeError } from "./errors.js";
|
|
2
|
+
|
|
3
|
+
const DEFAULT_SELECTOR = [
|
|
4
|
+
'script[type="solid-jsx"]',
|
|
5
|
+
'script[type="text/solid-jsx"]',
|
|
6
|
+
'script[type="solid-js"]',
|
|
7
|
+
'script[type="text/solid-js"]',
|
|
8
|
+
'script[type="solid-module"]',
|
|
9
|
+
'script[type="text/solid-module"]',
|
|
10
|
+
].join(",");
|
|
11
|
+
|
|
12
|
+
let anonymousSequence = 0;
|
|
13
|
+
|
|
14
|
+
export class HTMLModuleError extends SolidTagRuntimeError {
|
|
15
|
+
constructor(message, options = {}) {
|
|
16
|
+
super(message, options);
|
|
17
|
+
this.name = "HTMLModuleError";
|
|
18
|
+
this.element = options.element;
|
|
19
|
+
this.moduleId = options.moduleId;
|
|
20
|
+
this.sourceUrl = options.sourceUrl;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Discover runtime module scripts under a Document/Element-like root, define
|
|
26
|
+
* all of them first, then optionally execute scripts marked with `entry`.
|
|
27
|
+
*/
|
|
28
|
+
export async function registerHTML(runtime, options = {}) {
|
|
29
|
+
assertRuntime(runtime);
|
|
30
|
+
|
|
31
|
+
const root = getRoot(options, "registerHTML");
|
|
32
|
+
const elements = Array.from(root.querySelectorAll(options.selector ?? DEFAULT_SELECTOR));
|
|
33
|
+
|
|
34
|
+
return registerElements(runtime, elements, {
|
|
35
|
+
...options,
|
|
36
|
+
root,
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Observe a DOM root for newly-added runtime module scripts.
|
|
42
|
+
*
|
|
43
|
+
* Existing scripts are registered by default. Every discovered mutation batch
|
|
44
|
+
* preserves the same invariant as registerHTML(): all modules in the batch are
|
|
45
|
+
* defined before any entry module from that batch executes.
|
|
46
|
+
*
|
|
47
|
+
* v0.0.2 intentionally observes additions only. Mutating or removing a script
|
|
48
|
+
* that was already registered does not update/remove its runtime module.
|
|
49
|
+
*/
|
|
50
|
+
export async function observeHTML(runtime, options = {}) {
|
|
51
|
+
assertRuntime(runtime);
|
|
52
|
+
|
|
53
|
+
const root = getRoot(options, "observeHTML");
|
|
54
|
+
const MutationObserverImpl = options.MutationObserver ?? globalThis.MutationObserver;
|
|
55
|
+
|
|
56
|
+
if (typeof MutationObserverImpl !== "function") {
|
|
57
|
+
throw new TypeError(
|
|
58
|
+
"observeHTML() requires MutationObserver support or a MutationObserver option.",
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const selector = options.selector ?? DEFAULT_SELECTOR;
|
|
63
|
+
const seen = new WeakSet();
|
|
64
|
+
let connected = true;
|
|
65
|
+
let lastError;
|
|
66
|
+
let queue = Promise.resolve();
|
|
67
|
+
|
|
68
|
+
if (options.registerExisting === false) {
|
|
69
|
+
for (const element of Array.from(root.querySelectorAll(selector))) {
|
|
70
|
+
seen.add(element);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const scanAndRegister = async () => {
|
|
75
|
+
const elements = Array.from(root.querySelectorAll(selector)).filter(
|
|
76
|
+
element => !seen.has(element),
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
if (elements.length === 0) return emptyRegistrationResult();
|
|
80
|
+
|
|
81
|
+
return registerElements(
|
|
82
|
+
runtime,
|
|
83
|
+
elements,
|
|
84
|
+
{
|
|
85
|
+
...options,
|
|
86
|
+
root,
|
|
87
|
+
},
|
|
88
|
+
element => seen.add(element),
|
|
89
|
+
);
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
const reportError = error => {
|
|
93
|
+
lastError = error;
|
|
94
|
+
|
|
95
|
+
if (typeof options.onError === "function") {
|
|
96
|
+
options.onError(error);
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// MutationObserver callbacks cannot be awaited by the DOM. Keep the error
|
|
101
|
+
// available for flush(), but also make background failures visible.
|
|
102
|
+
globalThis.console?.error?.("solid-tag-runtime/html observer error", error);
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
const enqueue = () => {
|
|
106
|
+
const task = queue.then(scanAndRegister);
|
|
107
|
+
queue = task.then(
|
|
108
|
+
() => undefined,
|
|
109
|
+
error => {
|
|
110
|
+
reportError(error);
|
|
111
|
+
},
|
|
112
|
+
);
|
|
113
|
+
return task;
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
const observer = new MutationObserverImpl(() => {
|
|
117
|
+
// The settled queue owns background error reporting. Attach a rejection
|
|
118
|
+
// handler here as well so the callback never creates an unhandled promise.
|
|
119
|
+
void enqueue().catch(() => {});
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
observer.observe(root, {
|
|
123
|
+
childList: true,
|
|
124
|
+
subtree: options.subtree !== false,
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
let initial = emptyRegistrationResult();
|
|
128
|
+
|
|
129
|
+
try {
|
|
130
|
+
if (options.registerExisting !== false) {
|
|
131
|
+
initial = await enqueue();
|
|
132
|
+
}
|
|
133
|
+
} catch (error) {
|
|
134
|
+
connected = false;
|
|
135
|
+
observer.disconnect();
|
|
136
|
+
throw error;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return {
|
|
140
|
+
initial,
|
|
141
|
+
|
|
142
|
+
get connected() {
|
|
143
|
+
return connected;
|
|
144
|
+
},
|
|
145
|
+
|
|
146
|
+
async flush() {
|
|
147
|
+
await queue;
|
|
148
|
+
|
|
149
|
+
if (lastError) {
|
|
150
|
+
const error = lastError;
|
|
151
|
+
lastError = undefined;
|
|
152
|
+
throw error;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
return enqueue();
|
|
156
|
+
},
|
|
157
|
+
|
|
158
|
+
disconnect() {
|
|
159
|
+
if (!connected) return;
|
|
160
|
+
connected = false;
|
|
161
|
+
observer.disconnect();
|
|
162
|
+
},
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Define one <script> element as a runtime module without executing it.
|
|
168
|
+
*/
|
|
169
|
+
export async function defineScript(runtime, element, options = {}) {
|
|
170
|
+
assertRuntime(runtime);
|
|
171
|
+
|
|
172
|
+
if (!element || typeof element.getAttribute !== "function") {
|
|
173
|
+
throw new TypeError("defineScript() expects a script element-like object.");
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const type = normalizeType(element.getAttribute("type"));
|
|
177
|
+
const entry = hasAttribute(element, "entry");
|
|
178
|
+
const srcAttribute = nonEmpty(element.getAttribute("src"));
|
|
179
|
+
const explicitId = nonEmpty(element.getAttribute("module"));
|
|
180
|
+
const sourceUrl = srcAttribute
|
|
181
|
+
? resolveSourceUrl(srcAttribute, options.baseUrl ?? element.baseURI ?? options.root?.baseURI)
|
|
182
|
+
: undefined;
|
|
183
|
+
|
|
184
|
+
const format = inferFormat({
|
|
185
|
+
explicitFormat: options.format,
|
|
186
|
+
language: nonEmpty(element.getAttribute("language")),
|
|
187
|
+
type,
|
|
188
|
+
moduleId: explicitId,
|
|
189
|
+
sourceUrl,
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
const id = explicitId ?? sourceUrl ?? (entry ? createAnonymousId(format) : undefined);
|
|
193
|
+
|
|
194
|
+
if (!id) {
|
|
195
|
+
throw new HTMLModuleError(
|
|
196
|
+
"Inline solid runtime module scripts require a `module` attribute unless they are marked `entry`.",
|
|
197
|
+
{ element },
|
|
198
|
+
);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
let source;
|
|
202
|
+
if (sourceUrl) {
|
|
203
|
+
source = await fetchSource(sourceUrl, options.fetch ?? globalThis.fetch, element, id);
|
|
204
|
+
} else {
|
|
205
|
+
source = element.textContent ?? "";
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
runtime.define(id, source, { format });
|
|
209
|
+
|
|
210
|
+
return {
|
|
211
|
+
id: runtime.resolve(id),
|
|
212
|
+
format,
|
|
213
|
+
entry,
|
|
214
|
+
sourceUrl,
|
|
215
|
+
inline: !sourceUrl,
|
|
216
|
+
element,
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
export const htmlModuleSelector = DEFAULT_SELECTOR;
|
|
221
|
+
|
|
222
|
+
async function registerElements(runtime, elements, options, onDefined) {
|
|
223
|
+
const modules = [];
|
|
224
|
+
|
|
225
|
+
// Define the complete discovered batch before evaluating any entry. This
|
|
226
|
+
// allows an entry to import another module discovered later in the same scan.
|
|
227
|
+
for (const element of elements) {
|
|
228
|
+
const definition = await defineScript(runtime, element, options);
|
|
229
|
+
modules.push(definition);
|
|
230
|
+
onDefined?.(element, definition);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
const entries = modules.filter(module => module.entry);
|
|
234
|
+
const executed = [];
|
|
235
|
+
|
|
236
|
+
if (options.executeEntries !== false) {
|
|
237
|
+
for (const entry of entries) {
|
|
238
|
+
executed.push({
|
|
239
|
+
id: entry.id,
|
|
240
|
+
namespace: await runtime.import(entry.id),
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
return {
|
|
246
|
+
modules,
|
|
247
|
+
entries: entries.map(entry => entry.id),
|
|
248
|
+
executed,
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
function emptyRegistrationResult() {
|
|
253
|
+
return {
|
|
254
|
+
modules: [],
|
|
255
|
+
entries: [],
|
|
256
|
+
executed: [],
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
function getRoot(options, caller) {
|
|
261
|
+
const root = options.root ?? globalThis.document;
|
|
262
|
+
if (!root || typeof root.querySelectorAll !== "function") {
|
|
263
|
+
throw new TypeError(`${caller}() requires a DOM root with querySelectorAll().`);
|
|
264
|
+
}
|
|
265
|
+
return root;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
function assertRuntime(runtime) {
|
|
269
|
+
if (!runtime || typeof runtime.define !== "function" || typeof runtime.import !== "function") {
|
|
270
|
+
throw new TypeError("Expected a solid-tag-runtime instance.");
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
function normalizeType(value) {
|
|
275
|
+
return String(value ?? "").trim().toLowerCase();
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
function inferFormat({ explicitFormat, language, type, moduleId, sourceUrl }) {
|
|
279
|
+
if (explicitFormat != null) return normalizeFormat(explicitFormat);
|
|
280
|
+
if (language != null) return normalizeFormat(language);
|
|
281
|
+
|
|
282
|
+
if (type === "solid-js" || type === "text/solid-js") return "js";
|
|
283
|
+
if (type === "solid-jsx" || type === "text/solid-jsx") return "jsx";
|
|
284
|
+
|
|
285
|
+
const target = moduleId ?? sourceUrl ?? "";
|
|
286
|
+
const pathname = stripQueryAndHash(target).toLowerCase();
|
|
287
|
+
if (pathname.endsWith(".js") || pathname.endsWith(".mjs")) return "js";
|
|
288
|
+
return "jsx";
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
function normalizeFormat(value) {
|
|
292
|
+
const format = String(value).trim().toLowerCase();
|
|
293
|
+
if (format === "js" || format === "javascript") return "js";
|
|
294
|
+
if (format === "jsx") return "jsx";
|
|
295
|
+
throw new HTMLModuleError(`Unsupported HTML runtime module format ${JSON.stringify(value)}.`);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
async function fetchSource(url, fetchImpl, element, moduleId) {
|
|
299
|
+
if (typeof fetchImpl !== "function") {
|
|
300
|
+
throw new HTMLModuleError(
|
|
301
|
+
`Cannot load ${JSON.stringify(url)} because no fetch implementation is available.`,
|
|
302
|
+
{ element, moduleId, sourceUrl: url },
|
|
303
|
+
);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
let response;
|
|
307
|
+
try {
|
|
308
|
+
response = await fetchImpl(url);
|
|
309
|
+
} catch (cause) {
|
|
310
|
+
throw new HTMLModuleError(
|
|
311
|
+
`Failed to fetch runtime module ${JSON.stringify(url)}.`,
|
|
312
|
+
{ cause, element, moduleId, sourceUrl: url },
|
|
313
|
+
);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
if (!response || response.ok === false) {
|
|
317
|
+
const status = response?.status ? ` (${response.status}${response.statusText ? ` ${response.statusText}` : ""})` : "";
|
|
318
|
+
throw new HTMLModuleError(
|
|
319
|
+
`Failed to fetch runtime module ${JSON.stringify(url)}${status}.`,
|
|
320
|
+
{ element, moduleId, sourceUrl: url },
|
|
321
|
+
);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
try {
|
|
325
|
+
return await response.text();
|
|
326
|
+
} catch (cause) {
|
|
327
|
+
throw new HTMLModuleError(
|
|
328
|
+
`Failed to read runtime module ${JSON.stringify(url)} as text.`,
|
|
329
|
+
{ cause, element, moduleId, sourceUrl: url },
|
|
330
|
+
);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
function resolveSourceUrl(src, baseUrl) {
|
|
335
|
+
try {
|
|
336
|
+
if (baseUrl) return new URL(src, baseUrl).href;
|
|
337
|
+
return new URL(src).href;
|
|
338
|
+
} catch {
|
|
339
|
+
// A DOM element normally supplies baseURI. Keeping the original string is
|
|
340
|
+
// still useful for custom DOM-like environments and injected fetchers.
|
|
341
|
+
return src;
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
function createAnonymousId(format) {
|
|
346
|
+
const extension = format === "js" ? "js" : "jsx";
|
|
347
|
+
return `/__solid_tag_runtime__/html-entry-${++anonymousSequence}.${extension}`;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
function stripQueryAndHash(value) {
|
|
351
|
+
return String(value).split(/[?#]/, 1)[0];
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
function nonEmpty(value) {
|
|
355
|
+
const text = String(value ?? "").trim();
|
|
356
|
+
return text || undefined;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
function hasAttribute(element, name) {
|
|
360
|
+
if (typeof element.hasAttribute === "function") return element.hasAttribute(name);
|
|
361
|
+
return element.getAttribute(name) != null;
|
|
362
|
+
}
|