@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.
@@ -137,6 +137,90 @@ function createJSONSerializer({
137
137
  };
138
138
  }
139
139
 
140
+ const HEAD_ELIGIBLE_TAGS = new Set(["title", "meta", "link", "style", "script", "base"]);
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"]);
143
+ const RESOURCE_QUALIFIERS = ["as", "crossorigin", "type", "media", "imagesrcset", "imagesizes"];
144
+ const STYLESHEET_FETCH_META = new Set(["crossorigin", "integrity", "referrerpolicy", "fetchpriority"]);
145
+ function evalHeadValue(v) {
146
+ return typeof v === "function" ? v() : v;
147
+ }
148
+ function evalHeadProps(props, presets) {
149
+ const out = {};
150
+ for (const name in props) out[name] = presets && name in presets ? presets[name] : evalHeadValue(props[name]);
151
+ return out;
152
+ }
153
+ function classifyHeadTag(desc) {
154
+ const tag = desc.tag;
155
+ if (tag === "link") {
156
+ const rel = evalHeadValue(desc.props && desc.props.rel);
157
+ return {
158
+ resource: RESOURCE_LINK_RELS.has(rel),
159
+ rel
160
+ };
161
+ }
162
+ if (tag === "style") return {
163
+ resource: !!(desc.props && "href" in desc.props)
164
+ };
165
+ if (tag === "script") return {
166
+ resource: !!(desc.props && "src" in desc.props)
167
+ };
168
+ return {
169
+ resource: false
170
+ };
171
+ }
172
+ function resourceIdentity(tag, props) {
173
+ let id = "res:" + tag + ":" + (props.rel || "") + ":" + (props.href || props.src || "");
174
+ for (let i = 0; i < RESOURCE_QUALIFIERS.length; i++) {
175
+ const q = RESOURCE_QUALIFIERS[i];
176
+ if (props[q] != null) id += ":" + q + "=" + props[q];
177
+ }
178
+ return id;
179
+ }
180
+ function replaceableIdentity(tag, props, key, unique) {
181
+ if (tag === "title") return "title";
182
+ if (tag === "base") return "base";
183
+ if (tag === "meta" && props.charset != null) return "charset";
184
+ if (key != null) return tag + ":key:" + key;
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"];
189
+ return unique;
190
+ }
191
+ if (tag === "link") return "link:" + (props.rel || "") + ":" + (props.href || "");
192
+ return unique;
193
+ }
194
+ function resolveHead(groups) {
195
+ const winners = new Map();
196
+ const sorted = groups.slice().sort((a, b) => a.seq - b.seq);
197
+ for (let i = 0; i < sorted.length; i++) {
198
+ const group = sorted[i];
199
+ const byIdentity = new Map();
200
+ for (let j = 0; j < group.tags.length; j++) {
201
+ const t = group.tags[j];
202
+ let list = byIdentity.get(t.identity);
203
+ if (!list) byIdentity.set(t.identity, list = []);
204
+ list.push(t);
205
+ }
206
+ for (const [identity, tags] of byIdentity) {
207
+ if (identity === "title") {
208
+ if (tags.length > 1) console.warn("Multiple <title> tags in one head group; the last one wins.");
209
+ winners.set(identity, {
210
+ seq: group.seq,
211
+ tags: [tags[tags.length - 1]]
212
+ });
213
+ } else {
214
+ winners.set(identity, {
215
+ seq: group.seq,
216
+ tags
217
+ });
218
+ }
219
+ }
220
+ }
221
+ return winners;
222
+ }
223
+
140
224
  function joinAssetPath(base, file) {
141
225
  if (/^(?:[a-z][a-z0-9+.-]*:)?\/\//i.test(file)) return file;
142
226
  if (typeof base !== "string" || !base) base = "/";
@@ -278,7 +362,258 @@ function applyAssetTracking(context, tracking, manifest, noScripts) {
278
362
  context.resolveAssetsSync = resolve;
279
363
  }
280
364
  }
281
- 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])}`;
365
+ function isCssUrl(url) {
366
+ const q = url.search(/[?#]/);
367
+ return (q === -1 ? url : url.slice(0, q)).endsWith(".css");
368
+ }
369
+ function createHeadRegistry() {
370
+ return {
371
+ pending: [],
372
+ committed: [],
373
+ seq: 0,
374
+ uniq: 0,
375
+ resources: new Set(),
376
+ eagerHtml: "",
377
+ flushed: null,
378
+ shellFlushed: false
379
+ };
380
+ }
381
+ function registerHeadTags(registry, context, tracking, emitResource, nonce, tags) {
382
+ const boundary = context._currentBoundaryId || "";
383
+ let replaceable = null;
384
+ for (let i = 0; i < tags.length; i++) {
385
+ const desc = tags[i];
386
+ if (!desc || !HEAD_ELIGIBLE_TAGS.has(desc.tag)) {
387
+ console.warn(`useHead: ignoring non-head tag`, desc);
388
+ continue;
389
+ }
390
+ const cls = classifyHeadTag(desc);
391
+ if (cls.resource) {
392
+ emitHeadResource(registry, context, tracking, emitResource, nonce, desc, cls.rel);
393
+ } else {
394
+ (replaceable || (replaceable = [])).push(cls.rel !== undefined ? {
395
+ tag: desc.tag,
396
+ props: desc.props,
397
+ key: desc.key,
398
+ rel: cls.rel
399
+ } : desc);
400
+ }
401
+ }
402
+ if (replaceable) registry.pending.push({
403
+ boundary,
404
+ tags: replaceable
405
+ });
406
+ }
407
+ function emitHeadResource(registry, context, tracking, emitResource, nonce, desc, rel) {
408
+ let props;
409
+ try {
410
+ props = evalHeadProps(desc.props || {}, rel !== undefined ? {
411
+ rel
412
+ } : undefined);
413
+ } catch (err) {
414
+ console.warn(`useHead: error evaluating resource tag props`, err);
415
+ return;
416
+ }
417
+ const identity = resourceIdentity(desc.tag, props);
418
+ if (registry.resources.has(identity)) return;
419
+ registry.resources.add(identity);
420
+ if (desc.tag === "link" && (rel === "stylesheet" || rel === "modulepreload")) {
421
+ let plain = true;
422
+ let gateable = rel === "stylesheet";
423
+ for (const name in props) {
424
+ if (name === "rel" || name === "href") continue;
425
+ plain = false;
426
+ if (!STYLESHEET_FETCH_META.has(name)) gateable = false;
427
+ }
428
+ if (plain && props.href != null) {
429
+ const isCss = isCssUrl(props.href);
430
+ if (rel === "stylesheet" ? isCss : !isCss) {
431
+ context.registerAsset(rel === "stylesheet" ? "style" : "module", props.href);
432
+ return;
433
+ }
434
+ }
435
+ if (gateable && props.href != null) {
436
+ const attrHtml = renderHeadAttrHtml(props);
437
+ const entry = {
438
+ href: props.href,
439
+ attrHtml,
440
+ attrs: headAttrRecord(props)
441
+ };
442
+ if (tracking.currentBoundaryId) {
443
+ let styles = tracking.boundaryStyles.get(tracking.currentBoundaryId);
444
+ if (!styles) tracking.boundaryStyles.set(tracking.currentBoundaryId, styles = new Set());
445
+ styles.add(entry);
446
+ }
447
+ const markup = `<link${attrHtml}>`;
448
+ if (emitResource) emitResource(markup, entry);else {
449
+ registry.eagerHtml += markup;
450
+ entry.emitted = true;
451
+ }
452
+ return;
453
+ }
454
+ }
455
+ const url = props.href || props.src;
456
+ if (url != null && tracking.emittedAssets.has(url)) return;
457
+ const markup = renderHeadTagMarkup(desc.tag, props, null, nonce);
458
+ if (emitResource) emitResource(markup);else registry.eagerHtml += markup;
459
+ }
460
+ function commitHeadBoundary(registry, boundary, isPendingFragment) {
461
+ const keep = [];
462
+ const groups = [];
463
+ for (let i = 0; i < registry.pending.length; i++) {
464
+ const reg = registry.pending[i];
465
+ const mine = boundary === "" ? !(isPendingFragment && reg.boundary !== "" && isPendingFragment(reg.boundary)) : reg.boundary === boundary;
466
+ if (!mine) {
467
+ keep.push(reg);
468
+ continue;
469
+ }
470
+ const tags = [];
471
+ for (let j = 0; j < reg.tags.length; j++) {
472
+ const desc = reg.tags[j];
473
+ let props, key;
474
+ try {
475
+ props = evalHeadProps(desc.props || {}, desc.rel !== undefined ? {
476
+ rel: desc.rel
477
+ } : undefined);
478
+ key = evalHeadValue(desc.key);
479
+ } catch (err) {
480
+ console.warn(`useHead: error evaluating tag props`, err);
481
+ continue;
482
+ }
483
+ const identity = replaceableIdentity(desc.tag, props, key, "u:" + registry.uniq++);
484
+ if ((identity === "base" || identity === "charset") && registry.shellFlushed) {
485
+ console.warn(`useHead: <${desc.tag}> (${identity}) registered after shell flush is ignored`);
486
+ continue;
487
+ }
488
+ tags.push({
489
+ tag: desc.tag,
490
+ props,
491
+ identity
492
+ });
493
+ }
494
+ if (tags.length) groups.push({
495
+ seq: registry.seq++,
496
+ tags
497
+ });
498
+ }
499
+ registry.pending = keep;
500
+ for (let i = 0; i < groups.length; i++) registry.committed.push(groups[i]);
501
+ return groups;
502
+ }
503
+ function adoptHeadBoundary(registry, childKey, parentKey) {
504
+ for (let i = 0; i < registry.pending.length; i++) {
505
+ if (registry.pending[i].boundary === childKey) registry.pending[i].boundary = parentKey;
506
+ }
507
+ }
508
+ function dropHeadBoundary(registry, boundary) {
509
+ registry.pending = registry.pending.filter(reg => reg.boundary !== boundary);
510
+ }
511
+ function headGroupSignature(winner) {
512
+ let sig = "" + winner.seq;
513
+ for (let i = 0; i < winner.tags.length; i++) {
514
+ const t = winner.tags[i];
515
+ sig += "|" + t.tag + JSON.stringify(t.props);
516
+ }
517
+ return sig;
518
+ }
519
+ function renderShellHead(registry, nonce, isPendingFragment) {
520
+ commitHeadBoundary(registry, "", isPendingFragment);
521
+ registry.shellFlushed = true;
522
+ const winners = resolveHead(registry.committed);
523
+ registry.flushed = new Map();
524
+ let prelude = "";
525
+ let links = "";
526
+ let metas = "";
527
+ let others = "";
528
+ let scripts = "";
529
+ for (const [identity, winner] of winners) {
530
+ registry.flushed.set(identity, headGroupSignature(winner));
531
+ for (let i = 0; i < winner.tags.length; i++) {
532
+ const t = winner.tags[i];
533
+ const markup = renderHeadTagMarkup(t.tag, t.props, identity, nonce);
534
+ 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;
535
+ }
536
+ }
537
+ return {
538
+ prelude,
539
+ html: registry.eagerHtml + links + metas + others + scripts
540
+ };
541
+ }
542
+ function flushHeadFragment(registry, boundary) {
543
+ const groups = commitHeadBoundary(registry, boundary);
544
+ if (!groups.length) return null;
545
+ const winners = resolveHead(registry.committed);
546
+ const affected = new Set();
547
+ 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);
548
+ const ops = [];
549
+ for (const identity of affected) {
550
+ const winner = winners.get(identity);
551
+ const sig = headGroupSignature(winner);
552
+ if (registry.flushed.get(identity) === sig) continue;
553
+ const existed = registry.flushed.has(identity);
554
+ registry.flushed.set(identity, sig);
555
+ if (identity === "title") {
556
+ const children = winner.tags[0].props.children;
557
+ ops.push(["t", children == null ? "" : String(children)]);
558
+ continue;
559
+ }
560
+ if (existed) ops.push(["r", identity]);
561
+ for (let i = 0; i < winner.tags.length; i++) {
562
+ const t = winner.tags[i];
563
+ const attrs = {};
564
+ for (const name in t.props) {
565
+ if (name === "children" || name === "ref" || name.slice(0, 2) === "on") continue;
566
+ if (!HEAD_ATTR_NAME.test(name)) {
567
+ console.warn(`useHead: ignoring invalid attribute name "${name}"`);
568
+ continue;
569
+ }
570
+ const v = t.props[name];
571
+ if (v == null || v === false) continue;
572
+ attrs[name] = v === true ? "" : String(v);
573
+ }
574
+ const children = t.props.children;
575
+ ops.push(["a", identity, t.tag, attrs, children == null ? null : String(children)]);
576
+ }
577
+ }
578
+ return ops.length ? ops : null;
579
+ }
580
+ function renderHeadAttrHtml(props) {
581
+ let attrs = "";
582
+ for (const name in props) {
583
+ if (name === "children" || name === "ref" || name.slice(0, 2) === "on") continue;
584
+ if (!HEAD_ATTR_NAME.test(name)) {
585
+ console.warn(`useHead: ignoring invalid attribute name "${name}"`);
586
+ continue;
587
+ }
588
+ const v = props[name];
589
+ if (v == null || v === false) continue;
590
+ attrs += v === true ? ` ${name}` : ` ${name}="${escape(String(v), true)}"`;
591
+ }
592
+ return attrs;
593
+ }
594
+ function headAttrRecord(props, skipRelHref) {
595
+ let attrs = null;
596
+ for (const name in props) {
597
+ if (name === "children" || name === "ref" || name.slice(0, 2) === "on") continue;
598
+ if ((name === "rel" || name === "href")) continue;
599
+ if (!HEAD_ATTR_NAME.test(name)) continue;
600
+ const v = props[name];
601
+ if (v == null || v === false) continue;
602
+ (attrs || (attrs = {}))[name] = v === true ? "" : String(v);
603
+ }
604
+ return attrs;
605
+ }
606
+ function renderHeadTagMarkup(tag, props, identity, nonce) {
607
+ let attrs = renderHeadAttrHtml(props);
608
+ if (identity != null) attrs += ` data-dh="${escape(identity, true)}"`;
609
+ if (nonce && (tag === "script" || tag === "style")) attrs += ` nonce="${nonce}"`;
610
+ if (tag === "meta" || tag === "link" || tag === "base") return `<${tag}${attrs}>`;
611
+ let body = props.children == null ? "" : String(props.children);
612
+ if (tag === "script") body = body.replace(/<\/(script)/gi, "<\\/$1");else if (tag === "style") body = escapeStyleContent(body);else body = escape(body);
613
+ return `<${tag}${attrs}>${body}</${tag}>`;
614
+ }
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])}`;
616
+ 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)}`;
282
617
  function renderToStream(code, options = {}) {
283
618
  let {
284
619
  nonce,
@@ -286,7 +621,8 @@ function renderToStream(code, options = {}) {
286
621
  onCompleteAll,
287
622
  renderId = "",
288
623
  noScripts,
289
- manifest
624
+ manifest,
625
+ onHead
290
626
  } = options;
291
627
  let dispose;
292
628
  const blockingPromises = new Set();
@@ -328,8 +664,8 @@ function renderToStream(code, options = {}) {
328
664
  if (styles.links.length) {
329
665
  emitTask(`$dfs("${key}",${styles.links.length},${deferActivation ? 1 : 0})`);
330
666
  writeTasks();
331
- for (const url of styles.links) {
332
- buffer.write(`<link rel="stylesheet" href="${url}" onload="$dfc('${key}')" onerror="$dfc('${key}')">`);
667
+ for (const entry of styles.links) {
668
+ 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}')">`);
333
669
  }
