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

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.
@@ -139,7 +139,7 @@ function createJSONSerializer({
139
139
 
140
140
  const HEAD_ELIGIBLE_TAGS = new Set(["title", "meta", "link", "style", "script", "base"]);
141
141
  const HEAD_ATTR_NAME = /^[a-zA-Z_][a-zA-Z0-9_:.-]*$/;
142
- const RESOURCE_LINK_RELS = new Set(["preload", "modulepreload", "prefetch", "preconnect", "dns-prefetch", "icon", "stylesheet"]);
142
+ const RESOURCE_LINK_RELS = new Set(["preload", "modulepreload", "prefetch", "preconnect", "dns-prefetch", "stylesheet"]);
143
143
  const RESOURCE_QUALIFIERS = ["as", "crossorigin", "type", "media", "imagesrcset", "imagesizes"];
144
144
  const STYLESHEET_FETCH_META = new Set(["crossorigin", "integrity", "referrerpolicy", "fetchpriority"]);
145
145
  function evalHeadValue(v) {
@@ -183,12 +183,14 @@ function replaceableIdentity(tag, props, key, unique) {
183
183
  if (tag === "meta" && props.charset != null) return "charset";
184
184
  if (key != null) return tag + ":key:" + key;
185
185
  if (tag === "meta") {
186
- if (props.name != null) return "meta:name:" + props.name;
187
- if (props.property != null) return "meta:property:" + props.property;
188
- if (props["http-equiv"] != null) return "meta:http-equiv:" + props["http-equiv"];
186
+ for (const ns of ["name", "property", "http-equiv"]) if (props[ns] != null) return "meta:" + ns + ":" + props[ns] + (props.media != null ? ":media=" + props.media : "");
189
187
  return unique;
190
188
  }
191
- if (tag === "link") return "link:" + (props.rel || "") + ":" + (props.href || "");
189
+ if (tag === "link") {
190
+ const rel = props.rel || "";
191
+ if (rel === "icon" || rel === "apple-touch-icon") return "link:" + rel + (props.sizes != null ? ":sizes=" + props.sizes : "") + (props.type != null ? ":type=" + props.type : "");
192
+ return "link:" + rel + ":" + (props.href || "");
193
+ }
192
194
  return unique;
193
195
  }
194
196
  function resolveHead(groups) {
@@ -380,6 +382,15 @@ function createHeadRegistry() {
380
382
  }
381
383
  function registerHeadTags(registry, context, tracking, emitResource, nonce, tags) {
382
384
  const boundary = context._currentBoundaryId || "";
385
+ if (typeof tags === "function") {
386
+ registry.pending.push({
387
+ boundary,
388
+ list: tags,
389
+ resource: (desc, rel) => emitHeadResource(registry, context, tracking, emitResource, nonce, desc, rel)
390
+ });
391
+ return;
392
+ }
393
+ if (!Array.isArray(tags)) tags = [tags];
383
394
  let replaceable = null;
384
395
  for (let i = 0; i < tags.length; i++) {
385
396
  const desc = tags[i];
@@ -467,9 +478,39 @@ function commitHeadBoundary(registry, boundary, isPendingFragment) {
467
478
  keep.push(reg);
468
479
  continue;
469
480
  }
481
+ let descs = reg.tags;
482
+ if (reg.list) {
483
+ let resolved;
484
+ try {
485
+ resolved = reg.list();
486
+ } catch (err) {
487
+ console.warn(`useHead: error evaluating head group membership`, err);
488
+ continue;
489
+ }
490
+ if (!Array.isArray(resolved)) resolved = [resolved];
491
+ descs = [];
492
+ for (let j = 0; j < resolved.length; j++) {
493
+ const desc = resolved[j];
494
+ if (!desc || !HEAD_ELIGIBLE_TAGS.has(desc.tag)) {
495
+ console.warn(`useHead: ignoring non-head tag`, desc);
496
+ continue;
497
+ }
498
+ const cls = classifyHeadTag(desc);
499
+ if (cls.resource) {
500
+ reg.resource(desc, cls.rel);
501
+ } else {
502
+ descs.push(cls.rel !== undefined ? {
503
+ tag: desc.tag,
504
+ props: desc.props,
505
+ key: desc.key,
506
+ rel: cls.rel
507
+ } : desc);
508
+ }
509
+ }
510
+ }
470
511
  const tags = [];
471
- for (let j = 0; j < reg.tags.length; j++) {
472
- const desc = reg.tags[j];
512
+ for (let j = 0; j < descs.length; j++) {
513
+ const desc = descs[j];
473
514
  let props, key;
474
515
  try {
475
516
  props = evalHeadProps(desc.props || {}, desc.rel !== undefined ? {
@@ -612,7 +653,7 @@ function renderHeadTagMarkup(tag, props, identity, nonce) {
612
653
  if (tag === "script") body = body.replace(/<\/(script)/gi, "<\\/$1");else if (tag === "style") body = escapeStyleContent(body);else body = escape(body);
613
654
  return `<${tag}${attrs}>${body}</${tag}>`;
614
655
  }
615
- const REPLACE_SCRIPT = `function $df(e){return _$HY.f?_$HY.f(e):$dfr(e)}function $dfr(e,n,o,t){if(!(n=document.getElementById(e)))return 0;if(!(o=document.getElementById("pl-"+e)))return(_$HY.dq=_$HY.dq||{})[e]=1,0;for(;o&&(8!==o.nodeType||o.nodeValue!=="pl-"+e);)t=o.nextSibling,o.remove(),o=t;t=o.parentNode,o.replaceWith(n.content),n.remove(),_$HY.fe(e,t),_$HY.hp&&_$HY.hp[e]&&($dh(_$HY.hp[e]),delete _$HY.hp[e]),$dfd();return 1}function $dfl(e,o,n){if(!(o=document.getElementById("pl-"+e)))return(_$HY.dlq=_$HY.dlq||{})[e]=1,0;if(o._$fl)return 1;for(n=o.nextSibling;n;){if(8===n.nodeType&&n.nodeValue==="pl-"+e){o.parentNode&&o.parentNode.insertBefore(o.content.cloneNode(!0),n),o._$fl=1,$dfd();return 1}n=n.nextSibling}return 0}function $dflj(e,i){for(i=0;i<e.length;i++)$dfl(e[i])}function $dfd(e,i){if(e=_$HY.dq){_$HY.dq=0;for(i in e)$df(i)}if(e=_$HY.dlq){_$HY.dlq=0;for(i in e)$dfl(i)}}function $dfs(e,c,d){(_$HY.sc=_$HY.sc||{})[e]=c,d&&((_$HY.sd=_$HY.sd||{})[e]=1)}function $dfg(e,g,i,k){if(!(g=_$HY.sg&&_$HY.sg[e]))return;for(i=0;i<g.length;i++)if(_$HY.sc&&_$HY.sc[g[i]]>0)return;for(i=0;i<g.length;i++)k=g[i],delete _$HY.sg[k],$df(k)}function $dfc(e){if(--_$HY.sc[e]<=0){delete _$HY.sc[e],_$HY.sg&&_$HY.sg[e]?$dfg(e):!(_$HY.sd&&_$HY.sd[e])&&$df(e);_$HY.sd&&delete _$HY.sd[e]}}function $dfj(e,i,n){for(i=0;i<e.length;i++)if(_$HY.sc&&_$HY.sc[e[i]]>0){for(n=0;n<e.length;n++)(_$HY.sg=_$HY.sg||{})[e[n]]=e;return}for(i=0;i<e.length;i++)$df(e[i])}`;
656
+ const REPLACE_SCRIPT = `function $df(e){return _$HY.f?_$HY.f(e):$dfr(e)}function $dfr(e,n,o,t){if(!(n=document.getElementById(e)))return 0;if(!(o=document.getElementById("pl-"+e)))return(_$HY.dq=_$HY.dq||{})[e]=1,0;for(;o&&(8!==o.nodeType||o.nodeValue!=="pl-"+e);)t=o.nextSibling,o.remove(),o=t;t=o.parentNode,o.replaceWith(n.content),n.remove(),(_$HY.v=_$HY.v||{})[e]=1,_$HY.fe(e,t),_$HY.hp&&_$HY.hp[e]&&($dh(_$HY.hp[e]),delete _$HY.hp[e]),$dfd();return 1}function $dfl(e,o,n){if(!(o=document.getElementById("pl-"+e)))return(_$HY.dlq=_$HY.dlq||{})[e]=1,0;if(o._$fl)return 1;for(n=o.nextSibling;n;){if(8===n.nodeType&&n.nodeValue==="pl-"+e){o.parentNode&&o.parentNode.insertBefore(o.content.cloneNode(!0),n),o._$fl=1,$dfd();return 1}n=n.nextSibling}return 0}function $dflj(e,i){for(i=0;i<e.length;i++)$dfl(e[i])}function $dfd(e,i){if(e=_$HY.dq){_$HY.dq=0;for(i in e)$df(i)}if(e=_$HY.dlq){_$HY.dlq=0;for(i in e)$dfl(i)}}function $dfs(e,c,d){(_$HY.sc=_$HY.sc||{})[e]=c,d&&((_$HY.sd=_$HY.sd||{})[e]=1)}function $dfg(e,g,i,k){if(!(g=_$HY.sg&&_$HY.sg[e]))return;for(i=0;i<g.length;i++)if(_$HY.sc&&_$HY.sc[g[i]]>0)return;for(i=0;i<g.length;i++)k=g[i],delete _$HY.sg[k],$df(k)}function $dfc(e){if(--_$HY.sc[e]<=0){delete _$HY.sc[e],_$HY.sg&&_$HY.sg[e]?$dfg(e):!(_$HY.sd&&_$HY.sd[e])&&$df(e);_$HY.sd&&delete _$HY.sd[e]}}function $dfj(e,i,n){for(i=0;i<e.length;i++)if(_$HY.sc&&_$HY.sc[e[i]]>0){for(n=0;n<e.length;n++)(_$HY.sg=_$HY.sg||{})[e[n]]=e;return}for(i=0;i<e.length;i++)$df(e[i])}`;
616
657
  const HEAD_SCRIPT = `function $dha(o,i,e,n){for(i=0;i<o.length;i++)e=o[i],"t"==e[0]?((n=document.querySelector("title"))||(n=document.createElement("title"),document.head.appendChild(n)),n.textContent=e[1],n.setAttribute("data-dh","title")):"r"==e[0]?$dhr(e[1]):(n=document.createElement(e[2]),Object.keys(e[3]).forEach(function(a){n.setAttribute(a,e[3][a])}),null!=e[4]&&(n.textContent=e[4]),n.setAttribute("data-dh",e[1]),document.head.appendChild(n))}function $dhr(v,l,i){for(l=document.head.querySelectorAll("[data-dh]"),i=0;i<l.length;i++)l[i].getAttribute("data-dh")==v&&l[i].remove()}function $dh(o){_$HY.h?_$HY.h(o):$dha(o)}`;
617
658
  function renderToStream(code, options = {}) {
618
659
  let {
@@ -625,6 +666,41 @@ function renderToStream(code, options = {}) {
625
666
  onHead
626
667
  } = options;
627
668
  let dispose;
669
+ let dead = false;
670
+ const abandon = () => {
671
+ if (dead) return;
672
+ dead = true;
673
+ completed = true;
674
+ buffer = {
675
+ write() {}
676
+ };
677
+ writable = {
678
+ end() {}
679
+ };
680
+ if (dispose) {
681
+ const d = dispose;
682
+ dispose = () => {};
683
+ d();
684
+ }
685
+ };
686
+ const guardSink = w => ({
687
+ write(payload) {
688
+ if (dead) return;
689
+ try {
690
+ w.write(payload);
691
+ } catch (_) {
692
+ abandon();
693
+ }
694
+ },
695
+ end() {
696
+ if (dead) return;
697
+ try {
698
+ w.end();
699
+ } catch (_) {
700
+ abandon();
701
+ }
702
+ }
703
+ });
628
704
  const blockingPromises = new Set();
629
705
  let headerEmitted = false;
630
706
  const pushTask = task => {
@@ -968,8 +1044,8 @@ function renderToStream(code, options = {}) {
968
1044
  }
969
1045
  function doShell() {
970
1046
  if (shellCompleted) return;
971
- if (!resolveRootHoles()) return;
972
1047
  solidJs.sharedConfig.context = context;
1048
+ if (!resolveRootHoles()) return;
973
1049
  const assetsHtml = resolveAssetsHtml(context.assets);
974
1050
  headStyles = new Set();
975
1051
  for (const url of tracking.emittedAssets) {
@@ -1026,14 +1102,24 @@ function renderToStream(code, options = {}) {
1026
1102
  function flush() {
1027
1103
  allSettled(blockingPromises).then(() => {
1028
1104
  scheduleFlush(() => {
1105
+ if (dead) return resolve();
1029
1106
  doShell();
1030
1107
  if (!shellCompleted) return flush();
1031
1108
  const encoder = new TextEncoder();
1032
1109
  const writer = w.getWriter();
1033
1110
  let pendingWrites = Promise.resolve();
1111
+ let ended = false;
1112
+ const failed = () => {
1113
+ if (!ended) {
1114
+ abandon();
1115
+ resolve();
1116
+ }
1117
+ };
1118
+ writer.closed && writer.closed.catch(failed);
1034
1119
  writable = {
1035
1120
  end() {
1036
1121
  pendingWrites.then(() => {
1122
+ ended = true;
1037
1123
  writer.releaseLock();
1038
1124
  w.close().catch(() => {});
1039
1125
  resolve();
@@ -1042,7 +1128,7 @@ function renderToStream(code, options = {}) {
1042
1128
  };
1043
1129
  buffer = {
1044
1130
  write(payload) {
1045
- pendingWrites = pendingWrites.then(() => writer.write(encoder.encode(payload))).catch(() => {});
1131
+ pendingWrites = pendingWrites.then(() => writer.write(encoder.encode(payload))).catch(failed);
1046
1132
  }
1047
1133
  };
1048
1134
  buffer.write(tmp);
@@ -1085,9 +1171,10 @@ function renderToStream(code, options = {}) {
1085
1171
  function flush() {
1086
1172
  allSettled(blockingPromises).then(() => {
1087
1173
  scheduleFlush(() => {
1174
+ if (dead) return;
1088
1175
  doShell();
1089
1176
  if (!shellCompleted) return flush();
1090
- buffer = writable = w;
1177
+ buffer = writable = guardSink(w);
1091
1178
  buffer.write(tmp);
1092
1179
  firstFlushed = true;
1093
1180
  if (completed) {
@@ -1724,6 +1811,10 @@ function isFrameStreamResponse(response) {
1724
1811
  const SERVER_COMPONENT = /*#__PURE__*/Symbol.for("dom-expressions.server-component");
1725
1812
  const SERVER_COMPONENT_SOURCE = /*#__PURE__*/Symbol.for("dom-expressions.server-component-source");
1726
1813
  const SERVER_COMPONENT_ADDRESS = /*#__PURE__*/Symbol.for("dom-expressions.server-component-address");
1814
+ let serverComponentRegistryExpr;
1815
+ function setServerComponentBootstrap(resolve) {
1816
+ serverComponentRegistryExpr = resolve;
1817
+ }
1727
1818
  function parseServerComponent(value, ctx) {
1728
1819
  return {
1729
1820
  id: ctx.parse(value[SERVER_COMPONENT]),
@@ -1746,7 +1837,8 @@ const ServerComponentPlugin = /*#__PURE__*/seroval.createPlugin({
1746
1837
  stream: parseServerComponent
1747
1838
  },
1748
1839
  serialize(node, ctx) {
1749
- return "self._$SC.r(" + ctx.serialize(node.id) + "," + ctx.serialize(node.address) + ")";
1840
+ const registry = serverComponentRegistryExpr ? serverComponentRegistryExpr(ctx) : "self._$SC";
1841
+ return registry + ".r(" + ctx.serialize(node.id) + "," + ctx.serialize(node.address) + ")";
1750
1842
  },
1751
1843
  deserialize(node, ctx) {
1752
1844
  const id = ctx.deserialize(node.id);
@@ -1775,6 +1867,14 @@ function serverOwned(render) {
1775
1867
  function serverComponentScope(render) {
1776
1868
  return solidJs.runInServerComponentScope ? solidJs.runInServerComponentScope(render) : render();
1777
1869
  }
1870
+ const SERVER_COMPONENT_BOOTSTRAP_EXPR = "(self._$SC||(self._$SC={c:{},a:{},r(i,a){a&&(this.a[a]=i,this.reg&&this.reg(a,i));return this.c[i]||(this.c[i]=(p,b)=>self._$SC.impl(i,p,b))}}))";
1871
+ const bootstrappedScripts = new WeakSet();
1872
+ setServerComponentBootstrap(ctx => {
1873
+ if (bootstrappedScripts.has(ctx)) return "self._$SC";
1874
+ bootstrappedScripts.add(ctx);
1875
+ return SERVER_COMPONENT_BOOTSTRAP_EXPR;
1876
+ });
1877
+ const SERVER_COMPONENT_BOOTSTRAP = SERVER_COMPONENT_BOOTSTRAP_EXPR + ";";
1778
1878
  function createFrameSink(emit, frame) {
1779
1879
  const {
1780
1880
  id,
@@ -2115,24 +2215,19 @@ function createDocumentSlotProps(clientProps, frameId) {
2115
2215
  const unused = regions.filter(r => !r.used);
2116
2216
  if (solidJs.sharedConfig.context) {
2117
2217
  const args = {};
2118
- let any = false;
2119
2218
  for (const key of Object.keys(vals)) {
2120
2219
  const value = vals[key];
2121
2220
  const region = regions.find(r => r.key === key);
2122
2221
  if (region) {
2123
- if (!region.used) {
2124
- args[key] = {
2125
- $frame: region.childId
2126
- };
2127
- any = true;
2128
- }
2222
+ args[key] = {
2223
+ $frame: region.childId
2224
+ };
2129
2225
  continue;
2130
2226
  }
2131
2227
  if (isServerContent(value)) continue;
2132
2228
  args[key] = value;
2133
- any = true;
2134
2229
  }
2135
- if (any) solidJs.sharedConfig.context.serialize(`sc:slot:${frameId}:${occurrence}`, args);
2230
+ solidJs.sharedConfig.context.serialize(`sc:slot:${frameId}:${occurrence}`, args);
2136
2231
  for (const region of unused) {
2137
2232
  region.locked = true;
2138
2233
  solidJs.sharedConfig.context.serialize(`sc:region:${region.childId}`, resolveRegionHtml(solidJs.sharedConfig.context, region.value));
@@ -2187,7 +2282,6 @@ function resolveRegionHtml(ctx, node) {
2187
2282
  return out;
2188
2283
  });
2189
2284
  }
2190
- const SERVER_COMPONENT_BOOTSTRAP = "self._$SC={c:{},a:{},r(i,a){a&&(this.a[a]=i,this.reg&&this.reg(a,i));return this.c[i]||(this.c[i]=(p)=>self._$SC.impl(i,p))}};";
2191
2285
  function isServerContent(value) {
2192
2286
  if (value && typeof value === "object") {
2193
2287
  if ("t" in value) return true;
@@ -137,7 +137,7 @@ function createJSONSerializer({
137
137
 
138
138
  const HEAD_ELIGIBLE_TAGS = new Set(["title", "meta", "link", "style", "script", "base"]);
139
139
  const HEAD_ATTR_NAME = /^[a-zA-Z_][a-zA-Z0-9_:.-]*$/;
140
- const RESOURCE_LINK_RELS = new Set(["preload", "modulepreload", "prefetch", "preconnect", "dns-prefetch", "icon", "stylesheet"]);
140
+ const RESOURCE_LINK_RELS = new Set(["preload", "modulepreload", "prefetch", "preconnect", "dns-prefetch", "stylesheet"]);
141
141
  const RESOURCE_QUALIFIERS = ["as", "crossorigin", "type", "media", "imagesrcset", "imagesizes"];
142
142
  const STYLESHEET_FETCH_META = new Set(["crossorigin", "integrity", "referrerpolicy", "fetchpriority"]);
143
143
  function evalHeadValue(v) {
@@ -181,12 +181,14 @@ function replaceableIdentity(tag, props, key, unique) {
181
181
  if (tag === "meta" && props.charset != null) return "charset";
182
182
  if (key != null) return tag + ":key:" + key;
183
183
  if (tag === "meta") {
184
- if (props.name != null) return "meta:name:" + props.name;
185
- if (props.property != null) return "meta:property:" + props.property;
186
- if (props["http-equiv"] != null) return "meta:http-equiv:" + props["http-equiv"];
184
+ for (const ns of ["name", "property", "http-equiv"]) if (props[ns] != null) return "meta:" + ns + ":" + props[ns] + (props.media != null ? ":media=" + props.media : "");
187
185
  return unique;
188
186
  }
189
- if (tag === "link") return "link:" + (props.rel || "") + ":" + (props.href || "");
187
+ if (tag === "link") {
188
+ const rel = props.rel || "";
189
+ if (rel === "icon" || rel === "apple-touch-icon") return "link:" + rel + (props.sizes != null ? ":sizes=" + props.sizes : "") + (props.type != null ? ":type=" + props.type : "");
190
+ return "link:" + rel + ":" + (props.href || "");
191
+ }
190
192
  return unique;
191
193
  }
192
194
  function resolveHead(groups) {
@@ -378,6 +380,15 @@ function createHeadRegistry() {
378
380
  }
379
381
  function registerHeadTags(registry, context, tracking, emitResource, nonce, tags) {
380
382
  const boundary = context._currentBoundaryId || "";
383
+ if (typeof tags === "function") {
384
+ registry.pending.push({
385
+ boundary,
386
+ list: tags,
387
+ resource: (desc, rel) => emitHeadResource(registry, context, tracking, emitResource, nonce, desc, rel)
388
+ });
389
+ return;
390
+ }
391
+ if (!Array.isArray(tags)) tags = [tags];
381
392
  let replaceable = null;
382
393
  for (let i = 0; i < tags.length; i++) {
383
394
  const desc = tags[i];
@@ -465,9 +476,39 @@ function commitHeadBoundary(registry, boundary, isPendingFragment) {
465
476
  keep.push(reg);
466
477
  continue;
467
478
  }
479
+ let descs = reg.tags;
480
+ if (reg.list) {
481
+ let resolved;
482
+ try {
483
+ resolved = reg.list();
484
+ } catch (err) {
485
+ console.warn(`useHead: error evaluating head group membership`, err);
486
+ continue;
487
+ }
488
+ if (!Array.isArray(resolved)) resolved = [resolved];
489
+ descs = [];
490
+ for (let j = 0; j < resolved.length; j++) {
491
+ const desc = resolved[j];
492
+ if (!desc || !HEAD_ELIGIBLE_TAGS.has(desc.tag)) {
493
+ console.warn(`useHead: ignoring non-head tag`, desc);
494
+ continue;
495
+ }
496
+ const cls = classifyHeadTag(desc);
497
+ if (cls.resource) {
498
+ reg.resource(desc, cls.rel);
499
+ } else {
500
+ descs.push(cls.rel !== undefined ? {
501
+ tag: desc.tag,
502
+ props: desc.props,
503
+ key: desc.key,
504
+ rel: cls.rel
505
+ } : desc);
506
+ }
507
+ }
508
+ }
468
509
  const tags = [];
469
- for (let j = 0; j < reg.tags.length; j++) {
470
- const desc = reg.tags[j];
510
+ for (let j = 0; j < descs.length; j++) {
511
+ const desc = descs[j];
471
512
  let props, key;
472
513
  try {
473
514
  props = evalHeadProps(desc.props || {}, desc.rel !== undefined ? {
@@ -610,7 +651,7 @@ function renderHeadTagMarkup(tag, props, identity, nonce) {
610
651
  if (tag === "script") body = body.replace(/<\/(script)/gi, "<\\/$1");else if (tag === "style") body = escapeStyleContent(body);else body = escape(body);
611
652
  return `<${tag}${attrs}>${body}</${tag}>`;
612
653
  }
613
- const REPLACE_SCRIPT = `function $df(e){return _$HY.f?_$HY.f(e):$dfr(e)}function $dfr(e,n,o,t){if(!(n=document.getElementById(e)))return 0;if(!(o=document.getElementById("pl-"+e)))return(_$HY.dq=_$HY.dq||{})[e]=1,0;for(;o&&(8!==o.nodeType||o.nodeValue!=="pl-"+e);)t=o.nextSibling,o.remove(),o=t;t=o.parentNode,o.replaceWith(n.content),n.remove(),_$HY.fe(e,t),_$HY.hp&&_$HY.hp[e]&&($dh(_$HY.hp[e]),delete _$HY.hp[e]),$dfd();return 1}function $dfl(e,o,n){if(!(o=document.getElementById("pl-"+e)))return(_$HY.dlq=_$HY.dlq||{})[e]=1,0;if(o._$fl)return 1;for(n=o.nextSibling;n;){if(8===n.nodeType&&n.nodeValue==="pl-"+e){o.parentNode&&o.parentNode.insertBefore(o.content.cloneNode(!0),n),o._$fl=1,$dfd();return 1}n=n.nextSibling}return 0}function $dflj(e,i){for(i=0;i<e.length;i++)$dfl(e[i])}function $dfd(e,i){if(e=_$HY.dq){_$HY.dq=0;for(i in e)$df(i)}if(e=_$HY.dlq){_$HY.dlq=0;for(i in e)$dfl(i)}}function $dfs(e,c,d){(_$HY.sc=_$HY.sc||{})[e]=c,d&&((_$HY.sd=_$HY.sd||{})[e]=1)}function $dfg(e,g,i,k){if(!(g=_$HY.sg&&_$HY.sg[e]))return;for(i=0;i<g.length;i++)if(_$HY.sc&&_$HY.sc[g[i]]>0)return;for(i=0;i<g.length;i++)k=g[i],delete _$HY.sg[k],$df(k)}function $dfc(e){if(--_$HY.sc[e]<=0){delete _$HY.sc[e],_$HY.sg&&_$HY.sg[e]?$dfg(e):!(_$HY.sd&&_$HY.sd[e])&&$df(e);_$HY.sd&&delete _$HY.sd[e]}}function $dfj(e,i,n){for(i=0;i<e.length;i++)if(_$HY.sc&&_$HY.sc[e[i]]>0){for(n=0;n<e.length;n++)(_$HY.sg=_$HY.sg||{})[e[n]]=e;return}for(i=0;i<e.length;i++)$df(e[i])}`;
654
+ const REPLACE_SCRIPT = `function $df(e){return _$HY.f?_$HY.f(e):$dfr(e)}function $dfr(e,n,o,t){if(!(n=document.getElementById(e)))return 0;if(!(o=document.getElementById("pl-"+e)))return(_$HY.dq=_$HY.dq||{})[e]=1,0;for(;o&&(8!==o.nodeType||o.nodeValue!=="pl-"+e);)t=o.nextSibling,o.remove(),o=t;t=o.parentNode,o.replaceWith(n.content),n.remove(),(_$HY.v=_$HY.v||{})[e]=1,_$HY.fe(e,t),_$HY.hp&&_$HY.hp[e]&&($dh(_$HY.hp[e]),delete _$HY.hp[e]),$dfd();return 1}function $dfl(e,o,n){if(!(o=document.getElementById("pl-"+e)))return(_$HY.dlq=_$HY.dlq||{})[e]=1,0;if(o._$fl)return 1;for(n=o.nextSibling;n;){if(8===n.nodeType&&n.nodeValue==="pl-"+e){o.parentNode&&o.parentNode.insertBefore(o.content.cloneNode(!0),n),o._$fl=1,$dfd();return 1}n=n.nextSibling}return 0}function $dflj(e,i){for(i=0;i<e.length;i++)$dfl(e[i])}function $dfd(e,i){if(e=_$HY.dq){_$HY.dq=0;for(i in e)$df(i)}if(e=_$HY.dlq){_$HY.dlq=0;for(i in e)$dfl(i)}}function $dfs(e,c,d){(_$HY.sc=_$HY.sc||{})[e]=c,d&&((_$HY.sd=_$HY.sd||{})[e]=1)}function $dfg(e,g,i,k){if(!(g=_$HY.sg&&_$HY.sg[e]))return;for(i=0;i<g.length;i++)if(_$HY.sc&&_$HY.sc[g[i]]>0)return;for(i=0;i<g.length;i++)k=g[i],delete _$HY.sg[k],$df(k)}function $dfc(e){if(--_$HY.sc[e]<=0){delete _$HY.sc[e],_$HY.sg&&_$HY.sg[e]?$dfg(e):!(_$HY.sd&&_$HY.sd[e])&&$df(e);_$HY.sd&&delete _$HY.sd[e]}}function $dfj(e,i,n){for(i=0;i<e.length;i++)if(_$HY.sc&&_$HY.sc[e[i]]>0){for(n=0;n<e.length;n++)(_$HY.sg=_$HY.sg||{})[e[n]]=e;return}for(i=0;i<e.length;i++)$df(e[i])}`;
614
655
  const HEAD_SCRIPT = `function $dha(o,i,e,n){for(i=0;i<o.length;i++)e=o[i],"t"==e[0]?((n=document.querySelector("title"))||(n=document.createElement("title"),document.head.appendChild(n)),n.textContent=e[1],n.setAttribute("data-dh","title")):"r"==e[0]?$dhr(e[1]):(n=document.createElement(e[2]),Object.keys(e[3]).forEach(function(a){n.setAttribute(a,e[3][a])}),null!=e[4]&&(n.textContent=e[4]),n.setAttribute("data-dh",e[1]),document.head.appendChild(n))}function $dhr(v,l,i){for(l=document.head.querySelectorAll("[data-dh]"),i=0;i<l.length;i++)l[i].getAttribute("data-dh")==v&&l[i].remove()}function $dh(o){_$HY.h?_$HY.h(o):$dha(o)}`;
615
656
  function renderToStream(code, options = {}) {
616
657
  let {
@@ -623,6 +664,41 @@ function renderToStream(code, options = {}) {
623
664
  onHead
624
665
  } = options;
625
666
  let dispose;
667
+ let dead = false;
668
+ const abandon = () => {
669
+ if (dead) return;
670
+ dead = true;
671
+ completed = true;
672
+ buffer = {
673
+ write() {}
674
+ };
675
+ writable = {
676
+ end() {}
677
+ };
678
+ if (dispose) {
679
+ const d = dispose;
680
+ dispose = () => {};
681
+ d();
682
+ }
683
+ };
684
+ const guardSink = w => ({
685
+ write(payload) {
686
+ if (dead) return;
687
+ try {
688
+ w.write(payload);
689
+ } catch (_) {
690
+ abandon();
691
+ }
692
+ },
693
+ end() {
694
+ if (dead) return;
695
+ try {
696
+ w.end();
697
+ } catch (_) {
698
+ abandon();
699
+ }
700
+ }
701
+ });
626
702
  const blockingPromises = new Set();
627
703
  let headerEmitted = false;
628
704
  const pushTask = task => {
@@ -966,8 +1042,8 @@ function renderToStream(code, options = {}) {
966
1042
  }
967
1043
  function doShell() {
968
1044
  if (shellCompleted) return;
969
- if (!resolveRootHoles()) return;
970
1045
  sharedConfig.context = context;
1046
+ if (!resolveRootHoles()) return;
971
1047
  const assetsHtml = resolveAssetsHtml(context.assets);
972
1048
  headStyles = new Set();
973
1049
  for (const url of tracking.emittedAssets) {
@@ -1024,14 +1100,24 @@ function renderToStream(code, options = {}) {
1024
1100
  function flush() {
1025
1101
  allSettled(blockingPromises).then(() => {
1026
1102
  scheduleFlush(() => {
1103
+ if (dead) return resolve();
1027
1104
  doShell();
1028
1105
  if (!shellCompleted) return flush();
1029
1106
  const encoder = new TextEncoder();
1030
1107
  const writer = w.getWriter();
1031
1108
  let pendingWrites = Promise.resolve();
1109
+ let ended = false;
1110
+ const failed = () => {
1111
+ if (!ended) {
1112
+ abandon();
1113
+ resolve();
1114
+ }
1115
+ };
1116
+ writer.closed && writer.closed.catch(failed);
1032
1117
  writable = {
1033
1118
  end() {
1034
1119
  pendingWrites.then(() => {
1120
+ ended = true;
1035
1121
  writer.releaseLock();
1036
1122
  w.close().catch(() => {});
1037
1123
  resolve();
@@ -1040,7 +1126,7 @@ function renderToStream(code, options = {}) {
1040
1126
  };
1041
1127
  buffer = {
1042
1128
  write(payload) {
1043
- pendingWrites = pendingWrites.then(() => writer.write(encoder.encode(payload))).catch(() => {});
1129
+ pendingWrites = pendingWrites.then(() => writer.write(encoder.encode(payload))).catch(failed);
1044
1130
  }
1045
1131
  };
1046
1132
  buffer.write(tmp);
@@ -1083,9 +1169,10 @@ function renderToStream(code, options = {}) {
1083
1169
  function flush() {
1084
1170
  allSettled(blockingPromises).then(() => {
1085
1171
  scheduleFlush(() => {
1172
+ if (dead) return;
1086
1173
  doShell();
1087
1174
  if (!shellCompleted) return flush();
1088
- buffer = writable = w;
1175
+ buffer = writable = guardSink(w);
1089
1176
  buffer.write(tmp);
1090
1177
  firstFlushed = true;
1091
1178
  if (completed) {
@@ -1722,6 +1809,10 @@ function isFrameStreamResponse(response) {
1722
1809
  const SERVER_COMPONENT = /*#__PURE__*/Symbol.for("dom-expressions.server-component");
1723
1810
  const SERVER_COMPONENT_SOURCE = /*#__PURE__*/Symbol.for("dom-expressions.server-component-source");
1724
1811
  const SERVER_COMPONENT_ADDRESS = /*#__PURE__*/Symbol.for("dom-expressions.server-component-address");
1812
+ let serverComponentRegistryExpr;
1813
+ function setServerComponentBootstrap(resolve) {
1814
+ serverComponentRegistryExpr = resolve;
1815
+ }
1725
1816
  function parseServerComponent(value, ctx) {
1726
1817
  return {
1727
1818
  id: ctx.parse(value[SERVER_COMPONENT]),
@@ -1744,7 +1835,8 @@ const ServerComponentPlugin = /*#__PURE__*/createPlugin({
1744
1835
  stream: parseServerComponent
1745
1836
  },
1746
1837
  serialize(node, ctx) {
1747
- return "self._$SC.r(" + ctx.serialize(node.id) + "," + ctx.serialize(node.address) + ")";
1838
+ const registry = serverComponentRegistryExpr ? serverComponentRegistryExpr(ctx) : "self._$SC";
1839
+ return registry + ".r(" + ctx.serialize(node.id) + "," + ctx.serialize(node.address) + ")";
1748
1840
  },
1749
1841
  deserialize(node, ctx) {
1750
1842
  const id = ctx.deserialize(node.id);
@@ -1773,6 +1865,14 @@ function serverOwned(render) {
1773
1865
  function serverComponentScope(render) {
1774
1866
  return runInServerComponentScope ? runInServerComponentScope(render) : render();
1775
1867
  }
1868
+ const SERVER_COMPONENT_BOOTSTRAP_EXPR = "(self._$SC||(self._$SC={c:{},a:{},r(i,a){a&&(this.a[a]=i,this.reg&&this.reg(a,i));return this.c[i]||(this.c[i]=(p,b)=>self._$SC.impl(i,p,b))}}))";
1869
+ const bootstrappedScripts = new WeakSet();
1870
+ setServerComponentBootstrap(ctx => {
1871
+ if (bootstrappedScripts.has(ctx)) return "self._$SC";
1872
+ bootstrappedScripts.add(ctx);
1873
+ return SERVER_COMPONENT_BOOTSTRAP_EXPR;
1874
+ });
1875
+ const SERVER_COMPONENT_BOOTSTRAP = SERVER_COMPONENT_BOOTSTRAP_EXPR + ";";
1776
1876
  function createFrameSink(emit, frame) {
1777
1877
  const {
1778
1878
  id,
@@ -2113,24 +2213,19 @@ function createDocumentSlotProps(clientProps, frameId) {
2113
2213
  const unused = regions.filter(r => !r.used);
2114
2214
  if (sharedConfig.context) {
2115
2215
  const args = {};
2116
- let any = false;
2117
2216
  for (const key of Object.keys(vals)) {
2118
2217
  const value = vals[key];
2119
2218
  const region = regions.find(r => r.key === key);
2120
2219
  if (region) {
2121
- if (!region.used) {
2122
- args[key] = {
2123
- $frame: region.childId
2124
- };
2125
- any = true;
2126
- }
2220
+ args[key] = {
2221
+ $frame: region.childId
2222
+ };
2127
2223
  continue;
2128
2224
  }
2129
2225
  if (isServerContent(value)) continue;
2130
2226
  args[key] = value;
2131
- any = true;
2132
2227
  }
2133
- if (any) sharedConfig.context.serialize(`sc:slot:${frameId}:${occurrence}`, args);
2228
+ sharedConfig.context.serialize(`sc:slot:${frameId}:${occurrence}`, args);
2134
2229
  for (const region of unused) {
2135
2230
  region.locked = true;
2136
2231
  sharedConfig.context.serialize(`sc:region:${region.childId}`, resolveRegionHtml(sharedConfig.context, region.value));
@@ -2185,7 +2280,6 @@ function resolveRegionHtml(ctx, node) {
2185
2280
  return out;
2186
2281
  });
2187
2282
  }
2188
- const SERVER_COMPONENT_BOOTSTRAP = "self._$SC={c:{},a:{},r(i,a){a&&(this.a[a]=i,this.reg&&this.reg(a,i));return this.c[i]||(this.c[i]=(p)=>self._$SC.impl(i,p))}};";
2189
2283
  function isServerContent(value) {
2190
2284
  if (value && typeof value === "object") {
2191
2285
  if ("t" in value) return true;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@solidjs/web",
3
3
  "description": "Solid's web runtime: client rendering, hydration, SSR, and DOM-specific control flow (Portal, Dynamic).",
4
- "version": "2.0.0-beta.30",
4
+ "version": "2.0.0-beta.31",
5
5
  "author": "Ryan Carniato",
6
6
  "license": "MIT",
7
7
  "homepage": "https://solidjs.com",
@@ -316,10 +316,10 @@
316
316
  "seroval-plugins": "~1.5.4"
317
317
  },
318
318
  "peerDependencies": {
319
- "solid-js": "^2.0.0-beta.30"
319
+ "solid-js": "^2.0.0-beta.31"
320
320
  },
321
321
  "devDependencies": {
322
- "solid-js": "2.0.0-beta.30"
322
+ "solid-js": "2.0.0-beta.31"
323
323
  },
324
324
  "scripts": {
325
325
  "build": "npm-run-all -nl build:clean types:copy-jsx build:js",
@@ -342,7 +342,6 @@
342
342
  "coverage": "vitest run --coverage",
343
343
  "bench": "vitest bench --run",
344
344
  "bench:server": "vitest bench --run --config vite.config.server-bench.mjs",
345
- "test-types": "npm-run-all -nl types test-types:tsc",
346
- "test-types:tsc": "tsc --project tsconfig.test.json"
345
+ "test-types": "tsc --project tsconfig.test.json"
347
346
  }
348
347
  }
package/types/client.d.ts CHANGED
@@ -143,12 +143,14 @@ export type HeadTag = {
143
143
  };
144
144
  /**
145
145
  * Registers head tags with the ambient head registry under the current
146
- * owner. An array is a group — one replacement set. Resolution is
147
- * last-committed group per identity; disposal restores the previous winner.
148
- * During hydration the server-flushed head state stays authoritative until
149
- * hydration completes. See docs/head-management-rfc.md.
146
+ * owner. An array is a group — one replacement set; a function is a
147
+ * reactive group whose membership is tracked and re-read on change.
148
+ * Resolution is last-committed group per identity (reactive updates keep
149
+ * the registration's original commit position); disposal restores the
150
+ * previous winner. During hydration the server-flushed head state stays
151
+ * authoritative until hydration completes. See docs/head-management-rfc.md.
150
152
  */
151
- export function useHead(tag: HeadTag | HeadTag[]): void;
153
+ export function useHead(tag: HeadTag | HeadTag[] | (() => HeadTag | HeadTag[])): void;
152
154
  export type AssetDescriptor =
153
155
  | { type: "style"; href: string; attrs?: Record<string, string> }
154
156
  | { type: "inline-style"; id: string; content?: string; attrs?: Record<string, string> }
@@ -5,13 +5,15 @@ export type { Slot } from "./server.js";
5
5
  export declare function getFrameHost(): any;
6
6
  /**
7
7
  * Installs the server-component transport policy on the server-function
8
- * client: boundary identity is the call's intrinsic (function, arguments)
9
- * address — per-args, exactly like the query cache, so a cached component
10
- * always mounts the boundary showing the call it was cached for. Repeat
11
- * calls for the same args resolve the identical component (refetches morph
12
- * in place, cache hits pass `dynamic`'s equals-gate); a source switching
13
- * args swaps boundaries, re-materialized instantly from the host's
14
- * retained state.
8
+ * client the identity split (DR-1): CONTENT is keyed by the call's
9
+ * intrinsic (function, arguments) address — per-args, exactly like the
10
+ * query cache, so a cached resolution always names the content it was
11
+ * cached for — while the MOUNT belongs to the call site. Every call of a
12
+ * function resolves a binding wrapping the same per-function component
13
+ * (the document placeholder), so `dynamic` keeps its instance across both
14
+ * refetches AND argument changes; the instance follows delivered addresses
15
+ * by re-binding its frame's pull, and a preload for unshown args only ever
16
+ * warms that address's resident store.
15
17
  *
16
18
  * Call once in the client entry (an explicit call — the package is
17
19
  * `sideEffects: false`, so a bare import would be tree-shaken away);
@@ -239,6 +239,18 @@ export interface FrameOptions {
239
239
  * for the framework-agnostic imperative swap (no reactive reveal).
240
240
  */
241
241
  reveal?(seam: { before: Node; fallback: Node[]; content: () => Node | DocumentFragment }): void;
242
+ /**
243
+ * Document-face record-race guard (adopt path only — solidjs/solid#2968).
244
+ * Nothing on the wire formally orders an occurrence's args-record data
245
+ * script before the event that triggers adoption, so a recordless
246
+ * occurrence is ambiguous while this returns true: the frame defers its
247
+ * mount one macrotask (all currently parsed scripts run first), calls
248
+ * `drainRecords`, and classifies with whatever is then resolvable. Return
249
+ * false once the document can run no further data scripts.
250
+ */
251
+ recordsPending?(): boolean;
252
+ /** Re-absorb the document's arrived-by-now records (idempotent per key). */
253
+ drainRecords?(): void;
242
254
  }
243
255
 
244
256
  /**
@@ -161,8 +161,11 @@ export {
161
161
  } from "./frame-transport.js";
162
162
 
163
163
  /**
164
- * Inline bootstrap for the document shell: installs the `self._$SC`
165
- * placeholder registry the hydration references resolve through; the client
166
- * upgrades it via `installServerComponents()`.
164
+ * Statement form of the `self._$SC` placeholder-registry bootstrap
165
+ * (idempotent first definition wins). No longer required in the document
166
+ * shell: each hydration script's first serialized server-component reference
167
+ * self-bootstraps the registry. Kept for integrations still installing it
168
+ * document-wide; the client upgrades the registry via
169
+ * `installServerComponents()`.
167
170
  */
168
171
  export const SERVER_COMPONENT_BOOTSTRAP: string;