@symbo.ls/brender 3.14.667 → 3.14.669
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 +29 -0
- package/README.md +22 -2
- package/dist/esm/index.js +1 -1
- package/dist/esm/prefetch.js +1 -1
- package/dist/esm/render.js +30 -29
- package/dist/esm/shell.js +6 -6
- package/index.js +5 -3
- package/package.json +9 -9
- package/prefetch.js +3 -1
- package/render.js +98 -11
- package/shell.js +11 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,34 @@
|
|
|
1
1
|
# @symbo.ls/brender
|
|
2
2
|
|
|
3
|
+
## 3.14.669
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- XMA round 5 ("smbls 3.14.843: what XMA still needs"): container and current CSS props + a dev warning for a dropped CSS prop (S); `_redirects` file rules on Cloudflare (T); `build.prepareDesignSystem: false` covers the pre-render app shell (U); preloads after the charset and `build.modulePreload: "strict"` (K); Dialog re-opens on an `if:` re-mount and hides a closed modal from its first moment (G, R); DropdownDisclosure `open`, `toggle` and `keepOpen` (H); the first render keeps a scrolled reader on an adopted page (P); `addressLang: 'strict'` and languages from a function (I); `fetch.early` chained requests and `afterFirstView` (J, 8); adoption keeps unchanged text, adopts in place, keeps the page's font faces, puts the app's sheets before the server's (F, O, Q, N); the head of a pre-rendered page resolves the page's own metadata (L). Plus the unreleased fixes on main since round 4b.
|
|
8
|
+
- Updated dependencies
|
|
9
|
+
- @symbo.ls/fetch@3.14.669
|
|
10
|
+
- @symbo.ls/utils@3.14.845
|
|
11
|
+
- @symbo.ls/scratch@3.14.792
|
|
12
|
+
- @symbo.ls/css@3.14.782
|
|
13
|
+
- @symbo.ls/element@3.14.809
|
|
14
|
+
- @symbo.ls/default-config@3.14.789
|
|
15
|
+
- @symbo.ls/helmet@3.14.671
|
|
16
|
+
- smbls@3.14.844
|
|
17
|
+
|
|
18
|
+
## 3.14.668
|
|
19
|
+
|
|
20
|
+
### Patch Changes
|
|
21
|
+
|
|
22
|
+
- XMA round 3 (performance): a pre-rendered page is adopted in short tasks; the first render puts rows on screen whole; the design system is prepared at build time and adopted by the page; `smbls build` adds V8 compile hints and (opt-in, `build.modulePreload`) lazy-module preloads; brender's preload goes after the import map; pre-built pages can be flagged stale (`fetch.seed.isStale` / `maxAge` / `onRefresh`, `<html data-smbls-stale>`); routes without their own page answer with 200.html on every deploy target; the app starts in the address's language (`addressLang`) and adopts the seeded answers; `DropdownDisclosure` and Dialog `locked`; frank-audit accepts DOM work on a bridged library's and a parsed document's own nodes; a server render waits for its own labels (label loads in flight are per app); mermaid renderProjectHtml takes a caller-resolved head (`options.metadata`) and `metadata.source` is never a tag.
|
|
23
|
+
- Updated dependencies
|
|
24
|
+
- @symbo.ls/css@3.14.780
|
|
25
|
+
- @symbo.ls/default-config@3.14.788
|
|
26
|
+
- @symbo.ls/element@3.14.806
|
|
27
|
+
- @symbo.ls/scratch@3.14.790
|
|
28
|
+
- smbls@3.14.840
|
|
29
|
+
- @symbo.ls/utils@3.14.789
|
|
30
|
+
- @symbo.ls/fetch@3.14.667
|
|
31
|
+
|
|
3
32
|
## 3.14.667
|
|
4
33
|
|
|
5
34
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -157,6 +157,8 @@ Options:
|
|
|
157
157
|
| `adoptDom` | `false` | Ship the element-path registry, so the client adopts the markup instead of replacing it (`smbls build` passes it unless `brender: { hydrate: false }`) |
|
|
158
158
|
| `isr` | — | `{ clientScript }`: a standalone document that boots this client bundle |
|
|
159
159
|
| `hydrate` | `true` | With `isr`: boot the client over the markup (`false`: the legacy swap script) |
|
|
160
|
+
| `stamp` | the render time (ISO) | The build's data stamp, written into `<meta name="brender-stamp">` — a string or a number |
|
|
161
|
+
| `time` | now | The build time in ms, written as its `data-time`. A build passes one `stamp` and `time` to every page |
|
|
160
162
|
| `nonce` | — | Content-Security-Policy nonce for the inline scripts the `isr` document still has (the label seed, the legacy swap script) — a page rendered into a shell has none |
|
|
161
163
|
| `themeColor` | | See render.js. |
|
|
162
164
|
|
|
@@ -178,9 +180,12 @@ No inline script — a strict Content-Security-Policy (`script-src 'self'`, no `
|
|
|
178
180
|
| `data-br="br-N"` | each server element node | The node's key |
|
|
179
181
|
| `<script type="application/json" data-brender="registry">{…}</script>` | after the markup | Element path → `data-br` key (`__root` for the app's root); adoption pages only |
|
|
180
182
|
| `<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 |
|
|
183
|
+
| `<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 }`) |
|
|
181
184
|
|
|
182
185
|
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.
|
|
183
186
|
|
|
187
|
+
**The data is the build's.** A pre-rendered page shows the answers its render got until it is rendered again — a `smbls build` page until the next build. A page flagged stale (`<html data-smbls-stale>` from the host, or the project's `fetch.seed.isStale(stamp)` / `fetch.seed.maxAge`) shows them at once and then, after the adoption, asks again and swaps in what changed; `fetch.seed: false` keeps the answers out of the page. See `@symbo.ls/fetch`, "A pre-built page shows the data of its build".
|
|
188
|
+
|
|
184
189
|
What is recorded for the seed: a GET whose only headers are `Accept` / `Accept-Language` — so an anonymous read of a keyed backend counts when `fetch.reads` moves its key into the query string (`reads: { query: { apikey: 'apikey' }, rpc: 'get' }`, read-only RPCs as GETs) — with an ok JSON or text answer, at most 256 KB each and 1 MB per page. Never a POST, a call with `Authorization` or another header, or anything from a render that overlapped another.
|
|
185
190
|
|
|
186
191
|
### `prefetchPageData(data, route, options?)` (deprecated)
|
|
@@ -288,16 +293,31 @@ Output directory defaults to `dist-brender` (or `brenderDistDir` from `symbols.j
|
|
|
288
293
|
|
|
289
294
|
Param routes (e.g. `/blog/:id`) are skipped unless `brender.paths` lists addresses for them.
|
|
290
295
|
|
|
291
|
-
`smbls brender` and `smbls build` share one route loop: the same `brender` settings from `symbols.json` (`context`, `paths`, `exclude`, `strict`, `eagerImages`, `timeout`), `file:` URLs resolved into the output (a `file:` URL that names no project file fails its route), and the same diagnostics. `smbls brender` writes standalone pages in a directory layout (`/about` → `about/index.html`), each with the CSS of every page it rendered.
|
|
296
|
+
`smbls brender` and `smbls build` share one route loop: the same `brender` settings from `symbols.json` (`context`, `paths`, `exclude`, `strict`, `eagerImages`, `timeout`, `stamp`), `file:` URLs resolved into the output (a `file:` URL that names no project file fails its route), and the same diagnostics. `smbls brender` writes standalone pages in a directory layout (`/about` → `about/index.html`), each with the CSS of every page it rendered.
|
|
292
297
|
|
|
293
298
|
### `smbls build`
|
|
294
299
|
|
|
295
300
|
With brender on (the default), `smbls build` pre-renders every static route INTO the bundler's `dist/index.html`:
|
|
296
301
|
|
|
297
302
|
- `/about` → `about.html`, `/` → `index.html`, `/*` → `404.html` rendered at `/404`. Two routes that end in one file (`/404` and `/*`) are written once — the first in page order — and the other is reported.
|
|
298
|
-
- The bundler's own document is kept as **`200.html`** — serve it for client-side routes
|
|
303
|
+
- The bundler's own document is kept as **`200.html`** — serve it for client-side routes, never `index.html`, which is now the home page (see below). A `200.html` already in the output (`public/`) is not replaced.
|
|
299
304
|
- Every `file:` URL in a page (a `new URL('…', import.meta.url)` asset) is mapped to the file the bundler emitted from the same source (found by content), or copied into the output under a content hash. A `file:` URL that names no project file fails the build.
|
|
300
305
|
- A page made with failed data or label requests, or with raw label keys, is reported; `smbls build --strict` (or `brender.strict`) fails the build on it.
|
|
306
|
+
- Every page carries the data of the build time until the next build, and says which build that is: one stamp per build (`<meta name="brender-stamp">`). `brender.stamp` in `symbols.json` (a string or a number), else `brender.stamp` in the project's `config.js` — a value, or a function `({ time, data }) => stamp` run once per build (it may be async: the data's own version, such as the time of its latest change) — else the build time. A stamp function that throws fails the build. How a page learns that its data moved on: `@symbo.ls/fetch`, "A pre-built page shows the data of its build".
|
|
307
|
+
|
|
308
|
+
#### Routes without their own page: 200.html
|
|
309
|
+
|
|
310
|
+
**Serve 200.html, never another page, for a route without its own page** — a route with parameters that `brender.paths` does not list (`/campaigns/:id`), a page with `prerender: false`, an excluded or failed one. `index.html` is now the HOME page: a host's single-page fallback that serves it shows the home page first, then the client throws its nodes away and draws the right page (XMA: `removed: 439` on a campaign page). A host's 404 handling serves the pre-rendered not-found page, with a 404 status, under the right page's client. `smbls build` writes the rules into `<outDir>/_redirects` (after the project's own rules from `public/`; `brender.redirects: false` writes none). A pre-rendered address that a route pattern also matches gets a rule to itself first — Cloudflare follows a rule even where a file matches. The rule per host:
|
|
311
|
+
|
|
312
|
+
| Host | The rule | Written by |
|
|
313
|
+
|------|----------|------------|
|
|
314
|
+
| Cloudflare Workers (static assets) | `_redirects` in the output: each route without its own page → `/200` with status `200`; `assets.not_found_handling`: `"404-page"` with a 404 page, else `"none"` — never `"single-page-application"`, which serves index.html | `smbls build` (`_redirects`), `smbls deploy --target workers` (`wrangler.jsonc`) |
|
|
315
|
+
| Cloudflare Pages | the same `_redirects`; every other address gets 404.html. Without a 404.html, Pages falls back to index.html: give the project a `/*` page | `smbls build` |
|
|
316
|
+
| Netlify | the same `_redirects` (a file always wins over a rewrite there) | `smbls build` |
|
|
317
|
+
| Vercel | `vercel.json`: `"cleanUrls": true` and `"rewrites": [{ "source": "/(.*)", "destination": "/200" }]` — Vercel checks the filesystem first, so every pre-rendered page keeps its own file; an unknown address gets the app's not-found page with status 200 | `smbls deploy --provider vercel` |
|
|
318
|
+
| GitHub Pages | no rewrites: an address with no file gets 404.html (status 404). Without a 404 page of the project's own, copy 200.html to 404.html; better, pre-render the routes with parameters (`brender.paths`) | `smbls deploy --provider github-pages` (the workflow) |
|
|
319
|
+
| Surge | serves 200.html for every unknown address by itself | — |
|
|
320
|
+
| Your own server or Worker | try the file, then `<path>.html`, then 200.html — nginx: `try_files $uri $uri.html /200.html;`; a Worker: `env.ASSETS.fetch(new URL('/200', request.url))` | — |
|
|
301
321
|
|
|
302
322
|
### symbols.json
|
|
303
323
|
|
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 m,loadAndRenderAll as a}from"./load.js";import{render as p,renderElement 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 m,loadAndRenderAll as a}from"./load.js";import{render as p,renderElement as d,renderRoute as n,renderPage as i,resetGlobalCSSCache as l,getAccumulatedEmotionCSS as c,replaceEmotionCSS as f,adoptionScripts as s,prerenderParts as S,buildStamp as g}from"./render.js";import{extractMetadata as h,generateHeadHtml as u}from"./metadata.js";import{collectBrNodes as E,hydrate as P}from"./hydrate.js";import{generateSitemap as y}from"./sitemap.js";import{prefetchPageData as C,injectPrefetchedState as x}from"./prefetch.js";import{composeIntoShell as A}from"./shell.js";var I={createEnv:e,resetKeys:r,assignKeys:t,mapKeysToElements:o,loadProject:m,loadAndRenderAll:a,render:p,renderElement:d,renderRoute:n,renderPage:i,resetGlobalCSSCache:l,getAccumulatedEmotionCSS:c,replaceEmotionCSS:f,extractMetadata:h,generateHeadHtml:u,collectBrNodes:E,hydrate:P,generateSitemap:y,prefetchPageData:C,injectPrefetchedState:x,composeIntoShell:A,adoptionScripts:s,prerenderParts:S,buildStamp:g};export{s as adoptionScripts,t as assignKeys,g as buildStamp,E as collectBrNodes,A as composeIntoShell,e as createEnv,I as default,h as extractMetadata,u as generateHeadHtml,y as generateSitemap,c as getAccumulatedEmotionCSS,P as hydrate,x as injectPrefetchedState,a as loadAndRenderAll,m as loadProject,o as mapKeysToElements,C as prefetchPageData,S as prerenderParts,p as render,d as renderElement,i as renderPage,n as renderRoute,f as replaceEmotionCSS,l as resetGlobalCSSCache,r as resetKeys};
|
package/dist/esm/prefetch.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
const
|
|
1
|
+
const l=t=>typeof t=="function",S=t=>Array.isArray(t),g=t=>t!==null&&typeof t=="object"&&!Array.isArray(t),_=(t,r)=>{if(t){if(l(t))try{return t({state:r||{},call:()=>{},__ref:{}},r||{})}catch{return}return t}},j=(t,r)=>{if(!t)return null;if(typeof t=="string")return{from:t,method:"select"};const n=l(t)?null:{...t};return!n||(n.method||(n.method="select"),l(n.params)&&(n.params=_(n.params,r)),n.method==="insert"||n.method==="update"||n.method==="upsert"||n.method==="delete")||n.on&&n.on!=="create"?null:n},b=(t,r="")=>{if(!t||typeof t!="object")return[];if(l(t))return[];const n=[],s=t.state||{};if(t.fetch){const e=S(t.fetch)?t.fetch:[t.fetch];for(const c of e){const a=j(c,s);a&&n.push({config:a,stateKey:a.as,path:r,elementState:s})}}for(const e in t)e==="fetch"||e==="state"||e==="props"||e==="attr"||e==="on"||e==="define"||e==="childExtends"||e==="childProps"||e==="childrenAs"||e.charAt(0)>="A"&&e.charAt(0)<="Z"&&g(t[e])&&n.push(...b(t[e],r?`${r}.${e}`:e));return n},M=["rest","local"],w=new WeakMap,A=async t=>{if(!t)return null;if(g(t)&&(l(t.select)||l(t.rpc)))return t;const r=typeof t=="string"?{adapter:t}:t;if(!g(r)||typeof r.adapter!="string")return null;if(w.has(r))return w.get(r);const n=(async()=>{let s;try{s=await import("@symbo.ls/fetch")}catch{return null}if(!(l(s.hasAdapter)?s.hasAdapter(r.adapter):M.includes(r.adapter)))return null;try{return await s.resolveDb(r)}catch(c){return console.warn(`[brender] SSR adapter "${r.adapter}" unavailable: ${c.message}`),null}})();return w.set(r,n),n},k=async(t,r)=>{try{const{method:n,from:s,params:e,transform:c,limit:a,offset:u,order:h,single:f,headers:d,baseUrl:p}=r;let o;if(n==="rpc"){if(!l(t.rpc))return null;o=await t.rpc({from:s,params:e,headers:d,baseUrl:p})}else{if(!l(t.select))return null;o=await t.select({from:s,select:r.select,params:e,limit:a,offset:u,order:h,single:f,headers:d,baseUrl:p})}let i=o?.data??null;if(o?.error)return null;if(i&&c&&l(c))try{i=c(i)}catch{}return i}catch{return null}},P=async(t,r="/",n={})=>{const e=(t.pages||{})[r];if(!e)return new Map;const c=t.config||t.settings||{},a=c.fetch||t.fetch||c.db||t.db;if(!a)return new Map;const u=await A(a);if(!u)return new Map;const h=b(e);if(!h.length)return new Map;const f=new Map,d=await Promise.allSettled(h.map(async({config:p,stateKey:o,path:i})=>{const y=await k(u,p);if(y!==null){const m=f.get(i)||{};o?m[o]=y:g(y)&&Object.assign(m,y),f.set(i,m)}}));return f},x=async t=>{const r=t.config||{},n=r.polyglot||t.polyglot;if(!n?.fetch||typeof n.fetch=="function")return null;const s=r.fetch||t.fetch||r.db||t.db;if(!s)return null;const e=await A(s);if(!e)return null;const c=n.fetch,a=c.rpc||c.from||"get_translations_if_changed",u=(await import("@symbo.ls/utils")).polyglotLanguages(n,!1),h=u.length?u:[n.defaultLang||"en"],f={},d=await Promise.allSettled(h.map(async p=>{try{if(!l(e.rpc))return;const o=await e.rpc({from:a,params:{p_lang:p,p_cached_version:0}});if(o.error||!o.data)return;const i=o.data;i.translations&&(f[p]=i.translations)}catch(o){console.warn("[brender] SSR translation fetch failed:",o.message)}}));return Object.keys(f).length?f:null},D=(t,r)=>{if(!(!r||!r.size))for(const[n,s]of r){let e=t;if(n){const c=n.split(".");for(const a of c){if(!e||typeof e!="object")break;e=e[a]}}e&&typeof e=="object"&&((!e.state||typeof e.state!="object")&&(e.state={}),Object.assign(e.state,s))}};export{A as createSSRAdapter,x as fetchSSRTranslations,D as injectPrefetchedState,P as prefetchPageData};
|
package/dist/esm/render.js
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
import{resolve as u,join as Fe,dirname as j}from"path";import{existsSync as b,unlinkSync as Be,readFileSync as J,realpathSync as Ge}from"fs";import{resolvePackageEntry as Ke}from"./packageEntry.js";import{tmpdir as Ne}from"os";import{randomBytes as He}from"crypto";import{createRequire as oe}from"module";import{pathToFileURL as Ue}from"url";import{createEnv as ue}from"./env.js";import{resetKeys as de,assignKeys as me,mapKeysToElements as ge}from"./keys.js";import{extractMetadata as ne,generateHeadHtml as We}from"./metadata.js";import"./hydrate.js";import{composeIntoShell as Je}from"./shell.js";import{fetchSSRTranslations as ze}from"./prefetch.js";import{createEarlySeedScript as Ye}from"@symbo.ls/fetch";import{headerEntries as Xe}from"@symbo.ls/fetch/rest";import{brenderRegistryBlock as Ve,escapeRegExp as Qe,jsonForScript as Ze,polyglotFallbackLang as z,polyglotLangStorageKey as et,polyglotAddressLang as tt,polyglotStartupScript as ot}from"@symbo.ls/utils";let Y=null;const nt=async()=>{if(Y)return Y;try{return Y=(await import("@symbo.ls/funcql")).funcqlPlugin,Y}catch{return null}};import"linkedom";import{createEarlySeedBlock as st}from"@symbo.ls/fetch";import{reset as rt}from"@symbo.ls/css";const X=(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},se=(e,t)=>z(e?.lang,t),he=(e,t,o,n)=>{if(o?.translations){const r=o.translations[t];if(r){const i=X(r,e);if(i!==void 0)return i}}if(n?.translations){const r=n.translations[t];if(r){const i=X(r,e);if(i!==void 0)return i}}const s=z(void 0,o);if(s!==t&&o?.translations){const r=o.translations[s];if(r){const i=X(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=he(e,t||se(s,n),n,s);if(r!==void 0)return r;const i=ie(ce);return n&&i&&i.labelKeys.add(e),e},ye=function(){return se(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 A=null;try{import.meta.url&&(A=oe(import.meta.url))}catch{}const it=()=>{if(!import.meta.url||!A)return{isMonorepo:!1,monorepoRoot:null,resolvePackage:l=>l};const e=Ge(new URL(".",import.meta.url).pathname),t=u(e,"../..");if(b(u(t,"packages","smbls","src","createDomql.js")))return{isMonorepo:!0,monorepoRoot:t};let n;try{const l=A.resolve("smbls/package.json");n=j(l)}catch{let l=e;for(;l!==j(l);){const g=u(l,"node_modules","smbls");if(b(u(g,"package.json"))){n=g;break}l=j(l)}}let s=A;if(n)try{s=oe(u(n,"package.json"))}catch{}let r,i=process.cwd();for(;i!==j(i);){if(b(u(i,"package.json"))){r=i;break}i=j(i)}let d=s;if(r)try{d=oe(u(r,"package.json"))}catch{}return{isMonorepo:!1,smblsRoot:n,brenderDir:e,projectRoot:r,_smblsRequire:s,_projectRequire:d}},O=(e,t)=>{const o=e.isMonorepo?[A]:[e._smblsRequire,e._projectRequire,A].filter(Boolean);for(const n of o)try{return n.resolve(t)}catch{}return null},yo=(e,t,...o)=>{if(e.isMonorepo)return u(e.monorepoRoot,"packages",t,...o);const n=O(e,`${t}/package.json`);return n?u(j(n),...o):null},bo=(e,t,...o)=>{if(e.isMonorepo)return u(e.monorepoRoot,"plugins",t,...o);const n=O(e,`@symbo.ls/${t}/package.json`);return n?u(j(n),...o):null},re=(e,t,...o)=>{if(e.isMonorepo){for(const s of["packages","plugins"]){const r=u(e.monorepoRoot,s,t,...o);if(b(r))return r}return null}const n=O(e,`@symbo.ls/${t}/package.json`);return n?u(j(n),...o):null},be=(e,t,...o)=>{if(e.isMonorepo)return u(e.monorepoRoot,"packages","domql","packages",t,...o);const n=O(e,`@symbo.ls/${t}/package.json`);return n?u(j(n),...o):null};let V=null;const Se="smbls/ssr",at=async()=>{const e=await import("smbls/ssr");if(typeof e?.createDomqlElement!="function")throw new Error("smbls/ssr does not export createDomqlElement");return e},ke=()=>(V||(V=lt().catch(e=>{throw V=null,e})),V),lt=async()=>{const e=it();let t;if(A&&e.isMonorepo)t=u(e.monorepoRoot,"packages","smbls","src","createDomql.js");else{let f;try{return await at()}catch(a){f=a}if(!A)throw new Error(`brender: ${Se} 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&&u(e.smblsRoot,"src","createDomql.js");if(c&&b(c))t=c;else throw new Error(`brender: cannot load ${Se} (${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=Fe(Ne(),`br_createDomql_${He(6).toString("hex")}.mjs`),s=Ke,r=e.isMonorepo?await import(Ue(u(e.monorepoRoot,"scripts","vite-workspace-source.mjs")).href):null,i=r?new Set(r.workspacePackageRoots().map(f=>r.workspacePackageAt(f).name)):null,d=/^(?:smbls(?:\/.*)?|domql|css-in-props(?:\/.*)?|@symbo\.ls\/[^/]+)$/,l={name:"workspace-resolve",setup(f){r&&(f.onResolve({filter:/^[^./]/},c=>{if(d.test(c.path))return;const a=c.path.split("/"),p=c.path.startsWith("@")?a.slice(0,2).join("/"):a[0];if(!i.has(p))return;const m=r.resolveWorkspaceSource(c.path,c.importer||t);return m?{path:m}:{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 a=c.path.replace(/^smbls\/?/,""),p=e.isMonorepo?u(e.monorepoRoot,"packages","smbls"):e.smblsRoot;if(!p)return;if(!a){const S=s(p);return S?{path:S}:void 0}const m=u(p,a);if(b(m))return{path:m};if(b(m+".js"))return{path:m+".js"};const y=u(m,"index.js");if(b(y))return{path:y}}),f.onResolve({filter:/^domql$/},c=>{if(e.isMonorepo){const a=u(e.monorepoRoot,"packages","domql","src","index.js");if(b(a))return{path:a};const p=u(e.monorepoRoot,"packages","domql","index.js");if(b(p))return{path:p}}else try{const a=_require.resolve("domql/package.json"),p=s(j(a));if(p)return{path:p}}catch{}}),f.onResolve({filter:/^@symbo\.ls\//},c=>{const a=c.path.replace("@symbo.ls/","");if(a==="sync")return{path:"sync-stub",namespace:"brender-stub"};if(e.isMonorepo){for(const m of["packages","plugins"]){const y=u(e.monorepoRoot,m,a,"src","index.js");if(b(y))return{path:y};const S=u(e.monorepoRoot,m,a,"index.js");if(b(S))return{path:S}}const p=u(e.monorepoRoot,"packages","default-config","blank","index.js");if(a==="default-config"&&b(p))return{path:p}}else{const p=re(e,a,"src","index.js");if(p&&b(p))return{path:p};const m=re(e,a,"index.js");if(m&&b(m))return{path:m};if(a==="default-config"){const y=re(e,"default-config","blank","index.js");if(y&&b(y))return{path:y}}}}),f.onResolve({filter:/^@domql\//},c=>{const a=c.path.replace("@symbo.ls/","");if(e.isMonorepo){const p=u(e.monorepoRoot,"packages","domql","packages",a,"src","index.js");if(b(p))return{path:p};const m=u(e.monorepoRoot,"packages","domql","packages",a,"index.js");if(b(m))return{path:m}}else{const p=be(e,a,"src","index.js");if(p&&b(p))return{path:p};const m=be(e,a,"index.js");if(m&&b(m))return{path:m}}}),f.onResolve({filter:/^css-in-props/},c=>{let a;if(e.isMonorepo)a=u(e.monorepoRoot,"packages","css-in-props");else{const y=O(e,"css-in-props/package.json");y&&(a=j(y))}if(!a)return;const p=c.path.replace(/^css-in-props\/?/,"");if(p){const y=u(a,p),S=u(y,"index.js");if(b(S))return{path:S};if(b(y+".js"))return{path:y+".js"};if(b(y))return{path:y}}const m=s(a);if(m)return{path:m}}),f.onResolve({filter:/^@emotion\//},c=>{let a;if(e.isMonorepo)a=u(e.monorepoRoot,"node_modules",c.path);else{const p=O(e,`${c.path}/package.json`);if(!p)return;a=j(p)}if(b(a)){const p=u(a,"package.json");if(b(p))try{const m=JSON.parse(J(p,"utf8")),y=m.module||m.main||"dist/emotion-css.esm.js";return{path:u(a,y)}}catch{}return{path:a}}}),f.onResolve({filter:/\.json$/},c=>{if(c.resolveDir){const a=u(c.resolveDir,c.path);if(b(a))return{path:a}}}),f.onLoad({filter:/smbls\/.*options\.js$/},async c=>{if(!c.path.includes("smbls/")||!c.path.endsWith("options.js"))return;let a=J(c.path,"utf8");return a=a.replace(/import\s*\{[^}]*version[^}]*\}\s*from\s*['"][^'"]*package\.json['"][^;\n]*/,"const version = '0.0.0'"),a=a.replace(/import\s*\{[^}]*createRequire[^}]*\}\s*from\s*['"]module['"][^;\n]*/g,"// createRequire removed for brender build"),{contents:a,loader:"js"}}),f.onLoad({filter:/smbls\/.*init\.js$/},async c=>{if(!c.path.includes("smbls/")||!c.path.endsWith("init.js"))return;let a=J(c.path,"utf8");return a=a.replace(/import\s*\{[^}]*createRequire[^}]*\}\s*from\s*['"]module['"][^;\n]*/g,"// createRequire removed for brender build"),{contents:a,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 a=J(c.path,"utf8");return a=a.replace(/import\s*\{\s*Link\s*\}\s*from\s*['"]smbls['"]/,"const Link = { tag: 'a', attr: { href: (el) => el.href } }"),{contents:a,loader:"js"}}),f.onLoad({filter:/fetchOnCreate\.js$/},async c=>{if(!c.path.includes("smbls/"))return;let a=J(c.path,"utf8");return a=a.replace(/window\s*&&\s*window\.location\s*\?\s*window\.location\.host\.includes/g,"window && window.location && window.location.host ? window.location.host.includes"),{contents:a,loader:"js"}})}};await o.build({entryPoints:[t],bundle:!0,format:"esm",platform:"node",outfile:n,write:!0,logLevel:"warning",plugins:[l],nodePaths:e.isMonorepo?[u(e.monorepoRoot,"node_modules")]:[...e.smblsRoot?[u(e.smblsRoot,"node_modules")]:[],...e.projectRoot?[u(e.projectRoot,"node_modules")]:[],...e.smblsRoot?[u(e.smblsRoot,"..","..","node_modules")]:[]].filter(f=>b(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{Be(n)}catch{}return g},ft={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 Re(e,(n,s)=>{if(!s.__brKey)return;const r=t(n);o[r===""?"__root":r]=s.__brKey}),o},Re=(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)},ut=new Set(["app","config","pages","components","state","snippets","designSystem","dependencies","functions","methods","files","sharedLibraries"]);let P=0;const we=Symbol.for("brender.fetchTracked"),xe=()=>P,dt=256*1024,mt=1024*1024,gt=new Set(["accept-language","accept"]),je={renders:0,record:null},ht=50,ce={renders:0,record:null},_e=(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)}}},ie=e=>e.record&&!e.record.tainted?e.record:null,yt=e=>{try{const t=new URL(e);return t.origin+t.pathname}catch{return String(e).split("?")[0]}},ae=(e,t,o)=>{!e||e.tainted||e.failures.length>=ht||e.failures.push({url:yt(t),reason:o})},Ee=e=>e&&typeof e=="object"&&typeof e.url=="string"?e.url:String(e),bt=e=>{const[t,o]=e,n=t&&typeof t=="object"&&typeof t.url=="string",s=Ee(t),r=String(o&&o.method||n&&t.method||"GET").toUpperCase(),i={},d=l=>{if(!l)return;const g=!Array.isArray(l)&&typeof l.entries!="function";for(const[f,c]of Xe(l))g&&c==null||(i[String(f).toLowerCase()]=String(c))};return n&&d(t.headers),o&&d(o.headers),{url:s,method:r,headers:i}},St=(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>dt||e.bytes+i.length>mt||(e.bytes+=i.length,e.entries.push({url:t.url,lang:t.headers["accept-language"]||"",status:o.status,type:s,body:i}))},()=>{}))},kt=()=>{const e=globalThis.fetch;if(typeof e!="function"||e[we])return;const t=function(...o){P++;let n=!1;const s=()=>{n||(n=!0,P--)},r=c=>{P++,c.then(()=>{P--},()=>{P--})},i=ie(je),d=ie(ce);let l=null;if(i)try{l=bt(o),(l.method!=="GET"||!/^https?:/i.test(l.url)||Object.keys(l.headers).some(c=>!gt.has(c)))&&(l=null)}catch{l=null}const g=()=>Ee(o[0]);let f;try{f=e.apply(this,o)}catch(c){throw ae(d,g(),c&&c.message||"failed"),s(),c}return f&&typeof f.then=="function"?f.then(c=>{l&&St(i,l,c,r),d&&c&&c.ok===!1&&ae(d,g(),"HTTP "+c.status),s()},c=>{ae(d,g(),c&&c.message||"network error"),s()}):s(),f};t[we]=!0,globalThis.fetch=t},So=({brRegistry:e,fetchSeed:t,hydrateFlag:o=!0}={})=>{if(!e||!Object.keys(e).length)return"";const n=Ze(e);return o?`<script>window.__BR_REGISTRY__=${n};window.__BRENDER_HYDRATE__=true<\/script>`+(t&&t.length?`
|
|
2
|
-
`+
|
|
1
|
+
import{resolve as u,join as Ge,dirname as E}from"path";import{existsSync as y,unlinkSync as Ne,readFileSync as J,realpathSync as He}from"fs";import{resolvePackageEntry as Ue}from"./packageEntry.js";import{tmpdir as We}from"os";import{randomBytes as Je}from"crypto";import{createRequire as ie}from"module";import{pathToFileURL as ze}from"url";import{createEnv as ye}from"./env.js";import{resetKeys as be,assignKeys as Se,mapKeysToElements as we}from"./keys.js";import{extractMetadata as le,generateHeadHtml as Ye}from"./metadata.js";import"./hydrate.js";import{composeIntoShell as Xe}from"./shell.js";import{fetchSSRTranslations as Ve}from"./prefetch.js";import{createEarlySeedScript as Qe}from"@symbo.ls/fetch";import{headerEntries as Ze}from"@symbo.ls/fetch/rest";import{brenderRegistryBlock as et,brenderStampMeta as tt,escapeRegExp as ot,jsonForScript as nt,polyglotFallbackLang as V,polyglotLangStorageKey as st,polyglotAddressLang as rt,polyglotStartupScript as ct}from"@symbo.ls/utils";let Q=null;const at=async()=>{if(Q)return Q;try{return Q=(await import("@symbo.ls/funcql")).funcqlPlugin,Q}catch{return null}};import"linkedom";import{createEarlySeedBlock as it}from"@symbo.ls/fetch";import{reset as lt}from"@symbo.ls/css";const Z=(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},ee=(e,t)=>V(e?.lang,t),ke=(e,t,o,n)=>{if(o?.translations){const r=o.translations[t];if(r){const a=Z(r,e);if(a!==void 0)return a}}if(n?.translations){const r=n.translations[t];if(r){const a=Z(r,e);if(a!==void 0)return a}}const s=V(void 0,o);if(s!==t&&o?.translations){const r=o.translations[s];if(r){const a=Z(r,e);if(a!==void 0)return a}}},ft=function(e,t){if(!e)return"";const o=this?.context,n=o?.polyglot,s=this?.state?.root||o?.state?.root,r=ke(e,t||ee(s,n),n,s);if(r!==void 0)return r;const a=de(ue);return n&&a&&a.labelKeys.add(e),e},Re=function(){return ee(this?.state?.root,this?.context?.polyglot)},v=(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?v(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?v(s,t):s}return o};let $=null;try{import.meta.url&&($=ie(import.meta.url))}catch{}const pt=()=>{if(!import.meta.url||!$)return{isMonorepo:!1,monorepoRoot:null,resolvePackage:l=>l};const e=He(new URL(".",import.meta.url).pathname),t=u(e,"../..");if(y(u(t,"packages","smbls","src","createDomql.js")))return{isMonorepo:!0,monorepoRoot:t};let n;try{const l=$.resolve("smbls/package.json");n=E(l)}catch{let l=e;for(;l!==E(l);){const g=u(l,"node_modules","smbls");if(y(u(g,"package.json"))){n=g;break}l=E(l)}}let s=$;if(n)try{s=ie(u(n,"package.json"))}catch{}let r,a=process.cwd();for(;a!==E(a);){if(y(u(a,"package.json"))){r=a;break}a=E(a)}let d=s;if(r)try{d=ie(u(r,"package.json"))}catch{}return{isMonorepo:!1,smblsRoot:n,brenderDir:e,projectRoot:r,_smblsRequire:s,_projectRequire:d}},B=(e,t)=>{const o=e.isMonorepo?[$]:[e._smblsRequire,e._projectRequire,$].filter(Boolean);for(const n of o)try{return n.resolve(t)}catch{}return null},xo=(e,t,...o)=>{if(e.isMonorepo)return u(e.monorepoRoot,"packages",t,...o);const n=B(e,`${t}/package.json`);return n?u(E(n),...o):null},jo=(e,t,...o)=>{if(e.isMonorepo)return u(e.monorepoRoot,"plugins",t,...o);const n=B(e,`@symbo.ls/${t}/package.json`);return n?u(E(n),...o):null},fe=(e,t,...o)=>{if(e.isMonorepo){for(const s of["packages","plugins"]){const r=u(e.monorepoRoot,s,t,...o);if(y(r))return r}return null}const n=B(e,`@symbo.ls/${t}/package.json`);return n?u(E(n),...o):null},xe=(e,t,...o)=>{if(e.isMonorepo)return u(e.monorepoRoot,"packages","domql","packages",t,...o);const n=B(e,`@symbo.ls/${t}/package.json`);return n?u(E(n),...o):null};let te=null;const je="smbls/ssr",ut=async()=>{const e=await import("smbls/ssr");if(typeof e?.createDomqlElement!="function")throw new Error("smbls/ssr does not export createDomqlElement");return e},_e=()=>(te||(te=dt().catch(e=>{throw te=null,e})),te),dt=async()=>{const e=pt();let t;if($&&e.isMonorepo)t=u(e.monorepoRoot,"packages","smbls","src","createDomql.js");else{let f;try{return await ut()}catch(i){f=i}if(!$)throw new Error(`brender: ${je} 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&&u(e.smblsRoot,"src","createDomql.js");if(c&&y(c))t=c;else throw new Error(`brender: cannot load ${je} (${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=Ge(We(),`br_createDomql_${Je(6).toString("hex")}.mjs`),s=Ue,r=e.isMonorepo?await import(ze(u(e.monorepoRoot,"scripts","vite-workspace-source.mjs")).href):null,a=r?new Set(r.workspacePackageRoots().map(f=>r.workspacePackageAt(f).name)):null,d=/^(?:smbls(?:\/.*)?|domql|css-in-props(?:\/.*)?|@symbo\.ls\/[^/]+)$/,l={name:"workspace-resolve",setup(f){r&&(f.onResolve({filter:/^[^./]/},c=>{if(d.test(c.path))return;const i=c.path.split("/"),p=c.path.startsWith("@")?i.slice(0,2).join("/"):i[0];if(!a.has(p))return;const m=r.resolveWorkspaceSource(c.path,c.importer||t);return m?{path:m}:{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 i=c.path.replace(/^smbls\/?/,""),p=e.isMonorepo?u(e.monorepoRoot,"packages","smbls"):e.smblsRoot;if(!p)return;if(!i){const S=s(p);return S?{path:S}:void 0}const m=u(p,i);if(y(m))return{path:m};if(y(m+".js"))return{path:m+".js"};const b=u(m,"index.js");if(y(b))return{path:b}}),f.onResolve({filter:/^domql$/},c=>{if(e.isMonorepo){const i=u(e.monorepoRoot,"packages","domql","src","index.js");if(y(i))return{path:i};const p=u(e.monorepoRoot,"packages","domql","index.js");if(y(p))return{path:p}}else try{const i=_require.resolve("domql/package.json"),p=s(E(i));if(p)return{path:p}}catch{}}),f.onResolve({filter:/^@symbo\.ls\//},c=>{const i=c.path.replace("@symbo.ls/","");if(i==="sync")return{path:"sync-stub",namespace:"brender-stub"};if(e.isMonorepo){for(const m of["packages","plugins"]){const b=u(e.monorepoRoot,m,i,"src","index.js");if(y(b))return{path:b};const S=u(e.monorepoRoot,m,i,"index.js");if(y(S))return{path:S}}const p=u(e.monorepoRoot,"packages","default-config","blank","index.js");if(i==="default-config"&&y(p))return{path:p}}else{const p=fe(e,i,"src","index.js");if(p&&y(p))return{path:p};const m=fe(e,i,"index.js");if(m&&y(m))return{path:m};if(i==="default-config"){const b=fe(e,"default-config","blank","index.js");if(b&&y(b))return{path:b}}}}),f.onResolve({filter:/^@domql\//},c=>{const i=c.path.replace("@symbo.ls/","");if(e.isMonorepo){const p=u(e.monorepoRoot,"packages","domql","packages",i,"src","index.js");if(y(p))return{path:p};const m=u(e.monorepoRoot,"packages","domql","packages",i,"index.js");if(y(m))return{path:m}}else{const p=xe(e,i,"src","index.js");if(p&&y(p))return{path:p};const m=xe(e,i,"index.js");if(m&&y(m))return{path:m}}}),f.onResolve({filter:/^css-in-props/},c=>{let i;if(e.isMonorepo)i=u(e.monorepoRoot,"packages","css-in-props");else{const b=B(e,"css-in-props/package.json");b&&(i=E(b))}if(!i)return;const p=c.path.replace(/^css-in-props\/?/,"");if(p){const b=u(i,p),S=u(b,"index.js");if(y(S))return{path:S};if(y(b+".js"))return{path:b+".js"};if(y(b))return{path:b}}const m=s(i);if(m)return{path:m}}),f.onResolve({filter:/^@emotion\//},c=>{let i;if(e.isMonorepo)i=u(e.monorepoRoot,"node_modules",c.path);else{const p=B(e,`${c.path}/package.json`);if(!p)return;i=E(p)}if(y(i)){const p=u(i,"package.json");if(y(p))try{const m=JSON.parse(J(p,"utf8")),b=m.module||m.main||"dist/emotion-css.esm.js";return{path:u(i,b)}}catch{}return{path:i}}}),f.onResolve({filter:/\.json$/},c=>{if(c.resolveDir){const i=u(c.resolveDir,c.path);if(y(i))return{path:i}}}),f.onLoad({filter:/smbls\/.*options\.js$/},async c=>{if(!c.path.includes("smbls/")||!c.path.endsWith("options.js"))return;let i=J(c.path,"utf8");return i=i.replace(/import\s*\{[^}]*version[^}]*\}\s*from\s*['"][^'"]*package\.json['"][^;\n]*/,"const version = '0.0.0'"),i=i.replace(/import\s*\{[^}]*createRequire[^}]*\}\s*from\s*['"]module['"][^;\n]*/g,"// createRequire removed for brender build"),{contents:i,loader:"js"}}),f.onLoad({filter:/smbls\/.*init\.js$/},async c=>{if(!c.path.includes("smbls/")||!c.path.endsWith("init.js"))return;let i=J(c.path,"utf8");return i=i.replace(/import\s*\{[^}]*createRequire[^}]*\}\s*from\s*['"]module['"][^;\n]*/g,"// createRequire removed for brender build"),{contents:i,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 i=J(c.path,"utf8");return i=i.replace(/import\s*\{\s*Link\s*\}\s*from\s*['"]smbls['"]/,"const Link = { tag: 'a', attr: { href: (el) => el.href } }"),{contents:i,loader:"js"}}),f.onLoad({filter:/fetchOnCreate\.js$/},async c=>{if(!c.path.includes("smbls/"))return;let i=J(c.path,"utf8");return i=i.replace(/window\s*&&\s*window\.location\s*\?\s*window\.location\.host\.includes/g,"window && window.location && window.location.host ? window.location.host.includes"),{contents:i,loader:"js"}})}};await o.build({entryPoints:[t],bundle:!0,format:"esm",platform:"node",outfile:n,write:!0,logLevel:"warning",plugins:[l],nodePaths:e.isMonorepo?[u(e.monorepoRoot,"node_modules")]:[...e.smblsRoot?[u(e.smblsRoot,"node_modules")]:[],...e.projectRoot?[u(e.projectRoot,"node_modules")]:[],...e.smblsRoot?[u(e.smblsRoot,"..","..","node_modules")]:[]].filter(f=>y(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{Ne(n)}catch{}return g},mt={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"}},gt=(e,t)=>{const o={};return Ee(e,(n,s)=>{if(!s.__brKey)return;const r=t(n);o[r===""?"__root":r]=s.__brKey}),o},Ee=(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)},ht=new Set(["app","config","pages","components","state","snippets","designSystem","dependencies","functions","methods","files","sharedLibraries"]);let K=0;const ve=Symbol.for("brender.fetchTracked"),pe=()=>K,yt=256*1024,bt=1024*1024,St=new Set(["accept-language","accept"]),Te={renders:0,record:null},wt=50,ue={renders:0,record:null},Le=(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)}}},de=e=>e.record&&!e.record.tainted?e.record:null,kt=e=>{try{const t=new URL(e);return t.origin+t.pathname}catch{return String(e).split("?")[0]}},me=(e,t,o)=>{!e||e.tainted||e.failures.length>=wt||e.failures.push({url:kt(t),reason:o})},De=e=>e&&typeof e=="object"&&typeof e.url=="string"?e.url:String(e),Rt=e=>{const[t,o]=e,n=t&&typeof t=="object"&&typeof t.url=="string",s=De(t),r=String(o&&o.method||n&&t.method||"GET").toUpperCase(),a={},d=l=>{if(!l)return;const g=!Array.isArray(l)&&typeof l.entries!="function";for(const[f,c]of Ze(l))g&&c==null||(a[String(f).toLowerCase()]=String(c))};return n&&d(t.headers),o&&d(o.headers),{url:s,method:r,headers:a}},xt=(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(a=>{e.tainted||a.length>yt||e.bytes+a.length>bt||(e.bytes+=a.length,e.entries.push({url:t.url,lang:t.headers["accept-language"]||"",status:o.status,type:s,body:a}))},()=>{}))},jt=()=>{const e=globalThis.fetch;if(typeof e!="function"||e[ve])return;const t=function(...o){K++;let n=!1;const s=()=>{n||(n=!0,K--)},r=c=>{K++,c.then(()=>{K--},()=>{K--})},a=de(Te),d=de(ue);let l=null;if(a)try{l=Rt(o),(l.method!=="GET"||!/^https?:/i.test(l.url)||Object.keys(l.headers).some(c=>!St.has(c)))&&(l=null)}catch{l=null}const g=()=>De(o[0]);let f;try{f=e.apply(this,o)}catch(c){throw me(d,g(),c&&c.message||"failed"),s(),c}return f&&typeof f.then=="function"?f.then(c=>{l&&xt(a,l,c,r),d&&c&&c.ok===!1&&me(d,g(),"HTTP "+c.status),s()},c=>{me(d,g(),c&&c.message||"network error"),s()}):s(),f};t[ve]=!0,globalThis.fetch=t},_o=({brRegistry:e,fetchSeed:t,hydrateFlag:o=!0}={})=>{if(!e||!Object.keys(e).length)return"";const n=nt(e);return o?`<script>window.__BR_REGISTRY__=${n};window.__BRENDER_HYDRATE__=true<\/script>`+(t&&t.length?`
|
|
2
|
+
`+Qe(t):""):`<script>window.__BR_REGISTRY__=${n}<\/script>`},_t=3e4,Et=2147483647;let $e=Promise.resolve();const Ae=()=>{},vt=e=>{const t=$e.then(e);return $e=t.then(Ae,Ae),t},Tt=e=>{if(e===void 0)return _t;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,Et)},Lt=(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},Dt=e=>{e.closed=!0;for(const t of e.undo.splice(0))t()},$t=e=>{let t=!1;return()=>{t||(t=!0,e())}},qe=(e,t={})=>{let o;try{o=Tt(t.timeout)}catch(n){return Promise.reject(n)}return vt(()=>At(e,t,o))},At=async(e,t,o)=>{await _e();const n={closed:!1,undo:[]},s=qt(e,t,n);if(!o)return s;let r;const a=new Promise((d,l)=>{r=setTimeout(()=>{Dt(n),l(Lt(t,o))},o)});try{return await Promise.race([s,a])}finally{clearTimeout(r)}},qt=async(e,t,o)=>{const{route:n="/",pathname:s,state:r,context:a,prefetch:d=!1}=t,l=s||n;jt();const g=Le(ue,()=>({failures:[],labelKeys:new Set,tainted:!1}));try{return await Ct(e,{route:n,locationPath:l,stateOverrides:r,contextOverrides:a,prefetch:d},g.record,o)}finally{g.close()}},Ct=async(e,{route:t,locationPath:o,stateOverrides:n,contextOverrides:s,prefetch:r},a,d)=>{const l=()=>{if(d.closed)throw new Error("brender: this render was abandoned at its timeout")};let g;if(r){try{g=await Ve(e)}catch(m){d.closed||console.warn("[brender] SSR translation fetch failed:",m.message)}l()}const{window:f,document:c}=ye(),i=c.body;f.location.pathname=o;const p=[oe("document",c),oe("location",f.location),oe("localStorage",f.localStorage),oe("sessionStorage",f.sessionStorage)].map($t);d.undo.push(...p);try{const{createDomqlElement:m}=await _e();l();const b=v(e.app||{}),S={...e.config||{}};e.polyglot&&!S.polyglot&&(S.polyglot=e.polyglot),e.fetch&&!S.fetch&&(S.fetch=e.fetch),e.router&&!S.router&&(S.router=e.router);for(const w of Bt)e[w]!=null&&S[w]==null&&(S[w]=e[w]);const k=S.polyglot?{...S.polyglot}:void 0,z=rt(o,k);z&&f.localStorage.setItem(st(k),z),g&&k&&(k.translations={...k.translations||{},...g});const h=v(e.state||{});(g||k)&&(h.root||(h.root={}),k&&(h.root.lang=V(h.root.lang,k)),g&&(h.root.translations={...h.root.translations||{},...g})),lt();const A={};for(const w in e){if(ht.has(w))continue;const x=e[w];A[w]=x&&typeof x=="object"?v(x):x}const T={...A,state:h,...n?{state:{...h,...n}}:{},dependencies:v(e.dependencies||{}),components:v(e.components||{}),snippets:v(e.snippets||{}),pages:v(e.pages||{}),functions:{...e.functions||{},polyglot:ft,getActiveLang:Re,getLang:Re},methods:e.methods||{},designSystem:v(e.designSystem||{}),files:e.files||{},sharedLibraries:e.sharedLibraries||[],...S,...k?{polyglot:k}:{},document:c,window:f,parent:{node:i},initOptions:{},domqlOptions:{sourcemap:!1},firstRender:!1,sync:!1,inspect:!1,notifications:!1,isolateDesignSystem:!0,isolateFetchCache:!0,captureDesignSystem:!0,...s||{}},G=await at();l(),G&&(T.plugins=T.plugins||[],T.plugins.some(w=>w.name==="funcql")||T.plugins.push(G)),be();const I=Le(Te,()=>({entries:[],bytes:0,tainted:!1})),O=I.record;let j;try{j=await m(b,T),l();const w=Date.now(),x=r?2e3:50,W=ae=>new Promise(Ke=>setTimeout(Ke,ae));for(await W(Math.min(50,x));r&&!d.closed&&pe()>0&&Date.now()-w<x;)await W(10),pe()===0&&await W(Math.max(0,Math.min(50,x-(Date.now()-w))));if(l(),r&&await Mt(j,k)){l();const ae=Date.now();for(await W(Math.min(50,x));!d.closed&&pe()>0&&Date.now()-ae<x;)await W(10);l()}}finally{I.close()}const Y=O&&!O.tainted&&!(S.fetch&&S.fetch.seed===!1)?O.entries.slice():[];Se(i);const X=we(j),{elementPath:q}=await import("@symbo.ls/element");l();const N=gt(j,q),H=j?.context?.routerOptions?.contentElementKey||"content",L=j&&j[H],P=L&&typeof L=="object"&&L.__ref?L:void 0,C=le(e,t,j,j?.state,P),D=[],se=c.head||c.querySelector("head");if(se)for(const w of se.querySelectorAll("style")){if(w.sheet&&w.sheet.cssRules)for(const x of w.sheet.cssRules)x.cssText&&D.push(x);if(!D.length){const x=w.textContent||"";x&&D.push({cssText:x})}}const R=D.map(w=>w.cssText),{globalCSS:_,classRules:M}=Ht(D);let F=Ie(i.innerHTML);const U=j?.state?.root,re=ee(U,k),ce=It(g,U);ce&&(F=Me(F,Ce(ce,re,k)));const Be=a&&!a.tainted?{failedRequests:a.failures.slice(),rawLabelKeys:Ft(a.labelKeys,F,re,k,U)}:null;return{html:F,metadata:C,registry:X,brRegistry:N,element:j,emotionCSS:R,globalCSS:_,classRules:M,fetchSeed:Y,document:c,window:f,ssrTranslations:ce||void 0,activeLang:re,diagnostics:Be,preparedDesignSystem:T.preparedDesignSystem||null}}finally{for(const m of p)m()}},oe=(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]})},Mt=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=ee(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},ge=/\{\{\s*([^|{}]+?)\s*\|\s*polyglot\s*\}\}/g,he=e=>e&&typeof e.parse=="function"?e.parse():e,It=(e,t)=>{const o=he(t?.translations),n={};for(const s of[e,o])if(!(!s||typeof s!="object"))for(const r in s){const a=he(s[r]);a&&typeof a=="object"&&(n[r]={...n[r]||{},...a})}return Object.keys(n).length?n:null},Ce=(e,t,o)=>e[t]||e[V(void 0,o)]||Object.values(e)[0]||{},Me=(e,t)=>e.replace(ge,(o,n)=>Z(t,n.trim())??o),Ot=/<script\b[^>]*\btype\s*=\s*["']?application\/json["']?[^>]*>[\s\S]*?<\/script\s*>/gi,Pt=(e,t)=>new RegExp(`(?:^|[^\\w.-])${ot(t)}(?![\\w-]|\\.\\w)`).test(e),Ft=(e,t,o,n,s)=>{const r=new Set,a=t.replace(Ot,"");for(const d of e)/\s/.test(d)||ke(d,o,n,s)===void 0&&Pt(a,d)&&r.add(d);for(const d of a.matchAll(ge))r.add(d[1].trim());return[...r]},Eo=async(e,t={})=>{const{context:o={}}=t,{window:n,document:s}=ye(),r=s.body,{create:a}=await import("@symbo.ls/element"),d=await import("@symbo.ls/utils"),l={...mt,...o.components||{}},g={...d,...o.utils||{},...o.functions||{}};be();let f;try{f=a(e,{node:r},"root",{context:{document:s,window:n,...o,components:l,utils:g}})}catch{}Se(r);const c=f?we(f):{};return{html:Ie(r.innerHTML),registry:c,element:f}},Ie=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}),Bt=["globalTheme","themeStorageKey","themeRoot","useReset","useVariable","useFontImport","useIconSprite","useSvgSprite","useDocumentTheme","useDefaultConfig","useDefaultIcons","useThemeSuffixedVars","verbose","semanticIcons"],Kt=/\.[A-Za-z_\\-]/,Gt=e=>{if(typeof e!="string"||!e)return!1;const t=e.replace(/"(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*'/g,'""').replace(/\[[^\]]*\]/g,"[]");return!Kt.test(t)},Nt=/^\s*(:root|\[data-theme|\[data-skin)/,Oe=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 Gt(e.selectorText)?Nt.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(Oe));return n.has(null)||n.size!==1?n.has(null)?null:"reset":[...n][0]}return null},Ht=e=>{const t={root:[],reset:[],keyframes:[]},o=[];for(const n of e||[]){const s=Oe(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
|
|
6
|
-
`),
|
|
5
|
+
`),fontFaceCSS:""},classRules:o}};let ne=new Set;const vo=()=>{ne=new Set},To=()=>Array.from(ne).join(`
|
|
6
|
+
`),Lo=(e,t)=>e.replace(/<style data-emotion="smbls">[\s\S]*?<\/style>/,t?`<style data-emotion="smbls">
|
|
7
7
|
${t}
|
|
8
|
-
</style>`:""),
|
|
9
|
-
`),classRules:
|
|
10
|
-
`)
|
|
11
|
-
${
|
|
12
|
-
${
|
|
13
|
-
${
|
|
14
|
-
</style>`,G
|
|
15
|
-
${
|
|
16
|
-
</style>`:"",
|
|
17
|
-
`:"",
|
|
18
|
-
`:"";
|
|
8
|
+
</style>`:""),Do=async(e,t={})=>{const{route:o="/",pathname:n,prefetch:s=!0,timeout:r}=t,a=await qe(e,{route:o,pathname:n,prefetch:s,timeout:r});if(!a)return null;const d=e.designSystem||{},l=a.globalCSS;let g=null;try{const c=Ut(a.element);Object.keys(c).length&&(g=c)}catch{}const f=a.activeLang;return{html:a.html,css:a.classRules.join(`
|
|
9
|
+
`),classRules:a.classRules.slice(),globalCSS:l,resetCss:l.resetRules,fontLinks:Fe(d),metadata:a.metadata||le(e,o),brKeyCount:a.registry?Object.keys(a.registry).length:0,brRegistry:a.brRegistry||{},fetchSeed:a.fetchSeed||[],ssrTranslations:a.ssrTranslations,prefetchedState:g,activeLang:f,diagnostics:a.diagnostics,preparedDesignSystem:a.preparedDesignSystem||null}},Ut=e=>{const t={};return Ee(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 a=r.startsWith("~/"),d=a?r.slice(2):r,l=a?o.state.root:o.state,g=he(l&&l[d]);d&&g!=null&&typeof g!="function"&&(t[d]=g)}}),t},$o=async(e,t="/",o={})=>{const{lang:n,themeColor:s,isr:r,hydrate:a=!0,prefetch:d=!0,shell:l,adoptDom:g=!1,pathname:f,eagerImages:c=Pe,preloadScripts:i=!0,nonce:p,timeout:m,stamp:b,time:S}=o,k=Vt(p),z=n||e.state?.lang||e.app?.metadata?.lang||"en",h=await qe(e,{route:t,pathname:f,prefetch:d,timeout:m});if(!h)return null;const A={...h.metadata};if(s&&(A["theme-color"]=s),typeof l=="string"&&!e.name){const R="\0brender-undeclared-title\0";if(le({...e,name:R},t,h.element,h.element?.state).title===R)for(const M of["title","og:title","twitter:title"])A[M]===h.metadata.title&&delete A[M]}const T=Ye(A);for(const R of h.classRules)R&&ne.add(R);const G=Array.from(ne).join(`
|
|
10
|
+
`),I=h.globalCSS,O=`<style data-brender>
|
|
11
|
+
${I.rootRule||""}
|
|
12
|
+
${I.resetRules||""}
|
|
13
|
+
${I.keyframeRules||""}
|
|
14
|
+
</style>`,j=G?`<style data-emotion="smbls">
|
|
15
|
+
${G}
|
|
16
|
+
</style>`:"",Y=Fe(e.designSystem||{}),X=Object.keys(h.registry).length,q=Yt({brRegistry:h.brRegistry,fetchSeed:h.fetchSeed,adoptDom:g,stamp:b,time:S});let N="";if(r&&r.clientScript){const R=Number.isInteger(o.depth)?o.depth:t==="/"?0:t.replace(/^\/|\/$/g,"").split("/").length,_=R>0?"../".repeat(R):"./";if(a){const M=ct({polyglot:e.polyglot||e.config?.polyglot,translations:h.ssrTranslations,lang:h.activeLang,pathname:f||t,attrs:k}),F=M?M+`
|
|
17
|
+
`:"",U=q.blocks?q.blocks+`
|
|
18
|
+
`:"";N=`${F}${U}<script type="module" src="${_}${r.clientScript}"${k}><\/script>`}else N=`<script type="module"${k}>
|
|
19
19
|
{
|
|
20
20
|
const brEls = document.querySelectorAll('body > :not(script):not(style)')
|
|
21
21
|
const observer = new MutationObserver((mutations) => {
|
|
@@ -32,22 +32,23 @@ ${F}
|
|
|
32
32
|
observer.observe(document.body, { childList: true })
|
|
33
33
|
}
|
|
34
34
|
<\/script>
|
|
35
|
-
<script type="module" src="${
|
|
36
|
-
${
|
|
37
|
-
`),body:
|
|
38
|
-
<html lang="${
|
|
35
|
+
<script type="module" src="${_}${r.clientScript}"${k}><\/script>`}const H={...e.config||{}};e.polyglot&&!H.polyglot&&(H.polyglot=e.polyglot);const L=H.polyglot;let P=T;if(L){const R={...L.translations||{}};for(const _ in h.ssrTranslations||{})R[_]={...R[_]||{},...h.ssrTranslations[_]};P=Me(T,Ce(R,h.activeLang,L))}const C=h.diagnostics;if(C)for(const R of P.matchAll(ge)){const _=R[1].trim();C.rawLabelKeys.includes(_)||C.rawLabelKeys.push(_)}const D=zt(h.html,c);return typeof l=="string"?{html:Xe(l,{headTags:`${P}
|
|
36
|
+
${q.meta}`,headEnd:[Y,O,j].filter(Boolean).join(`
|
|
37
|
+
`),body:D,depth:Number.isInteger(o.depth)?o.depth:0,bootScripts:q.blocks,lang:n||(L?h.activeLang:void 0),preloadScripts:i}),route:t,brKeyCount:X,shell:!0,diagnostics:C,preparedDesignSystem:h.preparedDesignSystem||null}:{html:`<!DOCTYPE html>
|
|
38
|
+
<html lang="${z}">
|
|
39
39
|
<head>
|
|
40
|
-
${
|
|
41
|
-
${
|
|
42
|
-
${
|
|
40
|
+
${P}${r&&r.clientScript&&a?`
|
|
41
|
+
${q.meta}`:""}
|
|
42
|
+
${Y}
|
|
43
43
|
|
|
44
|
-
${
|
|
45
|
-
${
|
|
44
|
+
${O}
|
|
45
|
+
${j}
|
|
46
46
|
</head>
|
|
47
47
|
<body>
|
|
48
|
-
${
|
|
49
|
-
${
|
|
48
|
+
${D}
|
|
49
|
+
${N}
|
|
50
50
|
</body>
|
|
51
|
-
</html>`,route:t,brKeyCount:
|
|
52
|
-
`):""},
|
|
53
|
-
|
|
51
|
+
</html>`,route:t,brKeyCount:X,diagnostics:C,preparedDesignSystem:h.preparedDesignSystem||null}},Pe=2,Wt=/<img\b(?:[^>"']|"[^"]*"|'[^']*')*>/gi,Jt=/\sloading\s*=/i,zt=(e,t=Pe)=>{if(t===!1||t==null||typeof e!="string")return e;const o=Math.max(0,Number(t)||0);let n=0;return e.replace(Wt,s=>n++<o||Jt.test(s)?s:s.replace(/^<img\b/i,'<img loading="lazy"'))},Fe=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(`
|
|
52
|
+
`):""},Yt=({brRegistry:e,fetchSeed:t,adoptDom:o=!1,stamp:n,time:s}={})=>{const r=o&&e&&Object.keys(e).length?et(e):"",a=it(t);return{meta:`<meta name="brender" content="${r?"hydrate":"replace"}">
|
|
53
|
+
`+tt(Xt(n,s)),blocks:[r,a].filter(Boolean).join(`
|
|
54
|
+
`)}},Xt=(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}},Vt=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{Pe as EAGER_IMAGES_DEFAULT,je as SMBLS_SSR_SPECIFIER,_o as adoptionScripts,Xt as buildStamp,To as getAccumulatedEmotionCSS,zt as markLazyImages,Yt as prerenderParts,qe as render,Eo as renderElement,$o as renderPage,Do as renderRoute,Lo as replaceEmotionCSS,vo as resetGlobalCSSCache,Ht as splitCapturedCSS};
|
package/dist/esm/shell.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
const
|
|
2
|
-
`)},
|
|
3
|
-
${t}`:
|
|
1
|
+
import{insertAfterImportMap as E}from"@symbo.ls/utils";const P=/<head(\s[^>]*)?>/i,_=/<body(\s[^>]*)?>/i,f=(e,n)=>{const t=new RegExp(`\\s${n}\\s*=\\s*("([^"]*)"|'([^']*)'|([^\\s>]+))`,"i").exec(e);return t?t[2]??t[3]??t[4]??"":null},S=e=>{const n=e.slice(0,12).toLowerCase();if(n.startsWith("<title"))return"title";if(n.startsWith("<meta")){if(f(e,"charset")!==null)return"charset";for(const t of["name","property","http-equiv","itemprop"]){const r=f(e,t);if(r!==null)return`${t}:${r.toLowerCase()}`}return null}if(n.startsWith("<link")){const t=(f(e,"rel")||"").toLowerCase();return t==="canonical"?"link:canonical":t==="icon"||t==="shortcut icon"?`link:${t}:${f(e,"href")||""}`:t==="alternate"&&f(e,"hreflang")!==null?`link:alternate:${(f(e,"hreflang")||"").toLowerCase()}`:null}return null},v=/<title[^>]*>[\s\S]*?<\/title\s*>|<meta\b[^>]*>|<link\b[^>]*>/gi,j=/<(script|style|noscript|template|textarea)\b[\s\S]*?<\/\1\s*>/gi,x=e=>[...e.matchAll(j)].map(n=>[n.index,n.index+n[0].length,n[0]]),L=(e,n,t=r=>r)=>{let r="",o=0;for(const[i,l,a]of x(e))r+=n(e.slice(o,i))+t(a),o=l;return r+n(e.slice(o))},d=(e,n,t=0)=>{const r=x(e),o=new RegExp(n.source,n.flags.includes("g")?n.flags:n.flags+"g");o.lastIndex=t;let i;for(;i=o.exec(e);){const l=i.index,a=r.find(([s,p])=>l>s&&l<p);if(!a)return i;o.lastIndex=a[1]}return null},q=e=>!!e&&!/^(?:[a-z][a-z0-9+.-]*:|\/|#|\?)/i.test(e),I=(e,n)=>e.replace(/(\s(?:src|href)\s*=\s*)(?:(["'])([^"']*)\2|([^\s"'>]+))/gi,(t,r,o,i,l)=>{const a=o?i:l;if(!q(a))return t;const s=o||"";return`${r}${s}${n}${a.replace(/^\.\//,"")}${s}`}),W=(e,n)=>{if(!n)return e;const t="../".repeat(n);return L(e,r=>r.replace(/<link\b[^>]*>/gi,o=>I(o,t)),r=>/^<script\b/i.test(r)?r.replace(/^<script\b[^>]*>/i,o=>I(o,t)):r)},B=new Set(["","module","text/javascript","application/javascript"]),m=(e,n)=>new RegExp(`\\s${n}(?:\\s*=|\\s|>|/)`,"i").test(e),D=e=>{const n=[],t=new Set;for(const[,,r]of x(e)){if(!/^<script\b/i.test(r))continue;const o=r.slice(0,r.indexOf(">")+1),i=f(o,"src");if(!i||t.has(i))continue;const l=(f(o,"type")||"").trim().toLowerCase();if(!B.has(l)||m(o,"async")||m(o,"nomodule"))continue;t.add(i);const a=["crossorigin","integrity","referrerpolicy"].map(p=>{const h=f(o,p);return h===null?m(o,p)?` ${p}`:"":` ${p}="${h.replace(/"/g,""")}"`}).join(""),s=i.replace(/"/g,""");n.push(l==="module"?`<link rel="modulepreload" href="${s}" fetchpriority="high"${a}>`:`<link rel="preload" as="script" href="${s}" fetchpriority="high"${a}>`)}return n.join(`
|
|
2
|
+
`)},H=(e,n)=>{const t=d(e,/<html(\s[^>]*)?>/i);if(!t)return e;const r=t[0],o=String(n).replace(/"/g,"""),i=f(r,"lang")!==null?r.replace(/(\slang\s*=\s*)(?:"[^"]*"|'[^']*'|[^\s>]+)/i,`$1"${o}"`):r.replace(/^<html/i,`<html lang="${o}"`);return e.slice(0,t.index)+i+e.slice(t.index+r.length)},z=(e,{headTags:n="",headEnd:t="",body:r="",depth:o=0,bootScripts:i="",lang:l,preloadScripts:a=!1}={})=>{if(typeof e!="string"||!e.trim())throw new Error("brender: the shell document is empty");let s=W(e,o);l&&(s=H(s,l));const p=a?D(s):"";if(p){const c=E(s,p);c!==null?s=c:t=t?`${p}
|
|
3
|
+
${t}`:p}const h=new Set;for(const c of n.matchAll(v)){const u=S(c[0]);u&&h.add(u)}const w=c=>L(c,u=>u.replace(v,R=>h.has(S(R))?"":R)),y=n?`
|
|
4
4
|
${n}`:"",O=t?`
|
|
5
5
|
${t}
|
|
6
|
-
`:"",g=d(s,
|
|
6
|
+
`:"",g=d(s,P),b=g&&d(s,/<\/head\s*>/i,g.index);if(g&&b){const c=g.index+g[0].length,u=b.index;s=s.slice(0,c)+y+w(s.slice(c,u))+O+s.slice(u)}else{s=w(s);const c=d(s,/<html(\s[^>]*)?>/i)||d(s,/<!doctype[^>]*>/i),u=c?c.index+c[0].length:0;s=s.slice(0,u)+y+O+s.slice(u)}const k=`
|
|
7
7
|
${r}
|
|
8
|
-
${
|
|
9
|
-
`:""}`,$=d(s,
|
|
8
|
+
${i?i+`
|
|
9
|
+
`:""}`,$=d(s,_);if($){const c=$.index+$[0].length;return s.slice(0,c)+k+s.slice(c)}const A=d(s,/<\/(?:body|html)\s*>/i),C=A?A.index:s.length;return s.slice(0,C)+k+s.slice(C)};export{z as composeIntoShell,S as headTagKey,D as scriptPreloads};
|
package/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { createEnv } from './env.js'
|
|
2
2
|
import { resetKeys, assignKeys, mapKeysToElements } from './keys.js'
|
|
3
3
|
import { loadProject, loadAndRenderAll } from './load.js'
|
|
4
|
-
import { render, renderElement, renderRoute, renderPage, resetGlobalCSSCache, getAccumulatedEmotionCSS, replaceEmotionCSS, adoptionScripts, prerenderParts } from './render.js'
|
|
4
|
+
import { render, renderElement, renderRoute, renderPage, resetGlobalCSSCache, getAccumulatedEmotionCSS, replaceEmotionCSS, adoptionScripts, prerenderParts, buildStamp } from './render.js'
|
|
5
5
|
import { extractMetadata, generateHeadHtml } from './metadata.js'
|
|
6
6
|
import { collectBrNodes, hydrate } from './hydrate.js'
|
|
7
7
|
import { generateSitemap } from './sitemap.js'
|
|
@@ -31,7 +31,8 @@ export {
|
|
|
31
31
|
injectPrefetchedState,
|
|
32
32
|
composeIntoShell,
|
|
33
33
|
adoptionScripts,
|
|
34
|
-
prerenderParts
|
|
34
|
+
prerenderParts,
|
|
35
|
+
buildStamp
|
|
35
36
|
}
|
|
36
37
|
|
|
37
38
|
export default {
|
|
@@ -57,5 +58,6 @@ export default {
|
|
|
57
58
|
injectPrefetchedState,
|
|
58
59
|
composeIntoShell,
|
|
59
60
|
adoptionScripts,
|
|
60
|
-
prerenderParts
|
|
61
|
+
prerenderParts,
|
|
62
|
+
buildStamp
|
|
61
63
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@symbo.ls/brender",
|
|
3
|
-
"version": "3.14.
|
|
3
|
+
"version": "3.14.669",
|
|
4
4
|
"license": "CC-BY-NC-4.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"module": "./dist/esm/index.js",
|
|
@@ -46,18 +46,18 @@
|
|
|
46
46
|
"prepublishOnly": "npm run build"
|
|
47
47
|
},
|
|
48
48
|
"dependencies": {
|
|
49
|
-
"@symbo.ls/css": "^3.14.
|
|
50
|
-
"@symbo.ls/default-config": "^3.14.
|
|
51
|
-
"@symbo.ls/element": "^3.14.
|
|
52
|
-
"@symbo.ls/fetch": "^3.14.
|
|
53
|
-
"@symbo.ls/helmet": "^3.14.
|
|
54
|
-
"@symbo.ls/scratch": "^3.14.
|
|
55
|
-
"@symbo.ls/utils": "^3.14.
|
|
49
|
+
"@symbo.ls/css": "^3.14.782",
|
|
50
|
+
"@symbo.ls/default-config": "^3.14.789",
|
|
51
|
+
"@symbo.ls/element": "^3.14.809",
|
|
52
|
+
"@symbo.ls/fetch": "^3.14.669",
|
|
53
|
+
"@symbo.ls/helmet": "^3.14.671",
|
|
54
|
+
"@symbo.ls/scratch": "^3.14.792",
|
|
55
|
+
"@symbo.ls/utils": "^3.14.845",
|
|
56
56
|
"esbuild": "^0.28.1",
|
|
57
57
|
"linkedom": "^0.16.8"
|
|
58
58
|
},
|
|
59
59
|
"peerDependencies": {
|
|
60
|
-
"smbls": "^3.14.
|
|
60
|
+
"smbls": "^3.14.844"
|
|
61
61
|
},
|
|
62
62
|
"optionalDependencies": {
|
|
63
63
|
"@symbo.ls/funcql": "^3.14.0"
|
package/prefetch.js
CHANGED
|
@@ -269,7 +269,9 @@ export const fetchSSRTranslations = async (data) => {
|
|
|
269
269
|
|
|
270
270
|
const fetchConfig = polyglot.fetch
|
|
271
271
|
const rpcName = fetchConfig.rpc || fetchConfig.from || 'get_translations_if_changed'
|
|
272
|
-
|
|
272
|
+
// `languages` may be a function (utils polyglotLanguages); none: the default
|
|
273
|
+
const listed = (await import('@symbo.ls/utils')).polyglotLanguages(polyglot, false)
|
|
274
|
+
const languages = listed.length ? listed : [polyglot.defaultLang || 'en']
|
|
273
275
|
|
|
274
276
|
const translations = {}
|
|
275
277
|
|
package/render.js
CHANGED
|
@@ -13,7 +13,7 @@ import { composeIntoShell } from './shell.js'
|
|
|
13
13
|
import { fetchSSRTranslations } from './prefetch.js'
|
|
14
14
|
import { createEarlySeedScript } from '@symbo.ls/fetch'
|
|
15
15
|
import { headerEntries } from '@symbo.ls/fetch/rest'
|
|
16
|
-
import { brenderRegistryBlock, escapeRegExp, jsonForScript, polyglotFallbackLang, polyglotLangStorageKey, polyglotAddressLang, polyglotStartupScript } from '@symbo.ls/utils'
|
|
16
|
+
import { brenderRegistryBlock, brenderStampMeta, escapeRegExp, jsonForScript, polyglotFallbackLang, polyglotLangStorageKey, polyglotAddressLang, polyglotStartupScript } from '@symbo.ls/utils'
|
|
17
17
|
|
|
18
18
|
// funcql plugin — enables evaluation of funcql schemas as property values
|
|
19
19
|
// during SSR. Async-imported to avoid polluting non-funcql codepaths.
|
|
@@ -1262,6 +1262,12 @@ const renderWithDiagnostics = async (data, { route, locationPath, stateOverrides
|
|
|
1262
1262
|
// not carry them: the browser asked for them again after boot (xma.info,
|
|
1263
1263
|
// 2026-10-16: index.html 10 seed entries, about.html 3).
|
|
1264
1264
|
isolateFetchCache: true,
|
|
1265
|
+
// Keep the design system this render prepared — the processed config and
|
|
1266
|
+
// its global CSS — for the page to adopt instead of preparing it again
|
|
1267
|
+
// (smbls prepare.js, "The design system prepared at build time"; every
|
|
1268
|
+
// render returns it as `preparedDesignSystem`, and `smbls build` writes
|
|
1269
|
+
// it into one file each page preloads).
|
|
1270
|
+
captureDesignSystem: true,
|
|
1265
1271
|
// Caller overrides
|
|
1266
1272
|
...(contextOverrides || {})
|
|
1267
1273
|
}
|
|
@@ -1310,10 +1316,26 @@ const renderWithDiagnostics = async (data, { route, locationPath, stateOverrides
|
|
|
1310
1316
|
if (inFlightRequests() === 0) await sleep(Math.max(0, Math.min(50, flushCap - (Date.now() - flushStart))))
|
|
1311
1317
|
}
|
|
1312
1318
|
live()
|
|
1319
|
+
|
|
1320
|
+
// The page's labels, when the project's polyglot.fetch is still loading
|
|
1321
|
+
// them: the render waits for that load past the flush's cap — a page
|
|
1322
|
+
// without its labels is a failed page, not a slower one — bounded by the
|
|
1323
|
+
// render's own time limit (see "One render at a time"). See awaitLabels.
|
|
1324
|
+
if (prefetch && await awaitLabels(element, polyglotConfig)) {
|
|
1325
|
+
live()
|
|
1326
|
+
// what the labels' answer triggers: another quiet period, and the
|
|
1327
|
+
// requests it starts, under the flush's cap once more
|
|
1328
|
+
const labelsAt = Date.now()
|
|
1329
|
+
await sleep(Math.min(50, flushCap))
|
|
1330
|
+
while (!turn.closed && inFlightRequests() > 0 && Date.now() - labelsAt < flushCap) await sleep(10)
|
|
1331
|
+
live()
|
|
1332
|
+
}
|
|
1313
1333
|
} finally {
|
|
1314
1334
|
recording.close()
|
|
1315
1335
|
}
|
|
1316
|
-
|
|
1336
|
+
// `fetch.seed: false` — the project adopts no recorded answer (the client
|
|
1337
|
+
// asks for everything itself): the page carries none.
|
|
1338
|
+
const fetchSeed = seed && !seed.tainted && !(config.fetch && config.fetch.seed === false) ? seed.entries.slice() : []
|
|
1317
1339
|
|
|
1318
1340
|
// Assign data-br keys for hydration
|
|
1319
1341
|
assignKeys(body)
|
|
@@ -1329,7 +1351,13 @@ const renderWithDiagnostics = async (data, { route, locationPath, stateOverrides
|
|
|
1329
1351
|
// Extract metadata for the rendered route
|
|
1330
1352
|
// Pass the rendered element and its state so function-valued metadata
|
|
1331
1353
|
// (e.g. detail page titles from fetched data) can be resolved
|
|
1332
|
-
|
|
1354
|
+
// The routed page's own metadata resolves against the page element and its
|
|
1355
|
+
// state, where its fetched data lands — as the client's helmet resolves it
|
|
1356
|
+
// (XMA round 5, item L: the head carried the app's defaults).
|
|
1357
|
+
const contentKey = element?.context?.routerOptions?.contentElementKey || 'content'
|
|
1358
|
+
const routed = element && element[contentKey]
|
|
1359
|
+
const pageElement = routed && typeof routed === 'object' && routed.__ref ? routed : undefined
|
|
1360
|
+
const metadata = extractMetadata(data, route, element, element?.state, pageElement)
|
|
1333
1361
|
|
|
1334
1362
|
// Extract CSS from style tags in virtual head (atomic CSS engine writes here)
|
|
1335
1363
|
const capturedRules = []
|
|
@@ -1375,7 +1403,7 @@ const renderWithDiagnostics = async (data, { route, locationPath, stateOverrides
|
|
|
1375
1403
|
}
|
|
1376
1404
|
: null
|
|
1377
1405
|
|
|
1378
|
-
return { html, metadata, registry, brRegistry, element, emotionCSS, globalCSS, classRules, fetchSeed, document, window, ssrTranslations: labels || undefined, activeLang, diagnostics }
|
|
1406
|
+
return { html, metadata, registry, brRegistry, element, emotionCSS, globalCSS, classRules, fetchSeed, document, window, ssrTranslations: labels || undefined, activeLang, diagnostics, preparedDesignSystem: ctx.preparedDesignSystem || null }
|
|
1379
1407
|
} finally {
|
|
1380
1408
|
for (const restore of restoreGlobals) restore()
|
|
1381
1409
|
}
|
|
@@ -1398,6 +1426,39 @@ const swapGlobal = (name, value) => {
|
|
|
1398
1426
|
}
|
|
1399
1427
|
}
|
|
1400
1428
|
|
|
1429
|
+
// ── Labels the render is still loading ──────────────────────────────────────
|
|
1430
|
+
// A function `polyglot.fetch` loads the page's labels inside the render (the
|
|
1431
|
+
// polyglot plugin calls it with the root element, see fetchSSRTranslations).
|
|
1432
|
+
// The flush waits for requests up to its cap; the labels' load is more than
|
|
1433
|
+
// a request (the project's loader, its own awaits, the body, the answer into
|
|
1434
|
+
// the root state), and the first render of a process — a cold connection,
|
|
1435
|
+
// a cold JIT, a page that renders a lot — took longer than the cap: `smbls
|
|
1436
|
+
// build` of xma.info wrote `/` with 49 raw label keys while its labels landed
|
|
1437
|
+
// 2.9 s after the render had ended (2026-10-18). So when the root has no
|
|
1438
|
+
// labels for its language yet, the render waits for the load in flight for
|
|
1439
|
+
// this app's root: the polyglot plugin's `loadTranslations(lang, { inFlight:
|
|
1440
|
+
// true })` hands it back (@symbo.ls/polyglot loadsInFlight) and starts
|
|
1441
|
+
// nothing — a load that already ended, failed or answered "unchanged", is not
|
|
1442
|
+
// asked again. True when it asked (the render then gives the answer a quiet
|
|
1443
|
+
// period).
|
|
1444
|
+
const awaitLabels = async (element, poly) => {
|
|
1445
|
+
if (!poly || !poly.fetch || !element || typeof element.call !== 'function') return false
|
|
1446
|
+
const root = element.state && element.state.root
|
|
1447
|
+
if (!root) return false
|
|
1448
|
+
const lang = activeLangOf(root, poly)
|
|
1449
|
+
const have = root.translations && root.translations[lang]
|
|
1450
|
+
if (have && typeof have === 'object' && Object.keys(have).length) return false
|
|
1451
|
+
if (poly.translations && poly.translations[lang]) return false
|
|
1452
|
+
try {
|
|
1453
|
+
// undefined at once when no load is in flight
|
|
1454
|
+
await element.call('loadTranslations', lang, { inFlight: true })
|
|
1455
|
+
} catch (e) {
|
|
1456
|
+
// the load reports its own failure (polyglot logs it; the request shows
|
|
1457
|
+
// in the diagnostics); the raw keys check names what is missing
|
|
1458
|
+
}
|
|
1459
|
+
return true
|
|
1460
|
+
}
|
|
1461
|
+
|
|
1401
1462
|
// ── Labels after a render ───────────────────────────────────────────────────
|
|
1402
1463
|
|
|
1403
1464
|
const LABEL_TEMPLATE = /\{\{\s*([^|{}]+?)\s*\|\s*polyglot\s*\}\}/g
|
|
@@ -1702,7 +1763,11 @@ export const renderRoute = async (data, options = {}) => {
|
|
|
1702
1763
|
activeLang,
|
|
1703
1764
|
// failed requests and raw label keys (see `DIAG`); null when an
|
|
1704
1765
|
// overlapping render made them unknowable
|
|
1705
|
-
diagnostics: result.diagnostics
|
|
1766
|
+
diagnostics: result.diagnostics,
|
|
1767
|
+
// the design system this render prepared (smbls prepare.js), for the
|
|
1768
|
+
// caller to hand the page (@symbo.ls/utils designSystemLink /
|
|
1769
|
+
// designSystemBlock)
|
|
1770
|
+
preparedDesignSystem: result.preparedDesignSystem || null
|
|
1706
1771
|
}
|
|
1707
1772
|
}
|
|
1708
1773
|
|
|
@@ -1763,13 +1828,17 @@ const collectFetchedState = (element) => {
|
|
|
1763
1828
|
* The result then carries `shell: true`.
|
|
1764
1829
|
* @param {number|false} [options.timeout] - render()'s time limit (30 s by
|
|
1765
1830
|
* default; 0 or false: none) — see render()
|
|
1831
|
+
* @param {string|number} [options.stamp] - The build's data stamp, written into
|
|
1832
|
+
* the page (see buildStamp); default: the render time as an ISO string
|
|
1833
|
+
* @param {number} [options.time] - The build time in ms (see buildStamp);
|
|
1834
|
+
* default: now. A build passes one stamp and time to every page.
|
|
1766
1835
|
* @param {number} [options.depth] - Directory depth of the output file: rebases
|
|
1767
1836
|
* the shell's relative asset URLs (with `shell`, default 0) and the ISR
|
|
1768
1837
|
* client script path (default: derived from the route).
|
|
1769
1838
|
* @returns {Promise<{ html: string, route: string, brKeyCount: number, shell?: true }>}
|
|
1770
1839
|
*/
|
|
1771
1840
|
export const renderPage = async (data, route = '/', options = {}) => {
|
|
1772
|
-
const { lang, themeColor, isr, hydrate = true, prefetch = true, shell, adoptDom = false, pathname, eagerImages = EAGER_IMAGES_DEFAULT, preloadScripts = true, nonce, timeout } = options
|
|
1841
|
+
const { lang, themeColor, isr, hydrate = true, prefetch = true, shell, adoptDom = false, pathname, eagerImages = EAGER_IMAGES_DEFAULT, preloadScripts = true, nonce, timeout, stamp, time } = options
|
|
1773
1842
|
const nonceAttr = nonceAttribute(nonce)
|
|
1774
1843
|
|
|
1775
1844
|
// Detect lang from project config, app metadata, or default
|
|
@@ -1828,7 +1897,7 @@ export const renderPage = async (data, route = '/', options = {}) => {
|
|
|
1828
1897
|
// and the data blocks after the markup — the registry under `adoptDom`,
|
|
1829
1898
|
// and the recorded anonymous answers, so the client renders from the data
|
|
1830
1899
|
// the server used instead of asking for all of it again.
|
|
1831
|
-
const prerender = prerenderParts({ brRegistry: result.brRegistry, fetchSeed: result.fetchSeed, adoptDom })
|
|
1900
|
+
const prerender = prerenderParts({ brRegistry: result.brRegistry, fetchSeed: result.fetchSeed, adoptDom, stamp, time })
|
|
1832
1901
|
|
|
1833
1902
|
// ISR: include client SPA bundle for hydration + data fetching
|
|
1834
1903
|
let isrBody = ''
|
|
@@ -1936,7 +2005,7 @@ export const renderPage = async (data, route = '/', options = {}) => {
|
|
|
1936
2005
|
lang: lang || (polyglotCfg ? result.activeLang : undefined),
|
|
1937
2006
|
preloadScripts
|
|
1938
2007
|
})
|
|
1939
|
-
return { html, route, brKeyCount, shell: true, diagnostics }
|
|
2008
|
+
return { html, route, brKeyCount, shell: true, diagnostics, preparedDesignSystem: result.preparedDesignSystem || null }
|
|
1940
2009
|
}
|
|
1941
2010
|
|
|
1942
2011
|
// (The empty line after the font links held a @font-face <style> that
|
|
@@ -1956,7 +2025,7 @@ ${isrBody}
|
|
|
1956
2025
|
</body>
|
|
1957
2026
|
</html>`
|
|
1958
2027
|
|
|
1959
|
-
return { html, route, brKeyCount, diagnostics }
|
|
2028
|
+
return { html, route, brKeyCount, diagnostics, preparedDesignSystem: result.preparedDesignSystem || null }
|
|
1960
2029
|
}
|
|
1961
2030
|
|
|
1962
2031
|
// ── Images below the fold ───────────────────────────────────────────────────
|
|
@@ -2025,17 +2094,35 @@ const generateFontLinks = (ds) => {
|
|
|
2025
2094
|
// again. Placed after the markup, so they never delay its paint;
|
|
2026
2095
|
// the app's scripts run after them either way.
|
|
2027
2096
|
// `adoptionScripts` above stays for older callers (the platform's renderer).
|
|
2028
|
-
|
|
2097
|
+
// The build's stamp (XMA round 3, item B) goes into <head> after the mark:
|
|
2098
|
+
// `<meta name="brender-stamp">` (@symbo.ls/utils brenderStampMeta) — read by
|
|
2099
|
+
// the client's fetch plugin, which decides whether the page is stale
|
|
2100
|
+
// (@symbo.ls/fetch early.js "Stale pages"), by app code
|
|
2101
|
+
// (`el.context.prerender`, smbls prerender.js) and by a host that compares
|
|
2102
|
+
// it with its data's last change.
|
|
2103
|
+
export const prerenderParts = ({ brRegistry, fetchSeed, adoptDom = false, stamp, time } = {}) => {
|
|
2029
2104
|
const registry = adoptDom && brRegistry && Object.keys(brRegistry).length
|
|
2030
2105
|
? brenderRegistryBlock(brRegistry)
|
|
2031
2106
|
: ''
|
|
2032
2107
|
const seed = createEarlySeedBlock(fetchSeed)
|
|
2033
2108
|
return {
|
|
2034
|
-
meta: `<meta name="brender" content="${registry ? 'hydrate' : 'replace'}"
|
|
2109
|
+
meta: `<meta name="brender" content="${registry ? 'hydrate' : 'replace'}">\n` + brenderStampMeta(buildStamp(stamp, time)),
|
|
2035
2110
|
blocks: [registry, seed].filter(Boolean).join('\n')
|
|
2036
2111
|
}
|
|
2037
2112
|
}
|
|
2038
2113
|
|
|
2114
|
+
// ── The build's stamp ───────────────────────────────────────────────────────
|
|
2115
|
+
// A pre-rendered page carries the data of the time it was rendered until it
|
|
2116
|
+
// is rendered again (`smbls build` pages: until the next build). Each page
|
|
2117
|
+
// says when that was — `time`, ms since the epoch — and which data it holds:
|
|
2118
|
+
// `stamp`, a project's own version of its data (`brender.stamp`, run once
|
|
2119
|
+
// per build by the CLI), else the render time as an ISO string. One build
|
|
2120
|
+
// passes one stamp and time to every page.
|
|
2121
|
+
export const buildStamp = (stamp, time) => {
|
|
2122
|
+
const at = typeof time === 'number' && isFinite(time) ? Math.round(time) : Date.now()
|
|
2123
|
+
return { stamp: stamp != null && stamp !== '' ? String(stamp) : new Date(at).toISOString(), time: at }
|
|
2124
|
+
}
|
|
2125
|
+
|
|
2039
2126
|
// ` nonce="…"` for an inline script, or '' — a value that could break out of
|
|
2040
2127
|
// the attribute is refused.
|
|
2041
2128
|
const nonceAttribute = (nonce) => {
|
package/shell.js
CHANGED
|
@@ -32,6 +32,8 @@
|
|
|
32
32
|
* DOM parse + serialize round-trip does not guarantee.
|
|
33
33
|
*/
|
|
34
34
|
|
|
35
|
+
import { insertAfterImportMap } from '@symbo.ls/utils'
|
|
36
|
+
|
|
35
37
|
const HEAD_OPEN = /<head(\s[^>]*)?>/i
|
|
36
38
|
const BODY_OPEN = /<body(\s[^>]*)?>/i
|
|
37
39
|
|
|
@@ -199,8 +201,16 @@ export const composeIntoShell = (shell, { headTags = '', headEnd = '', body = ''
|
|
|
199
201
|
}
|
|
200
202
|
let html = rebaseAssets(shell, depth)
|
|
201
203
|
if (lang) html = setHtmlLang(html, lang)
|
|
204
|
+
// The app's preloads go right after the shell's import map when it has one
|
|
205
|
+
// (parcel's page does): a modulepreload before it gets the map rejected,
|
|
206
|
+
// and the app's lazy bundles resolve through it. Else first in what the
|
|
207
|
+
// render adds before </head>.
|
|
202
208
|
const preloads = preloadScripts ? scriptPreloads(html) : ''
|
|
203
|
-
if (preloads)
|
|
209
|
+
if (preloads) {
|
|
210
|
+
const placed = insertAfterImportMap(html, preloads)
|
|
211
|
+
if (placed !== null) html = placed
|
|
212
|
+
else headEnd = headEnd ? `${preloads}\n${headEnd}` : preloads
|
|
213
|
+
}
|
|
204
214
|
|
|
205
215
|
const owned = new Set()
|
|
206
216
|
for (const m of headTags.matchAll(HEAD_TAG)) {
|