@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.
package/dist/server.js CHANGED
@@ -100,6 +100,90 @@ function getLocalHeaderScript(id) {
100
100
  }
101
101
  Feature.RegExp;
102
102
 
103
+ const HEAD_ELIGIBLE_TAGS = new Set(["title", "meta", "link", "style", "script", "base"]);
104
+ const HEAD_ATTR_NAME = /^[a-zA-Z_][a-zA-Z0-9_:.-]*$/;
105
+ const RESOURCE_LINK_RELS = new Set(["preload", "modulepreload", "prefetch", "preconnect", "dns-prefetch", "icon", "stylesheet"]);
106
+ const RESOURCE_QUALIFIERS = ["as", "crossorigin", "type", "media", "imagesrcset", "imagesizes"];
107
+ const STYLESHEET_FETCH_META = new Set(["crossorigin", "integrity", "referrerpolicy", "fetchpriority"]);
108
+ function evalHeadValue(v) {
109
+ return typeof v === "function" ? v() : v;
110
+ }
111
+ function evalHeadProps(props, presets) {
112
+ const out = {};
113
+ for (const name in props) out[name] = presets && name in presets ? presets[name] : evalHeadValue(props[name]);
114
+ return out;
115
+ }
116
+ function classifyHeadTag(desc) {
117
+ const tag = desc.tag;
118
+ if (tag === "link") {
119
+ const rel = evalHeadValue(desc.props && desc.props.rel);
120
+ return {
121
+ resource: RESOURCE_LINK_RELS.has(rel),
122
+ rel
123
+ };
124
+ }
125
+ if (tag === "style") return {
126
+ resource: !!(desc.props && "href" in desc.props)
127
+ };
128
+ if (tag === "script") return {
129
+ resource: !!(desc.props && "src" in desc.props)
130
+ };
131
+ return {
132
+ resource: false
133
+ };
134
+ }
135
+ function resourceIdentity(tag, props) {
136
+ let id = "res:" + tag + ":" + (props.rel || "") + ":" + (props.href || props.src || "");
137
+ for (let i = 0; i < RESOURCE_QUALIFIERS.length; i++) {
138
+ const q = RESOURCE_QUALIFIERS[i];
139
+ if (props[q] != null) id += ":" + q + "=" + props[q];
140
+ }
141
+ return id;
142
+ }
143
+ function replaceableIdentity(tag, props, key, unique) {
144
+ if (tag === "title") return "title";
145
+ if (tag === "base") return "base";
146
+ if (tag === "meta" && props.charset != null) return "charset";
147
+ if (key != null) return tag + ":key:" + key;
148
+ if (tag === "meta") {
149
+ if (props.name != null) return "meta:name:" + props.name;
150
+ if (props.property != null) return "meta:property:" + props.property;
151
+ if (props["http-equiv"] != null) return "meta:http-equiv:" + props["http-equiv"];
152
+ return unique;
153
+ }
154
+ if (tag === "link") return "link:" + (props.rel || "") + ":" + (props.href || "");
155
+ return unique;
156
+ }
157
+ function resolveHead(groups) {
158
+ const winners = new Map();
159
+ const sorted = groups.slice().sort((a, b) => a.seq - b.seq);
160
+ for (let i = 0; i < sorted.length; i++) {
161
+ const group = sorted[i];
162
+ const byIdentity = new Map();
163
+ for (let j = 0; j < group.tags.length; j++) {
164
+ const t = group.tags[j];
165
+ let list = byIdentity.get(t.identity);
166
+ if (!list) byIdentity.set(t.identity, list = []);
167
+ list.push(t);
168
+ }
169
+ for (const [identity, tags] of byIdentity) {
170
+ if (identity === "title") {
171
+ if (tags.length > 1) console.warn("Multiple <title> tags in one head group; the last one wins.");
172
+ winners.set(identity, {
173
+ seq: group.seq,
174
+ tags: [tags[tags.length - 1]]
175
+ });
176
+ } else {
177
+ winners.set(identity, {
178
+ seq: group.seq,
179
+ tags
180
+ });
181
+ }
182
+ }
183
+ }
184
+ return winners;
185
+ }
186
+
103
187
  function joinAssetPath(base, file) {
104
188
  if (/^(?:[a-z][a-z0-9+.-]*:)?\/\//i.test(file)) return file;
105
189
  if (typeof base !== "string" || !base) base = "/";
@@ -241,14 +325,274 @@ function applyAssetTracking(context, tracking, manifest, noScripts) {
241
325
  context.resolveAssetsSync = resolve;
242
326
  }
243
327
  }
328
+ function isCssUrl(url) {
329
+ const q = url.search(/[?#]/);
330
+ return (q === -1 ? url : url.slice(0, q)).endsWith(".css");
331
+ }
332
+ function createHeadRegistry() {
333
+ return {
334
+ pending: [],
335
+ committed: [],
336
+ seq: 0,
337
+ uniq: 0,
338
+ resources: new Set(),
339
+ eagerHtml: "",
340
+ flushed: null,
341
+ shellFlushed: false
342
+ };
343
+ }
344
+ function registerHeadTags(registry, context, tracking, emitResource, nonce, tags) {
345
+ const boundary = context._currentBoundaryId || "";
346
+ let replaceable = null;
347
+ for (let i = 0; i < tags.length; i++) {
348
+ const desc = tags[i];
349
+ if (!desc || !HEAD_ELIGIBLE_TAGS.has(desc.tag)) {
350
+ console.warn(`useHead: ignoring non-head tag`, desc);
351
+ continue;
352
+ }
353
+ const cls = classifyHeadTag(desc);
354
+ if (cls.resource) {
355
+ emitHeadResource(registry, context, tracking, emitResource, nonce, desc, cls.rel);
356
+ } else {
357
+ (replaceable || (replaceable = [])).push(cls.rel !== undefined ? {
358
+ tag: desc.tag,
359
+ props: desc.props,
360
+ key: desc.key,
361
+ rel: cls.rel
362
+ } : desc);
363
+ }
364
+ }
365
+ if (replaceable) registry.pending.push({
366
+ boundary,
367
+ tags: replaceable
368
+ });
369
+ }
370
+ function emitHeadResource(registry, context, tracking, emitResource, nonce, desc, rel) {
371
+ let props;
372
+ try {
373
+ props = evalHeadProps(desc.props || {}, rel !== undefined ? {
374
+ rel
375
+ } : undefined);
376
+ } catch (err) {
377
+ console.warn(`useHead: error evaluating resource tag props`, err);
378
+ return;
379
+ }
380
+ const identity = resourceIdentity(desc.tag, props);
381
+ if (registry.resources.has(identity)) return;
382
+ registry.resources.add(identity);
383
+ if (desc.tag === "link" && (rel === "stylesheet" || rel === "modulepreload")) {
384
+ let plain = true;
385
+ let gateable = rel === "stylesheet";
386
+ for (const name in props) {
387
+ if (name === "rel" || name === "href") continue;
388
+ plain = false;
389
+ if (!STYLESHEET_FETCH_META.has(name)) gateable = false;
390
+ }
391
+ if (plain && props.href != null) {
392
+ const isCss = isCssUrl(props.href);
393
+ if (rel === "stylesheet" ? isCss : !isCss) {
394
+ context.registerAsset(rel === "stylesheet" ? "style" : "module", props.href);
395
+ return;
396
+ }
397
+ }
398
+ if (gateable && props.href != null) {
399
+ const attrHtml = renderHeadAttrHtml(props);
400
+ const entry = {
401
+ href: props.href,
402
+ attrHtml,
403
+ attrs: headAttrRecord(props)
404
+ };
405
+ if (tracking.currentBoundaryId) {
406
+ let styles = tracking.boundaryStyles.get(tracking.currentBoundaryId);
407
+ if (!styles) tracking.boundaryStyles.set(tracking.currentBoundaryId, styles = new Set());
408
+ styles.add(entry);
409
+ }
410
+ const markup = `<link${attrHtml}>`;
411
+ if (emitResource) emitResource(markup, entry);else {
412
+ registry.eagerHtml += markup;
413
+ entry.emitted = true;
414
+ }
415
+ return;
416
+ }
417
+ }
418
+ const url = props.href || props.src;
419
+ if (url != null && tracking.emittedAssets.has(url)) return;
420
+ const markup = renderHeadTagMarkup(desc.tag, props, null, nonce);
421
+ if (emitResource) emitResource(markup);else registry.eagerHtml += markup;
422
+ }
423
+ function commitHeadBoundary(registry, boundary, isPendingFragment) {
424
+ const keep = [];
425
+ const groups = [];
426
+ for (let i = 0; i < registry.pending.length; i++) {
427
+ const reg = registry.pending[i];
428
+ const mine = boundary === "" ? !(isPendingFragment && reg.boundary !== "" && isPendingFragment(reg.boundary)) : reg.boundary === boundary;
429
+ if (!mine) {
430
+ keep.push(reg);
431
+ continue;
432
+ }
433
+ const tags = [];
434
+ for (let j = 0; j < reg.tags.length; j++) {
435
+ const desc = reg.tags[j];
436
+ let props, key;
437
+ try {
438
+ props = evalHeadProps(desc.props || {}, desc.rel !== undefined ? {
439
+ rel: desc.rel
440
+ } : undefined);
441
+ key = evalHeadValue(desc.key);
442
+ } catch (err) {
443
+ console.warn(`useHead: error evaluating tag props`, err);
444
+ continue;
445
+ }
446
+ const identity = replaceableIdentity(desc.tag, props, key, "u:" + registry.uniq++);
447
+ if ((identity === "base" || identity === "charset") && registry.shellFlushed) {
448
+ console.warn(`useHead: <${desc.tag}> (${identity}) registered after shell flush is ignored`);
449
+ continue;
450
+ }
451
+ tags.push({
452
+ tag: desc.tag,
453
+ props,
454
+ identity
455
+ });
456
+ }
457
+ if (tags.length) groups.push({
458
+ seq: registry.seq++,
459
+ tags
460
+ });
461
+ }
462
+ registry.pending = keep;
463
+ for (let i = 0; i < groups.length; i++) registry.committed.push(groups[i]);
464
+ return groups;
465
+ }
466
+ function adoptHeadBoundary(registry, childKey, parentKey) {
467
+ for (let i = 0; i < registry.pending.length; i++) {
468
+ if (registry.pending[i].boundary === childKey) registry.pending[i].boundary = parentKey;
469
+ }
470
+ }
471
+ function dropHeadBoundary(registry, boundary) {
472
+ registry.pending = registry.pending.filter(reg => reg.boundary !== boundary);
473
+ }
474
+ function headGroupSignature(winner) {
475
+ let sig = "" + winner.seq;
476
+ for (let i = 0; i < winner.tags.length; i++) {
477
+ const t = winner.tags[i];
478
+ sig += "|" + t.tag + JSON.stringify(t.props);
479
+ }
480
+ return sig;
481
+ }
482
+ function renderShellHead(registry, nonce, isPendingFragment) {
483
+ commitHeadBoundary(registry, "", isPendingFragment);
484
+ registry.shellFlushed = true;
485
+ const winners = resolveHead(registry.committed);
486
+ registry.flushed = new Map();
487
+ let prelude = "";
488
+ let links = "";
489
+ let metas = "";
490
+ let others = "";
491
+ let scripts = "";
492
+ for (const [identity, winner] of winners) {
493
+ registry.flushed.set(identity, headGroupSignature(winner));
494
+ for (let i = 0; i < winner.tags.length; i++) {
495
+ const t = winner.tags[i];
496
+ const markup = renderHeadTagMarkup(t.tag, t.props, identity, nonce);
497
+ if (identity === "charset" || identity === "base") prelude += markup;else if (t.tag === "link" || t.tag === "style") links += markup;else if (t.tag === "meta") metas += markup;else if (t.tag === "script") scripts += markup;else others += markup;
498
+ }
499
+ }
500
+ return {
501
+ prelude,
502
+ html: registry.eagerHtml + links + metas + others + scripts
503
+ };
504
+ }
505
+ function flushHeadFragment(registry, boundary) {
506
+ const groups = commitHeadBoundary(registry, boundary);
507
+ if (!groups.length) return null;
508
+ const winners = resolveHead(registry.committed);
509
+ const affected = new Set();
510
+ for (let i = 0; i < groups.length; i++) for (let j = 0; j < groups[i].tags.length; j++) affected.add(groups[i].tags[j].identity);
511
+ const ops = [];
512
+ for (const identity of affected) {
513
+ const winner = winners.get(identity);
514
+ const sig = headGroupSignature(winner);
515
+ if (registry.flushed.get(identity) === sig) continue;
516
+ const existed = registry.flushed.has(identity);
517
+ registry.flushed.set(identity, sig);
518
+ if (identity === "title") {
519
+ const children = winner.tags[0].props.children;
520
+ ops.push(["t", children == null ? "" : String(children)]);
521
+ continue;
522
+ }
523
+ if (existed) ops.push(["r", identity]);
524
+ for (let i = 0; i < winner.tags.length; i++) {
525
+ const t = winner.tags[i];
526
+ const attrs = {};
527
+ for (const name in t.props) {
528
+ if (name === "children" || name === "ref" || name.slice(0, 2) === "on") continue;
529
+ if (!HEAD_ATTR_NAME.test(name)) {
530
+ console.warn(`useHead: ignoring invalid attribute name "${name}"`);
531
+ continue;
532
+ }
533
+ const v = t.props[name];
534
+ if (v == null || v === false) continue;
535
+ attrs[name] = v === true ? "" : String(v);
536
+ }
537
+ const children = t.props.children;
538
+ ops.push(["a", identity, t.tag, attrs, children == null ? null : String(children)]);
539
+ }
540
+ }
541
+ return ops.length ? ops : null;
542
+ }
543
+ function renderHeadAttrHtml(props) {
544
+ let attrs = "";
545
+ for (const name in props) {
546
+ if (name === "children" || name === "ref" || name.slice(0, 2) === "on") continue;
547
+ if (!HEAD_ATTR_NAME.test(name)) {
548
+ console.warn(`useHead: ignoring invalid attribute name "${name}"`);
549
+ continue;
550
+ }
551
+ const v = props[name];
552
+ if (v == null || v === false) continue;
553
+ attrs += v === true ? ` ${name}` : ` ${name}="${escape(String(v), true)}"`;
554
+ }
555
+ return attrs;
556
+ }
557
+ function headAttrRecord(props, skipRelHref) {
558
+ let attrs = null;
559
+ for (const name in props) {
560
+ if (name === "children" || name === "ref" || name.slice(0, 2) === "on") continue;
561
+ if ((name === "rel" || name === "href")) continue;
562
+ if (!HEAD_ATTR_NAME.test(name)) continue;
563
+ const v = props[name];
564
+ if (v == null || v === false) continue;
565
+ (attrs || (attrs = {}))[name] = v === true ? "" : String(v);
566
+ }
567
+ return attrs;
568
+ }
569
+ function renderHeadTagMarkup(tag, props, identity, nonce) {
570
+ let attrs = renderHeadAttrHtml(props);
571
+ if (identity != null) attrs += ` data-dh="${escape(identity, true)}"`;
572
+ if (nonce && (tag === "script" || tag === "style")) attrs += ` nonce="${nonce}"`;
573
+ if (tag === "meta" || tag === "link" || tag === "base") return `<${tag}${attrs}>`;
574
+ let body = props.children == null ? "" : String(props.children);
575
+ if (tag === "script") body = body.replace(/<\/(script)/gi, "<\\/$1");else if (tag === "style") body = escapeStyleContent(body);else body = escape(body);
576
+ return `<${tag}${attrs}>${body}</${tag}>`;
577
+ }
578
+ function useHead(tags) {
579
+ const ctx = sharedConfig.context;
580
+ if (!ctx || !ctx.registerHeadTags) {
581
+ console.warn("useHead() called outside of a server render; registration ignored.");
582
+ return;
583
+ }
584
+ ctx.registerHeadTags(Array.isArray(tags) ? tags : [tags]);
585
+ }
244
586
  const VOID_ELEMENTS = /^(?:area|base|br|col|embed|hr|img|input|keygen|link|menuitem|meta|param|source|track|wbr)$/i;
245
- const REPLACE_SCRIPT = `function $df(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,_$HY.done?o.remove():o.replaceWith(n.content),n.remove(),_$HY.fe(e,t),$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])}`;
587
+ 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])}`;
588
+ 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)}`;
246
589
  function renderToString(code, options = {}) {
247
590
  const {
248
591
  renderId = "",
249
592
  nonce,
250
593
  noScripts,
251
- manifest
594
+ manifest,
595
+ onHead
252
596
  } = options;
253
597
  let scripts = "";
254
598
  const serializer = createHydrationSerializer({
@@ -264,12 +608,16 @@ function renderToString(code, options = {}) {
264
608
  onError: options.onError
265
609
  });
266
610
  const tracking = createAssetTracking();
611
+ const headRegistry = createHeadRegistry();
267
612
  sharedConfig.context = {
268
613
  assets: [],
269
614
  nonce,
270
615
  escape: escape,
271
616
  resolve: resolveSSRNode,
272
617
  ssr: ssr,
618
+ registerHeadTags(tags) {
619
+ registerHeadTags(headRegistry, sharedConfig.context, tracking, null, nonce, tags);
620
+ },
273
621
  serialize(id, p) {
274
622
  if (sharedConfig.context.noHydrate) return;
275
623
  if (p != null && typeof p === "object" && (typeof p.then === "function" || typeof p[Symbol.asyncIterator] === "function")) {
@@ -305,7 +653,8 @@ function renderToString(code, options = {}) {
305
653
  sharedConfig.context.noHydrate = true;
306
654
  serializer.close();
307
655
  const assetsHtml = resolveAssetsHtml(sharedConfig.context.assets);
308
- return assembleDocument(html, assetsHtml, tracking.emittedAssets, tracking.inlineStyles, scripts.length ? scripts : "", nonce);
656
+ const head = renderShellHead(headRegistry, nonce, null);
657
+ return assembleDocument(html, assetsHtml, tracking.emittedAssets, tracking.inlineStyles, scripts.length ? scripts : "", nonce, head, onHead);
309
658
  }
310
659
  function renderToStream(code, options = {}) {
311
660
  let {
@@ -314,7 +663,8 @@ function renderToStream(code, options = {}) {
314
663
  onCompleteAll,
315
664
  renderId = "",
316
665
  noScripts,
317
- manifest
666
+ manifest,
667
+ onHead
318
668
  } = options;
319
669
  let dispose;
320
670
  const blockingPromises = new Set();
@@ -356,8 +706,8 @@ function renderToStream(code, options = {}) {
356
706
  if (styles.links.length) {
357
707
  emitTask(`$dfs("${key}",${styles.links.length},${deferActivation ? 1 : 0})`);
358
708
  writeTasks();
359
- for (const url of styles.links) {
360
- buffer.write(`<link rel="stylesheet" href="${url}" onload="$dfc('${key}')" onerror="$dfc('${key}')">`);
709
+ for (const entry of styles.links) {
710
+ buffer.write(typeof entry === "string" ? `<link rel="stylesheet" href="${entry}" onload="$dfc('${key}')" onerror="$dfc('${key}')">` : `<link${entry.attrHtml} onload="$dfc('${key}')" onerror="$dfc('${key}')">`);
361
711
  }
362
712
  buffer.write(`<template id="${key}">${value}</template>`);
363
713
  } else {
@@ -375,10 +725,12 @@ function renderToStream(code, options = {}) {
375
725
  buffer.write(`<link rel="modulepreload" href="${value}">`);
376
726
  } else if (type === "inline-style") {
377
727
  buffer.write(renderInlineStyle(value, nonce));
728
+ } else if (type === "head-tag") {
729
+ buffer.write(value);
378
730
  }
379
731
  },
380
732
  shell(shellHtml, meta) {
381
- buffer.write(assembleDocument(shellHtml, meta.assets, meta.preloads, meta.inlineStyles, meta.tasks.length ? meta.tasks : "", nonce));
733
+ buffer.write(assembleDocument(shellHtml, meta.assets, meta.preloads, meta.inlineStyles, meta.tasks.length ? meta.tasks : "", nonce, meta.head, onHead));
382
734
  },
383
735
  ...options.sink
384
736
  };
@@ -449,10 +801,28 @@ function renderToStream(code, options = {}) {
449
801
  }
450
802
  };
451
803
  const tracking = createAssetTracking();
804
+ const headRegistry = createHeadRegistry();
805
+ let headScriptFlushed = false;
806
+ const emitHeadOps = (key, ops) => {
807
+ const payload = JSON.stringify(ops).replace(/</g, "\\u003C");
808
+ emitTask(`${!headScriptFlushed ? HEAD_SCRIPT : ""}(_$HY.hp=_$HY.hp||{})[${JSON.stringify(key)}]=${payload}`);
809
+ headScriptFlushed = true;
810
+ };
452
811
  sharedConfig.context = context = {
453
812
  async: true,
454
813
  assets: [],
455
814
  nonce,
815
+ registerHeadTags(tags) {
816
+ registerHeadTags(headRegistry, context, tracking,
817
+ (markup, gateEntry) => {
818
+ if (!firstFlushed) {
819
+ headRegistry.eagerHtml += markup;
820
+ if (gateEntry) gateEntry.emitted = true;
821
+ } else if (!gateEntry || !tracking.currentBoundaryId) {
822
+ sink.asset("head-tag", markup);
823
+ }
824
+ }, nonce, tags);
825
+ },
456
826
  registerAsset(type, value) {
457
827
  if (type === "inline-style") {
458
828
  const entry = tracking.registerInlineStyle(value);
@@ -543,10 +913,12 @@ function renderToStream(code, options = {}) {
543
913
  parent.children[key] = value !== undefined ? value : "";
544
914
  serializeFragmentAssets(key, tracking.boundaryModules, context);
545
915
  propagateBoundaryStyles(key, parentKey, tracking);
916
+ adoptHeadBoundary(headRegistry, key, parentKey);
546
917
  item.resolve();
547
918
  return;
548
919
  }
549
920
  if (!completed) {
921
+ if (error) dropHeadBoundary(headRegistry, key);
550
922
  if (!firstFlushed) {
551
923
  queue(() => html = replacePlaceholder(html, key, value !== undefined ? value : ""));
552
924
  serializeFragmentAssets(key, tracking.boundaryModules, context);
@@ -554,6 +926,8 @@ function renderToStream(code, options = {}) {
554
926
  } else {
555
927
  serializeFragmentAssets(key, tracking.boundaryModules, context);
556
928
  const styles = collectStreamStyles(key, tracking, headStyles);
929
+ const headOps = error ? null : flushHeadFragment(headRegistry, key);
930
+ if (headOps) emitHeadOps(key, headOps);
557
931
  sink.fragment(key, value !== undefined ? value : " ", {
558
932
  styles,
559
933
  revealGroup,
@@ -641,14 +1015,16 @@ function renderToStream(code, options = {}) {
641
1015
  const assetsHtml = resolveAssetsHtml(context.assets);
642
1016
  headStyles = new Set();
643
1017
  for (const url of tracking.emittedAssets) {
644
- if (url.endsWith(".css")) headStyles.add(url);
1018
+ if (isCssUrl(url)) headStyles.add(url);
645
1019
  }
646
1020
  serializeRootAssets();
1021
+ const head = renderShellHead(headRegistry, nonce, k => registry.has(k));
647
1022
  sink.shell(html, {
648
1023
  assets: assetsHtml,
649
1024
  preloads: tracking.emittedAssets,
650
1025
  inlineStyles: tracking.inlineStyles,
651
- tasks
1026
+ tasks,
1027
+ head
652
1028
  });
653
1029
  tasks = "";
654
1030
  onCompleteShell && onCompleteShell({
@@ -1148,23 +1524,42 @@ function resolveAssetsHtml(assets) {
1148
1524
  for (let i = 0, len = assets.length; i < len; i++) out += assets[i]();
1149
1525
  return out;
1150
1526
  }
1151
- function assembleDocument(html, assetsHtml, emittedAssets, inlineStyles, scripts, nonce) {
1527
+ function assembleDocument(html, assetsHtml, emittedAssets, inlineStyles, scripts, nonce, headTags, onHead) {
1152
1528
  const scriptTag = scripts ? `<script${nonce ? ` nonce="${nonce}"` : ""}>${scripts}</script>` : "";
1153
- if (!assetsHtml && !(emittedAssets && emittedAssets.size) && !(inlineStyles && inlineStyles.size)) {
1529
+ const headTagsHtml = headTags ? headTags.html : "";
1530
+ const headPrelude = headTags ? headTags.prelude : "";
1531
+ if (!onHead && !assetsHtml && !headTagsHtml && !headPrelude && !(emittedAssets && emittedAssets.size) && !(inlineStyles && inlineStyles.size)) {
1154
1532
  if (!scriptTag) return html;
1155
1533
  const xs = html.indexOf("<!--xs-->");
1156
1534
  return xs === -1 ? html + scriptTag : html.slice(0, xs) + scriptTag + html.slice(xs);
1157
1535
  }
1536
+ if (headPrelude) {
1537
+ const open = html.match(/<head(?:\s[^>]*)?>/);
1538
+ if (open) {
1539
+ const at = open.index + open[0].length;
1540
+ html = html.slice(0, at) + headPrelude + html.slice(at);
1541
+ }
1542
+ }
1158
1543
  const headIdx = html.indexOf("</head>");
1159
1544
  if (headIdx === -1) {
1545
+ if (onHead) {
1546
+ onHead(headPrelude + headTagsHtml + (assetsHtml || "") + renderHeadAssets(emittedAssets, inlineStyles, nonce));
1547
+ }
1160
1548
  if (!scriptTag) return html;
1161
1549
  const xs = html.indexOf("<!--xs-->");
1162
1550
  return xs === -1 ? html + scriptTag : html.slice(0, xs) + scriptTag + html.slice(xs);
1163
1551
  }
1164
- let head = assetsHtml || "";
1552
+ const head = headTagsHtml + (assetsHtml || "") + renderHeadAssets(emittedAssets, inlineStyles, nonce);
1553
+ if (!scriptTag) return html.slice(0, headIdx) + head + html.slice(headIdx);
1554
+ const xsIdx = html.indexOf("<!--xs-->");
1555
+ if (xsIdx === -1) return html.slice(0, headIdx) + head + html.slice(headIdx) + scriptTag;
1556
+ return xsIdx < headIdx ? html.slice(0, xsIdx) + scriptTag + html.slice(xsIdx, headIdx) + head + html.slice(headIdx) : html.slice(0, headIdx) + head + html.slice(headIdx, xsIdx) + scriptTag + html.slice(xsIdx);
1557
+ }
1558
+ function renderHeadAssets(emittedAssets, inlineStyles, nonce) {
1559
+ let head = "";
1165
1560
  if (emittedAssets && emittedAssets.size) {
1166
1561
  for (const url of emittedAssets) {
1167
- head += url.endsWith(".css") ? `<link rel="stylesheet" href="${url}">` : `<link rel="modulepreload" href="${url}">`;
1562
+ head += isCssUrl(url) ? `<link rel="stylesheet" href="${url}">` : `<link rel="modulepreload" href="${url}">`;
1168
1563
  }
1169
1564
  }
1170
1565
  if (inlineStyles && inlineStyles.size) {
@@ -1174,10 +1569,7 @@ function assembleDocument(html, assetsHtml, emittedAssets, inlineStyles, scripts
1174
1569
  head += renderInlineStyle(entry, nonce);
1175
1570
  }
1176
1571
  }
1177
- if (!scriptTag) return html.slice(0, headIdx) + head + html.slice(headIdx);
1178
- const xsIdx = html.indexOf("<!--xs-->");
1179
- if (xsIdx === -1) return html.slice(0, headIdx) + head + html.slice(headIdx) + scriptTag;
1180
- return xsIdx < headIdx ? html.slice(0, xsIdx) + scriptTag + html.slice(xsIdx, headIdx) + head + html.slice(headIdx) : html.slice(0, headIdx) + head + html.slice(headIdx, xsIdx) + scriptTag + html.slice(xsIdx);
1572
+ return head;
1181
1573
  }
1182
1574
  function serializeFragmentAssets(key, boundaryModules, context) {
1183
1575
  const map = boundaryModules.get(key);
@@ -1207,7 +1599,12 @@ function collectStreamStyles(key, tracking, headStyles) {
1207
1599
  for (const entry of styles) {
1208
1600
  if (typeof entry === "string") {
1209
1601
  if (!headStyles || !headStyles.has(entry)) links.push(entry);
1210
- } else if (!entry.emitted) {
1602
+ } else if (entry.emitted) {
1603
+ continue;
1604
+ } else if (entry.attrHtml !== undefined) {
1605
+ entry.emitted = true;
1606
+ links.push(entry);
1607
+ } else {
1211
1608
  entry.emitted = true;
1212
1609
  inline.push(entry);
1213
1610
  }
@@ -1467,8 +1864,41 @@ function Portal(props) {
1467
1864
  if (o?.id != null) getNextChildId(o);
1468
1865
  return undefined;
1469
1866
  }
1470
- function clientOnly(_fn, _options = {}) {
1471
- return props => createMemo(() => props.fallback);
1867
+ function registerClientOnlyPreload(moduleUrl) {
1868
+ const ctx = sharedConfig.context;
1869
+ if (!ctx?.registerAsset || !ctx.resolveAssets) return;
1870
+ const registerAsset = ctx.registerAsset;
1871
+ const resolve = ctx.resolveAssets;
1872
+ const apply = assets => {
1873
+ if (!assets) return;
1874
+ for (let i = 0; i < assets.css.length; i++) {
1875
+ const css = assets.css[i];
1876
+ if (typeof css === "string") registerAsset("style", css);else registerAsset("inline-style", css);
1877
+ }
1878
+ for (let i = 0; i < assets.js.length; i++) registerAsset("module", assets.js[i]);
1879
+ };
1880
+ const assets = resolve(moduleUrl);
1881
+ if (assets && typeof assets.then === "function") {
1882
+ const boundary = ctx._currentBoundaryId;
1883
+ assets.then(resolved => {
1884
+ const current = ctx._currentBoundaryId;
1885
+ ctx._currentBoundaryId = boundary;
1886
+ try {
1887
+ apply(resolved);
1888
+ } finally {
1889
+ ctx._currentBoundaryId = current;
1890
+ }
1891
+ },
1892
+ () => {});
1893
+ } else {
1894
+ apply(assets);
1895
+ }
1896
+ }
1897
+ function clientOnly(_fn, _options = {}, moduleUrl) {
1898
+ return props => {
1899
+ if (moduleUrl) registerClientOnlyPreload(moduleUrl);
1900
+ return createMemo(() => props.fallback);
1901
+ };
1472
1902
  }
1473
1903
  function httpStatus(code, text) {
1474
1904
  const event = getRequestEvent();
@@ -1499,4 +1929,4 @@ function httpHeader(name, value, options) {
1499
1929
  }
1500
1930
  }
1501
1931
 
1502
- export { Assets, ChildProperties, DOMElements, DOMWithState, DelegatedEvents, Dynamic, HREF, HydrationScript, MathMLElements, Namespaces, Portal, REVALIDATE_HEADER, RawTextElements, RequestContext, ResponseEnvelope, SVGElements, VoidElements, notSup as acquireAsset, notSup as addEvent, applyRef, notSup as assign, claimElement, claimElementTree, notSup as className, clientOnly, notSup as delegateEvents, dynamic, notSup as dynamicProperty, effect, escape, generateHydrationScript, getAssets, notSup as getDelegatedRoot, getHydrationKey, notSup as getNextElement, notSup as getNextMarker, notSup as getNextMatch, getRequestEvent, httpHeader, httpStatus, notSup as hydrate, notSup as insert, isDev, isHref, isResponseEnvelope, isServer, memo, redirect, notSup as ref, notSup as registerDelegatedContainer, notSup as registerDelegatedRoot, registerElementClaim, reload, notSup as render, renderToStream, renderToString, renderToStringAsync, respond, notSup as runHydrationEvents, notSup as setAttribute, notSup as setAttributeNS, notSup as setProperty, notSup as setStyleProperty, notSup as spread, ssr, ssrAttribute, ssrClassName, ssrElement, ssrGroup, ssrHydrationKey, ssrStyle, ssrStyleProperty, notSup as style, notSup as template, notSup as unregisterDelegatedContainer, notSup as unregisterDelegatedRoot, useAssets };
1932
+ export { Assets, ChildProperties, DOMElements, DOMWithState, DelegatedEvents, Dynamic, HREF, HydrationScript, MathMLElements, Namespaces, Portal, REVALIDATE_HEADER, RawTextElements, RequestContext, ResponseEnvelope, SVGElements, VoidElements, notSup as acquireAsset, notSup as addEvent, applyRef, notSup as assign, claimElement, claimElementTree, notSup as className, clientOnly, notSup as delegateEvents, dynamic, notSup as dynamicProperty, effect, escape, generateHydrationScript, getAssets, notSup as getDelegatedRoot, getHydrationKey, notSup as getNextElement, notSup as getNextMarker, notSup as getNextMatch, getRequestEvent, httpHeader, httpStatus, notSup as hydrate, notSup as insert, isDev, isHref, isResponseEnvelope, isServer, memo, redirect, notSup as ref, notSup as registerDelegatedContainer, notSup as registerDelegatedRoot, registerElementClaim, reload, notSup as render, renderToStream, renderToString, renderToStringAsync, respond, notSup as runHydrationEvents, notSup as setAttribute, notSup as setAttributeNS, notSup as setProperty, notSup as setStyleProperty, notSup as spread, ssr, ssrAttribute, ssrClassName, ssrElement, ssrGroup, ssrHydrationKey, ssrStyle, ssrStyleProperty, notSup as style, notSup as template, notSup as unregisterDelegatedContainer, notSup as unregisterDelegatedRoot, useAssets, useHead };