@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/dev.cjs +329 -2
- package/dist/dev.js +329 -3
- package/dist/server.cjs +451 -20
- package/dist/server.js +451 -21
- package/dist/web.cjs +325 -2
- package/dist/web.js +325 -3
- package/frames/dist/client.cjs +177 -34
- package/frames/dist/client.dev.cjs +177 -34
- package/frames/dist/client.dev.js +178 -35
- package/frames/dist/client.js +178 -35
- package/frames/dist/server.cjs +405 -17
- package/frames/dist/server.js +405 -17
- package/package.json +3 -3
- package/types/client.d.ts +20 -0
- package/types/frames/frame-client.d.ts +25 -0
- package/types/frames/frame-transport.d.ts +26 -0
- package/types/index.d.ts +1 -22
- package/types/server.d.ts +50 -0
- package/types-cjs/client.d.cts +20 -0
- package/types-cjs/frames/frame-client.d.cts +25 -0
- package/types-cjs/frames/frame-transport.d.cts +26 -0
- package/types-cjs/index.d.cts +1 -22
- package/types-cjs/server.d.cts +50 -0
package/frames/dist/server.js
CHANGED
|
@@ -135,6 +135,90 @@ function createJSONSerializer({
|
|
|
135
135
|
};
|
|
136
136
|
}
|
|
137
137
|
|
|
138
|
+
const HEAD_ELIGIBLE_TAGS = new Set(["title", "meta", "link", "style", "script", "base"]);
|
|
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"]);
|
|
141
|
+
const RESOURCE_QUALIFIERS = ["as", "crossorigin", "type", "media", "imagesrcset", "imagesizes"];
|
|
142
|
+
const STYLESHEET_FETCH_META = new Set(["crossorigin", "integrity", "referrerpolicy", "fetchpriority"]);
|
|
143
|
+
function evalHeadValue(v) {
|
|
144
|
+
return typeof v === "function" ? v() : v;
|
|
145
|
+
}
|
|
146
|
+
function evalHeadProps(props, presets) {
|
|
147
|
+
const out = {};
|
|
148
|
+
for (const name in props) out[name] = presets && name in presets ? presets[name] : evalHeadValue(props[name]);
|
|
149
|
+
return out;
|
|
150
|
+
}
|
|
151
|
+
function classifyHeadTag(desc) {
|
|
152
|
+
const tag = desc.tag;
|
|
153
|
+
if (tag === "link") {
|
|
154
|
+
const rel = evalHeadValue(desc.props && desc.props.rel);
|
|
155
|
+
return {
|
|
156
|
+
resource: RESOURCE_LINK_RELS.has(rel),
|
|
157
|
+
rel
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
if (tag === "style") return {
|
|
161
|
+
resource: !!(desc.props && "href" in desc.props)
|
|
162
|
+
};
|
|
163
|
+
if (tag === "script") return {
|
|
164
|
+
resource: !!(desc.props && "src" in desc.props)
|
|
165
|
+
};
|
|
166
|
+
return {
|
|
167
|
+
resource: false
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
function resourceIdentity(tag, props) {
|
|
171
|
+
let id = "res:" + tag + ":" + (props.rel || "") + ":" + (props.href || props.src || "");
|
|
172
|
+
for (let i = 0; i < RESOURCE_QUALIFIERS.length; i++) {
|
|
173
|
+
const q = RESOURCE_QUALIFIERS[i];
|
|
174
|
+
if (props[q] != null) id += ":" + q + "=" + props[q];
|
|
175
|
+
}
|
|
176
|
+
return id;
|
|
177
|
+
}
|
|
178
|
+
function replaceableIdentity(tag, props, key, unique) {
|
|
179
|
+
if (tag === "title") return "title";
|
|
180
|
+
if (tag === "base") return "base";
|
|
181
|
+
if (tag === "meta" && props.charset != null) return "charset";
|
|
182
|
+
if (key != null) return tag + ":key:" + key;
|
|
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"];
|
|
187
|
+
return unique;
|
|
188
|
+
}
|
|
189
|
+
if (tag === "link") return "link:" + (props.rel || "") + ":" + (props.href || "");
|
|
190
|
+
return unique;
|
|
191
|
+
}
|
|
192
|
+
function resolveHead(groups) {
|
|
193
|
+
const winners = new Map();
|
|
194
|
+
const sorted = groups.slice().sort((a, b) => a.seq - b.seq);
|
|
195
|
+
for (let i = 0; i < sorted.length; i++) {
|
|
196
|
+
const group = sorted[i];
|
|
197
|
+
const byIdentity = new Map();
|
|
198
|
+
for (let j = 0; j < group.tags.length; j++) {
|
|
199
|
+
const t = group.tags[j];
|
|
200
|
+
let list = byIdentity.get(t.identity);
|
|
201
|
+
if (!list) byIdentity.set(t.identity, list = []);
|
|
202
|
+
list.push(t);
|
|
203
|
+
}
|
|
204
|
+
for (const [identity, tags] of byIdentity) {
|
|
205
|
+
if (identity === "title") {
|
|
206
|
+
if (tags.length > 1) console.warn("Multiple <title> tags in one head group; the last one wins.");
|
|
207
|
+
winners.set(identity, {
|
|
208
|
+
seq: group.seq,
|
|
209
|
+
tags: [tags[tags.length - 1]]
|
|
210
|
+
});
|
|
211
|
+
} else {
|
|
212
|
+
winners.set(identity, {
|
|
213
|
+
seq: group.seq,
|
|
214
|
+
tags
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
return winners;
|
|
220
|
+
}
|
|
221
|
+
|
|
138
222
|
function joinAssetPath(base, file) {
|
|
139
223
|
if (/^(?:[a-z][a-z0-9+.-]*:)?\/\//i.test(file)) return file;
|
|
140
224
|
if (typeof base !== "string" || !base) base = "/";
|
|
@@ -276,7 +360,258 @@ function applyAssetTracking(context, tracking, manifest, noScripts) {
|
|
|
276
360
|
context.resolveAssetsSync = resolve;
|
|
277
361
|
}
|
|
278
362
|
}
|
|
279
|
-
|
|
363
|
+
function isCssUrl(url) {
|
|
364
|
+
const q = url.search(/[?#]/);
|
|
365
|
+
return (q === -1 ? url : url.slice(0, q)).endsWith(".css");
|
|
366
|
+
}
|
|
367
|
+
function createHeadRegistry() {
|
|
368
|
+
return {
|
|
369
|
+
pending: [],
|
|
370
|
+
committed: [],
|
|
371
|
+
seq: 0,
|
|
372
|
+
uniq: 0,
|
|
373
|
+
resources: new Set(),
|
|
374
|
+
eagerHtml: "",
|
|
375
|
+
flushed: null,
|
|
376
|
+
shellFlushed: false
|
|
377
|
+
};
|
|
378
|
+
}
|
|
379
|
+
function registerHeadTags(registry, context, tracking, emitResource, nonce, tags) {
|
|
380
|
+
const boundary = context._currentBoundaryId || "";
|
|
381
|
+
let replaceable = null;
|
|
382
|
+
for (let i = 0; i < tags.length; i++) {
|
|
383
|
+
const desc = tags[i];
|
|
384
|
+
if (!desc || !HEAD_ELIGIBLE_TAGS.has(desc.tag)) {
|
|
385
|
+
console.warn(`useHead: ignoring non-head tag`, desc);
|
|
386
|
+
continue;
|
|
387
|
+
}
|
|
388
|
+
const cls = classifyHeadTag(desc);
|
|
389
|
+
if (cls.resource) {
|
|
390
|
+
emitHeadResource(registry, context, tracking, emitResource, nonce, desc, cls.rel);
|
|
391
|
+
} else {
|
|
392
|
+
(replaceable || (replaceable = [])).push(cls.rel !== undefined ? {
|
|
393
|
+
tag: desc.tag,
|
|
394
|
+
props: desc.props,
|
|
395
|
+
key: desc.key,
|
|
396
|
+
rel: cls.rel
|
|
397
|
+
} : desc);
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
if (replaceable) registry.pending.push({
|
|
401
|
+
boundary,
|
|
402
|
+
tags: replaceable
|
|
403
|
+
});
|
|
404
|
+
}
|
|
405
|
+
function emitHeadResource(registry, context, tracking, emitResource, nonce, desc, rel) {
|
|
406
|
+
let props;
|
|
407
|
+
try {
|
|
408
|
+
props = evalHeadProps(desc.props || {}, rel !== undefined ? {
|
|
409
|
+
rel
|
|
410
|
+
} : undefined);
|
|
411
|
+
} catch (err) {
|
|
412
|
+
console.warn(`useHead: error evaluating resource tag props`, err);
|
|
413
|
+
return;
|
|
414
|
+
}
|
|
415
|
+
const identity = resourceIdentity(desc.tag, props);
|
|
416
|
+
if (registry.resources.has(identity)) return;
|
|
417
|
+
registry.resources.add(identity);
|
|
418
|
+
if (desc.tag === "link" && (rel === "stylesheet" || rel === "modulepreload")) {
|
|
419
|
+
let plain = true;
|
|
420
|
+
let gateable = rel === "stylesheet";
|
|
421
|
+
for (const name in props) {
|
|
422
|
+
if (name === "rel" || name === "href") continue;
|
|
423
|
+
plain = false;
|
|
424
|
+
if (!STYLESHEET_FETCH_META.has(name)) gateable = false;
|
|
425
|
+
}
|
|
426
|
+
if (plain && props.href != null) {
|
|
427
|
+
const isCss = isCssUrl(props.href);
|
|
428
|
+
if (rel === "stylesheet" ? isCss : !isCss) {
|
|
429
|
+
context.registerAsset(rel === "stylesheet" ? "style" : "module", props.href);
|
|
430
|
+
return;
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
if (gateable && props.href != null) {
|
|
434
|
+
const attrHtml = renderHeadAttrHtml(props);
|
|
435
|
+
const entry = {
|
|
436
|
+
href: props.href,
|
|
437
|
+
attrHtml,
|
|
438
|
+
attrs: headAttrRecord(props)
|
|
439
|
+
};
|
|
440
|
+
if (tracking.currentBoundaryId) {
|
|
441
|
+
let styles = tracking.boundaryStyles.get(tracking.currentBoundaryId);
|
|
442
|
+
if (!styles) tracking.boundaryStyles.set(tracking.currentBoundaryId, styles = new Set());
|
|
443
|
+
styles.add(entry);
|
|
444
|
+
}
|
|
445
|
+
const markup = `<link${attrHtml}>`;
|
|
446
|
+
if (emitResource) emitResource(markup, entry);else {
|
|
447
|
+
registry.eagerHtml += markup;
|
|
448
|
+
entry.emitted = true;
|
|
449
|
+
}
|
|
450
|
+
return;
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
const url = props.href || props.src;
|
|
454
|
+
if (url != null && tracking.emittedAssets.has(url)) return;
|
|
455
|
+
const markup = renderHeadTagMarkup(desc.tag, props, null, nonce);
|
|
456
|
+
if (emitResource) emitResource(markup);else registry.eagerHtml += markup;
|
|
457
|
+
}
|
|
458
|
+
function commitHeadBoundary(registry, boundary, isPendingFragment) {
|
|
459
|
+
const keep = [];
|
|
460
|
+
const groups = [];
|
|
461
|
+
for (let i = 0; i < registry.pending.length; i++) {
|
|
462
|
+
const reg = registry.pending[i];
|
|
463
|
+
const mine = boundary === "" ? !(isPendingFragment && reg.boundary !== "" && isPendingFragment(reg.boundary)) : reg.boundary === boundary;
|
|
464
|
+
if (!mine) {
|
|
465
|
+
keep.push(reg);
|
|
466
|
+
continue;
|
|
467
|
+
}
|
|
468
|
+
const tags = [];
|
|
469
|
+
for (let j = 0; j < reg.tags.length; j++) {
|
|
470
|
+
const desc = reg.tags[j];
|
|
471
|
+
let props, key;
|
|
472
|
+
try {
|
|
473
|
+
props = evalHeadProps(desc.props || {}, desc.rel !== undefined ? {
|
|
474
|
+
rel: desc.rel
|
|
475
|
+
} : undefined);
|
|
476
|
+
key = evalHeadValue(desc.key);
|
|
477
|
+
} catch (err) {
|
|
478
|
+
console.warn(`useHead: error evaluating tag props`, err);
|
|
479
|
+
continue;
|
|
480
|
+
}
|
|
481
|
+
const identity = replaceableIdentity(desc.tag, props, key, "u:" + registry.uniq++);
|
|
482
|
+
if ((identity === "base" || identity === "charset") && registry.shellFlushed) {
|
|
483
|
+
console.warn(`useHead: <${desc.tag}> (${identity}) registered after shell flush is ignored`);
|
|
484
|
+
continue;
|
|
485
|
+
}
|
|
486
|
+
tags.push({
|
|
487
|
+
tag: desc.tag,
|
|
488
|
+
props,
|
|
489
|
+
identity
|
|
490
|
+
});
|
|
491
|
+
}
|
|
492
|
+
if (tags.length) groups.push({
|
|
493
|
+
seq: registry.seq++,
|
|
494
|
+
tags
|
|
495
|
+
});
|
|
496
|
+
}
|
|
497
|
+
registry.pending = keep;
|
|
498
|
+
for (let i = 0; i < groups.length; i++) registry.committed.push(groups[i]);
|
|
499
|
+
return groups;
|
|
500
|
+
}
|
|
501
|
+
function adoptHeadBoundary(registry, childKey, parentKey) {
|
|
502
|
+
for (let i = 0; i < registry.pending.length; i++) {
|
|
503
|
+
if (registry.pending[i].boundary === childKey) registry.pending[i].boundary = parentKey;
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
function dropHeadBoundary(registry, boundary) {
|
|
507
|
+
registry.pending = registry.pending.filter(reg => reg.boundary !== boundary);
|
|
508
|
+
}
|
|
509
|
+
function headGroupSignature(winner) {
|
|
510
|
+
let sig = "" + winner.seq;
|
|
511
|
+
for (let i = 0; i < winner.tags.length; i++) {
|
|
512
|
+
const t = winner.tags[i];
|
|
513
|
+
sig += "|" + t.tag + JSON.stringify(t.props);
|
|
514
|
+
}
|
|
515
|
+
return sig;
|
|
516
|
+
}
|
|
517
|
+
function renderShellHead(registry, nonce, isPendingFragment) {
|
|
518
|
+
commitHeadBoundary(registry, "", isPendingFragment);
|
|
519
|
+
registry.shellFlushed = true;
|
|
520
|
+
const winners = resolveHead(registry.committed);
|
|
521
|
+
registry.flushed = new Map();
|
|
522
|
+
let prelude = "";
|
|
523
|
+
let links = "";
|
|
524
|
+
let metas = "";
|
|
525
|
+
let others = "";
|
|
526
|
+
let scripts = "";
|
|
527
|
+
for (const [identity, winner] of winners) {
|
|
528
|
+
registry.flushed.set(identity, headGroupSignature(winner));
|
|
529
|
+
for (let i = 0; i < winner.tags.length; i++) {
|
|
530
|
+
const t = winner.tags[i];
|
|
531
|
+
const markup = renderHeadTagMarkup(t.tag, t.props, identity, nonce);
|
|
532
|
+
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;
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
return {
|
|
536
|
+
prelude,
|
|
537
|
+
html: registry.eagerHtml + links + metas + others + scripts
|
|
538
|
+
};
|
|
539
|
+
}
|
|
540
|
+
function flushHeadFragment(registry, boundary) {
|
|
541
|
+
const groups = commitHeadBoundary(registry, boundary);
|
|
542
|
+
if (!groups.length) return null;
|
|
543
|
+
const winners = resolveHead(registry.committed);
|
|
544
|
+
const affected = new Set();
|
|
545
|
+
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);
|
|
546
|
+
const ops = [];
|
|
547
|
+
for (const identity of affected) {
|
|
548
|
+
const winner = winners.get(identity);
|
|
549
|
+
const sig = headGroupSignature(winner);
|
|
550
|
+
if (registry.flushed.get(identity) === sig) continue;
|
|
551
|
+
const existed = registry.flushed.has(identity);
|
|
552
|
+
registry.flushed.set(identity, sig);
|
|
553
|
+
if (identity === "title") {
|
|
554
|
+
const children = winner.tags[0].props.children;
|
|
555
|
+
ops.push(["t", children == null ? "" : String(children)]);
|
|
556
|
+
continue;
|
|
557
|
+
}
|
|
558
|
+
if (existed) ops.push(["r", identity]);
|
|
559
|
+
for (let i = 0; i < winner.tags.length; i++) {
|
|
560
|
+
const t = winner.tags[i];
|
|
561
|
+
const attrs = {};
|
|
562
|
+
for (const name in t.props) {
|
|
563
|
+
if (name === "children" || name === "ref" || name.slice(0, 2) === "on") continue;
|
|
564
|
+
if (!HEAD_ATTR_NAME.test(name)) {
|
|
565
|
+
console.warn(`useHead: ignoring invalid attribute name "${name}"`);
|
|
566
|
+
continue;
|
|
567
|
+
}
|
|
568
|
+
const v = t.props[name];
|
|
569
|
+
if (v == null || v === false) continue;
|
|
570
|
+
attrs[name] = v === true ? "" : String(v);
|
|
571
|
+
}
|
|
572
|
+
const children = t.props.children;
|
|
573
|
+
ops.push(["a", identity, t.tag, attrs, children == null ? null : String(children)]);
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
return ops.length ? ops : null;
|
|
577
|
+
}
|
|
578
|
+
function renderHeadAttrHtml(props) {
|
|
579
|
+
let attrs = "";
|
|
580
|
+
for (const name in props) {
|
|
581
|
+
if (name === "children" || name === "ref" || name.slice(0, 2) === "on") continue;
|
|
582
|
+
if (!HEAD_ATTR_NAME.test(name)) {
|
|
583
|
+
console.warn(`useHead: ignoring invalid attribute name "${name}"`);
|
|
584
|
+
continue;
|
|
585
|
+
}
|
|
586
|
+
const v = props[name];
|
|
587
|
+
if (v == null || v === false) continue;
|
|
588
|
+
attrs += v === true ? ` ${name}` : ` ${name}="${escape(String(v), true)}"`;
|
|
589
|
+
}
|
|
590
|
+
return attrs;
|
|
591
|
+
}
|
|
592
|
+
function headAttrRecord(props, skipRelHref) {
|
|
593
|
+
let attrs = null;
|
|
594
|
+
for (const name in props) {
|
|
595
|
+
if (name === "children" || name === "ref" || name.slice(0, 2) === "on") continue;
|
|
596
|
+
if ((name === "rel" || name === "href")) continue;
|
|
597
|
+
if (!HEAD_ATTR_NAME.test(name)) continue;
|
|
598
|
+
const v = props[name];
|
|
599
|
+
if (v == null || v === false) continue;
|
|
600
|
+
(attrs || (attrs = {}))[name] = v === true ? "" : String(v);
|
|
601
|
+
}
|
|
602
|
+
return attrs;
|
|
603
|
+
}
|
|
604
|
+
function renderHeadTagMarkup(tag, props, identity, nonce) {
|
|
605
|
+
let attrs = renderHeadAttrHtml(props);
|
|
606
|
+
if (identity != null) attrs += ` data-dh="${escape(identity, true)}"`;
|
|
607
|
+
if (nonce && (tag === "script" || tag === "style")) attrs += ` nonce="${nonce}"`;
|
|
608
|
+
if (tag === "meta" || tag === "link" || tag === "base") return `<${tag}${attrs}>`;
|
|
609
|
+
let body = props.children == null ? "" : String(props.children);
|
|
610
|
+
if (tag === "script") body = body.replace(/<\/(script)/gi, "<\\/$1");else if (tag === "style") body = escapeStyleContent(body);else body = escape(body);
|
|
611
|
+
return `<${tag}${attrs}>${body}</${tag}>`;
|
|
612
|
+
}
|
|
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])}`;
|
|
614
|
+
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)}`;
|
|
280
615
|
function renderToStream(code, options = {}) {
|
|
281
616
|
let {
|
|
282
617
|
nonce,
|
|
@@ -284,7 +619,8 @@ function renderToStream(code, options = {}) {
|
|
|
284
619
|
onCompleteAll,
|
|
285
620
|
renderId = "",
|
|
286
621
|
noScripts,
|
|
287
|
-
manifest
|
|
622
|
+
manifest,
|
|
623
|
+
onHead
|
|
288
624
|
} = options;
|
|
289
625
|
let dispose;
|
|
290
626
|
const blockingPromises = new Set();
|
|
@@ -326,8 +662,8 @@ function renderToStream(code, options = {}) {
|
|
|
326
662
|
if (styles.links.length) {
|
|
327
663
|
emitTask(`$dfs("${key}",${styles.links.length},${deferActivation ? 1 : 0})`);
|
|
328
664
|
writeTasks();
|
|
329
|
-
for (const
|
|
330
|
-
buffer.write(`<link rel="stylesheet" href="${
|
|
665
|
+
for (const entry of styles.links) {
|
|
666
|
+
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}')">`);
|
|
331
667
|
}
|
|
332
668
|
buffer.write(`<template id="${key}">${value}</template>`);
|
|
333
669
|
} else {
|
|
@@ -345,10 +681,12 @@ function renderToStream(code, options = {}) {
|
|
|
345
681
|
buffer.write(`<link rel="modulepreload" href="${value}">`);
|
|
346
682
|
} else if (type === "inline-style") {
|
|
347
683
|
buffer.write(renderInlineStyle(value, nonce));
|
|
684
|
+
} else if (type === "head-tag") {
|
|
685
|
+
buffer.write(value);
|
|
348
686
|
}
|
|
349
687
|
},
|
|
350
688
|
shell(shellHtml, meta) {
|
|
351
|
-
buffer.write(assembleDocument(shellHtml, meta.assets, meta.preloads, meta.inlineStyles, meta.tasks.length ? meta.tasks : "", nonce));
|
|
689
|
+
buffer.write(assembleDocument(shellHtml, meta.assets, meta.preloads, meta.inlineStyles, meta.tasks.length ? meta.tasks : "", nonce, meta.head, onHead));
|
|
352
690
|
},
|
|
353
691
|
...options.sink
|
|
354
692
|
};
|
|
@@ -419,10 +757,28 @@ function renderToStream(code, options = {}) {
|
|
|
419
757
|
}
|
|
420
758
|
};
|
|
421
759
|
const tracking = createAssetTracking();
|
|
760
|
+
const headRegistry = createHeadRegistry();
|
|
761
|
+
let headScriptFlushed = false;
|
|
762
|
+
const emitHeadOps = (key, ops) => {
|
|
763
|
+
const payload = JSON.stringify(ops).replace(/</g, "\\u003C");
|
|
764
|
+
emitTask(`${!headScriptFlushed ? HEAD_SCRIPT : ""}(_$HY.hp=_$HY.hp||{})[${JSON.stringify(key)}]=${payload}`);
|
|
765
|
+
headScriptFlushed = true;
|
|
766
|
+
};
|
|
422
767
|
sharedConfig.context = context = {
|
|
423
768
|
async: true,
|
|
424
769
|
assets: [],
|
|
425
770
|
nonce,
|
|
771
|
+
registerHeadTags(tags) {
|
|
772
|
+
registerHeadTags(headRegistry, context, tracking,
|
|
773
|
+
(markup, gateEntry) => {
|
|
774
|
+
if (!firstFlushed) {
|
|
775
|
+
headRegistry.eagerHtml += markup;
|
|
776
|
+
if (gateEntry) gateEntry.emitted = true;
|
|
777
|
+
} else if (!gateEntry || !tracking.currentBoundaryId) {
|
|
778
|
+
sink.asset("head-tag", markup);
|
|
779
|
+
}
|
|
780
|
+
}, nonce, tags);
|
|
781
|
+
},
|
|
426
782
|
registerAsset(type, value) {
|
|
427
783
|
if (type === "inline-style") {
|
|
428
784
|
const entry = tracking.registerInlineStyle(value);
|
|
@@ -513,10 +869,12 @@ function renderToStream(code, options = {}) {
|
|
|
513
869
|
parent.children[key] = value !== undefined ? value : "";
|
|
514
870
|
serializeFragmentAssets(key, tracking.boundaryModules, context);
|
|
515
871
|
propagateBoundaryStyles(key, parentKey, tracking);
|
|
872
|
+
adoptHeadBoundary(headRegistry, key, parentKey);
|
|
516
873
|
item.resolve();
|
|
517
874
|
return;
|
|
518
875
|
}
|
|
519
876
|
if (!completed) {
|
|
877
|
+
if (error) dropHeadBoundary(headRegistry, key);
|
|
520
878
|
if (!firstFlushed) {
|
|
521
879
|
queue(() => html = replacePlaceholder(html, key, value !== undefined ? value : ""));
|
|
522
880
|
serializeFragmentAssets(key, tracking.boundaryModules, context);
|
|
@@ -524,6 +882,8 @@ function renderToStream(code, options = {}) {
|
|
|
524
882
|
} else {
|
|
525
883
|
serializeFragmentAssets(key, tracking.boundaryModules, context);
|
|
526
884
|
const styles = collectStreamStyles(key, tracking, headStyles);
|
|
885
|
+
const headOps = error ? null : flushHeadFragment(headRegistry, key);
|
|
886
|
+
if (headOps) emitHeadOps(key, headOps);
|
|
527
887
|
sink.fragment(key, value !== undefined ? value : " ", {
|
|
528
888
|
styles,
|
|
529
889
|
revealGroup,
|
|
@@ -611,14 +971,16 @@ function renderToStream(code, options = {}) {
|
|
|
611
971
|
const assetsHtml = resolveAssetsHtml(context.assets);
|
|
612
972
|
headStyles = new Set();
|
|
613
973
|
for (const url of tracking.emittedAssets) {
|
|
614
|
-
if (url
|
|
974
|
+
if (isCssUrl(url)) headStyles.add(url);
|
|
615
975
|
}
|
|
616
976
|
serializeRootAssets();
|
|
977
|
+
const head = renderShellHead(headRegistry, nonce, k => registry.has(k));
|
|
617
978
|
sink.shell(html, {
|
|
618
979
|
assets: assetsHtml,
|
|
619
980
|
preloads: tracking.emittedAssets,
|
|
620
981
|
inlineStyles: tracking.inlineStyles,
|
|
621
|
-
tasks
|
|
982
|
+
tasks,
|
|
983
|
+
head
|
|
622
984
|
});
|
|
623
985
|
tasks = "";
|
|
624
986
|
onCompleteShell && onCompleteShell({
|
|
@@ -1004,23 +1366,42 @@ function resolveAssetsHtml(assets) {
|
|
|
1004
1366
|
for (let i = 0, len = assets.length; i < len; i++) out += assets[i]();
|
|
1005
1367
|
return out;
|
|
1006
1368
|
}
|
|
1007
|
-
function assembleDocument(html, assetsHtml, emittedAssets, inlineStyles, scripts, nonce) {
|
|
1369
|
+
function assembleDocument(html, assetsHtml, emittedAssets, inlineStyles, scripts, nonce, headTags, onHead) {
|
|
1008
1370
|
const scriptTag = scripts ? `<script${nonce ? ` nonce="${nonce}"` : ""}>${scripts}</script>` : "";
|
|
1009
|
-
|
|
1371
|
+
const headTagsHtml = headTags ? headTags.html : "";
|
|
1372
|
+
const headPrelude = headTags ? headTags.prelude : "";
|
|
1373
|
+
if (!onHead && !assetsHtml && !headTagsHtml && !headPrelude && !(emittedAssets && emittedAssets.size) && !(inlineStyles && inlineStyles.size)) {
|
|
1010
1374
|
if (!scriptTag) return html;
|
|
1011
1375
|
const xs = html.indexOf("<!--xs-->");
|
|
1012
1376
|
return xs === -1 ? html + scriptTag : html.slice(0, xs) + scriptTag + html.slice(xs);
|
|
1013
1377
|
}
|
|
1378
|
+
if (headPrelude) {
|
|
1379
|
+
const open = html.match(/<head(?:\s[^>]*)?>/);
|
|
1380
|
+
if (open) {
|
|
1381
|
+
const at = open.index + open[0].length;
|
|
1382
|
+
html = html.slice(0, at) + headPrelude + html.slice(at);
|
|
1383
|
+
}
|
|
1384
|
+
}
|
|
1014
1385
|
const headIdx = html.indexOf("</head>");
|
|
1015
1386
|
if (headIdx === -1) {
|
|
1387
|
+
if (onHead) {
|
|
1388
|
+
onHead(headPrelude + headTagsHtml + (assetsHtml || "") + renderHeadAssets(emittedAssets, inlineStyles, nonce));
|
|
1389
|
+
}
|
|
1016
1390
|
if (!scriptTag) return html;
|
|
1017
1391
|
const xs = html.indexOf("<!--xs-->");
|
|
1018
1392
|
return xs === -1 ? html + scriptTag : html.slice(0, xs) + scriptTag + html.slice(xs);
|
|
1019
1393
|
}
|
|
1020
|
-
|
|
1394
|
+
const head = headTagsHtml + (assetsHtml || "") + renderHeadAssets(emittedAssets, inlineStyles, nonce);
|
|
1395
|
+
if (!scriptTag) return html.slice(0, headIdx) + head + html.slice(headIdx);
|
|
1396
|
+
const xsIdx = html.indexOf("<!--xs-->");
|
|
1397
|
+
if (xsIdx === -1) return html.slice(0, headIdx) + head + html.slice(headIdx) + scriptTag;
|
|
1398
|
+
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);
|
|
1399
|
+
}
|
|
1400
|
+
function renderHeadAssets(emittedAssets, inlineStyles, nonce) {
|
|
1401
|
+
let head = "";
|
|
1021
1402
|
if (emittedAssets && emittedAssets.size) {
|
|
1022
1403
|
for (const url of emittedAssets) {
|
|
1023
|
-
head += url
|
|
1404
|
+
head += isCssUrl(url) ? `<link rel="stylesheet" href="${url}">` : `<link rel="modulepreload" href="${url}">`;
|
|
1024
1405
|
}
|
|
1025
1406
|
}
|
|
1026
1407
|
if (inlineStyles && inlineStyles.size) {
|
|
@@ -1030,10 +1411,7 @@ function assembleDocument(html, assetsHtml, emittedAssets, inlineStyles, scripts
|
|
|
1030
1411
|
head += renderInlineStyle(entry, nonce);
|
|
1031
1412
|
}
|
|
1032
1413
|
}
|
|
1033
|
-
|
|
1034
|
-
const xsIdx = html.indexOf("<!--xs-->");
|
|
1035
|
-
if (xsIdx === -1) return html.slice(0, headIdx) + head + html.slice(headIdx) + scriptTag;
|
|
1036
|
-
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);
|
|
1414
|
+
return head;
|
|
1037
1415
|
}
|
|
1038
1416
|
function serializeFragmentAssets(key, boundaryModules, context) {
|
|
1039
1417
|
const map = boundaryModules.get(key);
|
|
@@ -1063,7 +1441,12 @@ function collectStreamStyles(key, tracking, headStyles) {
|
|
|
1063
1441
|
for (const entry of styles) {
|
|
1064
1442
|
if (typeof entry === "string") {
|
|
1065
1443
|
if (!headStyles || !headStyles.has(entry)) links.push(entry);
|
|
1066
|
-
} else if (
|
|
1444
|
+
} else if (entry.emitted) {
|
|
1445
|
+
continue;
|
|
1446
|
+
} else if (entry.attrHtml !== undefined) {
|
|
1447
|
+
entry.emitted = true;
|
|
1448
|
+
links.push(entry);
|
|
1449
|
+
} else {
|
|
1067
1450
|
entry.emitted = true;
|
|
1068
1451
|
inline.push(entry);
|
|
1069
1452
|
}
|
|
@@ -1460,7 +1843,12 @@ function createFrameSink(emit, frame) {
|
|
|
1460
1843
|
version,
|
|
1461
1844
|
key
|
|
1462
1845
|
};
|
|
1463
|
-
if (links.length)
|
|
1846
|
+
if (links.length) {
|
|
1847
|
+
chunk.styles = links.map(e => typeof e === "string" ? e : e.attrs ? {
|
|
1848
|
+
href: e.href,
|
|
1849
|
+
attrs: e.attrs
|
|
1850
|
+
} : e.href);
|
|
1851
|
+
}
|
|
1464
1852
|
if (inline.length) {
|
|
1465
1853
|
chunk.inlineStyles = inline.map(e => ({
|
|
1466
1854
|
id: e.id,
|
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.
|
|
4
|
+
"version": "2.0.0-beta.30",
|
|
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.
|
|
319
|
+
"solid-js": "^2.0.0-beta.30"
|
|
320
320
|
},
|
|
321
321
|
"devDependencies": {
|
|
322
|
-
"solid-js": "2.0.0-beta.
|
|
322
|
+
"solid-js": "2.0.0-beta.30"
|
|
323
323
|
},
|
|
324
324
|
"scripts": {
|
|
325
325
|
"build": "npm-run-all -nl build:clean types:copy-jsx build:js",
|
package/types/client.d.ts
CHANGED
|
@@ -127,8 +127,28 @@ export function getHydrationKey(): string | undefined;
|
|
|
127
127
|
export function getNextElement(template?: () => Element): Element;
|
|
128
128
|
export function getNextMatch(start: Node, elementName: string): Element;
|
|
129
129
|
export function getNextMarker(start: Node): [Node, Array<Node>];
|
|
130
|
+
/** @deprecated Use `useHead` — removed before `0.50.0` stable. */
|
|
130
131
|
export function useAssets(fn: () => JSX.Element): void;
|
|
132
|
+
/** @deprecated Use `useHead` — removed before `0.50.0` stable. */
|
|
131
133
|
export function getAssets(): string;
|
|
134
|
+
/**
|
|
135
|
+
* A head tag descriptor. Props values may be getters (reactive on the
|
|
136
|
+
* client); `children` is the text body. `key` overrides the built-in dedupe
|
|
137
|
+
* identity (`title` is a hard singleton that `key` cannot fork).
|
|
138
|
+
*/
|
|
139
|
+
export type HeadTag = {
|
|
140
|
+
tag: "title" | "meta" | "link" | "style" | "script" | "base";
|
|
141
|
+
props: Record<string, any>;
|
|
142
|
+
key?: string | (() => string);
|
|
143
|
+
};
|
|
144
|
+
/**
|
|
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.
|
|
150
|
+
*/
|
|
151
|
+
export function useHead(tag: HeadTag | HeadTag[]): void;
|
|
132
152
|
export type AssetDescriptor =
|
|
133
153
|
| { type: "style"; href: string; attrs?: Record<string, string> }
|
|
134
154
|
| { type: "inline-style"; id: string; content?: string; attrs?: Record<string, string> }
|
|
@@ -84,6 +84,15 @@ export interface SlotContext {
|
|
|
84
84
|
* server content, or the owning frame is disposed.
|
|
85
85
|
*/
|
|
86
86
|
onCleanup(fn: () => void): void;
|
|
87
|
+
/**
|
|
88
|
+
* Live-props opt-in: a binding that registers here receives the
|
|
89
|
+
* re-resolved props when a re-sent record's args CHANGE in value, instead
|
|
90
|
+
* of the occurrence being re-called — the invocation's instance (and its
|
|
91
|
+
* client state) survives the change. Register synchronously during the
|
|
92
|
+
* invocation; one updater per occurrence (last registration wins). A
|
|
93
|
+
* genuine re-call or unmount clears it before/with the binding it served.
|
|
94
|
+
*/
|
|
95
|
+
onUpdate(fn: (props: Record<string, unknown>) => void): void;
|
|
87
96
|
/**
|
|
88
97
|
* The range's current interior — server-rendered client content on an
|
|
89
98
|
* adopted document-SSR boot, or the previous output on a re-call. A
|
|
@@ -122,6 +131,22 @@ export interface Frame {
|
|
|
122
131
|
readonly error: unknown;
|
|
123
132
|
/** Whether the named fragment has been revealed into the boundary. */
|
|
124
133
|
isRevealed(segment: string): boolean;
|
|
134
|
+
/**
|
|
135
|
+
* Re-key this live frame to a different boundary id (the mount-preserving
|
|
136
|
+
* half of a call-site handoff): nothing tears down — the element, store,
|
|
137
|
+
* and slot state stay — while leaving the old id stashes a retention
|
|
138
|
+
* snapshot under it and joining the new id seeds/drains its retained
|
|
139
|
+
* store and buffered chunks. Version affinity resets: histories are per
|
|
140
|
+
* boundary id.
|
|
141
|
+
*/
|
|
142
|
+
rebind(id: string): void;
|
|
143
|
+
/**
|
|
144
|
+
* Forget the version baseline without touching content — the next write
|
|
145
|
+
* is accepted whatever its number. Called by the host after seeding a
|
|
146
|
+
* registration from a retained snapshot, whose numbering belongs to a
|
|
147
|
+
* different stream space.
|
|
148
|
+
*/
|
|
149
|
+
rebase(): void;
|
|
125
150
|
/** Tear down: slot cleanups cascade, later chunks are ignored. Idempotent. */
|
|
126
151
|
dispose(): void;
|
|
127
152
|
}
|
|
@@ -79,6 +79,32 @@ export const SERVER_COMPONENT_SOURCE: unique symbol;
|
|
|
79
79
|
/** The call's wire address (`frameAddress`), for regions to be emitted under. */
|
|
80
80
|
export const SERVER_COMPONENT_ADDRESS: unique symbol;
|
|
81
81
|
|
|
82
|
+
/**
|
|
83
|
+
* The handoff contract on components the transport resolves: `{ fnId,
|
|
84
|
+
* frameId, take(prev) }`. A reader whose source resolved a NEW component
|
|
85
|
+
* while a previous one is mounted offers the old one — `take` rebinds the
|
|
86
|
+
* live mount when both are boundaries of the same function (the element and
|
|
87
|
+
* its slot state stay; the incoming stream morphs it), and the reader keeps
|
|
88
|
+
* its previous value instead of remounting. `Symbol.for`, so frameworks can
|
|
89
|
+
* honor it without importing this module.
|
|
90
|
+
*/
|
|
91
|
+
export const COMPONENT_HANDOFF: unique symbol;
|
|
92
|
+
|
|
93
|
+
/** The value under `COMPONENT_HANDOFF` on a transport-resolved component. */
|
|
94
|
+
export interface ComponentHandoff {
|
|
95
|
+
/** The server function id both peers derive the boundary's calls from. */
|
|
96
|
+
fnId: string;
|
|
97
|
+
/** The frame id this component's fresh mounts register under. */
|
|
98
|
+
frameId: string;
|
|
99
|
+
/**
|
|
100
|
+
* Offer `prev` (the reader's current value) to this component. Returns
|
|
101
|
+
* true when the reader should KEEP prev — the mounted frame was rebound
|
|
102
|
+
* to this component's id (or already showed it); false means swap
|
|
103
|
+
* normally (different function, unbranded prev, or nothing mounted).
|
|
104
|
+
*/
|
|
105
|
+
take(prev: unknown): boolean;
|
|
106
|
+
}
|
|
107
|
+
|
|
82
108
|
/**
|
|
83
109
|
* Seroval plugin for a server component crossing a serialization boundary:
|
|
84
110
|
* a branded component serializes as a REFERENCE — a per-function document
|