@solidjs/web 2.0.0-beta.29 → 2.0.0-beta.30

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,4 @@
1
- import { getOwner, onCleanup, createMemo, runWithOwner, createLoadingBoundary, createOwner, sharedConfig } from 'solid-js';
1
+ import { getOwner, onCleanup, createMemo, runWithOwner, createLoadingBoundary, createOwner, sharedConfig, createSignal } from 'solid-js';
2
2
  import { insert } from '@solidjs/web';
3
3
  import { createPlugin, fromCrossJSON, Feature } from 'seroval';
4
4
  import { ChunkReader, frameAddress, SINGLE_FLIGHT_HEADER, deserializeStream, getServerFunctionsCodec, createChunk, getFlightDataConsumer, ERROR_HEADER, REVALIDATE_HEADER, configureServerFunctionsClient } from '@solidjs/web/server-functions/client';
@@ -100,12 +100,11 @@ function createFrameHost(options = {}) {
100
100
  const sibling = set.size ? set.values().next().value : undefined;
101
101
  set.add(frame);
102
102
  if (sibling) {
103
- if (sibling.version !== undefined) {
104
- frame.apply({
105
- version: sibling.version,
106
- r: sibling.store
107
- });
108
- }
103
+ frame.apply({
104
+ version: sibling.version ?? 0,
105
+ r: sibling.store
106
+ });
107
+ if (sibling.version === undefined) frame.rebase && frame.rebase();
109
108
  return;
110
109
  }
111
110
  const kept = retained.get(id);
@@ -115,6 +114,7 @@ function createFrameHost(options = {}) {
115
114
  version: kept.version,
116
115
  r: kept.records
117
116
  });
117
+ frame.rebase && frame.rebase();
118
118
  }
119
119
  const buffered = pending.get(id);
