@dom-expressions/runtime 0.50.0-next.15 → 0.50.0-next.16
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/CHANGELOG.md +53 -0
- package/package.json +2 -2
- package/src/client.d.ts +1 -0
- package/src/client.js +150 -28
- package/src/server.d.ts +1 -0
- package/src/server.js +29 -4
- package/src/universal.d.ts +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,58 @@
|
|
|
1
1
|
# dom-expressions
|
|
2
2
|
|
|
3
|
+
## 0.50.0-next.16
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- f2e56fe: fix(client): re-claim a hole's live DOM region when a streamed `$df` fragment swap replaced its tracked nodes mid-hydration (solidjs/solid#2801 bug 1, pending-stream case). A Loading fallback claimed during hydration is swapped out by `$df` before the boundary resumes; insert's node bookkeeping still pointed at the removed fallback, so the content pass fabricated detached text nodes and the first post-hydration refresh appended duplicates. When the tracked nodes are disconnected while hydrating, insert now re-derives the region (parent children, or back to the matching `<!--$-->` for marker-bounded holes) so loose text re-claims positionally — elements already recovered via `_hk`.
|
|
8
|
+
- b431fe7: Handle module preload failures during hydration instead of hanging silently (solidjs/solid#2817 layer 3). `loadModuleAssets` drops rejected entries from the loading cache so later boundaries/navigations can retry, and the root `_assets` path in `hydrate()` falls back to a fresh client render (with a console diagnostic) instead of leaving the page permanently dead.
|
|
9
|
+
- 016b460: Server rendering a plain (non-template) object child now dev-warns and skips it, matching the client, instead of crashing with `Cannot read properties of undefined (reading 'fn')` (solidjs/solid#2801 bug 6)
|
|
10
|
+
- c40ac21: Fix style object updates so shared or constant style objects are not mutated while diffing, and nullish property values correctly remove the applied style.
|
|
11
|
+
- c2a542b: Fix hydration key mismatches when async holes defer past eager siblings
|
|
12
|
+
(solidjs/solid#2801 bug 2). Dynamic element children that can allocate
|
|
13
|
+
hydration ids (conditionals, component-children access, call expressions)
|
|
14
|
+
are now compiled with their own id scope on both generates: the dom and ssr
|
|
15
|
+
generates wrap the hole expression in a new `scope()` runtime helper using a
|
|
16
|
+
shared predicate, so marking cannot desync.
|
|
17
|
+
|
|
18
|
+
On the client, `scope(fn)` tags the accessor and `insert()` makes the outer
|
|
19
|
+
render effect non-transparent (its own id scope) for tagged accessors; the
|
|
20
|
+
inner unwrapping effect stays transparent so content ids keep a fixed depth.
|
|
21
|
+
On the server, `scope` (framework-provided via rxcore as `ssrScope`) reserves
|
|
22
|
+
one id slot at registration and evaluates the hole — including async retries
|
|
23
|
+
— under that reserved id with a zeroed child counter, so retry timing can no
|
|
24
|
+
longer shift sibling ids. The ssr generate's `orderedInsert` sibling
|
|
25
|
+
thunk-wrapping is removed; it is superseded by hole scopes.
|
|
26
|
+
|
|
27
|
+
Hole content ids gain one nesting level (e.g. `_hk=10` instead of `_hk=1`)
|
|
28
|
+
identically on both sides. rxcore implementations must provide an `ssrScope`
|
|
29
|
+
export and honor a `scope: true` effect option (mapped to a non-transparent
|
|
30
|
+
render effect).
|
|
31
|
+
|
|
32
|
+
- fa24389: Fix delegated events never reaching outer roots when a render root is
|
|
33
|
+
rendered inside another root's DOM (embedded widgets, microfrontends).
|
|
34
|
+
The first (innermost) container listener marked the event consumed for
|
|
35
|
+
every other root, so an outer root's delegated handlers were silently
|
|
36
|
+
skipped even though the native event bubbled through its elements: a
|
|
37
|
+
plain `addEventListener` on the same element fired while the delegated
|
|
38
|
+
handler didn't.
|
|
39
|
+
|
|
40
|
+
`$$EVENT_OWNER` now records the boundary of the most recent walk instead
|
|
41
|
+
of a consumed flag: an ancestor container whose subtree contains that boundary
|
|
42
|
+
resumes the handler walk from it up to its own boundary, so each root's
|
|
43
|
+
handlers fire exactly once, innermost-out, matching native bubbling.
|
|
44
|
+
`stopPropagation()` inside a nested root still suppresses outer roots (it
|
|
45
|
+
stops the native event before their listeners run), and hydration event
|
|
46
|
+
replay now relays queued events through all matching roots innermost-first
|
|
47
|
+
so pre- and post-hydration clicks behave identically. Apps that relied on
|
|
48
|
+
nested roots to isolate clicks from outer handlers should use
|
|
49
|
+
`stopPropagation()`, which remains the documented mechanism. Non-nested
|
|
50
|
+
apps are unaffected; the resume path is unreachable unless an inner root
|
|
51
|
+
already handled the event.
|
|
52
|
+
|
|
53
|
+
- 75b4ab2: Normalize manifest asset URL joining in `resolveAssets` (solidjs/solid#2817 layers 1-2). A non-string `_base` (e.g. a dev-manifest proxy answering every key) falls back to `/`, leading-slash `file` values no longer produce `//` URLs, and absolute/protocol-relative URLs pass through untouched — the server runtime emits sane module URLs for any reasonable manifest shape instead of relying on bundler plugins getting the contract exactly right.
|
|
54
|
+
- 668264f: Universal JSX now passes compile-time static host props to `createElement(tag, staticProps)` so custom renderers can configure nodes before children are inserted. Dynamic props and elements with spreads continue to use the existing `setProp` / `spread` paths.
|
|
55
|
+
|
|
3
56
|
## 0.50.0-next.15
|
|
4
57
|
|
|
5
58
|
### Patch Changes
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dom-expressions/runtime",
|
|
3
3
|
"description": "A Fine-Grained Runtime for Performant DOM Rendering",
|
|
4
|
-
"version": "0.50.0-next.
|
|
4
|
+
"version": "0.50.0-next.16",
|
|
5
5
|
"author": "Ryan Carniato",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"repository": {
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
"csstype": "^3.1",
|
|
27
27
|
"seroval": "~1.5.0",
|
|
28
28
|
"seroval-plugins": "~1.5.0",
|
|
29
|
-
"@dom-expressions/babel-plugin-jsx": "0.50.0-next.
|
|
29
|
+
"@dom-expressions/babel-plugin-jsx": "0.50.0-next.16"
|
|
30
30
|
},
|
|
31
31
|
"scripts": {
|
|
32
32
|
"test": "jest --no-cache",
|
package/src/client.d.ts
CHANGED
|
@@ -23,6 +23,7 @@ export function render(
|
|
|
23
23
|
* - `2` — the template html is wrapped; the outer tag is stripped at clone time.
|
|
24
24
|
*/
|
|
25
25
|
export function template(html: string, flag?: 1 | 2): () => Element;
|
|
26
|
+
export function scope<T extends () => any>(fn: T): T;
|
|
26
27
|
export function effect<T>(fn: (prev?: T) => T, effect: (value: T, prev?: T) => void): void;
|
|
27
28
|
export function memo<T>(fn: () => T, equal: boolean): () => T;
|
|
28
29
|
export function untrack<T>(fn: () => T): T;
|
package/src/client.js
CHANGED
|
@@ -246,20 +246,45 @@ export function addEvent(node, name, handler, delegate) {
|
|
|
246
246
|
|
|
247
247
|
export function style(node, value, prev) {
|
|
248
248
|
if (!value) {
|
|
249
|
-
if (prev
|
|
249
|
+
if (prev || node._$styles) {
|
|
250
|
+
setAttribute(node, "style");
|
|
251
|
+
node._$styles = undefined;
|
|
252
|
+
}
|
|
250
253
|
return;
|
|
251
254
|
}
|
|
252
255
|
const nodeStyle = node.style;
|
|
253
|
-
if (typeof value === "string")
|
|
254
|
-
|
|
255
|
-
|
|
256
|
+
if (typeof value === "string") {
|
|
257
|
+
node._$styles = undefined;
|
|
258
|
+
return (nodeStyle.cssText = value);
|
|
259
|
+
}
|
|
260
|
+
if (typeof prev === "string") {
|
|
261
|
+
nodeStyle.cssText = "";
|
|
262
|
+
prev = undefined;
|
|
263
|
+
}
|
|
264
|
+
// Track declarations applied by style() itself. value/prev are user-owned
|
|
265
|
+
// and may be the same object on shared-effect reruns.
|
|
266
|
+
let applied = node._$styles;
|
|
267
|
+
if (!applied) {
|
|
268
|
+
// seed from prev so direct callers that track their own previous value
|
|
269
|
+
// still get removals on their first call here
|
|
270
|
+
applied = node._$styles = prev ? { ...prev } : {};
|
|
271
|
+
}
|
|
256
272
|
let v, s;
|
|
273
|
+
for (s in applied) {
|
|
274
|
+
if (value[s] == null) {
|
|
275
|
+
nodeStyle.removeProperty(s);
|
|
276
|
+
delete applied[s];
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
// Diff against applied state so in-place mutations are detected without
|
|
280
|
+
// rewriting unchanged DOM styles.
|
|
257
281
|
for (s in value) {
|
|
258
282
|
v = value[s];
|
|
259
|
-
if (v !==
|
|
260
|
-
|
|
283
|
+
if (v != null && v !== applied[s]) {
|
|
284
|
+
nodeStyle.setProperty(s, v);
|
|
285
|
+
applied[s] = v;
|
|
286
|
+
}
|
|
261
287
|
}
|
|
262
|
-
for (s in prev) value[s] == null && nodeStyle.removeProperty(s);
|
|
263
288
|
}
|
|
264
289
|
|
|
265
290
|
export function setStyleProperty(node, name, value) {
|
|
@@ -311,20 +336,35 @@ export function ref(fn, element) {
|
|
|
311
336
|
runWithOwner(null, () => applyRef(resolved, element));
|
|
312
337
|
}
|
|
313
338
|
|
|
339
|
+
// Compiler tag for holes that can allocate hydration ids: the outer insert
|
|
340
|
+
// effect gets its own (non-transparent) id scope, mirroring the server's
|
|
341
|
+
// `scope()` owner. Keeps sibling ids stable no matter when the hole runs.
|
|
342
|
+
export function scope(fn) {
|
|
343
|
+
fn.$s = true;
|
|
344
|
+
return fn;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
const SCOPE_OPTIONS = { scope: true };
|
|
348
|
+
|
|
349
|
+
// Drop the `<!--!$-->` text-hole separators the server emits so adjacent
|
|
350
|
+
// text nodes stay individually claimable; the array is compacted in place.
|
|
351
|
+
function stripTextSeparators(nodes) {
|
|
352
|
+
let j = 0;
|
|
353
|
+
for (let i = 0; i < nodes.length; i++) {
|
|
354
|
+
if (nodes[i].nodeType === 8 && nodes[i].nodeValue === "!$") nodes[i].remove();
|
|
355
|
+
else nodes[j++] = nodes[i];
|
|
356
|
+
}
|
|
357
|
+
nodes.length = j;
|
|
358
|
+
return nodes;
|
|
359
|
+
}
|
|
360
|
+
|
|
314
361
|
export function insert(parent, accessor, marker, initial, options) {
|
|
315
362
|
const multi = marker !== undefined;
|
|
316
363
|
const host = options && options.host;
|
|
317
364
|
if (multi && !initial) initial = [];
|
|
318
365
|
if (isHydrating(parent)) {
|
|
319
366
|
if (!multi && initial === undefined && parent) initial = [...parent.childNodes];
|
|
320
|
-
if (Array.isArray(initial))
|
|
321
|
-
let j = 0;
|
|
322
|
-
for (let i = 0; i < initial.length; i++) {
|
|
323
|
-
if (initial[i].nodeType === 8 && initial[i].nodeValue === "!$") initial[i].remove();
|
|
324
|
-
else initial[j++] = initial[i];
|
|
325
|
-
}
|
|
326
|
-
initial.length = j;
|
|
327
|
-
}
|
|
367
|
+
if (Array.isArray(initial)) stripTextSeparators(initial);
|
|
328
368
|
}
|
|
329
369
|
if (typeof accessor !== "function") {
|
|
330
370
|
accessor = normalize(accessor, initial, multi, true);
|
|
@@ -340,12 +380,44 @@ export function insert(parent, accessor, marker, initial, options) {
|
|
|
340
380
|
initial = [placeholder];
|
|
341
381
|
}
|
|
342
382
|
let current = initial;
|
|
383
|
+
// A streamed `$df` fragment swap replaces a hole's region out from under
|
|
384
|
+
// its bookkeeping (Loading fallback claimed during hydration, settled
|
|
385
|
+
// content swapped in later). When the tracked nodes are gone mid-hydration,
|
|
386
|
+
// re-claim the live region so the content pass can match loose text
|
|
387
|
+
// positionally — elements recover through the registry, text only has
|
|
388
|
+
// position. The region is `parent`'s children, or for marker-bounded holes
|
|
389
|
+
// the nodes back to the matching `<!--$-->` start marker.
|
|
390
|
+
function reclaimSwappedRegion() {
|
|
391
|
+
if (!sharedConfig.hydrating || !current || !parent.isConnected) return;
|
|
392
|
+
const first = Array.isArray(current) ? current[0] : current;
|
|
393
|
+
if (!first || !first.nodeType || first.isConnected) return;
|
|
394
|
+
let nodes;
|
|
395
|
+
if (marker) {
|
|
396
|
+
nodes = [];
|
|
397
|
+
let node = marker.previousSibling,
|
|
398
|
+
depth = 0;
|
|
399
|
+
while (node) {
|
|
400
|
+
if (node.nodeType === 8) {
|
|
401
|
+
const v = node.nodeValue;
|
|
402
|
+
if (v === "/") depth++;
|
|
403
|
+
else if (v === "$") {
|
|
404
|
+
if (depth === 0) break;
|
|
405
|
+
depth--;
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
nodes.unshift(node);
|
|
409
|
+
node = node.previousSibling;
|
|
410
|
+
}
|
|
411
|
+
} else nodes = [...parent.childNodes];
|
|
412
|
+
current = stripTextSeparators(nodes);
|
|
413
|
+
}
|
|
343
414
|
effect(
|
|
344
415
|
prev => {
|
|
416
|
+
reclaimSwappedRegion();
|
|
345
417
|
const value = normalize(accessor(), current, multi, true);
|
|
346
418
|
if (typeof value !== "function") return value;
|
|
347
419
|
effect(
|
|
348
|
-
() => normalize(value, current, multi),
|
|
420
|
+
() => (reclaimSwappedRegion(), normalize(value, current, multi)),
|
|
349
421
|
inner => {
|
|
350
422
|
insertExpression(parent, inner, current, marker);
|
|
351
423
|
current = inner;
|
|
@@ -363,7 +435,9 @@ export function insert(parent, accessor, marker, initial, options) {
|
|
|
363
435
|
current = value;
|
|
364
436
|
host && tagHost(current, host);
|
|
365
437
|
},
|
|
366
|
-
|
|
438
|
+
// Only the OUTER effect takes the scope — the inner unwrapping effect
|
|
439
|
+
// stays transparent so content ids keep a fixed depth per hole.
|
|
440
|
+
accessor.$s ? (options ? { ...options, scope: true } : SCOPE_OPTIONS) : options
|
|
367
441
|
);
|
|
368
442
|
}
|
|
369
443
|
|
|
@@ -394,9 +468,16 @@ function loadModuleAssets(mapping) {
|
|
|
394
468
|
if (hy.modules[moduleUrl]) continue;
|
|
395
469
|
const entryUrl = mapping[moduleUrl];
|
|
396
470
|
if (!hy.loading[moduleUrl]) {
|
|
397
|
-
hy.loading[moduleUrl] = import(/* @vite-ignore */ entryUrl).then(
|
|
398
|
-
|
|
399
|
-
|
|
471
|
+
hy.loading[moduleUrl] = import(/* @vite-ignore */ entryUrl).then(
|
|
472
|
+
mod => {
|
|
473
|
+
hy.modules[moduleUrl] = mod;
|
|
474
|
+
},
|
|
475
|
+
err => {
|
|
476
|
+
// drop the rejected entry so a later boundary/navigation can retry
|
|
477
|
+
delete hy.loading[moduleUrl];
|
|
478
|
+
throw err;
|
|
479
|
+
}
|
|
480
|
+
);
|
|
400
481
|
}
|
|
401
482
|
pending.push(hy.loading[moduleUrl]);
|
|
402
483
|
}
|
|
@@ -460,8 +541,15 @@ export function hydrate(code, element, options = {}) {
|
|
|
460
541
|
sharedConfig.hydrating = false;
|
|
461
542
|
}
|
|
462
543
|
},
|
|
463
|
-
|
|
544
|
+
err => {
|
|
545
|
+
// A chunk failed to preload; hydration can't claim the server DOM
|
|
546
|
+
// (lazy components have no module). Fall back to a fresh client
|
|
547
|
+
// render replacing the server markup — lazy's own import() gets to
|
|
548
|
+
// retry through normal channels — instead of a silently dead page.
|
|
549
|
+
console.error("Hydration module preload failed, falling back to client render:", err);
|
|
464
550
|
sharedConfig.hydrating = false;
|
|
551
|
+
sharedConfig.registry = undefined;
|
|
552
|
+
disposer = render(code, element, [...element.childNodes], options);
|
|
465
553
|
}
|
|
466
554
|
);
|
|
467
555
|
return () => disposer && disposer();
|
|
@@ -597,14 +685,34 @@ export function runHydrationEvents() {
|
|
|
597
685
|
const [el, e] = events[0];
|
|
598
686
|
if (!completed.has(el)) return;
|
|
599
687
|
events.shift();
|
|
600
|
-
let
|
|
688
|
+
let matchContainer, matchState, matchDistance, matches;
|
|
601
689
|
for (const [container, state] of delegatedContainers) {
|
|
602
690
|
if (!state.handlers.has(e.type)) continue;
|
|
603
691
|
const entry = findOwner(e.target, state);
|
|
604
|
-
if (
|
|
605
|
-
|
|
692
|
+
if (!entry) continue;
|
|
693
|
+
if (matchContainer) {
|
|
694
|
+
if (!matches)
|
|
695
|
+
matches = [
|
|
696
|
+
{
|
|
697
|
+
container: matchContainer,
|
|
698
|
+
state: matchState,
|
|
699
|
+
distance: matchDistance
|
|
700
|
+
}
|
|
701
|
+
];
|
|
702
|
+
matches.push({ container, state, distance: entry.distance });
|
|
703
|
+
} else {
|
|
704
|
+
matchContainer = container;
|
|
705
|
+
matchState = state;
|
|
706
|
+
matchDistance = entry.distance;
|
|
707
|
+
}
|
|
606
708
|
}
|
|
607
|
-
if (
|
|
709
|
+
if (matches) {
|
|
710
|
+
// Replay innermost-first so queued hydration events follow the same
|
|
711
|
+
// root-boundary handoff as live native bubbling.
|
|
712
|
+
matches.sort((a, b) => a.distance - b.distance);
|
|
713
|
+
for (let i = 0; i < matches.length; i++)
|
|
714
|
+
eventHandler(e, matches[i].container, matches[i].state);
|
|
715
|
+
} else if (matchContainer) eventHandler(e, matchContainer, matchState);
|
|
608
716
|
}
|
|
609
717
|
if (sharedConfig.done) {
|
|
610
718
|
sharedConfig.events = _$HY.events = null;
|
|
@@ -696,7 +804,15 @@ function eventHandler(e, container, state) {
|
|
|
696
804
|
if (sharedConfig.registry && sharedConfig.events) {
|
|
697
805
|
if (sharedConfig.events.find(([el, ev]) => ev === e)) return;
|
|
698
806
|
}
|
|
699
|
-
|
|
807
|
+
const prev = e[$$EVENT_OWNER];
|
|
808
|
+
let resumeNode;
|
|
809
|
+
if (prev) {
|
|
810
|
+
// An inner root already walked its segment. Ancestor roots resume from
|
|
811
|
+
// that boundary; unrelated/shared containers must not see the event as
|
|
812
|
+
// theirs. Native stopPropagation still prevents this listener from running.
|
|
813
|
+
if (prev === true || prev === container || !container.contains(prev)) return;
|
|
814
|
+
resumeNode = prev;
|
|
815
|
+
}
|
|
700
816
|
const owner =
|
|
701
817
|
state &&
|
|
702
818
|
(state.owners.size === 1 && state.owners.has(container)
|
|
@@ -705,7 +821,7 @@ function eventHandler(e, container, state) {
|
|
|
705
821
|
if (state && !owner) return;
|
|
706
822
|
e[$$EVENT_OWNER] = owner || true;
|
|
707
823
|
|
|
708
|
-
let node = e.target;
|
|
824
|
+
let node = resumeNode || e.target;
|
|
709
825
|
const key = `$$${e.type}`;
|
|
710
826
|
const oriTarget = e.target;
|
|
711
827
|
const boundary = owner || container || e.currentTarget;
|
|
@@ -742,7 +858,13 @@ function eventHandler(e, container, state) {
|
|
|
742
858
|
return node || boundary || document;
|
|
743
859
|
}
|
|
744
860
|
});
|
|
745
|
-
if (
|
|
861
|
+
if (resumeNode) {
|
|
862
|
+
// If the boundary was the target, the inner walk already fired it.
|
|
863
|
+
// Resume above it so boundary handlers do not run twice.
|
|
864
|
+
if (resumeNode === e.target)
|
|
865
|
+
node = resumeNode._$host || resumeNode.parentNode || resumeNode.host;
|
|
866
|
+
if (node && node !== boundary) walkUpTree();
|
|
867
|
+
} else if (e.composedPath) {
|
|
746
868
|
const path = e.composedPath();
|
|
747
869
|
if (path.length) {
|
|
748
870
|
retarget(path[0]);
|
package/src/server.d.ts
CHANGED
|
@@ -75,6 +75,7 @@ export function ssrStyle(value: string | { [k: string]: string }): string;
|
|
|
75
75
|
export function ssrStyleProperty(name: string, value: any): string;
|
|
76
76
|
export function ssrAttribute(key: string, value: any): string;
|
|
77
77
|
export function ssrGroup<T extends () => any[]>(fn: T, n: number): T;
|
|
78
|
+
export function scope<T>(fn: () => T): () => unknown;
|
|
78
79
|
export function ssrHydrationKey(): string;
|
|
79
80
|
export function resolveSSRNode(node: any, result?: any, top?: boolean): any;
|
|
80
81
|
export function escape(s: any, attr?: boolean): any;
|
package/src/server.js
CHANGED
|
@@ -7,6 +7,10 @@ import { createSerializer, getLocalHeaderScript } from "./serializer";
|
|
|
7
7
|
// core, and a local copy here drifts from them (it resolved function sources
|
|
8
8
|
// for key enumeration only, dropping their values in SSR output).
|
|
9
9
|
export { createComponent, effect, memo, untrack, mergeProps } from "rxcore";
|
|
10
|
+
// Hole owner scope (`_$scope(...)` in compiled ssr output) — owner-creating
|
|
11
|
+
// wrapper for deferred child holes that can allocate hydration ids. The
|
|
12
|
+
// framework owns the implementation (owner creation + per-attempt reset).
|
|
13
|
+
export { ssrScope as scope } from "rxcore";
|
|
10
14
|
export { getOwner };
|
|
11
15
|
|
|
12
16
|
export {
|
|
@@ -23,9 +27,22 @@ export {
|
|
|
23
27
|
|
|
24
28
|
// ---- Asset Manifest ----
|
|
25
29
|
|
|
30
|
+
// Join defensively rather than trusting the manifest's shape: dev manifests
|
|
31
|
+
// have answered `_base` with non-strings and emitted `file` values with a
|
|
32
|
+
// leading slash (solidjs/solid#2817 layers 1-2). Normalizing here keeps the
|
|
33
|
+
// emitted URLs sane for any reasonable manifest instead of playing contract
|
|
34
|
+
// ping-pong with bundler plugins.
|
|
35
|
+
function joinAssetPath(base, file) {
|
|
36
|
+
// absolute (`https://cdn/x.js`) and protocol-relative (`//cdn/x.js`) pass through
|
|
37
|
+
if (/^(?:[a-z][a-z0-9+.-]*:)?\/\//i.test(file)) return file;
|
|
38
|
+
if (typeof base !== "string" || !base) base = "/";
|
|
39
|
+
if (base[base.length - 1] !== "/") base += "/";
|
|
40
|
+
return base + (file[0] === "/" ? file.slice(1) : file);
|
|
41
|
+
}
|
|
42
|
+
|
|
26
43
|
function resolveAssets(moduleUrl, manifest) {
|
|
27
44
|
if (!manifest) return null;
|
|
28
|
-
const base = manifest._base
|
|
45
|
+
const base = manifest._base;
|
|
29
46
|
const entry = manifest[moduleUrl];
|
|
30
47
|
if (!entry) return null;
|
|
31
48
|
const css = [];
|
|
@@ -36,8 +53,8 @@ function resolveAssets(moduleUrl, manifest) {
|
|
|
36
53
|
visited.add(key);
|
|
37
54
|
const e = manifest[key];
|
|
38
55
|
if (!e) return;
|
|
39
|
-
js.push(base
|
|
40
|
-
if (e.css) for (let i = 0; i < e.css.length; i++) css.push(base
|
|
56
|
+
js.push(joinAssetPath(base, e.file));
|
|
57
|
+
if (e.css) for (let i = 0; i < e.css.length; i++) css.push(joinAssetPath(base, e.css[i]));
|
|
41
58
|
if (e.imports) for (let i = 0; i < e.imports.length; i++) walk(e.imports[i]);
|
|
42
59
|
};
|
|
43
60
|
walk(moduleUrl);
|
|
@@ -1178,6 +1195,12 @@ function tryResolveString(node) {
|
|
|
1178
1195
|
return s;
|
|
1179
1196
|
}
|
|
1180
1197
|
if (node.h && node.h.length > 0) return { merge: node };
|
|
1198
|
+
if (node.t === undefined) {
|
|
1199
|
+
// Not a template object — mirror the client's dev warn-and-skip
|
|
1200
|
+
// instead of crashing downstream on a malformed template shape.
|
|
1201
|
+
if ("_DX_DEV_") console.warn(`Unrecognized value. Skipped inserting`, node);
|
|
1202
|
+
return "";
|
|
1203
|
+
}
|
|
1181
1204
|
return Array.isArray(node.t) ? node.t[0] : node.t;
|
|
1182
1205
|
}
|
|
1183
1206
|
if (t === "function") {
|
|
@@ -1225,7 +1248,9 @@ function resolveSSRNode(
|
|
|
1225
1248
|
result.h.push(...node.h);
|
|
1226
1249
|
result.p.push(...node.p);
|
|
1227
1250
|
}
|
|
1228
|
-
} else
|
|
1251
|
+
} else if (node.t !== undefined) {
|
|
1252
|
+
result.t[result.t.length - 1] += node.t;
|
|
1253
|
+
} else if ("_DX_DEV_") console.warn(`Unrecognized value. Skipped inserting`, node);
|
|
1229
1254
|
} else if (t === "function") {
|
|
1230
1255
|
try {
|
|
1231
1256
|
resolveSSRNode(node(), result);
|
package/src/universal.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export interface RendererOptions<NodeType> {
|
|
2
|
-
createElement(tag: string): NodeType;
|
|
2
|
+
createElement(tag: string, staticProps?: Record<string, unknown>): NodeType;
|
|
3
3
|
createTextNode(value: string): NodeType;
|
|
4
4
|
createSentinel?(): NodeType;
|
|
5
5
|
replaceText(textNode: NodeType, value: string): void;
|
|
@@ -18,7 +18,7 @@ export interface Renderer<NodeType> {
|
|
|
18
18
|
effect<T>(fn: (prev?: T) => T, effect: (value: T, prev?: T) => void): void;
|
|
19
19
|
memo<T>(fn: () => T, equal: boolean): () => T;
|
|
20
20
|
createComponent<T>(Comp: (props: T) => NodeType, props: T): NodeType;
|
|
21
|
-
createElement(tag: string): NodeType;
|
|
21
|
+
createElement(tag: string, staticProps?: Record<string, unknown>): NodeType;
|
|
22
22
|
createTextNode(value: string): NodeType;
|
|
23
23
|
insertNode(parent: NodeType, node: NodeType, anchor?: NodeType): void;
|
|
24
24
|
insert<T>(parent: any, accessor: (() => T) | T, marker?: any | null, initial?: any): NodeType;
|