334
670
  buffer.write(`<template id="${key}">${value}</template>`);
335
671
  } else {
@@ -347,10 +683,12 @@ function renderToStream(code, options = {}) {
347
683
  buffer.write(`<link rel="modulepreload" href="${value}">`);
348
684
  } else if (type === "inline-style") {
349
685
  buffer.write(renderInlineStyle(value, nonce));
686
+ } else if (type === "head-tag") {
687
+ buffer.write(value);
350
688
  }
351
689
  },
352
690
  shell(shellHtml, meta) {
353
- buffer.write(assembleDocument(shellHtml, meta.assets, meta.preloads, meta.inlineStyles, meta.tasks.length ? meta.tasks : "", nonce));
691
+ buffer.write(assembleDocument(shellHtml, meta.assets, meta.preloads, meta.inlineStyles, meta.tasks.length ? meta.tasks : "", nonce, meta.head, onHead));
354
692
  },
355
693
  ...options.sink
356
694
  };
@@ -421,10 +759,28 @@ function renderToStream(code, options = {}) {
421
759
  }
422
760
  };
423
761
  const tracking = createAssetTracking();
762
+ const headRegistry = createHeadRegistry();
763
+ let headScriptFlushed = false;
764
+ const emitHeadOps = (key, ops) => {
765
+ const payload = JSON.stringify(ops).replace(/</g, "\\u003C");
766
+ emitTask(`${!headScriptFlushed ? HEAD_SCRIPT : ""}(_$HY.hp=_$HY.hp||{})[${JSON.stringify(key)}]=${payload}`);
767
+ headScriptFlushed = true;
768
+ };
424
769
  solidJs.sharedConfig.context = context = {
425
770
  async: true,
426
771
  assets: [],
427
772
  nonce,
773
+ registerHeadTags(tags) {
774
+ registerHeadTags(headRegistry, context, tracking,
775
+ (markup, gateEntry) => {
776
+ if (!firstFlushed) {
777
+ headRegistry.eagerHtml += markup;
778
+ if (gateEntry) gateEntry.emitted = true;
779
+ } else if (!gateEntry || !tracking.currentBoundaryId) {
780
+ sink.asset("head-tag", markup);
781
+ }
782
+ }, nonce, tags);
783
+ },
428
784
  registerAsset(type, value) {
429
785
  if (type === "inline-style") {
430
786
  const entry = tracking.registerInlineStyle(value);
@@ -515,10 +871,12 @@ function renderToStream(code, options = {}) {
515
871
  parent.children[key] = value !== undefined ? value : "";
516
872
  serializeFragmentAssets(key, tracking.boundaryModules, context);
517
873
  propagateBoundaryStyles(key, parentKey, tracking);
874
+ adoptHeadBoundary(headRegistry, key, parentKey);
518
875
  item.resolve();
519
876
  return;
520
877
  }
521
878
  if (!completed) {
879
+ if (error) dropHeadBoundary(headRegistry, key);
522
880
  if (!firstFlushed) {
523
881
  queue(() => html = replacePlaceholder(html, key, value !== undefined ? value : ""));
524
882
  serializeFragmentAssets(key, tracking.boundaryModules, context);
@@ -526,6 +884,8 @@ function renderToStream(code, options = {}) {
526
884
  } else {
527
885
  serializeFragmentAssets(key, tracking.boundaryModules, context);
528
886
  const styles = collectStreamStyles(key, tracking, headStyles);
887
+ const headOps = error ? null : flushHeadFragment(headRegistry, key);
888
+ if (headOps) emitHeadOps(key, headOps);
529
889
  sink.fragment(key, value !== undefined ? value : " ", {
530
890
  styles,
531
891
  revealGroup,
@@ -613,14 +973,16 @@ function renderToStream(code, options = {}) {
613
973
  const assetsHtml = resolveAssetsHtml(context.assets);
614
974
  headStyles = new Set();
615
975
  for (const url of tracking.emittedAssets) {
616
- if (url.endsWith(".css")) headStyles.add(url);
976
+ if (isCssUrl(url)) headStyles.add(url);
617
977
  }
618
978
  serializeRootAssets();
979
+ const head = renderShellHead(headRegistry, nonce, k => registry.has(k));
619
980
  sink.shell(html, {
620
981
  assets: assetsHtml,
621
982
  preloads: tracking.emittedAssets,
622
983
  inlineStyles: tracking.inlineStyles,
623
- tasks
984
+ tasks,
985
+ head
624
986
  });
625
987
  tasks = "";
626
988
  onCompleteShell && onCompleteShell({
@@ -1006,23 +1368,42 @@ function resolveAssetsHtml(assets) {
1006
1368
  for (let i = 0, len = assets.length; i < len; i++) out += assets[i]();
1007
1369
  return out;
1008
1370
  }
1009
- function assembleDocument(html, assetsHtml, emittedAssets, inlineStyles, scripts, nonce) {
1371
+ function assembleDocument(html, assetsHtml, emittedAssets, inlineStyles, scripts, nonce, headTags, onHead) {
1010
1372
  const scriptTag = scripts ? `<script${nonce ? ` nonce="${nonce}"` : ""}>${scripts}</script>` : "";
1011
- if (!assetsHtml && !(emittedAssets && emittedAssets.size) && !(inlineStyles && inlineStyles.size)) {
1373
+ const headTagsHtml = headTags ? headTags.html : "";
1374
+ const headPrelude = headTags ? headTags.prelude : "";
1375
+ if (!onHead && !assetsHtml && !headTagsHtml && !headPrelude && !(emittedAssets && emittedAssets.size) && !(inlineStyles && inlineStyles.size)) {
1012
1376
  if (!scriptTag) return html;
1013
1377
  const xs = html.indexOf("<!--xs-->");
1014
1378
  return xs === -1 ? html + scriptTag : html.slice(0, xs) + scriptTag + html.slice(xs);
1015
1379
  }
1380
+ if (headPrelude) {
1381
+ const open = html.match(/<head(?:\s[^>]*)?>/);
1382
+ if (open) {
1383
+ const at = open.index + open[0].length;
1384
+ html = html.slice(0, at) + headPrelude + html.slice(at);
1385
+ }
1386
+ }
1016
1387
  const headIdx = html.indexOf("</head>");
1017
1388
  if (headIdx === -1) {
1389
+ if (onHead) {
1390
+ onHead(headPrelude + headTagsHtml + (assetsHtml || "") + renderHeadAssets(emittedAssets, inlineStyles, nonce));
1391
+ }
1018
1392
  if (!scriptTag) return html;
1019
1393
  const xs = html.indexOf("<!--xs-->");
1020
1394
  return xs === -1 ? html + scriptTag : html.slice(0, xs) + scriptTag + html.slice(xs);
1021
1395
  }
1022
- let head = assetsHtml || "";
1396
+ const head = headTagsHtml + (assetsHtml || "") + renderHeadAssets(emittedAssets, inlineStyles, nonce);
1397
+ if (!scriptTag) return html.slice(0, headIdx) + head + html.slice(headIdx);
1398
+ const xsIdx = html.indexOf("<!--xs-->");
1399
+ if (xsIdx === -1) return html.slice(0, headIdx) + head + html.slice(headIdx) + scriptTag;
1400
+ 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);
1401
+ }
1402
+ function renderHeadAssets(emittedAssets, inlineStyles, nonce) {
1403
+ let head = "";
1023
1404
  if (emittedAssets && emittedAssets.size) {
1024
1405
  for (const url of emittedAssets) {
1025
- head += url.endsWith(".css") ? `<link rel="stylesheet" href="${url}">` : `<link rel="modulepreload" href="${url}">`;
1406
+ head += isCssUrl(url) ? `<link rel="stylesheet" href="${url}">` : `<link rel="modulepreload" href="${url}">`;
1026
1407
  }
1027
1408
  }
1028
1409
  if (inlineStyles && inlineStyles.size) {
@@ -1032,10 +1413,7 @@ function assembleDocument(html, assetsHtml, emittedAssets, inlineStyles, scripts
1032
1413
  head += renderInlineStyle(entry, nonce);
1033
1414
  }
1034
1415
  }
1035
- if (!scriptTag) return html.slice(0, headIdx) + head + html.slice(headIdx);
1036
- const xsIdx = html.indexOf("<!--xs-->");
1037
- if (xsIdx === -1) return html.slice(0, headIdx) + head + html.slice(headIdx) + scriptTag;
1038
- 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);
1416
+ return head;
1039
1417
  }
1040
1418
  function serializeFragmentAssets(key, boundaryModules, context) {
1041
1419
  const map = boundaryModules.get(key);
@@ -1065,7 +1443,12 @@ function collectStreamStyles(key, tracking, headStyles) {
1065
1443
  for (const entry of styles) {
1066
1444
  if (typeof entry === "string") {
1067
1445
  if (!headStyles || !headStyles.has(entry)) links.push(entry);
1068
- } else if (!entry.emitted) {
1446
+ } else if (entry.emitted) {
1447
+ continue;
1448
+ } else if (entry.attrHtml !== undefined) {
1449
+ entry.emitted = true;
1450
+ links.push(entry);
1451
+ } else {
1069
1452
  entry.emitted = true;
1070
1453
  inline.push(entry);
1071
1454
  }
@@ -1462,7 +1845,12 @@ function createFrameSink(emit, frame) {
1462
1845
  version,
1463
1846
  key
1464
1847
  };
1465
- if (links.length) chunk.styles = links;
1848
+ if (links.length) {
1849
+ chunk.styles = links.map(e => typeof e === "string" ? e : e.attrs ? {
1850
+ href: e.href,
1851
+ attrs: e.attrs
1852
+ } : e.href);
1853
+ }
1466
1854
  if (inline.length) {
1467
1855
  chunk.inlineStyles = inline.map(e => ({
1468
1856
  id: e.id,