120
120
  if (buffered) {
@@ -203,6 +203,7 @@ class FrameImpl {
203
203
  #mountedSlots = new Set();
204
204
  #slotCleanups = new Map();
205
205
  #slotArgs = new Map();
206
+ #slotUpdaters = new Map();
206
207
  #slotRegions = new Map();
207
208
  #slotResolvedRefs = new Map();
208
209
  #slotNodes = new Map();
@@ -380,6 +381,21 @@ class FrameImpl {
380
381
  } else if (record !== this.#slotArgs.get(occurrence)) {
381
382
  if (this.#refArgsUnchanged(occurrence, record)) {
382
383
  this.#slotArgs.set(occurrence, record);
384
+ this.#reconcileRegions(occurrence, record);
385
+ continue;
386
+ }
387
+ const update = this.#slotUpdaters.get(occurrence);
388
+ if (update) {
389
+ const props = this.#resolveArgs(occurrence, record.args);
390
+ const regions = this.#slotRegions.get(occurrence);
391
+ if (regions) {
392
+ for (const [argKey, entry] of regions) {
393
+ if (!(argKey in props)) props[argKey] = entry.element;
394
+ }
395
+ }
396
+ this.#slotArgs.set(occurrence, record);
397
+ update(props);
398
+ this.#bindRegions(occurrence);
383
399
  continue;
384
400
  }
385
401
  const nodes = this.#invokeSlot(occurrence, callback, record, start);
@@ -395,6 +411,7 @@ class FrameImpl {
395
411
  }
396
412
  }
397
413
  #invokeSlot(occurrence, callback, record, start, adopted) {
414
+ this.#slotUpdaters.delete(occurrence);
398
415
  const cleanups = this.#slotCleanups.get(occurrence) ?? [];
399
416
  let existing = [];
400
417
  let end = null;
@@ -413,6 +430,7 @@ class FrameImpl {
413
430
  adopted: !!adopted,
414
431
  invoked: !!(record && record.kind === "slot"),
415
432
  onCleanup: fn => cleanups.push(fn),
433
+ onUpdate: fn => this.#slotUpdaters.set(occurrence, fn),
416
434
  existing,
417
435
  range: end ? {
418
436
  start,
@@ -422,8 +440,7 @@ class FrameImpl {
422
440
  const props = record && record.kind === "slot" ? this.#resolveArgs(occurrence, record.args) : {};
423
441
  const regions = this.#slotRegions.get(occurrence);
424
442
  if (regions) {
425
- for (const entry of regions.values()) {
426
- const argKey = entry.childId.slice(entry.childId.lastIndexOf(".") + 1);
443
+ for (const [argKey, entry] of regions) {
427
444
  if (!(argKey in props)) props[argKey] = entry.element;
428
445
  }
429
446
  }
@@ -449,6 +466,7 @@ class FrameImpl {
449
466
  this.#mountedSlots.delete(key);
450
467
  this.#slotNodes.delete(key);
451
468
  this.#slotArgs.delete(key);
469
+ this.#slotUpdaters.delete(key);
452
470
  this.#slotResolvedRefs.delete(key);
453
471
  this.#removeSlotRecord(key);
454
472
  this.#runSlotCleanups(key);
@@ -483,7 +501,7 @@ class FrameImpl {
483
501
  cache[key] = resolved;
484
502
  props[key] = resolved;
485
503
  } else if (isFrameRef(value)) {
486
- let entry = regions.get(value.$frame);
504
+ let entry = regions.get(key);
487
505
  if (!entry) {
488
506
  const element = makeFrameElement(value.$frame);
489
507
  entry = {
@@ -491,7 +509,9 @@ class FrameImpl {
491
509
  element,
492
510
  frame: undefined
493
511
  };
494
- regions.set(value.$frame, entry);
512
+ regions.set(key, entry);
513
+ } else if (entry.childId !== value.$frame) {
514
+ renameRegion(entry, value.$frame);
495
515
  }
496
516
  props[key] = entry.element;
497
517
  } else {
@@ -508,9 +528,10 @@ class FrameImpl {
508
528
  this.#slotRegions.set(slotKey, regions);
509
529
  }
510
530
  const endData = slotEnd(slotKey);
531
+ const prefix = `${this.#options.id}.`;
511
532
  let n = start.nextSibling;
512
533
  while (n && !(n.nodeType === COMMENT_NODE && n.data === endData)) {
513
- collectRegionElements(n, regions);
534
+ collectRegionElements(n, regions, prefix);
514
535
  n = n.nextSibling;
515
536
  }
516
537
  }
@@ -529,7 +550,7 @@ class FrameImpl {
529
550
  if (key in a) continue;
530
551
  const vb = b[key];
531
552
  if (!vb || typeof vb !== "object" || typeof vb.$frame !== "string") return false;
532
- if (!regions || !regions.has(vb.$frame)) return false;
553
+ if (!regions || !regions.has(key)) return false;
533
554
  }
534
555
  }
535
556
  const cache = this.#slotResolvedRefs.get(occurrence);
@@ -538,7 +559,7 @@ class FrameImpl {
538
559
  const vb = b[key];
539
560
  if (va === vb) continue;
540
561
  if (!va || !vb || typeof va !== "object" || typeof vb !== "object") return false;
541
- if (typeof va.$frame === "string" && va.$frame === vb.$frame) continue;
562
+ if (typeof va.$frame === "string" && typeof vb.$frame === "string") continue;
542
563
  if (typeof va.$ref === "string" && typeof vb.$ref === "string" && cache && key in cache) {
543
564
  const host = this.#options.host;
544
565
  const next = host ? host.resolve(vb, this.#options.id) : undefined;
@@ -552,6 +573,18 @@ class FrameImpl {
552
573
  }
553
574
  return true;
554
575
  }
576
+ #reconcileRegions(occurrence, record) {
577
+ const args = record && record.args;
578
+ if (!args) return;
579
+ const regions = this.#slotRegions.get(occurrence);
580
+ if (!regions) return;
581
+ for (const key of Object.keys(args)) {
582
+ const value = args[key];
583
+ if (!isFrameRef(value)) continue;
584
+ const entry = regions.get(key);
585
+ if (entry && entry.childId !== value.$frame) renameRegion(entry, value.$frame);
586
+ }
587
+ }
555
588
  #bindRegions(slotKey) {
556
589
  const regions = this.#slotRegions.get(slotKey);
557
590
  if (!regions) return;
@@ -593,6 +626,31 @@ class FrameImpl {
593
626
  records
594
627
  };
595
628
  }
629
+ rebind(id) {
630
+ if (this.#disposed || id === this.#options.id) return;
631
+ const {
632
+ host,
633
+ id: oldId
634
+ } = this.#options;
635
+ if (host && oldId !== undefined) host.unregister(oldId, this);
636
+ this.#options = {
637
+ ...this.#options,
638
+ id
639
+ };
640
+ if (this.#element && this.#element.nodeType === ELEMENT_NODE) {
641
+ this.#element.setAttribute(FRAME_ID_ATTR, id);
642
+ }
643
+ this.#version = undefined;
644
+ this.#revealed.clear();
645
+ this.#fallbackShown.clear();
646
+ for (const key of Object.keys(this.#store)) {
647
+ if (key.startsWith("seg:") || key === ":error") delete this.#store[key];
648
+ }
649
+ if (host) host.register(id, this);
650
+ }
651
+ rebase() {
652
+ this.#version = undefined;
653
+ }
596
654
  dispose() {
597
655
  if (this.#disposed) return;
598
656
  const {
@@ -624,6 +682,7 @@ class FrameImpl {
624
682
  const ranges = new Map();
625
683
  this.#collectSlots(ranges);
626
684
  reconcileChildren(parent, fragment, this.#start, this.#end, claim, ranges);
685
+ if (ranges.size) restoreDisplacedRanges(this.#firstContent(), this.#end, ranges);
627
686
  }
628
687
  }
629
688
  #clearContent() {
@@ -650,8 +709,8 @@ class FrameImpl {
650
709
  const assets = this.#store[`seg:${name}:assets`];
651
710
  if (assets && assets.styles) {
652
711
  let ready = true;
653
- for (const href of assets.styles) {
654
- if (!ensureStylesheet(href, this.#styleFlush)) ready = false;
712
+ for (const entry of assets.styles) {
713
+ if (!ensureStylesheet(entry, this.#styleFlush)) ready = false;
655
714
  }
656
715
  if (!ready) return false;
657
716
  }
@@ -767,11 +826,15 @@ function findHeadElement(selector, attr, value) {
767
826
  }
768
827
  return null;
769
828
  }
770
- function ensureStylesheet(href, onSettle) {
829
+ function ensureStylesheet(entry, onSettle) {
830
+ const href = typeof entry === "string" ? entry : entry.href;
771
831
  let link = findHeadElement('link[rel="stylesheet"]', "href", href);
772
832
  if (!link) {
773
833
  link = document.createElement("link");
774
834
  link.rel = "stylesheet";
835
+ if (typeof entry !== "string" && entry.attrs) {
836
+ for (const name in entry.attrs) link.setAttribute(name, entry.attrs[name]);
837
+ }
775
838
  link.href = href;
776
839
  const waiters = new Set();
777
840
  link._$frWaiters = waiters;
@@ -842,21 +905,28 @@ function collectSlots(n, end, out) {
842
905
  n = n.nextSibling;
843
906
  }
844
907
  }
845
- function collectRegionElements(node, regions) {
908
+ function collectRegionElements(node, regions, prefix) {
846
909
  if (node.nodeType !== ELEMENT_NODE) return;
847
910
  if (isFrameElement(node)) {
848
911
  const childId = node.getAttribute(FRAME_ID_ATTR);
849
- if (childId && !regions.has(childId)) {
850
- regions.set(childId, {
851
- childId,
852
- element: node,
853
- frame: undefined,
854
- adopt: true
855
- });
912
+ if (childId && childId.startsWith(prefix)) {
913
+ const argKey = childId.slice(childId.lastIndexOf(".") + 1);
914
+ if (!regions.has(argKey)) {
915
+ regions.set(argKey, {
916
+ childId,
917
+ element: node,
918
+ frame: undefined,
919
+ adopt: true
920
+ });
921
+ }
856
922
  }
857
923
  return;
858
924
  }
859
- for (let c = node.firstChild; c; c = c.nextSibling) collectRegionElements(c, regions);
925
+ for (let c = node.firstChild; c; c = c.nextSibling) collectRegionElements(c, regions, prefix);
926
+ }
927
+ function renameRegion(entry, childId) {
928
+ entry.childId = childId;
929
+ if (entry.frame) entry.frame.rebind(childId);else entry.element.setAttribute(FRAME_ID_ATTR, childId);
860
930
  }
861
931
  function argsEquivalent(a, b) {
862
932
  if (a === b) return true;
@@ -1061,6 +1131,22 @@ function reconcileChildren(parent, source, boundStart = null, boundEnd = null, c
1061
1131
  oldChild = next;
1062
1132
  }
1063
1133
  }
1134
+ function restoreDisplacedRanges(first, end, ranges) {
1135
+ const found = new Map();
1136
+ collectSlots(first, end, found);
1137
+ for (const [id, start] of found) {
1138
+ const displaced = ranges.get(id);
1139
+ if (!displaced || displaced === start) continue;
1140
+ ranges.delete(id);
1141
+ const parent = start.parentNode;
1142
+ if (displaced.nodeType === 11 ) {
1143
+ parent.insertBefore(displaced, start);
1144
+ } else {
1145
+ moveRangeBefore(parent, displaced, id, start);
1146
+ }
1147
+ stashRange(document.createDocumentFragment(), start, id);
1148
+ }
1149
+ }
1064
1150
  function stashRange(frag, start, id) {
1065
1151
  const end = slotEnd(id);
1066
1152
  let n = start;
@@ -1104,6 +1190,7 @@ async function applyFrameResponse(response, host, options = {}) {
1104
1190
  }
1105
1191
  const SERVER_COMPONENT = /*#__PURE__*/Symbol.for("dom-expressions.server-component");
1106
1192
  const SERVER_COMPONENT_ADDRESS = /*#__PURE__*/Symbol.for("dom-expressions.server-component-address");
1193
+ const COMPONENT_HANDOFF = /*#__PURE__*/Symbol.for("dom-expressions.component-handoff");
1107
1194
  let resolveServerComponent;
1108
1195
  function parseServerComponent(value, ctx) {
1109
1196
  return {
@@ -1158,18 +1245,48 @@ function createServerComponentHandler({
1158
1245
  codec = getServerFunctionsCodec
1159
1246
  }) {
1160
1247
  const byAddress = new Map();
1161
- const boundaryFor = address => {
1248
+ const forwards = new Map();
1249
+ const brand = (comp, fnId, frameId) => {
1250
+ const brandable = typeof comp === "function" || typeof comp === "object" && comp !== null;
1251
+ if (brandable && !comp[COMPONENT_HANDOFF]) {
1252
+ comp[COMPONENT_HANDOFF] = {
1253
+ fnId,
1254
+ frameId,
1255
+ take(prev) {
1256
+ const meta = prev !== null && (typeof prev === "function" || typeof prev === "object") && prev[COMPONENT_HANDOFF];
1257
+ if (!meta || meta.fnId !== fnId) return false;
1258
+ const root = meta.frameId;
1259
+ let cur = forwards.get(root) ?? root;
1260
+ if (cur !== root && !host.get(cur)) {
1261
+ forwards.delete(root);
1262
+ cur = root;
1263
+ }
1264
+ if (cur === frameId) return true;
1265
+ let frame = host.get(cur);
1266
+ if (!frame) return false;
1267
+ while (frame) {
1268
+ frame.rebind(frameId);
1269
+ frame = host.get(cur);
1270
+ }
1271
+ if (frameId === root) forwards.delete(root);else forwards.set(root, frameId);
1272
+ return true;
1273
+ }
1274
+ };
1275
+ }
1276
+ return comp;
1277
+ };
1278
+ const boundaryFor = (address, fnId) => {
1162
1279
  let entry = byAddress.get(address);
1163
1280
  if (!entry) {
1164
1281
  entry = {
1165
1282
  frameId: address,
1166
- component: component(address)
1283
+ component: brand(component(address), fnId, address)
1167
1284
  };
1168
1285
  byAddress.set(address, entry);
1169
1286
  }
1170
1287
  return entry;
1171
1288
  };
1172
- const resolveAddress = (id, address) => boundaryFor(address).component;
1289
+ const resolveAddress = (id, address) => boundaryFor(address, id).component;
1173
1290
  resolveServerComponent = resolveAddress;
1174
1291
  const versions = new Map();
1175
1292
  const bump = frameId => {
@@ -1184,7 +1301,7 @@ function createServerComponentHandler({
1184
1301
  const address = frameAddress(info.id, info.args);
1185
1302
  byAddress.set(address, {
1186
1303
  frameId: info.id,
1187
- component: hit
1304
+ component: brand(hit, info.id, info.id)
1188
1305
  });
1189
1306
  }
1190
1307
  return hit;
@@ -1198,11 +1315,11 @@ function createServerComponentHandler({
1198
1315
  if (adopted) {
1199
1316
  entry = {
1200
1317
  frameId: ctx.id,
1201
- component: adopted
1318
+ component: brand(adopted, ctx.id, ctx.id)
1202
1319
  };
1203
1320
  byAddress.set(address, entry);
1204
1321
  } else {
1205
- entry = boundaryFor(address);
1322
+ entry = boundaryFor(address, ctx.id);
1206
1323
  }
1207
1324
  }
1208
1325
  if (response.headers.has(SINGLE_FLIGHT_HEADER)) {
@@ -1227,7 +1344,7 @@ function createServerComponentHandler({
1227
1344
  if (!byAddress.has(address)) {
1228
1345
  byAddress.set(address, {
1229
1346
  frameId: functionId,
1230
- component,
1347
+ component: brand(component, functionId, functionId),
1231
1348
  address
1232
1349
  });
1233
1350
  }
@@ -1359,6 +1476,15 @@ function gatherClaims(el, registry) {
1359
1476
  if (el.hasAttribute(FRAME_ID_ATTR)) return;
1360
1477
  for (let c = el.firstElementChild; c; c = c.nextElementSibling) gatherClaims(c, registry);
1361
1478
  }
1479
+ function hasPendingFragment(existing) {
1480
+ for (const n of existing) {
1481
+ if (n.nodeType !== 1) continue;
1482
+ const el = n;
1483
+ if (el.tagName === "TEMPLATE" && el.id.startsWith("pl-")) return true;
1484
+ if (el.querySelector?.('template[id^="pl-"]')) return true;
1485
+ }
1486
+ return false;
1487
+ }
1362
1488
  function claimRender(prefix, existing, render) {
1363
1489
  const sc = sharedConfig;
1364
1490
  if (!sc.getNextContextId) return render();
@@ -1367,10 +1493,11 @@ function claimRender(prefix, existing, render) {
1367
1493
  if (n.nodeType !== 1) continue;
1368
1494
  gatherClaims(n, registry);
1369
1495
  }
1370
- if (!registry.size) return render();
1496
+ if (!registry.size && !hasPendingFragment(existing)) return render();
1371
1497
  const prevRegistry = sc.registry;
1372
1498
  const prevHydrating = sc.hydrating;
1373
1499
  const prevClaimRoots = sc.claimRoots;
1500
+ if (prevRegistry) for (const key of registry.keys()) prevRegistry.delete(key);
1374
1501
  sc.registry = registry;
1375
1502
  sc.hydrating = true;
1376
1503
  sc.claimRoots = existing;
@@ -1384,6 +1511,19 @@ function claimRender(prefix, existing, render) {
1384
1511
  sc.claimRoots = prevClaimRoots;
1385
1512
  }
1386
1513
  }
1514
+ function liveSlotProps(initial, ctx) {
1515
+ const [args, setArgs] = createSignal(initial);
1516
+ ctx.onUpdate(next => setArgs(() => next));
1517
+ return new Proxy({}, {
1518
+ get: (_, key) => args()[key],
1519
+ has: (_, key) => key in args(),
1520
+ ownKeys: () => Reflect.ownKeys(args()),
1521
+ getOwnPropertyDescriptor: (_, key) => key in args() ? {
1522
+ enumerable: true,
1523
+ configurable: true
1524
+ } : undefined
1525
+ });
1526
+ }
1387
1527
  function isReactiveContent(value) {
1388
1528
  if (typeof value === "function") return true;
1389
1529
  if (Array.isArray(value)) {
@@ -1414,7 +1554,10 @@ function slotsFor(props) {
1414
1554
  };
1415
1555
  const evaluate = () => {
1416
1556
  const v = props[prop];
1417
- return typeof v === "function" && ctx && ctx.invoked ? v(slotProps) : v;
1557
+ if (typeof v === "function" && ctx && ctx.invoked) {
1558
+ return v(ctx.onUpdate ? liveSlotProps(slotProps, ctx) : slotProps);
1559
+ }
1560
+ return v;
1418
1561
  };
1419
1562
  const adopted = !!(ctx && ctx.frame && ctx.adopted && ctx.existing && ctx.existing.length);
1420
1563
  const prefix = ctx && ctx.frame ? `sc-${ctx.frame}-${ctx.key}-` : "";