@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
  }
@@ -764,11 +823,15 @@ function findHeadElement(selector, attr, value) {
764
823
  }
765
824
  return null;
766
825
  }
767
- function ensureStylesheet(href, onSettle) {
826
+ function ensureStylesheet(entry, onSettle) {
827
+ const href = typeof entry === "string" ? entry : entry.href;
768
828
  let link = findHeadElement('link[rel="stylesheet"]', "href", href);
769
829
  if (!link) {
770
830
  link = document.createElement("link");
771
831
  link.rel = "stylesheet";
832
+ if (typeof entry !== "string" && entry.attrs) {
833
+ for (const name in entry.attrs) link.setAttribute(name, entry.attrs[name]);
834
+ }
772
835
  link.href = href;
773
836
  const waiters = new Set();
774
837
  link._$frWaiters = waiters;
@@ -838,21 +901,28 @@ function collectSlots(n, end, out) {
838
901
  n = n.nextSibling;
839
902
  }
840
903
  }
841
- function collectRegionElements(node, regions) {
904
+ function collectRegionElements(node, regions, prefix) {
842
905
  if (node.nodeType !== ELEMENT_NODE) return;
843
906
  if (isFrameElement(node)) {
844
907
  const childId = node.getAttribute(FRAME_ID_ATTR);
845
- if (childId && !regions.has(childId)) {
846
- regions.set(childId, {
847
- childId,
848
- element: node,
849
- frame: undefined,
850
- adopt: true
851
- });
908
+ if (childId && childId.startsWith(prefix)) {
909
+ const argKey = childId.slice(childId.lastIndexOf(".") + 1);
910
+ if (!regions.has(argKey)) {
911
+ regions.set(argKey, {
912
+ childId,
913
+ element: node,
914
+ frame: undefined,
915
+ adopt: true
916
+ });
917
+ }
852
918
  }
853
919
  return;
854
920
  }
855
- for (let c = node.firstChild; c; c = c.nextSibling) collectRegionElements(c, regions);
921
+ for (let c = node.firstChild; c; c = c.nextSibling) collectRegionElements(c, regions, prefix);
922
+ }
923
+ function renameRegion(entry, childId) {
924
+ entry.childId = childId;
925
+ if (entry.frame) entry.frame.rebind(childId);else entry.element.setAttribute(FRAME_ID_ATTR, childId);
856
926
  }
857
927
  function argsEquivalent(a, b) {
858
928
  if (a === b) return true;
@@ -1048,6 +1118,22 @@ function reconcileChildren(parent, source, boundStart = null, boundEnd = null, c
1048
1118
  oldChild = next;
1049
1119
  }
1050
1120
  }
1121
+ function restoreDisplacedRanges(first, end, ranges) {
1122
+ const found = new Map();
1123
+ collectSlots(first, end, found);
1124
+ for (const [id, start] of found) {
1125
+ const displaced = ranges.get(id);
1126
+ if (!displaced || displaced === start) continue;
1127
+ ranges.delete(id);
1128
+ const parent = start.parentNode;
1129
+ if (displaced.nodeType === 11 ) {
1130
+ parent.insertBefore(displaced, start);
1131
+ } else {
1132
+ moveRangeBefore(parent, displaced, id, start);
1133
+ }
1134
+ stashRange(document.createDocumentFragment(), start, id);
1135
+ }
1136
+ }
1051
1137
  function stashRange(frag, start, id) {
1052
1138
  const end = slotEnd(id);
1053
1139
  let n = start;
@@ -1091,6 +1177,7 @@ async function applyFrameResponse(response, host, options = {}) {
1091
1177
  }
1092
1178
  const SERVER_COMPONENT = /*#__PURE__*/Symbol.for("dom-expressions.server-component");
1093
1179
  const SERVER_COMPONENT_ADDRESS = /*#__PURE__*/Symbol.for("dom-expressions.server-component-address");
1180
+ const COMPONENT_HANDOFF = /*#__PURE__*/Symbol.for("dom-expressions.component-handoff");
1094
1181
  let resolveServerComponent;
1095
1182
  function parseServerComponent(value, ctx) {
1096
1183
  return {
@@ -1145,18 +1232,48 @@ function createServerComponentHandler({
1145
1232
  codec = getServerFunctionsCodec
1146
1233
  }) {
1147
1234
  const byAddress = new Map();
1148
- const boundaryFor = address => {
1235
+ const forwards = new Map();
1236
+ const brand = (comp, fnId, frameId) => {
1237
+ const brandable = typeof comp === "function" || typeof comp === "object" && comp !== null;
1238
+ if (brandable && !comp[COMPONENT_HANDOFF]) {
1239
+ comp[COMPONENT_HANDOFF] = {
1240
+ fnId,
1241
+ frameId,
1242
+ take(prev) {
1243
+ const meta = prev !== null && (typeof prev === "function" || typeof prev === "object") && prev[COMPONENT_HANDOFF];
1244
+ if (!meta || meta.fnId !== fnId) return false;
1245
+ const root = meta.frameId;
1246
+ let cur = forwards.get(root) ?? root;
1247
+ if (cur !== root && !host.get(cur)) {
1248
+ forwards.delete(root);
1249
+ cur = root;
1250
+ }
1251
+ if (cur === frameId) return true;
1252
+ let frame = host.get(cur);
1253
+ if (!frame) return false;
1254
+ while (frame) {
1255
+ frame.rebind(frameId);
1256
+ frame = host.get(cur);
1257
+ }
1258
+ if (frameId === root) forwards.delete(root);else forwards.set(root, frameId);
1259
+ return true;
1260
+ }
1261
+ };
1262
+ }
1263
+ return comp;
1264
+ };
1265
+ const boundaryFor = (address, fnId) => {
1149
1266
  let entry = byAddress.get(address);
1150
1267
  if (!entry) {
1151
1268
  entry = {
1152
1269
  frameId: address,
1153
- component: component(address)
1270
+ component: brand(component(address), fnId, address)
1154
1271
  };
1155
1272
  byAddress.set(address, entry);
1156
1273
  }
1157
1274
  return entry;
1158
1275
  };
1159
- const resolveAddress = (id, address) => boundaryFor(address).component;
1276
+ const resolveAddress = (id, address) => boundaryFor(address, id).component;
1160
1277
  resolveServerComponent = resolveAddress;
1161
1278
  const versions = new Map();
1162
1279
  const bump = frameId => {
@@ -1171,7 +1288,7 @@ function createServerComponentHandler({
1171
1288
  const address = frameAddress(info.id, info.args);
1172
1289
  byAddress.set(address, {
1173
1290
  frameId: info.id,
1174
- component: hit
1291
+ component: brand(hit, info.id, info.id)
1175
1292
  });
1176
1293
  }
1177
1294
  return hit;
@@ -1185,11 +1302,11 @@ function createServerComponentHandler({
1185
1302
  if (adopted) {
1186
1303
  entry = {
1187
1304
  frameId: ctx.id,
1188
- component: adopted
1305
+ component: brand(adopted, ctx.id, ctx.id)
1189
1306
  };
1190
1307
  byAddress.set(address, entry);
1191
1308
  } else {
1192
- entry = boundaryFor(address);
1309
+ entry = boundaryFor(address, ctx.id);
1193
1310
  }
1194
1311
  }
1195
1312
  if (response.headers.has(SINGLE_FLIGHT_HEADER)) {
@@ -1214,7 +1331,7 @@ function createServerComponentHandler({
1214
1331
  if (!byAddress.has(address)) {
1215
1332
  byAddress.set(address, {
1216
1333
  frameId: functionId,
1217
- component,
1334
+ component: brand(component, functionId, functionId),
1218
1335
  address
1219
1336
  });
1220
1337
  }
@@ -1346,6 +1463,15 @@ function gatherClaims(el, registry) {
1346
1463
  if (el.hasAttribute(FRAME_ID_ATTR)) return;
1347
1464
  for (let c = el.firstElementChild; c; c = c.nextElementSibling) gatherClaims(c, registry);
1348
1465
  }
1466
+ function hasPendingFragment(existing) {
1467
+ for (const n of existing) {
1468
+ if (n.nodeType !== 1) continue;
1469
+ const el = n;
1470
+ if (el.tagName === "TEMPLATE" && el.id.startsWith("pl-")) return true;
1471
+ if (el.querySelector?.('template[id^="pl-"]')) return true;
1472
+ }
1473
+ return false;
1474
+ }
1349
1475
  function claimRender(prefix, existing, render) {
1350
1476
  const sc = sharedConfig;
1351
1477
  if (!sc.getNextContextId) return render();
@@ -1354,10 +1480,11 @@ function claimRender(prefix, existing, render) {
1354
1480
  if (n.nodeType !== 1) continue;
1355
1481
  gatherClaims(n, registry);
1356
1482
  }
1357
- if (!registry.size) return render();
1483
+ if (!registry.size && !hasPendingFragment(existing)) return render();
1358
1484
  const prevRegistry = sc.registry;
1359
1485
  const prevHydrating = sc.hydrating;
1360
1486
  const prevClaimRoots = sc.claimRoots;
1487
+ if (prevRegistry) for (const key of registry.keys()) prevRegistry.delete(key);
1361
1488
  sc.registry = registry;
1362
1489
  sc.hydrating = true;
1363
1490
  sc.claimRoots = existing;
@@ -1371,6 +1498,19 @@ function claimRender(prefix, existing, render) {
1371
1498
  sc.claimRoots = prevClaimRoots;
1372
1499
  }
1373
1500
  }
1501
+ function liveSlotProps(initial, ctx) {
1502
+ const [args, setArgs] = createSignal(initial);
1503
+ ctx.onUpdate(next => setArgs(() => next));
1504
+ return new Proxy({}, {
1505
+ get: (_, key) => args()[key],
1506
+ has: (_, key) => key in args(),
1507
+ ownKeys: () => Reflect.ownKeys(args()),
1508
+ getOwnPropertyDescriptor: (_, key) => key in args() ? {
1509
+ enumerable: true,
1510
+ configurable: true
1511
+ } : undefined
1512
+ });
1513
+ }
1374
1514
  function isReactiveContent(value) {
1375
1515
  if (typeof value === "function") return true;
1376
1516
  if (Array.isArray(value)) {
@@ -1401,7 +1541,10 @@ function slotsFor(props) {
1401
1541
  };
1402
1542
  const evaluate = () => {
1403
1543
  const v = props[prop];
1404
- return typeof v === "function" && ctx && ctx.invoked ? v(slotProps) : v;
1544
+ if (typeof v === "function" && ctx && ctx.invoked) {
1545
+ return v(ctx.onUpdate ? liveSlotProps(slotProps, ctx) : slotProps);
1546
+ }
1547
+ return v;
1405
1548
  };
1406
1549
  const adopted = !!(ctx && ctx.frame && ctx.adopted && ctx.existing && ctx.existing.length);
1407
1550
  const prefix = ctx && ctx.frame ? `sc-${ctx.frame}-${ctx.key}-` : "";