@symbo.ls/brender 3.14.676 → 3.14.678
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/CHANGELOG.md +39 -0
- package/README.md +10 -0
- package/dist/esm/env.js +1 -1
- package/dist/esm/faces.js +5 -0
- package/dist/esm/index.js +1 -1
- package/dist/esm/render.js +25 -28
- package/env.js +370 -2
- package/faces.js +193 -0
- package/index.js +10 -2
- package/package.json +11 -6
- package/render.js +5 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,44 @@
|
|
|
1
1
|
# @symbo.ls/brender
|
|
2
2
|
|
|
3
|
+
## 3.14.678
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 9e31e2f: The faces sheet `<style data-smbls-faces>` carries `data-smbls-faces-version` (sha256 of the sheet's rules, 12 hex: equal rules give an equal version) and `data-smbls-faces-files` (per `@font-face`, in rule order, the first `url()` of its `src`; `-` for a rule with no file). The helpers (`facesSheetTag`, `facesSheetMarks`, `markFacesSheet`, `faceFileOf`) are exported from the root and from the new light subpath `@symbo.ls/brender/faces`. `smbls build` / `smbls brender` mark each page again after its `file:` URLs are mapped into the output, so the version follows the shipped files. No sheet file is written.
|
|
8
|
+
|
|
9
|
+
The built-page script defines `window.__smblsFacesReady`: a promise for the faces the page draws (`document.fonts.load` per family, weight and style). It resolves with the loaded `FontFace` objects, at the time limit (the hold cap, else 3000 ms) with the ones loaded so far, and at once with `[]` when the page has no faces. It never rejects. A property that already exists is kept.
|
|
10
|
+
|
|
11
|
+
`brender.app: { start: 'afterFirstView', after: 'faces' | '<window property>' | [ … ] }`: the app's files also wait for the page's faces promise and/or project promises (a property set later is watched; a rejection counts as in; the first input and `wait` still start the app). Without `after` the start is unchanged; `after` with an eager start is an error.
|
|
12
|
+
|
|
13
|
+
A failed AVIF `<source>` (the one whose `srcset` names the image's `currentSrc`) is taken out of its `<picture>` once, on the image's error or when it failed before the script ran; the browser then picks the WebP or the `<img>` src. No loop; an image still loading is untouched.
|
|
14
|
+
|
|
15
|
+
The built-page script stays hand-written ES5: 2,115 → 4,084 B raw, 760 → 1,363 B Brotli.
|
|
16
|
+
|
|
17
|
+
- 2522f1c: A server render keeps the rules a browser keeps that linkedom's CSS parser cannot read: `@container` (the breakpoints of `breakpointsFrom: 'container'`), `@layer`, `@scope`, `@starting-style`, and an `@media` / `@supports` that holds one. They were dropped, so the page's classes went into the markup without their rules and a phone's first frame showed the desktop layout.
|
|
18
|
+
|
|
19
|
+
`smbls publish --ssr` honours a page's `prerender: false` and symbols.json `brender.exclude`, as `smbls build` and `smbls brender` do: an opted-out page costs no render and no budget, and the publish report lists it.
|
|
20
|
+
|
|
21
|
+
Page code that throws after its render (a timer, an animation frame, a rejected promise) is reported once per message and never ends the publish; each server window has its own timers.
|
|
22
|
+
|
|
23
|
+
- Updated dependencies [2522f1c]
|
|
24
|
+
- Updated dependencies [2522f1c]
|
|
25
|
+
- Updated dependencies [f718247]
|
|
26
|
+
- Updated dependencies [c21bff7]
|
|
27
|
+
- Updated dependencies [c62254b]
|
|
28
|
+
- Updated dependencies [77a4634]
|
|
29
|
+
- Updated dependencies [0d684b1]
|
|
30
|
+
- @symbo.ls/fetch@3.14.674
|
|
31
|
+
- smbls@3.14.860
|
|
32
|
+
- @symbo.ls/utils@3.14.854
|
|
33
|
+
- @symbo.ls/helmet@3.14.673
|
|
34
|
+
- @symbo.ls/scratch@3.14.796
|
|
35
|
+
|
|
36
|
+
## 3.14.677
|
|
37
|
+
|
|
38
|
+
### Patch Changes
|
|
39
|
+
|
|
40
|
+
- 0d2a8e3: A server render keeps a CSS custom property's name as it is written. linkedom's style object turned every name given to `setProperty`, `removeProperty` and `getPropertyValue` into kebab case, so `--coverBoxLeft` went into the HTML as `--cover-box-left`. That is another property (custom property names are case-sensitive): the first paint used the `var()` fallback, and the content moved when the browser wrote `--coverBoxLeft` (XMA's hero text moved 30px on the desktop home, CLS 0.0003). A `--*` name now keeps its case in the server environment; every other name goes through linkedom as before.
|
|
41
|
+
|
|
3
42
|
## 3.14.676
|
|
4
43
|
|
|
5
44
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -198,6 +198,16 @@ No inline script — a strict Content-Security-Policy (`script-src 'self'`, no `
|
|
|
198
198
|
| `<script type="application/json" data-fetch-seed>[…]</script>` | after the markup | The anonymous GET answers the render got (`@symbo.ls/fetch`), adopted by the client request that sends exactly the same request |
|
|
199
199
|
| `<meta name="brender-stamp" content="<stamp>" data-time="<ms>">` | `<head>`, after the mark | The build the page comes from: `stamp` (the `stamp` option, else the render time as an ISO string) and the render time in ms. App code reads it as `el.context.prerender` (`{ stamp, time }`) |
|
|
200
200
|
| `<link rel="expect" href="#smbls-whole" blocking="render">` + `<template id="smbls-whole">` | after the charset declaration; after the markup | `paint: { whole: true }` only: the first paint waits for the whole markup |
|
|
201
|
+
| `<style data-smbls-faces data-smbls-faces-version="…" data-smbls-faces-files="…">` | `<head>`, ahead of the page's styles | Every `@font-face` rule of the page, in one sheet, with two marks for a host (below). Only when the page has faces |
|
|
202
|
+
|
|
203
|
+
**The faces sheet and its marks.** A host that serves a returning reader the faces it keeps by address, and the rest inline, reads two marks on the sheet; brender writes no other faces file:
|
|
204
|
+
|
|
205
|
+
| Mark | Value |
|
|
206
|
+
|------|-------|
|
|
207
|
+
| `data-smbls-faces-version` | A content hash of the sheet's rules (12 hex characters): equal rules in the same order give the same version on every page and every render; any change to a rule (a file, a range, a `font-display`), a rule more or less, or another order gives a new one. |
|
|
208
|
+
| `data-smbls-faces-files` | Per `@font-face` rule, in rule order, its file address: the first `url()` of its `src` — a `local()` before it is skipped, a later `url()` (another format) is not listed — as the rule names it. `-` for a rule that names no file (only `local()`, or a `data:` URL). Space-separated; a space in an address is written `%20`. |
|
|
209
|
+
|
|
210
|
+
The marks describe the faces by address. `smbls build` marks each page again after its `file:` URLs point into the build output, so the version follows the files the page ships (every page of a build carries the same one when the font URLs are site-root or absolute; a relative URL differs by the page's directory, and so does the version). Its `brender.faces.inline` step moves files into the sheet as `data:` URLs and keeps both marks as they were: an inlined face still names its file. A tool that changes the addresses in a page itself calls `markFacesSheet(html)` (exported): it writes both marks again from the rules the sheet holds, leaves the sheet's text and its other attributes as they are, and returns a page without a faces sheet unchanged. `facesSheetMarks(rules)` returns `{ version, files }` for a sheet's text or its rules (`null` without a `@font-face`).
|
|
201
211
|
|
|
202
212
|
The runtime also reads what pages from brender up to 3.14.664 carry — the inline `window.__BRENDER__` flag, `window.__BR_REGISTRY__`, `window.__BRENDER_HYDRATE__` — and, when a policy blocked that flag, takes a `data-br` element directly in the mount node for the mark. An smbls runtime older than this format does not know the `<meta>`: keep `smbls` and `@symbo.ls/brender` at the same release.
|
|
203
213
|
|
package/dist/esm/env.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{parseHTML as
|
|
1
|
+
import{parseHTML as A}from"linkedom";const S=new WeakSet;class M{static[Symbol.hasInstance](t){return S.has(t)}}const C=r=>{let t=Object.getPrototypeOf(r.createElement("select")),e;for(;t&&!(e=Object.getOwnPropertyDescriptor(t,"value"));)t=Object.getPrototypeOf(t);!t||!e||!e.get||e.set||Object.defineProperty(t,"value",{configurable:!0,enumerable:e.enumerable,get:e.get,set(o){const s=o==null?"":String(o);let i=!1;for(const l of this.querySelectorAll("option")){const n=l.hasAttribute("value")?l.getAttribute("value"):l.textContent;!i&&n===s?(l.setAttribute("selected",""),i=!0):l.removeAttribute("selected")}}})},d=Symbol.for("@symbo.ls/brender:styleMap"),w=r=>typeof r=="string"&&r.charCodeAt(0)===45&&r.charCodeAt(1)===45,U=r=>{let t=Object.getPrototypeOf(r.createElement("div").style);for(;t&&!Object.prototype.hasOwnProperty.call(t,"setProperty");)t=Object.getPrototypeOf(t);if(!t||Object.prototype.hasOwnProperty.call(t,d))return;const{setProperty:e,removeProperty:o,getPropertyValue:s}=t;if(typeof e!="function"||typeof o!="function"||typeof s!="function")return;Object.defineProperty(t,d,{configurable:!0,get(){return this}});const i=(n,c)=>{let u="";for(;c.has(u);)u+=" ";o.call(n,u)},l=(n,c,u)=>{const a=n[d];i(n,a),u==null?a.delete(c):a.set(c,u),i(n,a)};Object.defineProperties(t,{setProperty:{configurable:!0,writable:!0,value:function(n,c){if(!w(n))return e.apply(this,arguments);l(this,n,c)}},removeProperty:{configurable:!0,writable:!0,value:function(n){if(!w(n))return o.apply(this,arguments);l(this,n,null)}},getPropertyValue:{configurable:!0,writable:!0,value:function(n){return w(n)?(s.call(this,n),this[d].get(n)):s.apply(this,arguments)}}})},T=Symbol.for("@symbo.ls/brender:groupRules"),W=/^@(container|layer|scope|starting-style|media|supports)(?![\w-])/i,_=/^@layer\s+[-\w.]+(?:\s*,\s*[-\w.]+)*\s*;?$/i,I={media:4,supports:12},O=r=>{const t=[];let e=0,o=0,s=null;const i=l=>{const n=r.slice(o,l).trim();n&&t.push(n),o=l};for(let l=0;l<r.length;l++){const n=r[l];if(s){n==="\\"?l++:n===s&&(s=null);continue}if(n==="\\"){l++;continue}if(n==="/"&&r[l+1]==="*"){const c=r.indexOf("*/",l+2);if(c===-1)return null;e===0&&!r.slice(o,l).trim()&&(o=c+2),l=c+1;continue}if(n==='"'||n==="'")s=n;else if(n==="{")e++;else if(n==="}"){if(e===0)return null;--e===0&&i(l+1)}else n===";"&&e===0&&i(l+1)}return s||e?null:(i(r.length),t)};class R{constructor(t,e,o){this.keyword=t,this.condition=e,this.cssRules=o,this.parentRule=null,this.parentStyleSheet=null;for(const s of o)s.parentRule=this}get type(){return I[this.keyword]||0}get conditionText(){return this.condition}get name(){return this.keyword==="layer"?this.condition:void 0}get cssText(){let t="";for(const e of this.cssRules)t+=e.cssText;return`@${this.keyword}${this.condition?" "+this.condition:""} {${t}}`}}class ${constructor(t){this.nameList=t,this.parentRule=null,this.parentStyleSheet=null}get type(){return 0}get cssText(){return`@layer ${this.nameList.join(", ")};`}}const N=(r,t)=>{const e={cssRules:[]};let o=null;try{if(t.call(e,r,0),e.cssRules[0])return e.cssRules[0]}catch(i){o=i}const s=P(String(r).trim(),t);if(s)return s;throw o||new SyntaxError(`Failed to parse the rule '${r}'`)},P=(r,t)=>{if(_.test(r))return new $(r.replace(/^@layer\s+/i,"").replace(/\s*;$/,"").split(/\s*,\s*/));const e=W.exec(r);if(!e)return null;const o=O(r);if(!o||o.length!==1||!r.endsWith("}"))return null;const s=q(r);if(s===-1)return null;const i=O(r.slice(s+1,-1));if(!i)return null;const l=[];for(const n of i)try{l.push(N(n,t))}catch{return null}return new R(e[1].toLowerCase(),r.slice(e[0].length,s).trim(),l)},q=r=>{let t=null;for(let e=0;e<r.length;e++){const o=r[e];if(t){o==="\\"?e++:o===t&&(t=null);continue}if(o==="\\"){e++;continue}if(o==="/"&&r[e+1]==="*"){const s=r.indexOf("*/",e+2);if(s===-1)return-1;e=s+1;continue}if(o==='"'||o==="'")t=o;else if(o==="{")return e}return-1},E=(r,t)=>{if(r.parentStyleSheet=t,r instanceof R)for(const e of r.cssRules)E(e,t)},F=r=>{const t=r.createElement("style");let e;try{e=t.sheet}catch{return}let o=e&&Object.getPrototypeOf(e);for(;o&&!Object.prototype.hasOwnProperty.call(o,"insertRule");)o=Object.getPrototypeOf(o);if(!o||Object.prototype.hasOwnProperty.call(o,T))return;const s=o.insertRule;if(typeof s!="function")return;const i=e.constructor;Object.defineProperty(o,T,{configurable:!0,value:!0}),Object.defineProperty(o,"insertRule",{configurable:!0,writable:!0,value:function(p,h){try{return s.apply(this,arguments)}catch(b){const f=String(p),g=P(f.trim(),s);if(!g)throw b;const y=h===void 0?0:Number(h);if(!(y>=0&&y<=this.cssRules.length))throw new RangeError("INDEX_SIZE_ERR");return E(g,this),this.cssRules.splice(y,0,g),y}}});let l=Object.getPrototypeOf(t),n;for(;l&&!(n=Object.getOwnPropertyDescriptor(l,"sheet"));)l=Object.getPrototypeOf(l);if(!l||!n||typeof n.get!="function"||typeof i!="function")return;const c=n.get,u=new WeakMap;Object.defineProperty(l,"sheet",{configurable:!0,enumerable:n.enumerable,get(){try{return c.call(this)}catch(a){const p=this.textContent,h=u.get(this);if(h&&h.text===p)return h.sheet;const b=O(p);if(!b)throw a;const f=new i;for(const g of b)try{f.insertRule(g,f.cssRules.length)}catch{}return u.set(this,{text:p,sheet:f}),f}}})},m=new Set,j=r=>{const t=r&&r.message||String(r);m.has(t)||(m.size>=200&&m.clear(),m.add(t),console.warn(`[brender] page code threw in a timer; reported as a browser reports it, the render goes on (once per message): ${t}`))},v=r=>function(...t){try{const e=r.apply(this,t);return e&&typeof e.then=="function"&&e.then(null,j),e}catch(e){j(e)}},L=r=>(t,...e)=>globalThis[r](typeof t=="function"?v(t):t,...e),k=()=>{const r={};return{getItem:t=>r[t]??null,setItem:(t,e)=>{r[t]=String(e)},removeItem:t=>{delete r[t]},clear:()=>{for(const t in r)delete r[t]},get length(){return Object.keys(r).length},key:t=>Object.keys(r)[t]??null}},G=(r="<!DOCTYPE html><html><head></head><body></body></html>")=>{const t={location:{pathname:"/",search:"",hash:"",origin:"http://localhost",protocol:"http:",host:"localhost",hostname:"localhost",port:"",href:"http://localhost/"},localStorage:k(),sessionStorage:k(),history:{pushState:()=>{},replaceState:()=>{},state:null},setTimeout:L("setTimeout"),setInterval:L("setInterval")},{window:e,document:o}=A(r,t);return e.requestAnimationFrame||(e.requestAnimationFrame=s=>{const i=setTimeout(typeof s=="function"?v(s):s,0);return i?.unref&&i.unref(),i}),e.cancelAnimationFrame||(e.cancelAnimationFrame=s=>clearTimeout(s)),e.URL||(e.URL=URL),e.scrollTo||(e.scrollTo=()=>{}),globalThis.localStorage=t.localStorage,globalThis.sessionStorage=t.sessionStorage,globalThis.history||(globalThis.history=t.history),C(o),U(o),F(o),e.MutationObserver||(e.MutationObserver=class{constructor(){}observe(){}disconnect(){}takeRecords(){return[]}}),globalThis.window=e,globalThis.document=o,globalThis.Node=e.Node||globalThis.Node,globalThis.Element=e.Element||globalThis.Element,globalThis.HTMLElement=e.HTMLElement||globalThis.HTMLElement,globalThis.MutationObserver=e.MutationObserver,S.add(e),e.constructor&&e.constructor!==Object?globalThis.Window=e.constructor:(typeof globalThis.Window!="function"||globalThis.Window===Object)&&(globalThis.Window=M),{window:e,document:o}};export{G as createEnv};
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import{createHash as S}from"crypto";import{stylesheetRules as x}from"./pageWeight.js";const u="data-smbls-faces",h="data-smbls-faces-version",A="data-smbls-faces-files",F="-",y=/^@font-face\b/i,T=t=>typeof t=="string"&&y.test(t.trim()),E=t=>{const e={},s=t.indexOf("{");if(s===-1)return e;let o=s+1,n=null,i=0;const c=r=>{const l=t.slice(o,r),a=l.indexOf(":");if(a!==-1){const f=l.slice(0,a).trim().toLowerCase();f&&!(f in e)&&(e[f]=l.slice(a+1).trim())}o=r+1};for(let r=o;r<t.length;r++){const l=t[r];if(n){l==="\\"?r++:l===n&&(n=null);continue}if(l==="\\"){r++;continue}if(l==='"'||l==="'")n=l;else if(l==="(")i++;else if(l===")")i=Math.max(0,i-1);else{if(i)continue;if(l===";")c(r);else if(l==="}"){c(r);break}}}return e},_=t=>{const e=parseInt(t,16);return e>0&&e<=1114111&&!(e>=55296&&e<=57343)?String.fromCodePoint(e):"\uFFFD"},$=t=>{const e=t.trim(),s=e[0];return((s==='"'||s==="'")&&e.length>1&&e[e.length-1]===s?e.slice(1,-1):e).replace(/\\(?:([0-9a-fA-F]{1,6})\s?|([\s\S]))/g,(n,i,c)=>i?_(i):c).trim()},b=t=>{if(typeof t!="string")return null;let e=null;for(let s=0;s<t.length;s++){const o=t[s];if(e){o==="\\"?s++:o===e&&(e=null);continue}if(o==='"'||o==="'"){e=o;continue}if(!/^url\(/i.test(t.slice(s,s+4))||/[\w-]/.test(t[s-1]||""))continue;let n=s+4;for(;n<t.length&&/\s/.test(t[n]);)n++;let i;if(t[n]==='"'||t[n]==="'"){for(i=n+1;i<t.length&&t[i]!==t[n];)i+=t[i]==="\\"?2:1;i++}else i=t.indexOf(")",n),i===-1&&(i=t.length);const c=$(t.slice(n,i));return c&&!/^data:/i.test(c)?c:null}return null},R=t=>t?t.replace(/\s/g,e=>"%"+e.charCodeAt(0).toString(16).toUpperCase().padStart(2,"0")):F,p=t=>{const e=typeof t=="string"?x(t):Array.isArray(t)?t:null;if(!e)return null;const s=e.map(n=>String(n).trim()).filter(Boolean),o=s.filter(T);return o.length?{version:S("sha256").update(s.join(`
|
|
2
|
+
`)).digest("hex").slice(0,12),files:o.map(n=>R(b(E(n).src)))}:null},g=t=>String(t).replace(/&/g,"&").replace(/"/g,""").replace(/</g,"<"),d=t=>t?` ${h}="${g(t.version)}" ${A}="${g(t.files.join(" "))}"`:"",I=t=>!Array.isArray(t)||!t.length?"":`<style ${u}${d(p(t))}>
|
|
3
|
+
${t.join(`
|
|
4
|
+
`)}
|
|
5
|
+
</style>`,C=/<script\b[\s\S]*?<\/script\s*>|<style\b([^>]*)>([\s\S]*?)(<\/style\s*>)/gi,m=/(^|\s)data-smbls-faces(?:\s*=\s*(?:"[^"]*"|'[^']*'|[^\s>]+))?(?=[\s/]|$)/i,O=/\s(?:data-smbls-faces-version|data-smbls-faces-files)(?:\s*=\s*(?:"[^"]*"|'[^']*'|[^\s>]+))?/gi,j=t=>{if(typeof t!="string"||!t.includes(u))return t;let e=!1;return t.replace(C,(s,o,n,i)=>{if(e||o===void 0||!m.test(o))return s;e=!0;const c=p(n);if(!c)return s;const r=o.replace(O,""),l=m.exec(r),a=l.index+l[0].length;return`<style${r.slice(0,a)}${d(c)}${r.slice(a)}>${n}${i}`})};export{A as FACES_FILES_ATTR,u as FACES_SHEET_ATTR,h as FACES_VERSION_ATTR,F as NO_FACE_FILE,b as faceFileOf,p as facesSheetMarks,I as facesSheetTag,j as markFacesSheet};
|
package/dist/esm/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{createEnv as e}from"./env.js";import{resetKeys as r,assignKeys as t,mapKeysToElements as o}from"./keys.js";import{loadProject as a,loadAndRenderAll as m}from"./load.js";import{render as
|
|
1
|
+
import{createEnv as e}from"./env.js";import{resetKeys as r,assignKeys as t,mapKeysToElements as o}from"./keys.js";import{loadProject as a,loadAndRenderAll as m}from"./load.js";import{render as s,renderElement as p,renderRoute as i,renderPage as l,resetGlobalCSSCache as d,getAccumulatedEmotionCSS as n,replaceEmotionCSS as c,adoptionScripts as f,prerenderParts as S,buildStamp as g}from"./render.js";import{extractMetadata as h,generateHeadHtml as u}from"./metadata.js";import{collectBrNodes as P,hydrate as y}from"./hydrate.js";import{generateSitemap as C}from"./sitemap.js";import{prefetchPageData as E,injectPrefetchedState as R}from"./prefetch.js";import{composeIntoShell as b}from"./shell.js";import{trimSpriteToPage as k,classRulesForPage as x,markupClasses as A,trimVariablesToPage as K,stylesheetRules as T}from"./pageWeight.js";import{markFacesSheet as j,facesSheetMarks as F}from"./faces.js";var z={createEnv:e,resetKeys:r,assignKeys:t,mapKeysToElements:o,loadProject:a,loadAndRenderAll:m,render:s,renderElement:p,renderRoute:i,renderPage:l,resetGlobalCSSCache:d,getAccumulatedEmotionCSS:n,replaceEmotionCSS:c,extractMetadata:h,generateHeadHtml:u,collectBrNodes:P,hydrate:y,generateSitemap:C,prefetchPageData:E,injectPrefetchedState:R,composeIntoShell:b,adoptionScripts:f,prerenderParts:S,buildStamp:g,trimSpriteToPage:k,classRulesForPage:x,markupClasses:A,trimVariablesToPage:K,stylesheetRules:T,markFacesSheet:j,facesSheetMarks:F};export{f as adoptionScripts,t as assignKeys,g as buildStamp,x as classRulesForPage,P as collectBrNodes,b as composeIntoShell,e as createEnv,z as default,h as extractMetadata,F as facesSheetMarks,u as generateHeadHtml,C as generateSitemap,n as getAccumulatedEmotionCSS,y as hydrate,R as injectPrefetchedState,m as loadAndRenderAll,a as loadProject,o as mapKeysToElements,j as markFacesSheet,A as markupClasses,E as prefetchPageData,S as prerenderParts,s as render,p as renderElement,l as renderPage,i as renderRoute,c as replaceEmotionCSS,d as resetGlobalCSSCache,r as resetKeys,T as stylesheetRules,k as trimSpriteToPage,K as trimVariablesToPage};
|
package/dist/esm/render.js
CHANGED
|
@@ -1,23 +1,20 @@
|
|
|
1
|
-
import{resolve as d,join as tt,dirname as j}from"path";import{existsSync as S,unlinkSync as ot,readFileSync as Y,realpathSync as nt}from"fs";import{resolvePackageEntry as st}from"./packageEntry.js";import{tmpdir as rt}from"os";import{randomBytes as ct}from"crypto";import{createRequire as ge}from"module";import{pathToFileURL as it}from"url";import{createEnv as je}from"./env.js";import{resetKeys as Te,assignKeys as ve,mapKeysToElements as Le}from"./keys.js";import{extractMetadata as he,generateHeadHtml as lt}from"./metadata.js";import"./hydrate.js";import{composeIntoShell as at}from"./shell.js";import{trimSpriteToPage as ft,classRulesForPage as pt,markupClasses as ut,trimVariablesToPage as mt}from"./pageWeight.js";import{fetchSSRTranslations as dt}from"./prefetch.js";import{createEarlySeedScript as gt}from"@symbo.ls/fetch";import{headerEntries as ht}from"@symbo.ls/fetch/rest";import{brenderStampMeta as yt,brenderLazyMeta as $e,escapeRegExp as bt,jsonForScript as St,libraryPackageSpecs as Rt,polyglotFallbackLang as ye,polyglotLangStorageKey as wt,polyglotAddressLang as kt,polyglotStartupScript as xt}from"@symbo.ls/utils";import{brenderRegistryBlock as _t,brenderRegistryTreeBlock as Et}from"@symbo.ls/utils/server";import*as jt from"@symbo.ls/utils/server";let ce=null;const Tt=async()=>{if(ce)return ce;try{return ce=(await import("@symbo.ls/funcql")).funcqlPlugin,ce}catch{return null}};import"linkedom";import{createEarlySeedBlock as vt,fetchRunsOf as Lt}from"@symbo.ls/fetch";import{reset as $t}from"@symbo.ls/css";const ie=(e,t)=>{if(!e||!t)return;if(e[t]!==void 0)return e[t];const o=t.split(".");let n=e;for(const s of o){if(n==null||typeof n!="object")return;n=n[s]}return n},le=(e,t)=>ye(e?.lang,t),At=e=>{const t=jt.revivePolyglotDefaultLang;if(typeof t!="function"||!e||typeof e!="object")return e;const o=e.polyglot?t(e.polyglot):e.polyglot,n=e.config&&e.config.polyglot,s=n&&t(n);if(o===e.polyglot&&s===n)return e;const r={...e};return o!==e.polyglot&&(r.polyglot=o),s!==n&&(r.config={...e.config,polyglot:s}),r},Ae=(e,t,o,n)=>{if(o?.translations){const r=o.translations[t];if(r){const i=ie(r,e);if(i!==void 0)return i}}if(n?.translations){const r=n.translations[t];if(r){const i=ie(r,e);if(i!==void 0)return i}}const s=ye(void 0,o);if(s!==t&&o?.translations){const r=o.translations[s];if(r){const i=ie(r,e);if(i!==void 0)return i}}},Dt=function(e,t){if(!e)return"";const o=this?.context,n=o?.polyglot,s=this?.state?.root||o?.state?.root,r=Ae(e,t||le(s,n),n,s);if(r!==void 0)return r;const i=we(Re);return n&&i&&i.labelKeys.add(e),e},De=function(){return le(this?.state?.root,this?.context?.polyglot)},L=(e,t=new WeakMap)=>{if(e===null||typeof e!="object")return e;if(t.has(e))return t.get(e);if(Array.isArray(e)){const n=[];t.set(e,n);for(const s of e)n.push(typeof s=="object"&&s!==null?L(s,t):s);return n}const o={};t.set(e,o);for(const n of Object.keys(e)){const s=e[n];o[n]=typeof s=="object"&&s!==null?L(s,t):s}return o};let q=null;try{import.meta.url&&(q=ge(import.meta.url))}catch{}const Ct=()=>{if(!import.meta.url||!q)return{isMonorepo:!1,monorepoRoot:null,resolvePackage:a=>a};const e=nt(new URL(".",import.meta.url).pathname),t=d(e,"../..");if(S(d(t,"packages","smbls","src","createDomql.js")))return{isMonorepo:!0,monorepoRoot:t};let n;try{const a=q.resolve("smbls/package.json");n=j(a)}catch{let a=e;for(;a!==j(a);){const g=d(a,"node_modules","smbls");if(S(d(g,"package.json"))){n=g;break}a=j(a)}}let s=q;if(n)try{s=ge(d(n,"package.json"))}catch{}let r,i=process.cwd();for(;i!==j(i);){if(S(d(i,"package.json"))){r=i;break}i=j(i)}let u=s;if(r)try{u=ge(d(r,"package.json"))}catch{}return{isMonorepo:!1,smblsRoot:n,brenderDir:e,projectRoot:r,_smblsRequire:s,_projectRequire:u}},H=(e,t)=>{const o=e.isMonorepo?[q]:[e._smblsRequire,e._projectRequire,q].filter(Boolean);for(const n of o)try{return n.resolve(t)}catch{}return null},en=(e,t,...o)=>{if(e.isMonorepo)return d(e.monorepoRoot,"packages",t,...o);const n=H(e,`${t}/package.json`);return n?d(j(n),...o):null},tn=(e,t,...o)=>{if(e.isMonorepo)return d(e.monorepoRoot,"plugins",t,...o);const n=H(e,`@symbo.ls/${t}/package.json`);return n?d(j(n),...o):null},be=(e,t,...o)=>{if(e.isMonorepo){for(const s of["packages","plugins"]){const r=d(e.monorepoRoot,s,t,...o);if(S(r))return r}return null}const n=H(e,`@symbo.ls/${t}/package.json`);return n?d(j(n),...o):null},Ce=(e,t,...o)=>{if(e.isMonorepo)return d(e.monorepoRoot,"packages","domql","packages",t,...o);const n=H(e,`@symbo.ls/${t}/package.json`);return n?d(j(n),...o):null};let ae=null;const Me="smbls/ssr",Mt=async()=>{const e=await import("smbls/ssr");if(typeof e?.createDomqlElement!="function")throw new Error("smbls/ssr does not export createDomqlElement");return e},Oe=()=>(ae||(ae=Ot().catch(e=>{throw ae=null,e})),ae),Ot=async()=>{const e=Ct();let t;if(q&&e.isMonorepo)t=d(e.monorepoRoot,"packages","smbls","src","createDomql.js");else{let f;try{return await Mt()}catch(l){f=l}if(!q)throw new Error(`brender: ${Me} is not available in this bundled runtime (${f.message}). Bundle smbls \u2265 the release that adds the smbls/ssr export alongside @symbo.ls/brender.`);const c=e.smblsRoot&&d(e.smblsRoot,"src","createDomql.js");if(c&&S(c))t=c;else throw new Error(`brender: cannot load ${Me} (${f.message}). Server rendering needs smbls installed in this project, at a release that exports smbls/ssr`+(e.smblsRoot?` (found smbls at ${e.smblsRoot}).`:" (no smbls package found)."))}const o=await import("esbuild"),n=tt(rt(),`br_createDomql_${ct(6).toString("hex")}.mjs`),s=st,r=e.isMonorepo?await import(it(d(e.monorepoRoot,"scripts","vite-workspace-source.mjs")).href):null,i=r?new Set(r.workspacePackageRoots().map(f=>r.workspacePackageAt(f).name)):null,u=/^(?:smbls(?:\/.*)?|domql|css-in-props(?:\/.*)?|@symbo\.ls\/[^/]+)$/,a={name:"workspace-resolve",setup(f){r&&(f.onResolve({filter:/^[^./]/},c=>{if(u.test(c.path))return;const l=c.path.split("/"),p=c.path.startsWith("@")?l.slice(0,2).join("/"):l[0];if(!i.has(p))return;const h=r.resolveWorkspaceSource(c.path,c.importer||t);return h?{path:h}:{errors:[{text:`"${c.path}" names no source file in the workspace`}]}}),f.onLoad({filter:/[\\/]dist[\\/]/},c=>r.isWorkspaceDist(c.path)?{errors:[{text:`${c.path} is a BUILD of a workspace package \u2014 the render bundles source`}]}:void 0)),f.onResolve({filter:/^smbls/},c=>{const l=c.path.replace(/^smbls\/?/,""),p=e.isMonorepo?d(e.monorepoRoot,"packages","smbls"):e.smblsRoot;if(!p)return;if(!l){const k=s(p);return k?{path:k}:void 0}const h=d(p,l);if(S(h))return{path:h};if(S(h+".js"))return{path:h+".js"};const R=d(h,"index.js");if(S(R))return{path:R}}),f.onResolve({filter:/^domql$/},c=>{if(e.isMonorepo){const l=d(e.monorepoRoot,"packages","domql","src","index.js");if(S(l))return{path:l};const p=d(e.monorepoRoot,"packages","domql","index.js");if(S(p))return{path:p}}else try{const l=_require.resolve("domql/package.json"),p=s(j(l));if(p)return{path:p}}catch{}}),f.onResolve({filter:/^@symbo\.ls\//},c=>{const l=c.path.replace("@symbo.ls/","");if(l==="sync")return{path:"sync-stub",namespace:"brender-stub"};if(e.isMonorepo){for(const h of["packages","plugins"]){const R=d(e.monorepoRoot,h,l,"src","index.js");if(S(R))return{path:R};const k=d(e.monorepoRoot,h,l,"index.js");if(S(k))return{path:k}}const p=d(e.monorepoRoot,"packages","default-config","blank","index.js");if(l==="default-config"&&S(p))return{path:p}}else{const p=be(e,l,"src","index.js");if(p&&S(p))return{path:p};const h=be(e,l,"index.js");if(h&&S(h))return{path:h};if(l==="default-config"){const R=be(e,"default-config","blank","index.js");if(R&&S(R))return{path:R}}}}),f.onResolve({filter:/^@domql\//},c=>{const l=c.path.replace("@symbo.ls/","");if(e.isMonorepo){const p=d(e.monorepoRoot,"packages","domql","packages",l,"src","index.js");if(S(p))return{path:p};const h=d(e.monorepoRoot,"packages","domql","packages",l,"index.js");if(S(h))return{path:h}}else{const p=Ce(e,l,"src","index.js");if(p&&S(p))return{path:p};const h=Ce(e,l,"index.js");if(h&&S(h))return{path:h}}}),f.onResolve({filter:/^css-in-props/},c=>{let l;if(e.isMonorepo)l=d(e.monorepoRoot,"packages","css-in-props");else{const R=H(e,"css-in-props/package.json");R&&(l=j(R))}if(!l)return;const p=c.path.replace(/^css-in-props\/?/,"");if(p){const R=d(l,p),k=d(R,"index.js");if(S(k))return{path:k};if(S(R+".js"))return{path:R+".js"};if(S(R))return{path:R}}const h=s(l);if(h)return{path:h}}),f.onResolve({filter:/^@emotion\//},c=>{let l;if(e.isMonorepo)l=d(e.monorepoRoot,"node_modules",c.path);else{const p=H(e,`${c.path}/package.json`);if(!p)return;l=j(p)}if(S(l)){const p=d(l,"package.json");if(S(p))try{const h=JSON.parse(Y(p,"utf8")),R=h.module||h.main||"dist/emotion-css.esm.js";return{path:d(l,R)}}catch{}return{path:l}}}),f.onResolve({filter:/\.json$/},c=>{if(c.resolveDir){const l=d(c.resolveDir,c.path);if(S(l))return{path:l}}}),f.onLoad({filter:/smbls\/.*options\.js$/},async c=>{if(!c.path.includes("smbls/")||!c.path.endsWith("options.js"))return;let l=Y(c.path,"utf8");return l=l.replace(/import\s*\{[^}]*version[^}]*\}\s*from\s*['"][^'"]*package\.json['"][^;\n]*/,"const version = '0.0.0'"),l=l.replace(/import\s*\{[^}]*createRequire[^}]*\}\s*from\s*['"]module['"][^;\n]*/g,"// createRequire removed for brender build"),{contents:l,loader:"js"}}),f.onLoad({filter:/smbls\/.*init\.js$/},async c=>{if(!c.path.includes("smbls/")||!c.path.endsWith("init.js"))return;let l=Y(c.path,"utf8");return l=l.replace(/import\s*\{[^}]*createRequire[^}]*\}\s*from\s*['"]module['"][^;\n]*/g,"// createRequire removed for brender build"),{contents:l,loader:"js"}}),f.onLoad({filter:/.*/,namespace:"brender-stub"},()=>({contents:"export const SyncComponent = {}; export const Inspect = {}; export const Notifications = {}; export const syncPlugin = () => ({}); export const notificationsPlugin = () => ({}); export const seedRawDesignSystem = () => {}; export const inspectPlugin = () => ({}); export default {}",loader:"js"})),f.onLoad({filter:/smbls\/src\/router\.js$/},async c=>{let l=Y(c.path,"utf8");return l=l.replace(/import\s*\{\s*Link\s*\}\s*from\s*['"]smbls['"]/,"const Link = { tag: 'a', attr: { href: (el) => el.href } }"),{contents:l,loader:"js"}}),f.onLoad({filter:/fetchOnCreate\.js$/},async c=>{if(!c.path.includes("smbls/"))return;let l=Y(c.path,"utf8");return l=l.replace(/window\s*&&\s*window\.location\s*\?\s*window\.location\.host\.includes/g,"window && window.location && window.location.host ? window.location.host.includes"),{contents:l,loader:"js"}})}};await o.build({entryPoints:[t],bundle:!0,format:"esm",platform:"node",outfile:n,write:!0,logLevel:"warning",plugins:[a],nodePaths:e.isMonorepo?[d(e.monorepoRoot,"node_modules")]:[...e.smblsRoot?[d(e.smblsRoot,"node_modules")]:[],...e.projectRoot?[d(e.projectRoot,"node_modules")]:[],...e.smblsRoot?[d(e.smblsRoot,"..","..","node_modules")]:[]].filter(f=>S(f)),supported:{"import-attributes":!1},external:["fs","path","os","crypto","url","http","https","stream","util","events","buffer","child_process","worker_threads","net","tls","dns","dgram","zlib","assert","querystring","string_decoder","readline","perf_hooks","async_hooks","v8","vm","cluster","inspector","module","process","tty","color-contrast-checker","linkedom"]});const g=await import(`file://${n}`);if(process.env.BRENDER_DEBUG)console.log("[brender] Bundle saved:",n);else try{ot(n)}catch{}return g},qt={Box:{},Focusable:{},Block:{display:"block"},Inline:{display:"inline"},Flex:{display:"flex"},InlineFlex:{display:"inline-flex"},Grid:{display:"grid"},InlineGrid:{display:"inline-grid"},Link:{tag:"a",attr:{href:e=>e.href,target:e=>e.target,rel:e=>e.rel}},A:{extends:"Link"},RouteLink:{extends:"Link"},Img:{tag:"img",attr:{src:e=>{let t=e.src;return typeof t=="string"&&t.includes("{{")&&(t=e.call("replaceLiteralsWithObjectFields",t,e.state)),t},alt:e=>e.alt,loading:e=>e.loading}},Image:{extends:"Img"},Button:{tag:"button"},FocusableComponent:{tag:"button"},Form:{tag:"form"},Input:{tag:"input"},TextArea:{tag:"textarea"},Textarea:{tag:"textarea"},Select:{tag:"select"},Label:{tag:"label"},Iframe:{tag:"iframe"},Video:{tag:"video"},Audio:{tag:"audio"},Canvas:{tag:"canvas"},Span:{tag:"span"},P:{tag:"p"},H1:{tag:"h1"},H2:{tag:"h2"},H3:{tag:"h3"},H4:{tag:"h4"},H5:{tag:"h5"},H6:{tag:"h6"},Svg:{tag:"svg",attr:{xmlns:"http://www.w3.org/2000/svg","xmlns:xlink":"http://www.w3.org/1999/xlink"}},Text:{tag:"span"}},Pt=(e,t)=>{const o={};return qe(e,(n,s)=>{if(!s.__brKey)return;const r=t(n);o[r===""?"__root":r]=s.__brKey}),o},qe=(e,t)=>{const o=n=>{const s=n&&n.__ref;if(s&&(t(n,s),s.__children))for(const r of s.__children)o(n[r])};o(e)},It=new Set(["app","config","pages","components","state","snippets","designSystem","dependencies","functions","methods","files","sharedLibraries"]);let z=0;const Pe=Symbol.for("brender.fetchTracked"),Se=()=>z,Ft=256*1024,Gt=1024*1024,Bt=new Set(["accept-language","accept"]),Ie={renders:0,record:null},Kt=50,Re={renders:0,record:null},Fe=(e,t)=>{e.renders++,e.renders===1?e.record=t():e.record&&(e.record.tainted=!0);const o=e.record;return{record:o,close:()=>{e.renders--,e.record===o&&(e.record=null)}}},we=e=>e.record&&!e.record.tainted?e.record:null,Nt=e=>{try{const t=new URL(e);return t.origin+t.pathname}catch{return String(e).split("?")[0]}},ke=(e,t,o)=>{!e||e.tainted||e.failures.length>=Kt||e.failures.push({url:Nt(t),reason:o})},Ge=e=>e&&typeof e=="object"&&typeof e.url=="string"?e.url:String(e),Ht=e=>{const[t,o]=e,n=t&&typeof t=="object"&&typeof t.url=="string",s=Ge(t),r=String(o&&o.method||n&&t.method||"GET").toUpperCase(),i={},u=a=>{if(!a)return;const g=!Array.isArray(a)&&typeof a.entries!="function";for(const[f,c]of ht(a))g&&c==null||(i[String(f).toLowerCase()]=String(c))};return n&&u(t.headers),o&&u(o.headers),{url:s,method:r,headers:i}},zt=(e,t,o,n)=>{if(!o||!o.ok||typeof o.clone!="function")return;const s=o.headers&&o.headers.get&&o.headers.get("content-type")||"";if(!/json|^text\//i.test(s))return;let r;try{r=o.clone()}catch{return}n(r.text().then(i=>{e.tainted||i.length>Ft||e.bytes+i.length>Gt||(e.bytes+=i.length,e.entries.push({url:t.url,lang:t.headers["accept-language"]||"",status:o.status,type:s,body:i}))},()=>{}))},Wt=()=>{const e=globalThis.fetch;if(typeof e!="function"||e[Pe])return;const t=function(...o){z++;let n=!1;const s=()=>{n||(n=!0,z--)},r=c=>{z++,c.then(()=>{z--},()=>{z--})},i=we(Ie),u=we(Re);let a=null;if(i)try{a=Ht(o),(a.method!=="GET"||!/^https?:/i.test(a.url)||Object.keys(a.headers).some(c=>!Bt.has(c)))&&(a=null)}catch{a=null}const g=()=>Ge(o[0]);let f;try{f=e.apply(this,o)}catch(c){throw ke(u,g(),c&&c.message||"failed"),s(),c}return f&&typeof f.then=="function"?f.then(c=>{a&&zt(i,a,c,r),u&&c&&c.ok===!1&&ke(u,g(),"HTTP "+c.status),s()},c=>{ke(u,g(),c&&c.message||"network error"),s()}):s(),f};t[Pe]=!0,globalThis.fetch=t},on=({brRegistry:e,fetchSeed:t,hydrateFlag:o=!0}={})=>{if(!e||!Object.keys(e).length)return"";const n=St(e);return o?`<script>window.__BR_REGISTRY__=${n};window.__BRENDER_HYDRATE__=true<\/script>`+(t&&t.length?`
|
|
2
|
-
`+
|
|
1
|
+
import{resolve as d,join as tt,dirname as j}from"path";import{existsSync as S,unlinkSync as ot,readFileSync as J,realpathSync as nt}from"fs";import{resolvePackageEntry as st}from"./packageEntry.js";import{tmpdir as rt}from"os";import{randomBytes as ct}from"crypto";import{createRequire as ge}from"module";import{pathToFileURL as it}from"url";import{createEnv as je}from"./env.js";import{resetKeys as Te,assignKeys as ve,mapKeysToElements as Le}from"./keys.js";import{extractMetadata as he,generateHeadHtml as lt}from"./metadata.js";import"./hydrate.js";import{composeIntoShell as at}from"./shell.js";import{trimSpriteToPage as ft,classRulesForPage as pt,markupClasses as ut,trimVariablesToPage as mt}from"./pageWeight.js";import{facesSheetTag as dt}from"./faces.js";import{fetchSSRTranslations as gt}from"./prefetch.js";import{createEarlySeedScript as ht}from"@symbo.ls/fetch";import{headerEntries as yt}from"@symbo.ls/fetch/rest";import{brenderStampMeta as bt,brenderLazyMeta as Ae,escapeRegExp as St,jsonForScript as Rt,libraryPackageSpecs as wt,polyglotFallbackLang as ye,polyglotLangStorageKey as kt,polyglotAddressLang as xt,polyglotStartupScript as _t}from"@symbo.ls/utils";import{brenderRegistryBlock as Et,brenderRegistryTreeBlock as jt}from"@symbo.ls/utils/server";import*as Tt from"@symbo.ls/utils/server";let ce=null;const vt=async()=>{if(ce)return ce;try{return ce=(await import("@symbo.ls/funcql")).funcqlPlugin,ce}catch{return null}};import"linkedom";import{createEarlySeedBlock as Lt,fetchRunsOf as At}from"@symbo.ls/fetch";import{reset as $t}from"@symbo.ls/css";const ie=(e,t)=>{if(!e||!t)return;if(e[t]!==void 0)return e[t];const o=t.split(".");let n=e;for(const s of o){if(n==null||typeof n!="object")return;n=n[s]}return n},le=(e,t)=>ye(e?.lang,t),Dt=e=>{const t=Tt.revivePolyglotDefaultLang;if(typeof t!="function"||!e||typeof e!="object")return e;const o=e.polyglot?t(e.polyglot):e.polyglot,n=e.config&&e.config.polyglot,s=n&&t(n);if(o===e.polyglot&&s===n)return e;const r={...e};return o!==e.polyglot&&(r.polyglot=o),s!==n&&(r.config={...e.config,polyglot:s}),r},$e=(e,t,o,n)=>{if(o?.translations){const r=o.translations[t];if(r){const i=ie(r,e);if(i!==void 0)return i}}if(n?.translations){const r=n.translations[t];if(r){const i=ie(r,e);if(i!==void 0)return i}}const s=ye(void 0,o);if(s!==t&&o?.translations){const r=o.translations[s];if(r){const i=ie(r,e);if(i!==void 0)return i}}},Ct=function(e,t){if(!e)return"";const o=this?.context,n=o?.polyglot,s=this?.state?.root||o?.state?.root,r=$e(e,t||le(s,n),n,s);if(r!==void 0)return r;const i=we(Re);return n&&i&&i.labelKeys.add(e),e},De=function(){return le(this?.state?.root,this?.context?.polyglot)},L=(e,t=new WeakMap)=>{if(e===null||typeof e!="object")return e;if(t.has(e))return t.get(e);if(Array.isArray(e)){const n=[];t.set(e,n);for(const s of e)n.push(typeof s=="object"&&s!==null?L(s,t):s);return n}const o={};t.set(e,o);for(const n of Object.keys(e)){const s=e[n];o[n]=typeof s=="object"&&s!==null?L(s,t):s}return o};let q=null;try{import.meta.url&&(q=ge(import.meta.url))}catch{}const Mt=()=>{if(!import.meta.url||!q)return{isMonorepo:!1,monorepoRoot:null,resolvePackage:a=>a};const e=nt(new URL(".",import.meta.url).pathname),t=d(e,"../..");if(S(d(t,"packages","smbls","src","createDomql.js")))return{isMonorepo:!0,monorepoRoot:t};let n;try{const a=q.resolve("smbls/package.json");n=j(a)}catch{let a=e;for(;a!==j(a);){const g=d(a,"node_modules","smbls");if(S(d(g,"package.json"))){n=g;break}a=j(a)}}let s=q;if(n)try{s=ge(d(n,"package.json"))}catch{}let r,i=process.cwd();for(;i!==j(i);){if(S(d(i,"package.json"))){r=i;break}i=j(i)}let u=s;if(r)try{u=ge(d(r,"package.json"))}catch{}return{isMonorepo:!1,smblsRoot:n,brenderDir:e,projectRoot:r,_smblsRequire:s,_projectRequire:u}},H=(e,t)=>{const o=e.isMonorepo?[q]:[e._smblsRequire,e._projectRequire,q].filter(Boolean);for(const n of o)try{return n.resolve(t)}catch{}return null},on=(e,t,...o)=>{if(e.isMonorepo)return d(e.monorepoRoot,"packages",t,...o);const n=H(e,`${t}/package.json`);return n?d(j(n),...o):null},nn=(e,t,...o)=>{if(e.isMonorepo)return d(e.monorepoRoot,"plugins",t,...o);const n=H(e,`@symbo.ls/${t}/package.json`);return n?d(j(n),...o):null},be=(e,t,...o)=>{if(e.isMonorepo){for(const s of["packages","plugins"]){const r=d(e.monorepoRoot,s,t,...o);if(S(r))return r}return null}const n=H(e,`@symbo.ls/${t}/package.json`);return n?d(j(n),...o):null},Ce=(e,t,...o)=>{if(e.isMonorepo)return d(e.monorepoRoot,"packages","domql","packages",t,...o);const n=H(e,`@symbo.ls/${t}/package.json`);return n?d(j(n),...o):null};let ae=null;const Me="smbls/ssr",Ot=async()=>{const e=await import("smbls/ssr");if(typeof e?.createDomqlElement!="function")throw new Error("smbls/ssr does not export createDomqlElement");return e},Oe=()=>(ae||(ae=qt().catch(e=>{throw ae=null,e})),ae),qt=async()=>{const e=Mt();let t;if(q&&e.isMonorepo)t=d(e.monorepoRoot,"packages","smbls","src","createDomql.js");else{let f;try{return await Ot()}catch(l){f=l}if(!q)throw new Error(`brender: ${Me} is not available in this bundled runtime (${f.message}). Bundle smbls \u2265 the release that adds the smbls/ssr export alongside @symbo.ls/brender.`);const c=e.smblsRoot&&d(e.smblsRoot,"src","createDomql.js");if(c&&S(c))t=c;else throw new Error(`brender: cannot load ${Me} (${f.message}). Server rendering needs smbls installed in this project, at a release that exports smbls/ssr`+(e.smblsRoot?` (found smbls at ${e.smblsRoot}).`:" (no smbls package found)."))}const o=await import("esbuild"),n=tt(rt(),`br_createDomql_${ct(6).toString("hex")}.mjs`),s=st,r=e.isMonorepo?await import(it(d(e.monorepoRoot,"scripts","vite-workspace-source.mjs")).href):null,i=r?new Set(r.workspacePackageRoots().map(f=>r.workspacePackageAt(f).name)):null,u=/^(?:smbls(?:\/.*)?|domql|css-in-props(?:\/.*)?|@symbo\.ls\/[^/]+)$/,a={name:"workspace-resolve",setup(f){r&&(f.onResolve({filter:/^[^./]/},c=>{if(u.test(c.path))return;const l=c.path.split("/"),p=c.path.startsWith("@")?l.slice(0,2).join("/"):l[0];if(!i.has(p))return;const h=r.resolveWorkspaceSource(c.path,c.importer||t);return h?{path:h}:{errors:[{text:`"${c.path}" names no source file in the workspace`}]}}),f.onLoad({filter:/[\\/]dist[\\/]/},c=>r.isWorkspaceDist(c.path)?{errors:[{text:`${c.path} is a BUILD of a workspace package \u2014 the render bundles source`}]}:void 0)),f.onResolve({filter:/^smbls/},c=>{const l=c.path.replace(/^smbls\/?/,""),p=e.isMonorepo?d(e.monorepoRoot,"packages","smbls"):e.smblsRoot;if(!p)return;if(!l){const k=s(p);return k?{path:k}:void 0}const h=d(p,l);if(S(h))return{path:h};if(S(h+".js"))return{path:h+".js"};const R=d(h,"index.js");if(S(R))return{path:R}}),f.onResolve({filter:/^domql$/},c=>{if(e.isMonorepo){const l=d(e.monorepoRoot,"packages","domql","src","index.js");if(S(l))return{path:l};const p=d(e.monorepoRoot,"packages","domql","index.js");if(S(p))return{path:p}}else try{const l=_require.resolve("domql/package.json"),p=s(j(l));if(p)return{path:p}}catch{}}),f.onResolve({filter:/^@symbo\.ls\//},c=>{const l=c.path.replace("@symbo.ls/","");if(l==="sync")return{path:"sync-stub",namespace:"brender-stub"};if(e.isMonorepo){for(const h of["packages","plugins"]){const R=d(e.monorepoRoot,h,l,"src","index.js");if(S(R))return{path:R};const k=d(e.monorepoRoot,h,l,"index.js");if(S(k))return{path:k}}const p=d(e.monorepoRoot,"packages","default-config","blank","index.js");if(l==="default-config"&&S(p))return{path:p}}else{const p=be(e,l,"src","index.js");if(p&&S(p))return{path:p};const h=be(e,l,"index.js");if(h&&S(h))return{path:h};if(l==="default-config"){const R=be(e,"default-config","blank","index.js");if(R&&S(R))return{path:R}}}}),f.onResolve({filter:/^@domql\//},c=>{const l=c.path.replace("@symbo.ls/","");if(e.isMonorepo){const p=d(e.monorepoRoot,"packages","domql","packages",l,"src","index.js");if(S(p))return{path:p};const h=d(e.monorepoRoot,"packages","domql","packages",l,"index.js");if(S(h))return{path:h}}else{const p=Ce(e,l,"src","index.js");if(p&&S(p))return{path:p};const h=Ce(e,l,"index.js");if(h&&S(h))return{path:h}}}),f.onResolve({filter:/^css-in-props/},c=>{let l;if(e.isMonorepo)l=d(e.monorepoRoot,"packages","css-in-props");else{const R=H(e,"css-in-props/package.json");R&&(l=j(R))}if(!l)return;const p=c.path.replace(/^css-in-props\/?/,"");if(p){const R=d(l,p),k=d(R,"index.js");if(S(k))return{path:k};if(S(R+".js"))return{path:R+".js"};if(S(R))return{path:R}}const h=s(l);if(h)return{path:h}}),f.onResolve({filter:/^@emotion\//},c=>{let l;if(e.isMonorepo)l=d(e.monorepoRoot,"node_modules",c.path);else{const p=H(e,`${c.path}/package.json`);if(!p)return;l=j(p)}if(S(l)){const p=d(l,"package.json");if(S(p))try{const h=JSON.parse(J(p,"utf8")),R=h.module||h.main||"dist/emotion-css.esm.js";return{path:d(l,R)}}catch{}return{path:l}}}),f.onResolve({filter:/\.json$/},c=>{if(c.resolveDir){const l=d(c.resolveDir,c.path);if(S(l))return{path:l}}}),f.onLoad({filter:/smbls\/.*options\.js$/},async c=>{if(!c.path.includes("smbls/")||!c.path.endsWith("options.js"))return;let l=J(c.path,"utf8");return l=l.replace(/import\s*\{[^}]*version[^}]*\}\s*from\s*['"][^'"]*package\.json['"][^;\n]*/,"const version = '0.0.0'"),l=l.replace(/import\s*\{[^}]*createRequire[^}]*\}\s*from\s*['"]module['"][^;\n]*/g,"// createRequire removed for brender build"),{contents:l,loader:"js"}}),f.onLoad({filter:/smbls\/.*init\.js$/},async c=>{if(!c.path.includes("smbls/")||!c.path.endsWith("init.js"))return;let l=J(c.path,"utf8");return l=l.replace(/import\s*\{[^}]*createRequire[^}]*\}\s*from\s*['"]module['"][^;\n]*/g,"// createRequire removed for brender build"),{contents:l,loader:"js"}}),f.onLoad({filter:/.*/,namespace:"brender-stub"},()=>({contents:"export const SyncComponent = {}; export const Inspect = {}; export const Notifications = {}; export const syncPlugin = () => ({}); export const notificationsPlugin = () => ({}); export const seedRawDesignSystem = () => {}; export const inspectPlugin = () => ({}); export default {}",loader:"js"})),f.onLoad({filter:/smbls\/src\/router\.js$/},async c=>{let l=J(c.path,"utf8");return l=l.replace(/import\s*\{\s*Link\s*\}\s*from\s*['"]smbls['"]/,"const Link = { tag: 'a', attr: { href: (el) => el.href } }"),{contents:l,loader:"js"}}),f.onLoad({filter:/fetchOnCreate\.js$/},async c=>{if(!c.path.includes("smbls/"))return;let l=J(c.path,"utf8");return l=l.replace(/window\s*&&\s*window\.location\s*\?\s*window\.location\.host\.includes/g,"window && window.location && window.location.host ? window.location.host.includes"),{contents:l,loader:"js"}})}};await o.build({entryPoints:[t],bundle:!0,format:"esm",platform:"node",outfile:n,write:!0,logLevel:"warning",plugins:[a],nodePaths:e.isMonorepo?[d(e.monorepoRoot,"node_modules")]:[...e.smblsRoot?[d(e.smblsRoot,"node_modules")]:[],...e.projectRoot?[d(e.projectRoot,"node_modules")]:[],...e.smblsRoot?[d(e.smblsRoot,"..","..","node_modules")]:[]].filter(f=>S(f)),supported:{"import-attributes":!1},external:["fs","path","os","crypto","url","http","https","stream","util","events","buffer","child_process","worker_threads","net","tls","dns","dgram","zlib","assert","querystring","string_decoder","readline","perf_hooks","async_hooks","v8","vm","cluster","inspector","module","process","tty","color-contrast-checker","linkedom"]});const g=await import(`file://${n}`);if(process.env.BRENDER_DEBUG)console.log("[brender] Bundle saved:",n);else try{ot(n)}catch{}return g},Pt={Box:{},Focusable:{},Block:{display:"block"},Inline:{display:"inline"},Flex:{display:"flex"},InlineFlex:{display:"inline-flex"},Grid:{display:"grid"},InlineGrid:{display:"inline-grid"},Link:{tag:"a",attr:{href:e=>e.href,target:e=>e.target,rel:e=>e.rel}},A:{extends:"Link"},RouteLink:{extends:"Link"},Img:{tag:"img",attr:{src:e=>{let t=e.src;return typeof t=="string"&&t.includes("{{")&&(t=e.call("replaceLiteralsWithObjectFields",t,e.state)),t},alt:e=>e.alt,loading:e=>e.loading}},Image:{extends:"Img"},Button:{tag:"button"},FocusableComponent:{tag:"button"},Form:{tag:"form"},Input:{tag:"input"},TextArea:{tag:"textarea"},Textarea:{tag:"textarea"},Select:{tag:"select"},Label:{tag:"label"},Iframe:{tag:"iframe"},Video:{tag:"video"},Audio:{tag:"audio"},Canvas:{tag:"canvas"},Span:{tag:"span"},P:{tag:"p"},H1:{tag:"h1"},H2:{tag:"h2"},H3:{tag:"h3"},H4:{tag:"h4"},H5:{tag:"h5"},H6:{tag:"h6"},Svg:{tag:"svg",attr:{xmlns:"http://www.w3.org/2000/svg","xmlns:xlink":"http://www.w3.org/1999/xlink"}},Text:{tag:"span"}},It=(e,t)=>{const o={};return qe(e,(n,s)=>{if(!s.__brKey)return;const r=t(n);o[r===""?"__root":r]=s.__brKey}),o},qe=(e,t)=>{const o=n=>{const s=n&&n.__ref;if(s&&(t(n,s),s.__children))for(const r of s.__children)o(n[r])};o(e)},Ft=new Set(["app","config","pages","components","state","snippets","designSystem","dependencies","functions","methods","files","sharedLibraries"]);let z=0;const Pe=Symbol.for("brender.fetchTracked"),Se=()=>z,Gt=256*1024,Bt=1024*1024,Kt=new Set(["accept-language","accept"]),Ie={renders:0,record:null},Nt=50,Re={renders:0,record:null},Fe=(e,t)=>{e.renders++,e.renders===1?e.record=t():e.record&&(e.record.tainted=!0);const o=e.record;return{record:o,close:()=>{e.renders--,e.record===o&&(e.record=null)}}},we=e=>e.record&&!e.record.tainted?e.record:null,Ht=e=>{try{const t=new URL(e);return t.origin+t.pathname}catch{return String(e).split("?")[0]}},ke=(e,t,o)=>{!e||e.tainted||e.failures.length>=Nt||e.failures.push({url:Ht(t),reason:o})},Ge=e=>e&&typeof e=="object"&&typeof e.url=="string"?e.url:String(e),zt=e=>{const[t,o]=e,n=t&&typeof t=="object"&&typeof t.url=="string",s=Ge(t),r=String(o&&o.method||n&&t.method||"GET").toUpperCase(),i={},u=a=>{if(!a)return;const g=!Array.isArray(a)&&typeof a.entries!="function";for(const[f,c]of yt(a))g&&c==null||(i[String(f).toLowerCase()]=String(c))};return n&&u(t.headers),o&&u(o.headers),{url:s,method:r,headers:i}},Wt=(e,t,o,n)=>{if(!o||!o.ok||typeof o.clone!="function")return;const s=o.headers&&o.headers.get&&o.headers.get("content-type")||"";if(!/json|^text\//i.test(s))return;let r;try{r=o.clone()}catch{return}n(r.text().then(i=>{e.tainted||i.length>Gt||e.bytes+i.length>Bt||(e.bytes+=i.length,e.entries.push({url:t.url,lang:t.headers["accept-language"]||"",status:o.status,type:s,body:i}))},()=>{}))},Ut=()=>{const e=globalThis.fetch;if(typeof e!="function"||e[Pe])return;const t=function(...o){z++;let n=!1;const s=()=>{n||(n=!0,z--)},r=c=>{z++,c.then(()=>{z--},()=>{z--})},i=we(Ie),u=we(Re);let a=null;if(i)try{a=zt(o),(a.method!=="GET"||!/^https?:/i.test(a.url)||Object.keys(a.headers).some(c=>!Kt.has(c)))&&(a=null)}catch{a=null}const g=()=>Ge(o[0]);let f;try{f=e.apply(this,o)}catch(c){throw ke(u,g(),c&&c.message||"failed"),s(),c}return f&&typeof f.then=="function"?f.then(c=>{a&&Wt(i,a,c,r),u&&c&&c.ok===!1&&ke(u,g(),"HTTP "+c.status),s()},c=>{ke(u,g(),c&&c.message||"network error"),s()}):s(),f};t[Pe]=!0,globalThis.fetch=t},sn=({brRegistry:e,fetchSeed:t,hydrateFlag:o=!0}={})=>{if(!e||!Object.keys(e).length)return"";const n=Rt(e);return o?`<script>window.__BR_REGISTRY__=${n};window.__BRENDER_HYDRATE__=true<\/script>`+(t&&t.length?`
|
|
2
|
+
`+ht(t):""):`<script>window.__BR_REGISTRY__=${n}<\/script>`},Vt=e=>{const t=globalThis.__SMBLS_PKGS__,o=new Set(t&&typeof t=="object"?Object.getOwnPropertyNames(t):[]);return()=>{const n=globalThis.__SMBLS_PKGS__;if(!n||typeof n!="object")return;let s;try{s=wt(e())}catch{return}for(const[r]of s)!o.has(r)&&Object.prototype.hasOwnProperty.call(n,r)&&Reflect.deleteProperty(n,r)}},Jt=3e4,Yt=2147483647;let Be=Promise.resolve();const Ke=()=>{},Xt=e=>{const t=Be.then(e);return Be=t.then(Ke,Ke),t},Qt=e=>{if(e===void 0)return Jt;if(e===0||e===!1||e===1/0)return 0;if(typeof e!="number"||!(e>0))throw new TypeError(`brender: options.timeout is a number of milliseconds, or 0 / false for no limit \u2014 not ${JSON.stringify(e)}`);return Math.min(e,Yt)},Zt=(e,t)=>{const o=e.route||"/",n=e.pathname||o,s=n===o?o:`${n} (route ${o})`,r=new Error(`brender: the render of ${s} did not finish within ${t} ms and was abandoned, so the renders after it can run (options.timeout)`);return r.code="BRENDER_RENDER_TIMEOUT",r.route=o,r.pathname=n,r.timeout=t,r},eo=e=>{e.closed=!0;for(const t of e.undo.splice(0))t()},to=e=>{let t=!1;return()=>{t||(t=!0,e())}},Ne=(e,t={})=>{let o;try{o=Qt(t.timeout)}catch(n){return Promise.reject(n)}return Xt(()=>oo(e,t,o))},oo=async(e,t,o)=>{await Oe();const n={closed:!1,undo:[]},s=no(e,t,n);if(!o)return s;let r;const i=new Promise((u,a)=>{r=setTimeout(()=>{eo(n),a(Zt(t,o))},o)});try{return await Promise.race([s,i])}finally{clearTimeout(r)}},no=async(e,t,o)=>{const{route:n="/",pathname:s,state:r,context:i,prefetch:u=!1,lazyGroups:a}=t,g=s||n;Ut();const f=Fe(Re,()=>({failures:[],labelKeys:new Set,tainted:!1}));try{return await so(Dt(e),{route:n,locationPath:g,stateOverrides:r,contextOverrides:i,prefetch:u,lazyGroups:a},f.record,o)}finally{f.close()}},so=async(e,{route:t,locationPath:o,stateOverrides:n,contextOverrides:s,prefetch:r,lazyGroups:i},u,a)=>{const g=()=>{if(a.closed)throw new Error("brender: this render was abandoned at its timeout")};let f;if(r){try{f=await gt(e)}catch(k){a.closed||console.warn("[brender] SSR translation fetch failed:",k.message)}g()}const{window:c,document:l}=je(),p=l.body;c.location.pathname=o;let h=null;const R=[fe("document",l),fe("location",c.location),fe("localStorage",c.localStorage),fe("sessionStorage",c.sessionStorage),Vt(()=>h&&h.sharedLibraries)].map(to);a.undo.push(...R);try{const{createDomqlElement:k,PRERENDER_REGISTRY_FORMATS:Y}=await Oe();g();const me=L(e.app||{}),_={...e.config||{}};e.polyglot&&!_.polyglot&&(_.polyglot=e.polyglot),e.fetch&&!_.fetch&&(_.fetch=e.fetch),e.router&&!_.router&&(_.router=e.router);for(const m of po)e[m]!=null&&_[m]==null&&(_[m]=e[m]);const E=_.polyglot?{..._.polyglot,prefetch:!1}:void 0,X=xt(o,E);X&&c.localStorage.setItem(kt(E),X),f&&E&&(E.translations={...E.translations||{},...f});const Q=L(e.state||{});$t();const P={};for(const m in e){if(Ft.has(m))continue;const b=e[m];P[m]=b&&typeof b=="object"?L(b):b}const x={...P,state:Q,...n?{state:{...Q,...n}}:{},dependencies:L(e.dependencies||{}),components:L(e.components||{}),snippets:L(e.snippets||{}),pages:L(e.pages||{}),functions:{...e.functions||{},polyglot:Ct,getActiveLang:De,getLang:De},methods:e.methods||{},designSystem:L(e.designSystem||{}),files:e.files||{},sharedLibraries:e.sharedLibraries||[],..._,...E?{polyglot:E}:{},document:l,window:c,parent:{node:p},initOptions:{},domqlOptions:{sourcemap:!1},firstRender:!1,sync:!1,inspect:!1,notifications:!1,afterFirstView:null,isolateDesignSystem:!0,isolateFetchCache:!0,captureDesignSystem:!0,...ro(i),...s||{},ssr:!0};h=x;const Z=await vt();g(),Z&&(x.plugins=x.plugins||[],x.plugins.some(m=>m.name==="funcql")||x.plugins.push(Z)),Te();const B=Fe(Ie,()=>({entries:[],bytes:0,tainted:!1})),y=B.record;let w;try{if(w=await k(me,x),g(),x.lazyFirstView){const D=(w&&w.context||x).__firstNavigation;if(D&&typeof D.then=="function"){try{await D}catch{}g()}}const m=Date.now(),b=r?2e3:50,T=D=>new Promise(re=>setTimeout(re,D));for(await T(Math.min(50,b));r&&!a.closed&&Se()>0&&Date.now()-m<b;)await T(10),Se()===0&&await T(Math.max(0,Math.min(50,b-(Date.now()-m))));if(g(),r&&await co(w,E)){g();const D=Date.now();for(await T(Math.min(50,b));!a.closed&&Se()>0&&Date.now()-D<b;)await T(10);g()}}finally{B.close()}const W=y&&!y.tainted&&!(_.fetch&&_.fetch.seed===!1)?y.entries.slice():[];ve(p);const A=Le(w),{elementPath:K}=await import("@symbo.ls/element");g();const ee=It(w,K),te=W.length?At(w&&w.context||x,K):{},U=w?.context?.routerOptions?.contentElementKey||"content",I=w&&w[U],C=I&&typeof I=="object"&&I.__ref?I:void 0,Ee=he(e,t,w,w?.state,C),M=[],V=l.head||l.querySelector("head");if(V)for(const m of V.querySelectorAll("style")){if(m.sheet&&m.sheet.cssRules)for(const b of m.sheet.cssRules)b.cssText&&M.push(b);if(!M.length){const b=m.textContent||"";b&&M.push({cssText:b})}}const oe=M.map(m=>m.cssText),{globalCSS:ne,classRules:de}=bo(M);let v=We(p.innerHTML);const F=w?.state?.root,G=le(F,E),O=io(f,F);O&&(v=ze(v,He(O,G,E)));const N=u&&!u.tainted?{failedRequests:u.failures.slice(),rawLabelKeys:fo(u.labelKeys,v,G,E,F)}:null,$=(w&&w.context||x).__lazyFirstViews,se=$&&typeof $.forEach=="function"?[...$]:[];return{html:v,metadata:Ee,registry:A,brRegistry:ee,element:w,emotionCSS:oe,lazyGroups:se,globalCSS:ne,classRules:de,fetchSeed:W,fetchRuns:te,document:l,window:c,ssrTranslations:O||void 0,activeLang:G,diagnostics:N,preparedDesignSystem:x.preparedDesignSystem||null,registryFormats:Array.isArray(Y)?Y.slice():["paths"]}}finally{for(const k of R)k()}},ro=e=>{if(e==="load"||e===!0)return{lazyFirstView:!0};if(Array.isArray(e)){const t=e.filter(o=>typeof o=="string"&&o);return t.length?{lazyFirstView:t}:{}}return{}},fe=(e,t)=>{const o=Object.getOwnPropertyDescriptor(globalThis,e);return o&&!o.configurable?!o.writable&&!o.set?()=>{}:(globalThis[e]=t,()=>{}):(Object.defineProperty(globalThis,e,{value:t,writable:!0,configurable:!0,enumerable:!0}),()=>{o?Object.defineProperty(globalThis,e,o):delete globalThis[e]})},co=async(e,t)=>{if(!t||!t.fetch||!e||typeof e.call!="function")return!1;const o=e.state&&e.state.root;if(!o)return!1;const n=le(o,t),s=o.translations&&o.translations[n];if(s&&typeof s=="object"&&Object.keys(s).length||t.translations&&t.translations[n])return!1;try{await e.call("loadTranslations",n,{inFlight:!0})}catch{}return!0},xe=/\{\{\s*([^|{}]+?)\s*\|\s*polyglot\s*\}\}/g,_e=e=>e&&typeof e.parse=="function"?e.parse():e,io=(e,t)=>{const o=_e(t?.translations),n={};for(const s of[e,o])if(!(!s||typeof s!="object"))for(const r in s){const i=_e(s[r]);i&&typeof i=="object"&&(n[r]={...n[r]||{},...i})}return Object.keys(n).length?n:null},He=(e,t,o)=>e[t]||e[ye(void 0,o)]||Object.values(e)[0]||{},ze=(e,t)=>e.replace(xe,(o,n)=>ie(t,n.trim())??o),lo=/<script\b[^>]*\btype\s*=\s*["']?application\/json["']?[^>]*>[\s\S]*?<\/script\s*>/gi,ao=(e,t)=>new RegExp(`(?:^|[^\\w.-])${St(t)}(?![\\w-]|\\.\\w)`).test(e),fo=(e,t,o,n,s)=>{const r=new Set,i=t.replace(lo,"");for(const u of e)/\s/.test(u)||$e(u,o,n,s)===void 0&&ao(i,u)&&r.add(u);for(const u of i.matchAll(xe))r.add(u[1].trim());return[...r]},rn=async(e,t={})=>{const{context:o={}}=t,{window:n,document:s}=je(),r=s.body,{create:i}=await import("@symbo.ls/element"),u=await import("@symbo.ls/utils"),a={...Pt,...o.components||{}},g={...u,...o.utils||{},...o.functions||{}};Te();let f;try{f=i(e,{node:r},"root",{context:{document:s,window:n,...o,components:a,utils:g,ssr:!0}})}catch{}ve(r);const c=f?Le(f):{};return{html:We(r.innerHTML),registry:c,element:f}},We=e=>e.replace(/(<svg\b[^>]*>)([\s\S]*?)(<\/svg>)/gi,(t,o,n,s)=>{if(n.includes("<")){const r=n.replace(/</g,"<").replace(/>/g,">").replace(/&/g,"&").replace(/"/g,'"').replace(/'/g,"'");return o+r+s}return t}),po=["globalTheme","themeStorageKey","themeRoot","useReset","useVariable","useFontImport","useIconSprite","useSvgSprite","useDocumentTheme","useDefaultConfig","useDefaultIcons","useThemeSuffixedVars","verbose","semanticIcons"],uo=new Set(["auto","system"]),Ue=e=>{if(!e||typeof e!="object")return null;const t=e.config&&typeof e.config=="object"?e.config:{};if((t.themeRoot!=null?t.themeRoot:e.themeRoot)!=null)return null;const n=t.globalTheme!=null?t.globalTheme:e.globalTheme;if(typeof n!="string")return null;const s=n.trim();return!s||uo.has(s)?null:s},mo=e=>String(e).replace(/&/g,"&").replace(/"/g,""").replace(/</g,"<"),go=/\.[A-Za-z_\\-]/,ho=e=>{if(typeof e!="string"||!e)return!1;const t=e.replace(/"(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*'/g,'""').replace(/\[[^\]]*\]/g,"[]");return!go.test(t)},yo=/^\s*(:root|\[data-theme|\[data-skin)/,Ve=e=>{const t=e&&e.cssText;if(typeof t!="string")return null;if(/^@(-webkit-)?keyframes\b/.test(t))return"keyframes";if(t.startsWith("@font-face"))return null;if(typeof e.selectorText=="string")return ho(e.selectorText)?yo.test(e.selectorText)?"root":"reset":null;const o=e.cssRules&&Array.from(e.cssRules);if(o&&o.length&&/^@(media|supports|container)\b/.test(t)){const n=new Set(o.map(Ve));return n.has(null)||n.size!==1?n.has(null)?null:"reset":[...n][0]}return null},Je=e=>typeof e=="string"&&/^\s*@font-face\b/i.test(e),bo=e=>{const t={root:[],reset:[],keyframes:[]},o=[];for(const n of e||[]){const s=Ve(n);s?t[s].push(n.cssText):n&&n.cssText&&o.push(n.cssText)}return{globalCSS:{rootRule:t.root.join(`
|
|
3
3
|
`),resetRules:t.reset.join(`
|
|
4
4
|
`),keyframeRules:t.keyframes.join(`
|
|
5
|
-
`),fontFaceCSS:""},classRules:o}};let pe=new Set;const
|
|
6
|
-
`),
|
|
5
|
+
`),fontFaceCSS:""},classRules:o}};let pe=new Set;const cn=()=>{pe=new Set},ln=()=>Array.from(pe).join(`
|
|
6
|
+
`),an=(e,t)=>e.replace(/<style data-emotion="smbls">[\s\S]*?<\/style>/,t?`<style data-emotion="smbls">
|
|
7
7
|
${t}
|
|
8
|
-
</style>`:""),
|
|
9
|
-
`),classRules:i.classRules.slice(),globalCSS:a,resetCss:a.resetRules,fontLinks:et(u),metadata:i.metadata||he(e,o),brKeyCount:i.registry?Object.keys(i.registry).length:0,brRegistry:i.brRegistry||{},fetchSeed:i.fetchSeed||[],fetchRuns:i.fetchRuns||{},ssrTranslations:i.ssrTranslations,prefetchedState:g,activeLang:f,theme:Ue(e),diagnostics:i.diagnostics,preparedDesignSystem:i.preparedDesignSystem||null}},
|
|
8
|
+
</style>`:""),fn=async(e,t={})=>{const{route:o="/",pathname:n,prefetch:s=!0,timeout:r}=t,i=await Ne(e,{route:o,pathname:n,prefetch:s,timeout:r});if(!i)return null;const u=e.designSystem||{},a=i.globalCSS;let g=null;try{const c=So(i.element);Object.keys(c).length&&(g=c)}catch{}const f=i.activeLang;return{html:i.html,css:i.classRules.join(`
|
|
9
|
+
`),classRules:i.classRules.slice(),globalCSS:a,resetCss:a.resetRules,fontLinks:et(u),metadata:i.metadata||he(e,o),brKeyCount:i.registry?Object.keys(i.registry).length:0,brRegistry:i.brRegistry||{},fetchSeed:i.fetchSeed||[],fetchRuns:i.fetchRuns||{},ssrTranslations:i.ssrTranslations,prefetchedState:g,activeLang:f,theme:Ue(e),diagnostics:i.diagnostics,preparedDesignSystem:i.preparedDesignSystem||null}},So=e=>{const t={};return qe(e,o=>{const n=o.fetch;if(!(!n||typeof n!="object"||!o.state))for(const s of Array.isArray(n)?n:[n]){const r=s&&typeof s=="object"&&typeof s.as=="string"?s.as:null;if(!r)continue;const i=r.startsWith("~/"),u=i?r.slice(2):r,a=i?o.state.root:o.state,g=_e(a&&a[u]);u&&g!=null&&typeof g!="function"&&(t[u]=g)}}),t},pn=async(e,t="/",o={})=>{const{lang:n,themeColor:s,isr:r,hydrate:i=!0,prefetch:u=!0,shell:a,adoptDom:g=!1,pathname:f,eagerImages:c=Ze,preloadScripts:l=!0,nonce:p,timeout:h,stamp:R,time:k,spriteIcons:Y="all",cssClasses:me="all",cssVariables:_="all",registryFormat:E="paths",lazyGroups:X,paint:Q}=o,P=Ro(Q),x=vo(p),Z=n||e.state?.lang||e.app?.metadata?.lang||"en",B=Ue(e),y=await Ne(e,{route:t,pathname:f,prefetch:u,timeout:h,lazyGroups:X});if(!y)return null;const w={...y.metadata};if(s&&(w["theme-color"]=s),typeof a=="string"&&!e.name){const m="\0brender-undeclared-title\0";if(he({...e,name:m},t,y.element,y.element?.state).title===m)for(const T of["title","og:title","twitter:title"])w[T]===y.metadata.title&&delete w[T]}const W=lt(w);for(const m of y.classRules)m&&pe.add(m);let A=Eo(y.html,c);Y==="page"&&(A=ft(A,typeof a=="string"?a:"").html);const K=Array.from(pe),ee=K.filter(Je),te=ee.length?K.filter(m=>!Je(m)):K,U=me==="page"?pt(te,ut(A,typeof a=="string"?a:"")).join(`
|
|
10
10
|
`):te.join(`
|
|
11
|
-
`),I=
|
|
12
|
-
${U.
|
|
13
|
-
`)}
|
|
14
|
-
</style>`:"",C=y.globalCSS,M=`<style data-brender>
|
|
15
|
-
${(_==="page"?mt(C.rootRule||"",C.resetRules,C.keyframeRules,V,$,typeof a=="string"?a:"").css:C.rootRule)||""}
|
|
11
|
+
`),I=dt(ee),C=y.globalCSS,M=`<style data-brender>
|
|
12
|
+
${(_==="page"?mt(C.rootRule||"",C.resetRules,C.keyframeRules,U,A,typeof a=="string"?a:"").css:C.rootRule)||""}
|
|
16
13
|
${C.resetRules||""}
|
|
17
14
|
${C.keyframeRules||""}
|
|
18
|
-
</style>`,
|
|
19
|
-
${
|
|
20
|
-
</style>`:"",oe=et(e.designSystem||{}),ne=Object.keys(y.registry).length,de=E==="tree"||E==="auto"&&(y.registryFormats||[]).includes("tree"),v=
|
|
15
|
+
</style>`,V=U?`<style data-emotion="smbls">
|
|
16
|
+
${U}
|
|
17
|
+
</style>`:"",oe=et(e.designSystem||{}),ne=Object.keys(y.registry).length,de=E==="tree"||E==="auto"&&(y.registryFormats||[]).includes("tree"),v=jo({brRegistry:y.brRegistry,fetchSeed:y.fetchSeed,fetchRuns:y.fetchRuns,adoptDom:g,stamp:R,time:k,registryFormat:de?"tree":"paths",lazyGroups:y.lazyGroups});let F="";if(r&&r.clientScript){const m=Number.isInteger(o.depth)?o.depth:t==="/"?0:t.replace(/^\/|\/$/g,"").split("/").length,b=m>0?"../".repeat(m):"./";if(i){const T=_t({polyglot:e.polyglot||e.config?.polyglot,translations:y.ssrTranslations,lang:y.activeLang,pathname:f||t,attrs:x}),D=T?T+`
|
|
21
18
|
`:"",re=v.blocks?v.blocks+`
|
|
22
19
|
`:"";F=`${D}${re}<script type="module" src="${b}${r.clientScript}"${x}><\/script>`}else F=`<script type="module"${x}>
|
|
23
20
|
{
|
|
@@ -36,29 +33,29 @@ ${V}
|
|
|
36
33
|
observer.observe(document.body, { childList: true })
|
|
37
34
|
}
|
|
38
35
|
<\/script>
|
|
39
|
-
<script type="module" src="${b}${r.clientScript}"${x}><\/script>`}const G={...e.config||{}};e.polyglot&&!G.polyglot&&(G.polyglot=e.polyglot);const O=G.polyglot;let N=W;if(O){const m={...O.translations||{}};for(const b in y.ssrTranslations||{})m[b]={...m[b]||{},...y.ssrTranslations[b]};N=ze(W,He(m,y.activeLang,O))}const
|
|
40
|
-
${v.meta}`,headEnd:[oe,I,M,
|
|
41
|
-
`),body:P
|
|
42
|
-
`+Xe
|
|
43
|
-
<html lang="${
|
|
36
|
+
<script type="module" src="${b}${r.clientScript}"${x}><\/script>`}const G={...e.config||{}};e.polyglot&&!G.polyglot&&(G.polyglot=e.polyglot);const O=G.polyglot;let N=W;if(O){const m={...O.translations||{}};for(const b in y.ssrTranslations||{})m[b]={...m[b]||{},...y.ssrTranslations[b]};N=ze(W,He(m,y.activeLang,O))}const $=y.diagnostics;if($)for(const m of N.matchAll(xe)){const b=m[1].trim();$.rawLabelKeys.includes(b)||$.rawLabelKeys.push(b)}if(typeof a=="string"){const m=at(a,{headTags:`${N}
|
|
37
|
+
${v.meta}`,headEnd:[oe,I,M,V].filter(Boolean).join(`
|
|
38
|
+
`),body:P?A+`
|
|
39
|
+
`+Xe:A,depth:Number.isInteger(o.depth)?o.depth:0,bootScripts:v.blocks,lang:n||(O?y.activeLang:void 0),theme:B,preloadScripts:l});return{html:P?Qe(m):m,route:t,brKeyCount:ne,shell:!0,diagnostics:$,preparedDesignSystem:y.preparedDesignSystem||null,lazyGroups:y.lazyGroups||[]}}const se=`<!DOCTYPE html>
|
|
40
|
+
<html lang="${Z}"${B?` data-theme="${mo(B)}"`:""}>
|
|
44
41
|
<head>
|
|
45
42
|
${N}${r&&r.clientScript&&i?`
|
|
46
43
|
${v.meta}`:""}
|
|
47
44
|
${oe}
|
|
48
45
|
${I}
|
|
49
46
|
${M}
|
|
50
|
-
${
|
|
47
|
+
${V}
|
|
51
48
|
</head>
|
|
52
49
|
<body>
|
|
53
|
-
${
|
|
50
|
+
${A}${P?`
|
|
54
51
|
${Xe}`:""}
|
|
55
52
|
${F}
|
|
56
53
|
</body>
|
|
57
|
-
</html>`;return{html:P?Qe(se):se,route:t,brKeyCount:ne,diagnostics
|
|
58
|
-
`+ue+e.slice(o)}for(const o of
|
|
54
|
+
</html>`;return{html:P?Qe(se):se,route:t,brKeyCount:ne,diagnostics:$,preparedDesignSystem:y.preparedDesignSystem||null,lazyGroups:y.lazyGroups||[]}},Ye="smbls-whole",Xe=`<template id="${Ye}"></template>`,ue=`<link rel="expect" href="#${Ye}" blocking="render">`,Ro=e=>!!(e&&typeof e=="object"&&e.whole===!0),wo=/<meta\b[^>]*\bcharset\s*=[^>]*>/i,ko=[/<head\b[^>]*>/i,/<html\b[^>]*>/i,/^\s*<!doctype\b[^>]*>/i],Qe=e=>{if(typeof e!="string"||e.includes(ue))return e;const t=wo.exec(e.slice(0,1024));if(t){const o=t.index+t[0].length;return e.slice(0,o)+`
|
|
55
|
+
`+ue+e.slice(o)}for(const o of ko){const n=o.exec(e);if(n)return e.slice(0,n.index+n[0].length)+`
|
|
59
56
|
`+ue+e.slice(n.index+n[0].length)}return ue+`
|
|
60
|
-
`+e},Ze=2,
|
|
61
|
-
`):""},
|
|
62
|
-
`+
|
|
63
|
-
|
|
64
|
-
`)}},
|
|
57
|
+
`+e},Ze=2,xo=/<img\b(?:[^>"']|"[^"]*"|'[^']*')*>/gi,_o=/\sloading\s*=/i,Eo=(e,t=Ze)=>{if(t===!1||t==null||typeof e!="string")return e;const o=Math.max(0,Number(t)||0);let n=0;return e.replace(xo,s=>n++<o||_o.test(s)?s:s.replace(/^<img\b/i,'<img loading="lazy"'))},et=e=>{if(!e)return"";const t=e.font_family||e.fontFamily||{},o=new Set;for(const s of Object.values(t)){if(typeof s!="string")continue;const r=s.match(/'([^']+)'/);r&&o.add(r[1])}return o.size?['<link rel="preconnect" href="https://fonts.googleapis.com">','<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>',`<link href="https://fonts.googleapis.com/css2?${[...o].map(s=>`family=${s.replace(/\s+/g,"+")}:wght@300;400;500;600;700`).join("&")}&display=swap" rel="stylesheet">`].join(`
|
|
58
|
+
`):""},jo=({brRegistry:e,fetchSeed:t,fetchRuns:o,adoptDom:n=!1,stamp:s,time:r,registryFormat:i="paths",lazyGroups:u}={})=>{const g=n&&e&&Object.keys(e).length?i==="tree"&&jt(e)||Et(e):"",f=Lt(t,o);return{meta:`<meta name="brender" content="${g?"hydrate":"replace"}">
|
|
59
|
+
`+bt(To(s,r))+(Ae(u)?`
|
|
60
|
+
`+Ae(u):""),blocks:[g,f].filter(Boolean).join(`
|
|
61
|
+
`)}},To=(e,t)=>{const o=typeof t=="number"&&isFinite(t)?Math.round(t):Date.now();return{stamp:e!=null&&e!==""?String(e):new Date(o).toISOString(),time:o}},vo=e=>{if(e==null||e==="")return"";const t=String(e);if(!/^[A-Za-z0-9+/=_-]+$/.test(t))throw new Error("brender: a nonce is base64 or base64url text");return` nonce="${t}"`};export{Ze as EAGER_IMAGES_DEFAULT,Me as SMBLS_SSR_SPECIFIER,Ye as WHOLE_MARKER_ID,sn as adoptionScripts,To as buildStamp,Ue as fixedThemeOf,ln as getAccumulatedEmotionCSS,Eo as markLazyImages,Ro as paintsWhole,jo as prerenderParts,Ne as render,rn as renderElement,pn as renderPage,fn as renderRoute,an as replaceEmotionCSS,cn as resetGlobalCSSCache,bo as splitCapturedCSS,Qe as withWholePaint};
|
package/env.js
CHANGED
|
@@ -33,6 +33,352 @@ const patchSelectValue = (document) => {
|
|
|
33
33
|
})
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
+
// Keep a custom property's name as it is in linkedom's CSSStyleDeclaration
|
|
37
|
+
// (see createEnv). The prototype is linkedom's, shared by every document it
|
|
38
|
+
// parses: patched once, like select.value above. The marker is a registered
|
|
39
|
+
// symbol, so two copies of this module in one process patch it once too.
|
|
40
|
+
const STYLE_MAP = Symbol.for('@symbo.ls/brender:styleMap')
|
|
41
|
+
const isCustomProperty = (name) =>
|
|
42
|
+
typeof name === 'string' && name.charCodeAt(0) === 45 && name.charCodeAt(1) === 45
|
|
43
|
+
|
|
44
|
+
const patchCustomPropertyNames = (document) => {
|
|
45
|
+
let proto = Object.getPrototypeOf(document.createElement('div').style)
|
|
46
|
+
while (proto && !Object.prototype.hasOwnProperty.call(proto, 'setProperty')) proto = Object.getPrototypeOf(proto)
|
|
47
|
+
if (!proto || Object.prototype.hasOwnProperty.call(proto, STYLE_MAP)) return
|
|
48
|
+
const { setProperty, removeProperty, getPropertyValue } = proto
|
|
49
|
+
if (typeof setProperty !== 'function' || typeof removeProperty !== 'function' || typeof getPropertyValue !== 'function') return
|
|
50
|
+
// linkedom's style object is a Proxy over a Map of the declarations, and it
|
|
51
|
+
// reads a name its prototype has from that Map: this getter gives the Map.
|
|
52
|
+
Object.defineProperty(proto, STYLE_MAP, { configurable: true, get () { return this } })
|
|
53
|
+
// A linkedom write that changes no declaration (it removes a name no
|
|
54
|
+
// declaration has). linkedom parses the attribute again when it changed,
|
|
55
|
+
// makes the style attribute when there is none, and writes the attribute
|
|
56
|
+
// from the Map. Before an edit of the Map it makes the Map current — a parse
|
|
57
|
+
// after the edit would lose it; after the edit it puts it in the attribute.
|
|
58
|
+
// The attribute text is never parsed again for the edit, so a value with a
|
|
59
|
+
// `;` in it (a data URI) stays whole, as with linkedom's own writes.
|
|
60
|
+
const settle = (style, map) => {
|
|
61
|
+
let none = ''
|
|
62
|
+
while (map.has(none)) none += ' '
|
|
63
|
+
removeProperty.call(style, none)
|
|
64
|
+
}
|
|
65
|
+
// linkedom's own write with the name as it is: null removes, any other
|
|
66
|
+
// value is set as it is given.
|
|
67
|
+
const write = (style, name, value) => {
|
|
68
|
+
const map = style[STYLE_MAP]
|
|
69
|
+
settle(style, map)
|
|
70
|
+
if (value == null) map.delete(name)
|
|
71
|
+
else map.set(name, value)
|
|
72
|
+
settle(style, map)
|
|
73
|
+
}
|
|
74
|
+
Object.defineProperties(proto, {
|
|
75
|
+
setProperty: {
|
|
76
|
+
configurable: true,
|
|
77
|
+
writable: true,
|
|
78
|
+
value: function (name, value) {
|
|
79
|
+
if (!isCustomProperty(name)) return setProperty.apply(this, arguments)
|
|
80
|
+
write(this, name, value)
|
|
81
|
+
}
|
|
82
|
+
},
|
|
83
|
+
removeProperty: {
|
|
84
|
+
configurable: true,
|
|
85
|
+
writable: true,
|
|
86
|
+
value: function (name) {
|
|
87
|
+
if (!isCustomProperty(name)) return removeProperty.apply(this, arguments)
|
|
88
|
+
write(this, name, null)
|
|
89
|
+
}
|
|
90
|
+
},
|
|
91
|
+
getPropertyValue: {
|
|
92
|
+
configurable: true,
|
|
93
|
+
writable: true,
|
|
94
|
+
value: function (name) {
|
|
95
|
+
if (!isCustomProperty(name)) return getPropertyValue.apply(this, arguments)
|
|
96
|
+
// linkedom's read parses the attribute again when it changed
|
|
97
|
+
getPropertyValue.call(this, name)
|
|
98
|
+
return this[STYLE_MAP].get(name)
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
})
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// ── Rules a browser keeps that linkedom's CSS parser cannot read ───────────
|
|
105
|
+
// linkedom gives every <style> a sheet from `cssom` 0.5: `style.sheet` parses
|
|
106
|
+
// the style's text, `sheet.insertRule` parses one rule. That parser knows
|
|
107
|
+
// @media, @supports, @font-face, @keyframes, @import, @document and @host and
|
|
108
|
+
// nothing newer: a rule that holds a container query threw ("Unexpected }").
|
|
109
|
+
// Under a `breakpointsFrom` ancestor the atomic engine writes every breakpoint
|
|
110
|
+
// as one (`@container breakpoints (max-width: 1024px){._cX{display:none}}`,
|
|
111
|
+
// @symbo.ls/element resolveMediaCssKey) and counts a throw as an invalid rule:
|
|
112
|
+
// the server page carried the class with no rule. On the merged landing
|
|
113
|
+
// (`Page` declares `breakpointsFrom: 'container'`) 153 of the 343 `_c`
|
|
114
|
+
// classes of `/` had none, and a phone's first frame showed the desktop header
|
|
115
|
+
// until the client replaced it (W7-FIXA). Reading the sheet of a <style> whose
|
|
116
|
+
// TEXT held such a rule threw too, and the render failed with it.
|
|
117
|
+
// Kept now, as a browser keeps them: a block rule whose at-keyword cssom does
|
|
118
|
+
// not know (@container, @layer, @scope, @starting-style), or an @media /
|
|
119
|
+
// @supports that holds one, is read as a grouping rule — its condition, its
|
|
120
|
+
// rules and its text — and a `@layer a, b;` statement as a statement. A rule
|
|
121
|
+
// a browser refuses still throws: text after the one rule (a stray `}`, a
|
|
122
|
+
// second rule), or a rule inside the block that cannot be read either. Every
|
|
123
|
+
// rule cssom reads is cssom's own, as before.
|
|
124
|
+
const GROUP_RULES = Symbol.for('@symbo.ls/brender:groupRules')
|
|
125
|
+
const GROUP_AT_RULE = /^@(container|layer|scope|starting-style|media|supports)(?![\w-])/i
|
|
126
|
+
const LAYER_STATEMENT = /^@layer\s+[-\w.]+(?:\s*,\s*[-\w.]+)*\s*;?$/i
|
|
127
|
+
// The CSSRule type a browser reports; a rule kind newer than the type
|
|
128
|
+
// constants reports 0.
|
|
129
|
+
const GROUP_RULE_TYPES = { media: 4, supports: 12 }
|
|
130
|
+
|
|
131
|
+
// The top-level rules of a CSS text, each as its own text; null when the text
|
|
132
|
+
// is not whole (an unclosed block, string or comment, a stray `}`). A comment
|
|
133
|
+
// between rules belongs to none of them.
|
|
134
|
+
const topLevelRules = (css) => {
|
|
135
|
+
const rules = []
|
|
136
|
+
let depth = 0
|
|
137
|
+
let start = 0
|
|
138
|
+
let quote = null
|
|
139
|
+
const take = (end) => {
|
|
140
|
+
const rule = css.slice(start, end).trim()
|
|
141
|
+
if (rule) rules.push(rule)
|
|
142
|
+
start = end
|
|
143
|
+
}
|
|
144
|
+
for (let i = 0; i < css.length; i++) {
|
|
145
|
+
const ch = css[i]
|
|
146
|
+
if (quote) {
|
|
147
|
+
if (ch === '\\') i++
|
|
148
|
+
else if (ch === quote) quote = null
|
|
149
|
+
continue
|
|
150
|
+
}
|
|
151
|
+
if (ch === '\\') {
|
|
152
|
+
i++
|
|
153
|
+
continue
|
|
154
|
+
}
|
|
155
|
+
if (ch === '/' && css[i + 1] === '*') {
|
|
156
|
+
const close = css.indexOf('*/', i + 2)
|
|
157
|
+
if (close === -1) return null
|
|
158
|
+
if (depth === 0 && !css.slice(start, i).trim()) start = close + 2
|
|
159
|
+
i = close + 1
|
|
160
|
+
continue
|
|
161
|
+
}
|
|
162
|
+
if (ch === '"' || ch === "'") quote = ch
|
|
163
|
+
else if (ch === '{') depth++
|
|
164
|
+
else if (ch === '}') {
|
|
165
|
+
if (depth === 0) return null
|
|
166
|
+
if (--depth === 0) take(i + 1)
|
|
167
|
+
} else if (ch === ';' && depth === 0) take(i + 1)
|
|
168
|
+
}
|
|
169
|
+
if (quote || depth) return null
|
|
170
|
+
take(css.length)
|
|
171
|
+
return rules
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// A grouping rule cssom cannot read, the way a browser reads it.
|
|
175
|
+
class ServerGroupingRule {
|
|
176
|
+
constructor (keyword, condition, rules) {
|
|
177
|
+
this.keyword = keyword
|
|
178
|
+
this.condition = condition
|
|
179
|
+
this.cssRules = rules
|
|
180
|
+
this.parentRule = null
|
|
181
|
+
this.parentStyleSheet = null
|
|
182
|
+
for (const rule of rules) rule.parentRule = this
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
get type () { return GROUP_RULE_TYPES[this.keyword] || 0 }
|
|
186
|
+
get conditionText () { return this.condition }
|
|
187
|
+
get name () { return this.keyword === 'layer' ? this.condition : undefined }
|
|
188
|
+
get cssText () {
|
|
189
|
+
// the way cssom writes an @media rule: `@media <condition> {<rules>}`
|
|
190
|
+
let inner = ''
|
|
191
|
+
for (const rule of this.cssRules) inner += rule.cssText
|
|
192
|
+
return `@${this.keyword}${this.condition ? ' ' + this.condition : ''} {${inner}}`
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// A `@layer a, b;` statement.
|
|
197
|
+
class ServerLayerStatementRule {
|
|
198
|
+
constructor (names) {
|
|
199
|
+
this.nameList = names
|
|
200
|
+
this.parentRule = null
|
|
201
|
+
this.parentStyleSheet = null
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
get type () { return 0 }
|
|
205
|
+
get cssText () { return `@layer ${this.nameList.join(', ')};` }
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// One rule's text as a rule: cssom's reading of it, else the grouping rule
|
|
209
|
+
// (or @layer statement) a browser reads; else the error cssom threw.
|
|
210
|
+
// `cssomInsertRule` is cssom's own CSSStyleSheet#insertRule, run on a holder.
|
|
211
|
+
const readRule = (text, cssomInsertRule) => {
|
|
212
|
+
const holder = { cssRules: [] }
|
|
213
|
+
let error = null
|
|
214
|
+
try {
|
|
215
|
+
cssomInsertRule.call(holder, text, 0)
|
|
216
|
+
if (holder.cssRules[0]) return holder.cssRules[0]
|
|
217
|
+
} catch (e) {
|
|
218
|
+
error = e
|
|
219
|
+
}
|
|
220
|
+
const rule = groupingRuleOf(String(text).trim(), cssomInsertRule)
|
|
221
|
+
if (rule) return rule
|
|
222
|
+
throw error || new SyntaxError(`Failed to parse the rule '${text}'`)
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
const groupingRuleOf = (text, cssomInsertRule) => {
|
|
226
|
+
if (LAYER_STATEMENT.test(text)) {
|
|
227
|
+
return new ServerLayerStatementRule(text.replace(/^@layer\s+/i, '').replace(/\s*;$/, '').split(/\s*,\s*/))
|
|
228
|
+
}
|
|
229
|
+
const at = GROUP_AT_RULE.exec(text)
|
|
230
|
+
if (!at) return null
|
|
231
|
+
// exactly one block rule: `<prelude>{<rules>}` and nothing after it
|
|
232
|
+
const whole = topLevelRules(text)
|
|
233
|
+
if (!whole || whole.length !== 1 || !text.endsWith('}')) return null
|
|
234
|
+
const open = blockStart(text)
|
|
235
|
+
if (open === -1) return null
|
|
236
|
+
const inner = topLevelRules(text.slice(open + 1, -1))
|
|
237
|
+
if (!inner) return null
|
|
238
|
+
const rules = []
|
|
239
|
+
for (const ruleText of inner) {
|
|
240
|
+
try {
|
|
241
|
+
rules.push(readRule(ruleText, cssomInsertRule))
|
|
242
|
+
} catch (e) {
|
|
243
|
+
return null
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
return new ServerGroupingRule(at[1].toLowerCase(), text.slice(at[0].length, open).trim(), rules)
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// Where a rule's block opens: its first `{` outside strings and comments.
|
|
250
|
+
const blockStart = (text) => {
|
|
251
|
+
let quote = null
|
|
252
|
+
for (let i = 0; i < text.length; i++) {
|
|
253
|
+
const ch = text[i]
|
|
254
|
+
if (quote) {
|
|
255
|
+
if (ch === '\\') i++
|
|
256
|
+
else if (ch === quote) quote = null
|
|
257
|
+
continue
|
|
258
|
+
}
|
|
259
|
+
if (ch === '\\') {
|
|
260
|
+
i++
|
|
261
|
+
continue
|
|
262
|
+
}
|
|
263
|
+
if (ch === '/' && text[i + 1] === '*') {
|
|
264
|
+
const close = text.indexOf('*/', i + 2)
|
|
265
|
+
if (close === -1) return -1
|
|
266
|
+
i = close + 1
|
|
267
|
+
continue
|
|
268
|
+
}
|
|
269
|
+
if (ch === '"' || ch === "'") quote = ch
|
|
270
|
+
else if (ch === '{') return i
|
|
271
|
+
}
|
|
272
|
+
return -1
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
const adopt = (rule, sheet) => {
|
|
276
|
+
rule.parentStyleSheet = sheet
|
|
277
|
+
if (rule instanceof ServerGroupingRule) for (const inner of rule.cssRules) adopt(inner, sheet)
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// Patch linkedom's sheets once per process (the prototypes are cssom's and
|
|
281
|
+
// linkedom's, shared by every document): insertRule keeps a rule cssom cannot
|
|
282
|
+
// read and a browser keeps; `style.sheet` of a text holding one reads that
|
|
283
|
+
// text rule by rule, as a browser does, dropping only a rule it cannot read.
|
|
284
|
+
const patchStyleSheetRules = (document) => {
|
|
285
|
+
const style = document.createElement('style')
|
|
286
|
+
let sheet
|
|
287
|
+
try { sheet = style.sheet } catch (e) { return }
|
|
288
|
+
let sheetProto = sheet && Object.getPrototypeOf(sheet)
|
|
289
|
+
while (sheetProto && !Object.prototype.hasOwnProperty.call(sheetProto, 'insertRule')) sheetProto = Object.getPrototypeOf(sheetProto)
|
|
290
|
+
if (!sheetProto || Object.prototype.hasOwnProperty.call(sheetProto, GROUP_RULES)) return
|
|
291
|
+
const cssomInsertRule = sheetProto.insertRule
|
|
292
|
+
if (typeof cssomInsertRule !== 'function') return
|
|
293
|
+
const SheetConstructor = sheet.constructor
|
|
294
|
+
Object.defineProperty(sheetProto, GROUP_RULES, { configurable: true, value: true })
|
|
295
|
+
Object.defineProperty(sheetProto, 'insertRule', {
|
|
296
|
+
configurable: true,
|
|
297
|
+
writable: true,
|
|
298
|
+
value: function insertRule (rule, index) {
|
|
299
|
+
try {
|
|
300
|
+
return cssomInsertRule.apply(this, arguments)
|
|
301
|
+
} catch (error) {
|
|
302
|
+
const text = String(rule)
|
|
303
|
+
const kept = groupingRuleOf(text.trim(), cssomInsertRule)
|
|
304
|
+
if (!kept) throw error
|
|
305
|
+
const at = index === undefined ? 0 : Number(index)
|
|
306
|
+
if (!(at >= 0 && at <= this.cssRules.length)) throw new RangeError('INDEX_SIZE_ERR')
|
|
307
|
+
adopt(kept, this)
|
|
308
|
+
this.cssRules.splice(at, 0, kept)
|
|
309
|
+
return at
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
})
|
|
313
|
+
|
|
314
|
+
let styleProto = Object.getPrototypeOf(style)
|
|
315
|
+
let desc
|
|
316
|
+
while (styleProto && !(desc = Object.getOwnPropertyDescriptor(styleProto, 'sheet'))) styleProto = Object.getPrototypeOf(styleProto)
|
|
317
|
+
if (!styleProto || !desc || typeof desc.get !== 'function' || typeof SheetConstructor !== 'function') return
|
|
318
|
+
const readSheet = desc.get
|
|
319
|
+
// the sheet read from a style's text, while the text holds
|
|
320
|
+
const textSheets = new WeakMap()
|
|
321
|
+
Object.defineProperty(styleProto, 'sheet', {
|
|
322
|
+
configurable: true,
|
|
323
|
+
enumerable: desc.enumerable,
|
|
324
|
+
get () {
|
|
325
|
+
try {
|
|
326
|
+
return readSheet.call(this)
|
|
327
|
+
} catch (error) {
|
|
328
|
+
const text = this.textContent
|
|
329
|
+
const known = textSheets.get(this)
|
|
330
|
+
if (known && known.text === text) return known.sheet
|
|
331
|
+
const rules = topLevelRules(text)
|
|
332
|
+
if (!rules) throw error
|
|
333
|
+
const read = new SheetConstructor()
|
|
334
|
+
for (const rule of rules) {
|
|
335
|
+
try { read.insertRule(rule, read.cssRules.length) } catch (e) { /* a browser drops a rule it cannot read */ }
|
|
336
|
+
}
|
|
337
|
+
textSheets.set(this, { text, sheet: read })
|
|
338
|
+
return read
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
})
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
// ── A page timer that throws ends nothing ──────────────────────────────────
|
|
345
|
+
// In a browser, a timer callback that throws (or whose promise rejects) is a
|
|
346
|
+
// line in the console, and the page goes on. In Node it is an uncaught
|
|
347
|
+
// exception (an unhandled rejection), and a host that runs renders ends the
|
|
348
|
+
// process on one: the smbls CLI's handler exits 1. W7-FIXA (fly/landing-fly
|
|
349
|
+
// 1.5.0, a dry run that rendered every static route): `/ask` (fly/core
|
|
350
|
+
// askReveal / askStay) scheduled `win.setTimeout(...)` callbacks that call
|
|
351
|
+
// `win.getComputedStyle`, which the server window does not have; they fired
|
|
352
|
+
// during the next four renders, and `smbls publish` stopped in its
|
|
353
|
+
// pre-render, before it published. The window's own timers (setTimeout,
|
|
354
|
+
// setInterval, the requestAnimationFrame shim) now run their callback inside
|
|
355
|
+
// a try and report what it throws, once per message. What stays: page code
|
|
356
|
+
// that calls the BARE `setTimeout` gets Node's own, and a timer a render
|
|
357
|
+
// left behind still fires during a later render (render.js "One render at a
|
|
358
|
+
// time").
|
|
359
|
+
const reportedTimerErrors = new Set()
|
|
360
|
+
const reportTimerError = (err) => {
|
|
361
|
+
const message = (err && err.message) || String(err)
|
|
362
|
+
if (reportedTimerErrors.has(message)) return
|
|
363
|
+
if (reportedTimerErrors.size >= 200) reportedTimerErrors.clear()
|
|
364
|
+
reportedTimerErrors.add(message)
|
|
365
|
+
console.warn(`[brender] page code threw in a timer; reported as a browser reports it, the render goes on (once per message): ${message}`)
|
|
366
|
+
}
|
|
367
|
+
const containedCallback = (fn) => function (...args) {
|
|
368
|
+
try {
|
|
369
|
+
const out = fn.apply(this, args)
|
|
370
|
+
if (out && typeof out.then === 'function') out.then(null, reportTimerError)
|
|
371
|
+
return out
|
|
372
|
+
} catch (err) {
|
|
373
|
+
reportTimerError(err)
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
// The process's timer, read when the page calls it (as the window read it
|
|
377
|
+
// before: a host's fake timers still apply). Node refuses a callback that is
|
|
378
|
+
// not a function; so does this.
|
|
379
|
+
const pageTimer = (name) => (fn, ...rest) =>
|
|
380
|
+
globalThis[name](typeof fn === 'function' ? containedCallback(fn) : fn, ...rest)
|
|
381
|
+
|
|
36
382
|
// Web Storage for one environment.
|
|
37
383
|
const createStorage = () => {
|
|
38
384
|
const store = {}
|
|
@@ -82,14 +428,19 @@ export const createEnv = (html = '<!DOCTYPE html><html><head></head><body></body
|
|
|
82
428
|
pushState: () => {},
|
|
83
429
|
replaceState: () => {},
|
|
84
430
|
state: null
|
|
85
|
-
}
|
|
431
|
+
},
|
|
432
|
+
// the page's timers: a callback that throws is reported, never the end of
|
|
433
|
+
// the process (see "A page timer that throws ends nothing"); the bare
|
|
434
|
+
// globals stay Node's
|
|
435
|
+
setTimeout: pageTimer('setTimeout'),
|
|
436
|
+
setInterval: pageTimer('setInterval')
|
|
86
437
|
}
|
|
87
438
|
const { window, document } = parseHTML(html, own)
|
|
88
439
|
|
|
89
440
|
// Stub APIs that DOMQL/smbls may call during rendering
|
|
90
441
|
if (!window.requestAnimationFrame) {
|
|
91
442
|
window.requestAnimationFrame = (fn) => {
|
|
92
|
-
const id = setTimeout(fn, 0)
|
|
443
|
+
const id = setTimeout(typeof fn === 'function' ? containedCallback(fn) : fn, 0)
|
|
93
444
|
if (id?.unref) id.unref()
|
|
94
445
|
return id
|
|
95
446
|
}
|
|
@@ -123,6 +474,23 @@ export const createEnv = (html = '<!DOCTYPE html><html><head></head><body></body
|
|
|
123
474
|
// setter: the option with that value becomes the selected one.
|
|
124
475
|
patchSelectValue(document)
|
|
125
476
|
|
|
477
|
+
// linkedom's CSSStyleDeclaration sends every name through `uhyphen`
|
|
478
|
+
// (setProperty, removeProperty, getPropertyValue): a custom property
|
|
479
|
+
// `--coverBoxLeft` went into the markup as `--cover-box-left`. That is
|
|
480
|
+
// another property — a custom property's name is case-sensitive — so the
|
|
481
|
+
// first paint read the `var()` fallback, and the browser's own write of
|
|
482
|
+
// `--coverBoxLeft` moved the content when the adoption came (XMA's hero
|
|
483
|
+
// text, 30px to the right on the desktop home: CLS 0.0003; r11 smoke check).
|
|
484
|
+
// A `--*` name is now kept as it is; every other name goes through linkedom
|
|
485
|
+
// as before.
|
|
486
|
+
patchCustomPropertyNames(document)
|
|
487
|
+
|
|
488
|
+
// linkedom's sheets parse with `cssom` 0.5, which cannot read a container
|
|
489
|
+
// query: every breakpoint under a `breakpointsFrom` ancestor lost its rule
|
|
490
|
+
// (W7-FIXA: 153 of the 343 `_c` classes of the merged landing's `/`). A rule
|
|
491
|
+
// a browser keeps is kept (see patchStyleSheetRules).
|
|
492
|
+
patchStyleSheetRules(document)
|
|
493
|
+
|
|
126
494
|
// Stub MutationObserver for SSR (not provided by linkedom)
|
|
127
495
|
if (!window.MutationObserver) {
|
|
128
496
|
window.MutationObserver = class MutationObserver {
|
package/faces.js
ADDED
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
// ── The page's font faces: one sheet, marked for a host ─────────────────────
|
|
2
|
+
// renderPage writes every @font-face rule of the page into ONE
|
|
3
|
+
// `<style data-smbls-faces>` ahead of the server's styles (render.js,
|
|
4
|
+
// facesStyle). XMA r12, ask 2 (mirc #430, decided in #440/#441): a host
|
|
5
|
+
// that serves a returning reader the faces it already keeps by address, and
|
|
6
|
+
// the rest inline, needs to tell kept faces from new ones and to warm the
|
|
7
|
+
// files. So the sheet carries two marks:
|
|
8
|
+
//
|
|
9
|
+
// data-smbls-faces-version a content hash of the sheet's rules — equal
|
|
10
|
+
// rules (same text, same order) give the same
|
|
11
|
+
// version, any change to a rule a new one
|
|
12
|
+
// data-smbls-faces-files per @font-face rule, in rule order, its file
|
|
13
|
+
// address: the first url() of its `src` (a
|
|
14
|
+
// `local()` source before it is skipped), as the
|
|
15
|
+
// rule names it; `-` for a rule that names no
|
|
16
|
+
// file (no url(), or a data: url); a space in an
|
|
17
|
+
// address is written %20
|
|
18
|
+
//
|
|
19
|
+
// The marks describe the faces BY ADDRESS. `smbls build` marks each page
|
|
20
|
+
// again after its file: URLs point into the build output (the version then
|
|
21
|
+
// follows the files the page names, and every page of a build carries the
|
|
22
|
+
// same one when the font URLs are site-root or absolute); its
|
|
23
|
+
// `brender.faces.inline` step moves files into the sheet as data: URLs and
|
|
24
|
+
// keeps both marks as they are, so an inlined face still names its file.
|
|
25
|
+
// No sheet file is written: the host builds a by-address sheet from the list.
|
|
26
|
+
import { createHash } from 'crypto'
|
|
27
|
+
import { stylesheetRules } from './pageWeight.js'
|
|
28
|
+
|
|
29
|
+
export const FACES_SHEET_ATTR = 'data-smbls-faces'
|
|
30
|
+
export const FACES_VERSION_ATTR = 'data-smbls-faces-version'
|
|
31
|
+
export const FACES_FILES_ATTR = 'data-smbls-faces-files'
|
|
32
|
+
// The list entry of a rule that names no file.
|
|
33
|
+
export const NO_FACE_FILE = '-'
|
|
34
|
+
|
|
35
|
+
const FACE_RULE_START = /^@font-face\b/i
|
|
36
|
+
const isFaceRule = (rule) => typeof rule === 'string' && FACE_RULE_START.test(rule.trim())
|
|
37
|
+
|
|
38
|
+
// The declarations of one rule's block, name (lowercase) → value, read past
|
|
39
|
+
// strings and parentheses: a `;` inside `url(data:…;base64,…)` or a quoted
|
|
40
|
+
// url does not end a declaration.
|
|
41
|
+
const declarationsOf = (rule) => {
|
|
42
|
+
const out = {}
|
|
43
|
+
const open = rule.indexOf('{')
|
|
44
|
+
if (open === -1) return out
|
|
45
|
+
let start = open + 1
|
|
46
|
+
let quote = null
|
|
47
|
+
let parens = 0
|
|
48
|
+
const take = (end) => {
|
|
49
|
+
const text = rule.slice(start, end)
|
|
50
|
+
const colon = text.indexOf(':')
|
|
51
|
+
if (colon !== -1) {
|
|
52
|
+
const name = text.slice(0, colon).trim().toLowerCase()
|
|
53
|
+
if (name && !(name in out)) out[name] = text.slice(colon + 1).trim()
|
|
54
|
+
}
|
|
55
|
+
start = end + 1
|
|
56
|
+
}
|
|
57
|
+
for (let i = start; i < rule.length; i++) {
|
|
58
|
+
const ch = rule[i]
|
|
59
|
+
if (quote) {
|
|
60
|
+
if (ch === '\\') i++
|
|
61
|
+
else if (ch === quote) quote = null
|
|
62
|
+
continue
|
|
63
|
+
}
|
|
64
|
+
if (ch === '\\') { i++; continue }
|
|
65
|
+
if (ch === '"' || ch === "'") quote = ch
|
|
66
|
+
else if (ch === '(') parens++
|
|
67
|
+
else if (ch === ')') parens = Math.max(0, parens - 1)
|
|
68
|
+
else if (parens) continue
|
|
69
|
+
else if (ch === ';') take(i)
|
|
70
|
+
else if (ch === '}') { take(i); break }
|
|
71
|
+
}
|
|
72
|
+
return out
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// A code point from a CSS hex escape (an invalid one is U+FFFD).
|
|
76
|
+
const codePoint = (hex) => {
|
|
77
|
+
const n = parseInt(hex, 16)
|
|
78
|
+
return n > 0 && n <= 0x10FFFF && !(n >= 0xD800 && n <= 0xDFFF) ? String.fromCodePoint(n) : '�'
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// The text of a url() argument: quotes off, CSS escapes resolved.
|
|
82
|
+
const urlText = (text) => {
|
|
83
|
+
const t = text.trim()
|
|
84
|
+
const q = t[0]
|
|
85
|
+
const body = (q === '"' || q === "'") && t.length > 1 && t[t.length - 1] === q ? t.slice(1, -1) : t
|
|
86
|
+
return body.replace(/\\(?:([0-9a-fA-F]{1,6})\s?|([\s\S]))/g, (m, hex, ch) => hex ? codePoint(hex) : ch).trim()
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* The file address a `src` value names: its first `url()` (a `local()`
|
|
91
|
+
* before it is skipped). null when it names none, or only a data: url.
|
|
92
|
+
* @param {string} src - the value of a `@font-face` rule's `src`
|
|
93
|
+
* @returns {string|null}
|
|
94
|
+
*/
|
|
95
|
+
export const faceFileOf = (src) => {
|
|
96
|
+
if (typeof src !== 'string') return null
|
|
97
|
+
let quote = null
|
|
98
|
+
for (let i = 0; i < src.length; i++) {
|
|
99
|
+
const ch = src[i]
|
|
100
|
+
if (quote) {
|
|
101
|
+
if (ch === '\\') i++
|
|
102
|
+
else if (ch === quote) quote = null
|
|
103
|
+
continue
|
|
104
|
+
}
|
|
105
|
+
if (ch === '"' || ch === "'") { quote = ch; continue }
|
|
106
|
+
if (!/^url\(/i.test(src.slice(i, i + 4)) || /[\w-]/.test(src[i - 1] || '')) continue
|
|
107
|
+
// the argument: a quoted string, or the raw text up to the `)`
|
|
108
|
+
let j = i + 4
|
|
109
|
+
while (j < src.length && /\s/.test(src[j])) j++
|
|
110
|
+
let end
|
|
111
|
+
if (src[j] === '"' || src[j] === "'") {
|
|
112
|
+
end = j + 1
|
|
113
|
+
while (end < src.length && src[end] !== src[j]) end += src[end] === '\\' ? 2 : 1
|
|
114
|
+
end++
|
|
115
|
+
} else {
|
|
116
|
+
end = src.indexOf(')', j)
|
|
117
|
+
if (end === -1) end = src.length
|
|
118
|
+
}
|
|
119
|
+
const value = urlText(src.slice(j, end))
|
|
120
|
+
return value && !/^data:/i.test(value) ? value : null
|
|
121
|
+
}
|
|
122
|
+
return null
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// A whitespace character in an address, percent-encoded: the list is
|
|
126
|
+
// space-separated.
|
|
127
|
+
const listEntry = (file) => file
|
|
128
|
+
? file.replace(/\s/g, (c) => '%' + c.charCodeAt(0).toString(16).toUpperCase().padStart(2, '0'))
|
|
129
|
+
: NO_FACE_FILE
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* The marks of a faces sheet, from its rules.
|
|
133
|
+
* @param {string|string[]} rules - the sheet's text, or its rules in order
|
|
134
|
+
* @returns {{ version: string, files: string[] }|null} null for a sheet with
|
|
135
|
+
* no @font-face rule, or a text that cannot be read whole
|
|
136
|
+
*/
|
|
137
|
+
export const facesSheetMarks = (rules) => {
|
|
138
|
+
const list = typeof rules === 'string' ? stylesheetRules(rules) : Array.isArray(rules) ? rules : null
|
|
139
|
+
if (!list) return null
|
|
140
|
+
const all = list.map((rule) => String(rule).trim()).filter(Boolean)
|
|
141
|
+
const faces = all.filter(isFaceRule)
|
|
142
|
+
if (!faces.length) return null
|
|
143
|
+
return {
|
|
144
|
+
version: createHash('sha256').update(all.join('\n')).digest('hex').slice(0, 12),
|
|
145
|
+
files: faces.map((rule) => listEntry(faceFileOf(declarationsOf(rule).src)))
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const escapeAttr = (value) => String(value).replace(/&/g, '&').replace(/"/g, '"').replace(/</g, '<')
|
|
150
|
+
|
|
151
|
+
const marksText = (marks) => marks
|
|
152
|
+
? ` ${FACES_VERSION_ATTR}="${escapeAttr(marks.version)}" ${FACES_FILES_ATTR}="${escapeAttr(marks.files.join(' '))}"`
|
|
153
|
+
: ''
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* The page's `<style data-smbls-faces>` for its @font-face rules, marked.
|
|
157
|
+
* @param {string[]} rules - the rules, in order
|
|
158
|
+
* @returns {string} '' for no rule
|
|
159
|
+
*/
|
|
160
|
+
export const facesSheetTag = (rules) => {
|
|
161
|
+
if (!Array.isArray(rules) || !rules.length) return ''
|
|
162
|
+
return `<style ${FACES_SHEET_ATTR}${marksText(facesSheetMarks(rules))}>\n${rules.join('\n')}\n</style>`
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// A <style> element, or a <script> one (skipped: its text is code, not markup).
|
|
166
|
+
const STYLE_OR_SCRIPT = /<script\b[\s\S]*?<\/script\s*>|<style\b([^>]*)>([\s\S]*?)(<\/style\s*>)/gi
|
|
167
|
+
const SHEET_ATTR = /(^|\s)data-smbls-faces(?:\s*=\s*(?:"[^"]*"|'[^']*'|[^\s>]+))?(?=[\s/]|$)/i
|
|
168
|
+
const OLD_MARK = /\s(?:data-smbls-faces-version|data-smbls-faces-files)(?:\s*=\s*(?:"[^"]*"|'[^']*'|[^\s>]+))?/gi
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Mark a page's faces sheet again from the rules it holds now — after a
|
|
172
|
+
* step changed the addresses its rules name (`smbls build` maps file: URLs
|
|
173
|
+
* into the build output). The sheet's text and its other attributes stay as
|
|
174
|
+
* they are; the two marks are written again after `data-smbls-faces`. A page
|
|
175
|
+
* without a faces sheet, or with one whose text cannot be read whole, comes
|
|
176
|
+
* back as it was.
|
|
177
|
+
* @param {string} html
|
|
178
|
+
* @returns {string}
|
|
179
|
+
*/
|
|
180
|
+
export const markFacesSheet = (html) => {
|
|
181
|
+
if (typeof html !== 'string' || !html.includes(FACES_SHEET_ATTR)) return html
|
|
182
|
+
let done = false
|
|
183
|
+
return html.replace(STYLE_OR_SCRIPT, (whole, attrs, css, close) => {
|
|
184
|
+
if (done || attrs === undefined || !SHEET_ATTR.test(attrs)) return whole
|
|
185
|
+
done = true
|
|
186
|
+
const marks = facesSheetMarks(css)
|
|
187
|
+
if (!marks) return whole
|
|
188
|
+
const rest = attrs.replace(OLD_MARK, '')
|
|
189
|
+
const at = SHEET_ATTR.exec(rest)
|
|
190
|
+
const end = at.index + at[0].length
|
|
191
|
+
return `<style${rest.slice(0, end)}${marksText(marks)}${rest.slice(end)}>${css}${close}`
|
|
192
|
+
})
|
|
193
|
+
}
|
package/index.js
CHANGED
|
@@ -12,6 +12,10 @@ import { composeIntoShell } from './shell.js'
|
|
|
12
12
|
// or trims a stored page as it serves it (stylesheetRules reads a stored
|
|
13
13
|
// stylesheet text back into the rules classRulesForPage takes).
|
|
14
14
|
import { trimSpriteToPage, classRulesForPage, markupClasses, trimVariablesToPage, stylesheetRules } from './pageWeight.js'
|
|
15
|
+
// The faces sheet's marks (XMA r12): a caller that changes the addresses a
|
|
16
|
+
// page's faces name (`smbls build` maps file: URLs into its output) marks the
|
|
17
|
+
// sheet again.
|
|
18
|
+
import { markFacesSheet, facesSheetMarks } from './faces.js'
|
|
15
19
|
|
|
16
20
|
export {
|
|
17
21
|
createEnv,
|
|
@@ -42,7 +46,9 @@ export {
|
|
|
42
46
|
classRulesForPage,
|
|
43
47
|
markupClasses,
|
|
44
48
|
trimVariablesToPage,
|
|
45
|
-
stylesheetRules
|
|
49
|
+
stylesheetRules,
|
|
50
|
+
markFacesSheet,
|
|
51
|
+
facesSheetMarks
|
|
46
52
|
}
|
|
47
53
|
|
|
48
54
|
export default {
|
|
@@ -74,5 +80,7 @@ export default {
|
|
|
74
80
|
classRulesForPage,
|
|
75
81
|
markupClasses,
|
|
76
82
|
trimVariablesToPage,
|
|
77
|
-
stylesheetRules
|
|
83
|
+
stylesheetRules,
|
|
84
|
+
markFacesSheet,
|
|
85
|
+
facesSheetMarks
|
|
78
86
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@symbo.ls/brender",
|
|
3
|
-
"version": "3.14.
|
|
3
|
+
"version": "3.14.678",
|
|
4
4
|
"license": "CC-BY-NC-4.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"module": "./dist/esm/index.js",
|
|
@@ -25,6 +25,11 @@
|
|
|
25
25
|
"development": "./load.js",
|
|
26
26
|
"import": "./dist/esm/load.js",
|
|
27
27
|
"default": "./dist/esm/load.js"
|
|
28
|
+
},
|
|
29
|
+
"./faces": {
|
|
30
|
+
"development": "./faces.js",
|
|
31
|
+
"import": "./dist/esm/faces.js",
|
|
32
|
+
"default": "./dist/esm/faces.js"
|
|
28
33
|
}
|
|
29
34
|
},
|
|
30
35
|
"source": "index.js",
|
|
@@ -49,15 +54,15 @@
|
|
|
49
54
|
"@symbo.ls/css": "^3.14.782",
|
|
50
55
|
"@symbo.ls/default-config": "^3.14.789",
|
|
51
56
|
"@symbo.ls/element": "^3.14.818",
|
|
52
|
-
"@symbo.ls/fetch": "^3.14.
|
|
53
|
-
"@symbo.ls/helmet": "^3.14.
|
|
54
|
-
"@symbo.ls/scratch": "^3.14.
|
|
55
|
-
"@symbo.ls/utils": "^3.14.
|
|
57
|
+
"@symbo.ls/fetch": "^3.14.674",
|
|
58
|
+
"@symbo.ls/helmet": "^3.14.673",
|
|
59
|
+
"@symbo.ls/scratch": "^3.14.796",
|
|
60
|
+
"@symbo.ls/utils": "^3.14.854",
|
|
56
61
|
"esbuild": "^0.28.1",
|
|
57
62
|
"linkedom": "^0.16.8"
|
|
58
63
|
},
|
|
59
64
|
"peerDependencies": {
|
|
60
|
-
"smbls": "^3.14.
|
|
65
|
+
"smbls": "^3.14.860"
|
|
61
66
|
},
|
|
62
67
|
"optionalDependencies": {
|
|
63
68
|
"@symbo.ls/funcql": "^3.14.0"
|
package/render.js
CHANGED
|
@@ -11,6 +11,7 @@ import { extractMetadata, generateHeadHtml } from './metadata.js'
|
|
|
11
11
|
import { hydrate } from './hydrate.js'
|
|
12
12
|
import { composeIntoShell } from './shell.js'
|
|
13
13
|
import { trimSpriteToPage, classRulesForPage, markupClasses, trimVariablesToPage } from './pageWeight.js'
|
|
14
|
+
import { facesSheetTag } from './faces.js'
|
|
14
15
|
import { fetchSSRTranslations } from './prefetch.js'
|
|
15
16
|
import { createEarlySeedScript } from '@symbo.ls/fetch'
|
|
16
17
|
import { headerEntries } from '@symbo.ls/fetch/rest'
|
|
@@ -2114,7 +2115,10 @@ export const renderPage = async (data, route = '/', options = {}) => {
|
|
|
2114
2115
|
const emotionCSS = cssClasses === 'page'
|
|
2115
2116
|
? classRulesForPage(classRules, markupClasses(bodyHtml, typeof shell === 'string' ? shell : '')).join('\n')
|
|
2116
2117
|
: classRules.join('\n')
|
|
2117
|
-
|
|
2118
|
+
// Marked for a host (faces.js): `data-smbls-faces-version` (a hash of the
|
|
2119
|
+
// rules) and `data-smbls-faces-files` (each rule's file address, in rule
|
|
2120
|
+
// order) — XMA r12, ask 2.
|
|
2121
|
+
const facesStyle = facesSheetTag(faceRules)
|
|
2118
2122
|
|
|
2119
2123
|
// Global CSS (variables, reset, keyframes): this render's own init output.
|
|
2120
2124
|
// The style tags, built once for both documents below.
|