@symbo.ls/brender 3.14.675 → 3.14.676
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 +51 -0
- package/README.md +18 -0
- package/dist/esm/index.js +1 -1
- package/dist/esm/pageWeight.js +1 -1
- package/dist/esm/prefetch.js +1 -1
- package/dist/esm/render.js +40 -34
- package/index.js +17 -2
- package/package.json +6 -6
- package/pageWeight.js +98 -12
- package/prefetch.js +13 -3
- package/render.js +146 -30
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,56 @@
|
|
|
1
1
|
# @symbo.ls/brender
|
|
2
2
|
|
|
3
|
+
## 3.14.676
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- abeeed0: Built-page options for `smbls build` (XMA questions 1–4 and ask C; all opt-in, a build without them writes the same pages): `brender.onProblem: 'skip'` leaves out only a page with a failed request or a raw label key (its address falls to `200.html`), `'fail'` = `strict`; `brender.report` writes a versioned JSON report of every page (status, failed requests, raw label keys, inline faces, ms); `brender.paint: { whole: true }` holds the first paint until the markup is in (`<link rel="expect" blocking="render">` + a marker, no script; `renderPage({ paint })` in brender); `brender.faces: { inline, wait }` writes a page's own faces inline and keeps it unseen until they are in (CSS hold with its own cap); `brender.app: 'afterFirstView'` holds the app's scripts and preloads until the first view is in, the first input, or 3 s; `brender.beforeWrite(html, page)` gives the project each page before its file is written (`page.afterMarkup`). The page's script ships as a file (`smbls-built.<hash>.js`, no inline script), or with `brender.pageScript: 'inline'` inline with one constant CSP hash (logged and in the report). `brender.paths` may be a function, and `brender.module` names a build-only module for the function-valued settings. Fix: `brender.stamp` (and the new settings) in config.js are found also when a `brender.context` module spreads config.js into the context. Fix: the `fetch.early` script tag goes right after `<meta charset>` (it went ahead of it on a page without `<head>`), else first in `<head>`.
|
|
8
|
+
- 908c4e3: A server render no longer puts a `root: { lang, translations }` key into the state it hands smbls. That key never reached the state (smbls merges state with `mergeStateData`, which keeps the store's `parent` / `root` slots out) and made every `smbls build` with brender print `[smbls/state] the declared state key "root" … was NOT copied`. The language and the loaded labels come from where the render already reads them (polyglot's start language, `polyglot.translations`); the pages are the same.
|
|
9
|
+
- df41d88: `prefetchPageData` reads `on` as `@symbo.ls/fetch` does: a list that names `'create'` (`on: ['create', 'stateChange']`) or an empty list is prefetched; a list without `'create'` is not. Before, every config whose `on` was not the string `'create'` was skipped.
|
|
10
|
+
- a529b80: Note for code that reads the page's fetch seed (`script[type="application/json"][data-fetch-seed]`): since brender 3.14.675 the seed list ends with one more item, `{"r":{…}}` — which of its queries each element ran in the render — after the query items (`{"k":…,"b":…}`). A reader must skip an item it does not know (one without `k`), as `@symbo.ls/fetch` does.
|
|
11
|
+
- f8d78c4: A lazy route can be built ahead as its group's real page (XMA question 6).
|
|
12
|
+
symbols.json `brender.lazyGroups: 'load' | ['group', …]` makes the build's
|
|
13
|
+
first navigation load the group and render the route as the group's page
|
|
14
|
+
(the group's `when` is asked at build time; false builds the placeholder).
|
|
15
|
+
The page names the groups it was built with (`<meta name="brender-lazy">`)
|
|
16
|
+
and `smbls build` preloads each group's chunk (`<link rel="modulepreload">`
|
|
17
|
+
after the page's own preloads, found through the chunk's source map;
|
|
18
|
+
`build.modulePreload: "strict"` fails the build without one). In the browser
|
|
19
|
+
the boot reads the tag into the new `context.lazyFirstView` (router): the
|
|
20
|
+
first navigation loads those groups and renders their pages, so the takeover
|
|
21
|
+
adopts the built page instead of mounting the placeholder over it. Public
|
|
22
|
+
pages can move out of the main bundle and still arrive built.
|
|
23
|
+
|
|
24
|
+
The default build (no `brender.lazyGroups`) is fixed the same way: a route
|
|
25
|
+
whose group loaded during the render (a context that declares `lazy`, e.g.
|
|
26
|
+
through `brender.context`) already showed the group's page, but the page
|
|
27
|
+
named no group, so the browser mounted the placeholder over it (1 adoption
|
|
28
|
+
miss; on a slow run the built page was removed and rendered again). The
|
|
29
|
+
router now records a first navigation that showed the placeholder and then
|
|
30
|
+
the group's page; the page names that group and preloads its chunk. The
|
|
31
|
+
built content does not change.
|
|
32
|
+
|
|
33
|
+
- b11e6c6: `polyglot.defaultLang` may be a function `(polyglot) => code`, called each time the default is asked (utils `polyglotDefaultLang`; every reader of the default goes through `polyglotFallbackLang`). The `fetch.early` pre-boot script carries its source and calls it before it decides. The language the pre-boot `startLang(ctx)` hook chose for a page load is recorded by the script (utils `polyglotEarlyStartLang`) and is the start language in every `addressLang` mode: under `'strict'` an unprefixed page started in the static default and asked for its labels a second time. A visitor's own stored choice still never wins under `'strict'`; an admin whose hook returns `null` keeps its own rule.
|
|
34
|
+
- d33cb15: `polyglot.prefetch: true` (or `'idle'`): once the page has loaded and the browser is idle (2 s at most), once per app, each other language's labels load through `loadTranslations` (stored, so a first switch applies them at once) and the font faces their letters need load in the body's font. Skipped for a reader who saves data. brender switches it off for its server renders.
|
|
35
|
+
- 8ddfa44: `polyglot.defaultLang` given as function TEXT (frank JSON — what the runner's `smbls start` page, mermaid's head and a brender render handed the stored payload read) gives the function's answer: `<html lang>` was `en` for `defaultLang: () => 'ka'`. The server readers revive the config before they ask: new `@symbo.ls/utils/server` `revivePolyglotDefaultLang(polyglot, { evaluate })` (deepDestringifyFunctions, the global eval — as the browser revives it), used by brender's render and mermaid's page; the runner, whose utils floor predates that export, revives with the index's `deepDestringifyFunctions`. The shared helpers (`polyglotDefaultLang`, `polyglotFallbackLang`, `polyglotStartupScript`) compile nothing — every browser bundle carries them and the browser never holds the text — so no browser bundle grows. Where nothing may be compiled from text (an EvalError: a Cloudflare Worker, a CSP without `unsafe-eval`) or the caller passes `{ evaluate: false }` (mermaid passes its `canEval`, the `evalAvailable` seam), the text stays and counts as none, as before, and nothing throws.
|
|
36
|
+
- 6ecdb94: `afterFirstView` create option (config.js / context.js / `create(App, { afterFirstView })`): `(app, state, context)` runs once per app, a task after the first view is in — after `create()` rendered it or adopted the pre-rendered page, after the first navigation settled — the moment fetch.early's `afterFirstView` hook has. An app no longer polls `window.__BRENDER_HYDRATION__` to learn that a built page was taken over. A hook that throws or rejects is reported, never rethrown. brender switches it off for its server renders.
|
|
37
|
+
- a529b80: Server render and page replace (W7): `el.context.ssr` is true while a component renders in a server render, so app code can tell it runs there. smbls replaces a pre-rendered page in short tasks and swaps it in at once; an adoption that throws falls back to the one-task replace. brender exports its page-weight trims (text between sprite symbols no longer stops the sprite trim) and `stylesheetRules`, which turns a stored stylesheet text back into its rules. `smbls build`: each pre-rendered route carries its own icons after its content (Fix B), and the cli reads brender's page trims as optional exports, so an older brender still builds.
|
|
38
|
+
- Updated dependencies [3aa9e6a]
|
|
39
|
+
- Updated dependencies [8797734]
|
|
40
|
+
- Updated dependencies [b0240a2]
|
|
41
|
+
- Updated dependencies [e8eaf28]
|
|
42
|
+
- Updated dependencies [f8d78c4]
|
|
43
|
+
- Updated dependencies [b11e6c6]
|
|
44
|
+
- Updated dependencies [8ddfa44]
|
|
45
|
+
- Updated dependencies [6ecdb94]
|
|
46
|
+
- Updated dependencies [a529b80]
|
|
47
|
+
- Updated dependencies [d42696c]
|
|
48
|
+
- @symbo.ls/utils@3.14.853
|
|
49
|
+
- smbls@3.14.859
|
|
50
|
+
- @symbo.ls/fetch@3.14.673
|
|
51
|
+
- @symbo.ls/element@3.14.818
|
|
52
|
+
- @symbo.ls/helmet@3.14.672
|
|
53
|
+
|
|
3
54
|
## 3.14.675
|
|
4
55
|
|
|
5
56
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -161,6 +161,8 @@ Options:
|
|
|
161
161
|
| `time` | now | The build time in ms, written as its `data-time`. A build passes one `stamp` and `time` to every page |
|
|
162
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 |
|
|
163
163
|
| `themeColor` | | See render.js. |
|
|
164
|
+
| `lazyGroups` | — | A lazy route (`@symbo.ls/router` "Lazy Routes") built as its group's REAL page, not its placeholder: `'load'` for every group, or an array of group names. The render's first navigation loads the group, then renders the route; the group's `when` is asked as in the browser, and a `when` that says false (or a load that fails) builds the placeholder. Unset, the render does not wait for a group: the route shows the group's page when the group loaded during the render (its loader settled in time — a context that declares `lazy`), else its placeholder. Either way the page names the groups whose page it shows (`<meta name="brender-lazy">`, `result.lazyGroups`); the browser's first navigation loads them before it adopts the page. `smbls build` reads it from symbols.json `brender.lazyGroups` and preloads each group's chunk on those pages. |
|
|
165
|
+
| `paint` | — | `{ whole: true }`: the first paint waits until the page's markup is in — `<link rel="expect" href="#smbls-whole" blocking="render">` right after the charset declaration, and an empty `<template id="smbls-whole">` right after the markup (before the data blocks and the app's scripts). No script; the browser also stops the wait when it finishes parsing a document without the element. `smbls build` passes it with `symbols.json` `brender.paint` |
|
|
164
166
|
|
|
165
167
|
**Data and labels.** A page's declarative `fetch` runs during the render, through the fetch plugin, with the real element: its `transform(data, el, state)`, and `children` re-run on the answer. The render waits for the requests it starts. Labels: a table/RPC `polyglot.fetch` (`{ rpc }`) is fetched before the render, for every language; a FUNCTION `polyglot.fetch` is the project's own loader and runs inside the render, called by the polyglot plugin with the root element. Labels resolve in the page's language — the root state's `lang` (a router guard that reads `/en/…` sets it), else `polyglot.defaultLang`.
|
|
166
168
|
|
|
@@ -170,6 +172,20 @@ Options:
|
|
|
170
172
|
|
|
171
173
|
**Every render is the same render.** Each render processes the design system on a config of its own, the way the page's single boot does (`context.isolateDesignSystem`), with its own `localStorage`/`sessionStorage`, so the tenth page of a build has the theme variables, language and labels of the first. Each render also has a `@symbo.ls/fetch` query cache of its own (`context.isolateFetchCache`): a page makes every request its markup depends on itself, so its fetch seed carries every answer it shows, even when an earlier page of the build read the same key; and renders in one process — one after another or at once, of one project or of several — never share rows, requests in flight or invalidations. Its queries bind no window focus, reconnect or `refetchInterval` refetch: once its markup is taken, a render's queries leave no listener, no timer and no request behind. A process builds the framework bundle once, also when its first renders start together.
|
|
172
174
|
|
|
175
|
+
**App code knows it runs on the server.** Every render sets `el.context.ssr` to `true` (`render`, `renderRoute`, `renderPage`, `renderElement`); no payload key and no `context` override turns it off. The server document lays nothing out and observes nothing: no `getComputedStyle`, no `ResizeObserver` or `IntersectionObserver`, no scroll. Code that measures, observes or plays an entrance renders the final state there — test `el.context.ssr === true` before it hides anything (an entrance that sets `opacity: 0` and waits for an observer stores a blank block in the page). A browser has no `ssr` key; a client that boots over a pre-rendered page has `el.context.prerendered === true` instead (step 6 above).
|
|
176
|
+
|
|
177
|
+
### Page-weight trims (`pageWeight.js`)
|
|
178
|
+
|
|
179
|
+
`renderPage` options `spriteIcons: 'page'`, `cssClasses: 'page'` and `cssVariables: 'page'` keep only what the page paints before the client boots: the icons its markup uses, the atomic rules of its classes, the CSS variables its CSS and markup read (each defaults to `'all'`; `smbls build` turns the first two on). The client adds the rest as it boots. The four trims behind them are exported for a tool that weighs a page of its own — the CLI's publish pre-render (`@symbo.ls/cli` `helpers/ssrCache.js`) stores each route's own icons with them — with `stylesheetRules`, which reads a stored stylesheet text back into the rules `classRulesForPage` takes (the platform's renderer trims a stored route's CSS with them as it serves the route):
|
|
180
|
+
|
|
181
|
+
| Export | Returns |
|
|
182
|
+
|--------|---------|
|
|
183
|
+
| `trimSpriteToPage(html, context?)` | `{ html, kept, dropped }`: `html` with its `<svg id="svgSprite">` holding only the symbols the rest of `html` (and `context`) references, and the symbols those reference. Text and comments between the symbols stay in place and never stop the trim (a library icon whose value is not an `<svg>` leaves a stray `<>` there); a sprite that holds an element other than a symbol is left whole (`kept: 0, dropped: 0`). |
|
|
184
|
+
| `markupClasses(...texts)` | a `Set` of the class names in the texts' `class` attributes |
|
|
185
|
+
| `classRulesForPage(rules, classes)` | the rules (cssText) that name one of `classes`, and every rule that names no class (an `@font-face`), in their order |
|
|
186
|
+
| `trimVariablesToPage(rootCss, ...readers)` | `{ css, kept, dropped }`: `rootCss` with only the custom properties the readers read through `var(--…)`, and the ones those read |
|
|
187
|
+
| `stylesheetRules(css)` | the rules of a stylesheet text, each as its own cssText, in order — whatever the layout (one rule per line, as a stored pre-render keeps them, or several per line); strings, comments, escapes and parentheses are skipped, a comment between rules is dropped. `null` for a text it cannot read whole (an unclosed block, string, comment or parenthesis): keep that text as it is. Never pass a string to `classRulesForPage` — it reads it a character at a time |
|
|
188
|
+
|
|
173
189
|
### The page the browser gets (the client's contract)
|
|
174
190
|
|
|
175
191
|
No inline script — a strict Content-Security-Policy (`script-src 'self'`, no `'unsafe-inline'`) blocks inline scripts, never `<meta>` tags or JSON data blocks:
|
|
@@ -181,6 +197,7 @@ No inline script — a strict Content-Security-Policy (`script-src 'self'`, no `
|
|
|
181
197
|
| `<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 |
|
|
182
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 |
|
|
183
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
|
+
| `<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 |
|
|
184
201
|
|
|
185
202
|
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.
|
|
186
203
|
|
|
@@ -304,6 +321,7 @@ With brender on (the default), `smbls build` pre-renders every static route INTO
|
|
|
304
321
|
- 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.
|
|
305
322
|
- 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
323
|
- 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".
|
|
324
|
+
- Opt-in, per page: `brender.onProblem` (`'skip'` leaves out only a page with a failed request or a raw label key), `brender.report` (a JSON report of every page), `brender.paths` as a function, `brender.paint: { whole: true }`, `brender.faces: { inline, wait }` (faces inline, the page unseen until they are in), `brender.app: 'afterFirstView'` (the app's files after the first view) and `brender.beforeWrite(html, page)`; functions live in a build-only `brender.module`. See the CLI README, "Built-page options".
|
|
307
325
|
|
|
308
326
|
#### Routes without their own page: 200.html
|
|
309
327
|
|
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
|
|
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 l,renderElement as p,renderRoute as i,renderPage as s,resetGlobalCSSCache as d,getAccumulatedEmotionCSS as n,replaceEmotionCSS as c,adoptionScripts as f,prerenderParts as S,buildStamp as g}from"./render.js";import{extractMetadata as u,generateHeadHtml as P}from"./metadata.js";import{collectBrNodes as h,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 x,classRulesForPage as A,markupClasses as K,trimVariablesToPage as T,stylesheetRules as j}from"./pageWeight.js";var V={createEnv:e,resetKeys:r,assignKeys:t,mapKeysToElements:o,loadProject:a,loadAndRenderAll:m,render:l,renderElement:p,renderRoute:i,renderPage:s,resetGlobalCSSCache:d,getAccumulatedEmotionCSS:n,replaceEmotionCSS:c,extractMetadata:u,generateHeadHtml:P,collectBrNodes:h,hydrate:y,generateSitemap:C,prefetchPageData:E,injectPrefetchedState:R,composeIntoShell:b,adoptionScripts:f,prerenderParts:S,buildStamp:g,trimSpriteToPage:x,classRulesForPage:A,markupClasses:K,trimVariablesToPage:T,stylesheetRules:j};export{f as adoptionScripts,t as assignKeys,g as buildStamp,A as classRulesForPage,h as collectBrNodes,b as composeIntoShell,e as createEnv,V as default,u as extractMetadata,P as generateHeadHtml,C as generateSitemap,n as getAccumulatedEmotionCSS,y as hydrate,R as injectPrefetchedState,m as loadAndRenderAll,a as loadProject,o as mapKeysToElements,K as markupClasses,E as prefetchPageData,S as prerenderParts,l as render,p as renderElement,s as renderPage,i as renderRoute,c as replaceEmotionCSS,d as resetGlobalCSSCache,r as resetKeys,j as stylesheetRules,x as trimSpriteToPage,T as trimVariablesToPage};
|
package/dist/esm/pageWeight.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
const
|
|
1
|
+
const N=/<svg\b[^>]*\sid="svgSprite"[^>]*>/,m=/\s*<symbol\b[^>]*>[\s\S]*?<\/symbol>/y,w=/\s*<\/svg>/y,C=/^\s*<symbol\b[^>]*?\sid="([^"]+)"/,k=/<symbol\b|<\/svg>/g,q=/<\/?[A-Za-z]/,F=/<!--[\s\S]*?-->/g,E=/#([^\s"'()<>&,;#]+)/g,y=(e,l)=>{if(typeof e!="string"||!e)return l;E.lastIndex=0;let t;for(;t=E.exec(e);)l.add(t[1]);return l},K=(e,l="")=>{const t={html:e,kept:0,dropped:0};if(typeof e!="string")return t;const n=N.exec(e);if(!n)return t;const o=n.index;let s=o+n[0].length;const c=[],i=[];for(;;){m.lastIndex=s;const u=m.exec(e);if(u){const A=C.exec(u[0]),b={text:u[0],id:A?A[1]:null};c.push(b),i.push(b),s=m.lastIndex;continue}if(w.lastIndex=s,w.exec(e))break;k.lastIndex=s;const d=k.exec(e);if(!d)return t;const g=e.slice(s,d.index);if(q.test(g.replace(F,"")))return t;i.push(g),s=d.index}const r=w.lastIndex,f=e.slice(0,o)+e.slice(r),p=y(l,y(f,new Set)),a=new Set,x=new Map;for(const u of c)u.id&&x.set(u.id,u);const S=[...p];for(;S.length;){const u=S.pop(),d=x.get(u);if(!(!d||a.has(d))){a.add(d);for(const g of y(d.text,new Set))x.has(g)&&S.push(g)}}const T=u=>!u.id||a.has(u),h=c.filter(T);if(h.length===c.length)return{html:e,kept:h.length,dropped:0};const P=i.map(u=>typeof u=="string"?u:T(u)?u.text:"").join(""),L=n[0]+P+e.slice(s,r);return{html:e.slice(0,o)+L+e.slice(r),kept:h.length,dropped:c.length-h.length}},z=e=>e.replace(/"(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*'/g,'""').replace(/\[[^\]]*\]/g,"[]"),_=/\.(-?(?:[_A-Za-z -]|\\.)(?:[\w --]|\\.)*)/g,B=e=>e.replace(/\\(?:([0-9a-fA-F]{1,6})\s?|(.))/g,(l,t,n)=>t?String.fromCodePoint(parseInt(t,16)):n),D=e=>{const l=[];let t=0,n=null;for(let o=0;o<e.length;o++){const s=e[o];if(n){s==="\\"?o++:s===n&&(n=null);continue}s==='"'||s==="'"?n=s:s==="{"?(l.push(e.slice(t,o).trim()),t=o+1):(s==="}"||s===";")&&(t=o+1)}return l},G=e=>{const l=[];for(const t of D(String(e||""))){if(!t||t[0]==="@")continue;_.lastIndex=0;const n=z(t);let o;for(;o=_.exec(n);)l.push(B(o[1]))}return l},R=/\sclass\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s"'=<>`]+))/gi,V=(...e)=>{const l=new Set;for(const t of e){if(typeof t!="string"||!t)continue;R.lastIndex=0;let n;for(;n=R.exec(t);){const o=n[1]??n[2]??n[3]??"";for(const s of o.split(/\s+/))s&&l.add(s.replace(/&/g,"&"))}}return l},Y=(e,l)=>{const t=[];for(const n of e||[]){if(!n)continue;const o=G(n);(!o.length||o.some(s=>l.has(s)))&&t.push(n)}return t},Z=e=>{if(typeof e!="string")return null;const l=[];let t=0,n=0,o=0,s=null;const c=i=>{const r=e.slice(t,i).trim();r&&l.push(r),t=i};for(let i=0;i<e.length;i++){const r=e[i];if(s){r==="\\"?i++:r===s&&(s=null);continue}if(r==="\\"){i++;continue}if(r==="/"&&e[i+1]==="*"){const f=e.indexOf("*/",i+2);if(f===-1)return null;!n&&!o&&!e.slice(t,i).trim()&&(t=f+2),i=f+1;continue}if(r==='"'||r==="'")s=r;else if(r==="(")o++;else if(r===")"){if(!o)return null;o--}else{if(o)continue;if(r==="{")n++;else if(r==="}"){if(!n)return null;--n||c(i+1)}else r===";"&&!n&&c(i+1)}}return s||n||o?null:(c(e.length),l)},M=/var\(\s*(--[\w-]+)/g,O=/^\s*(--[\w-]+)\s*:([\s\S]*)$/,I=(e,l)=>{if(typeof e!="string"||!e)return l;M.lastIndex=0;let t;for(;t=M.exec(e);)l.add(t[1]);return l},v=(e,l)=>{let t="",n=0,o=null,s=0;for(let c=0;c<e.length;c++){const i=e[c];if(o){i==="\\"?c++:i===o&&(o=null);continue}if(i==='"'||i==="'")o=i;else if(i==="(")s++;else if(i===")")s=Math.max(0,s-1);else{if(s)continue;i==="{"?(t+=e.slice(n,c+1),n=c+1):i===";"?(t+=l(e.slice(n,c),";"),n=c+1):i==="}"&&(t+=l(e.slice(n,c),"")+"}",n=c+1)}}return t+e.slice(n)},j=(e,...l)=>{if(typeof e!="string"||!e)return{css:e,kept:0,dropped:0};const t=new Map,n=new Set;v(e,f=>{const p=O.exec(f);if(p){const a=t.get(p[1])||[];a.push(p[2]),t.set(p[1],a)}else I(f,n);return""});for(const f of l)I(f,n);const o=new Set,s=[...n];for(;s.length;){const f=s.pop();if(!(o.has(f)||!t.has(f))){o.add(f);for(const p of t.get(f))for(const a of I(p,new Set))s.push(a)}}let c=0,i=0;return{css:v(e,(f,p)=>{const a=O.exec(f);return!a||o.has(a[1])?(a&&c++,f+p):(i++,"")}),kept:c,dropped:i}};export{Y as classRulesForPage,V as markupClasses,G as ruleClasses,Z as stylesheetRules,K as trimSpriteToPage,j as trimVariablesToPage};
|
package/dist/esm/prefetch.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
const l=t=>typeof t=="function",
|
|
1
|
+
const l=t=>typeof t=="function",A=t=>Array.isArray(t),y=t=>t!==null&&typeof t=="object"&&!Array.isArray(t),_=(t,n)=>{if(t){if(l(t))try{return t({state:n||{},call:()=>{},__ref:{}},n||{})}catch{return}return t}},j=t=>A(t)?t.length===0||t.includes("create")||!t.some(n=>typeof n=="string"&&n):!t||t==="create",k=(t,n)=>{if(!t)return null;if(typeof t=="string")return{from:t,method:"select"};const r=l(t)?null:{...t};return!r||(r.method||(r.method="select"),l(r.params)&&(r.params=_(r.params,n)),r.method==="insert"||r.method==="update"||r.method==="upsert"||r.method==="delete")||!j(r.on)?null:r},S=(t,n="")=>{if(!t||typeof t!="object")return[];if(l(t))return[];const r=[],s=t.state||{};if(t.fetch){const e=A(t.fetch)?t.fetch:[t.fetch];for(const c of e){const o=k(c,s);o&&r.push({config:o,stateKey:o.as,path:n,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"&&y(t[e])&&r.push(...S(t[e],n?`${n}.${e}`:e));return r},M=["rest","local"],w=new WeakMap,b=async t=>{if(!t)return null;if(y(t)&&(l(t.select)||l(t.rpc)))return t;const n=typeof t=="string"?{adapter:t}:t;if(!y(n)||typeof n.adapter!="string")return null;if(w.has(n))return w.get(n);const r=(async()=>{let s;try{s=await import("@symbo.ls/fetch")}catch{return null}if(!(l(s.hasAdapter)?s.hasAdapter(n.adapter):M.includes(n.adapter)))return null;try{return await s.resolveDb(n)}catch(c){return console.warn(`[brender] SSR adapter "${n.adapter}" unavailable: ${c.message}`),null}})();return w.set(n,r),r},P=async(t,n)=>{try{const{method:r,from:s,params:e,transform:c,limit:o,offset:f,order:u,single:p,headers:h,baseUrl:g}=n;let i;if(r==="rpc"){if(!l(t.rpc))return null;i=await t.rpc({from:s,params:e,headers:h,baseUrl:g})}else{if(!l(t.select))return null;i=await t.select({from:s,select:n.select,params:e,limit:o,offset:f,order:u,single:p,headers:h,baseUrl:g})}let a=i?.data??null;if(i?.error)return null;if(a&&c&&l(c))try{a=c(a)}catch{}return a}catch{return null}},x=async(t,n="/",r={})=>{const e=(t.pages||{})[n];if(!e)return new Map;const c=t.config||t.settings||{},o=c.fetch||t.fetch||c.db||t.db;if(!o)return new Map;const f=await b(o);if(!f)return new Map;const u=S(e);if(!u.length)return new Map;const p=new Map,h=await Promise.allSettled(u.map(async({config:g,stateKey:i,path:a})=>{const d=await P(f,g);if(d!==null){const m=p.get(a)||{};i?m[i]=d:y(d)&&Object.assign(m,d),p.set(a,m)}}));return p},D=async t=>{const n=t.config||{},r=n.polyglot||t.polyglot;if(!r?.fetch||typeof r.fetch=="function")return null;const s=n.fetch||t.fetch||n.db||t.db;if(!s)return null;const e=await b(s);if(!e)return null;const c=r.fetch,o=c.rpc||c.from||"get_translations_if_changed",f=await import("@symbo.ls/utils"),u=f.polyglotLanguages(r,!1),p=u.length?u:[f.polyglotFallbackLang(void 0,r)],h={},g=await Promise.allSettled(p.map(async i=>{try{if(!l(e.rpc))return;const a=await e.rpc({from:o,params:{p_lang:i,p_cached_version:0}});if(a.error||!a.data)return;const d=a.data;d.translations&&(h[i]=d.translations)}catch(a){console.warn("[brender] SSR translation fetch failed:",a.message)}}));return Object.keys(h).length?h:null},E=(t,n)=>{if(!(!n||!n.size))for(const[r,s]of n){let e=t;if(r){const c=r.split(".");for(const o of c){if(!e||typeof e!="object")break;e=e[o]}}e&&typeof e=="object"&&((!e.state||typeof e.state!="object")&&(e.state={}),Object.assign(e.state,s))}};export{b as createSSRAdapter,D as fetchSSRTranslations,E as injectPrefetchedState,x as prefetchPageData};
|
package/dist/esm/render.js
CHANGED
|
@@ -1,25 +1,25 @@
|
|
|
1
|
-
import{resolve as m,join as Je,dirname as E}from"path";import{existsSync as S,unlinkSync as Ye,readFileSync as X,realpathSync as ze}from"fs";import{resolvePackageEntry as Ve}from"./packageEntry.js";import{tmpdir as Xe}from"os";import{randomBytes as Qe}from"crypto";import{createRequire as ue}from"module";import{pathToFileURL as Ze}from"url";import{createEnv as we}from"./env.js";import{resetKeys as _e,assignKeys as je,mapKeysToElements as xe}from"./keys.js";import{extractMetadata as me,generateHeadHtml as et}from"./metadata.js";import"./hydrate.js";import{composeIntoShell as tt}from"./shell.js";import{trimSpriteToPage as ot,classRulesForPage as nt,markupClasses as st,trimVariablesToPage as rt}from"./pageWeight.js";import{fetchSSRTranslations as ct}from"./prefetch.js";import{createEarlySeedScript as at}from"@symbo.ls/fetch";import{headerEntries as it}from"@symbo.ls/fetch/rest";import{brenderStampMeta as lt,escapeRegExp as ft,jsonForScript as pt,libraryPackageSpecs as ut,polyglotFallbackLang as oe,polyglotLangStorageKey as mt,polyglotAddressLang as dt,polyglotStartupScript as gt}from"@symbo.ls/utils";import{brenderRegistryBlock as ht,brenderRegistryTreeBlock as yt}from"@symbo.ls/utils/server";let ne=null;const bt=async()=>{if(ne)return ne;try{return ne=(await import("@symbo.ls/funcql")).funcqlPlugin,ne}catch{return null}};import"linkedom";import{createEarlySeedBlock as St,fetchRunsOf as Rt}from"@symbo.ls/fetch";import{reset as kt}from"@symbo.ls/css";const se=(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},re=(e,t)=>oe(e?.lang,t),Te=(e,t,o,n)=>{if(o?.translations){const r=o.translations[t];if(r){const a=se(r,e);if(a!==void 0)return a}}if(n?.translations){const r=n.translations[t];if(r){const a=se(r,e);if(a!==void 0)return a}}const s=oe(void 0,o);if(s!==t&&o?.translations){const r=o.translations[s];if(r){const a=se(r,e);if(a!==void 0)return a}}},wt=function(e,t){if(!e)return"";const o=this?.context,n=o?.polyglot,s=this?.state?.root||o?.state?.root,r=Te(e,t||re(s,n),n,s);if(r!==void 0)return r;const a=ye(he);return n&&a&&a.labelKeys.add(e),e},Ee=function(){return re(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 O=null;try{import.meta.url&&(O=ue(import.meta.url))}catch{}const _t=()=>{if(!import.meta.url||!O)return{isMonorepo:!1,monorepoRoot:null,resolvePackage:l=>l};const e=ze(new URL(".",import.meta.url).pathname),t=m(e,"../..");if(S(m(t,"packages","smbls","src","createDomql.js")))return{isMonorepo:!0,monorepoRoot:t};let n;try{const l=O.resolve("smbls/package.json");n=E(l)}catch{let l=e;for(;l!==E(l);){const g=m(l,"node_modules","smbls");if(S(m(g,"package.json"))){n=g;break}l=E(l)}}let s=O;if(n)try{s=ue(m(n,"package.json"))}catch{}let r,a=process.cwd();for(;a!==E(a);){if(S(m(a,"package.json"))){r=a;break}a=E(a)}let u=s;if(r)try{u=ue(m(r,"package.json"))}catch{}return{isMonorepo:!1,smblsRoot:n,brenderDir:e,projectRoot:r,_smblsRequire:s,_projectRequire:u}},K=(e,t)=>{const o=e.isMonorepo?[O]:[e._smblsRequire,e._projectRequire,O].filter(Boolean);for(const n of o)try{return n.resolve(t)}catch{}return null},Bo=(e,t,...o)=>{if(e.isMonorepo)return m(e.monorepoRoot,"packages",t,...o);const n=K(e,`${t}/package.json`);return n?m(E(n),...o):null},Ko=(e,t,...o)=>{if(e.isMonorepo)return m(e.monorepoRoot,"plugins",t,...o);const n=K(e,`@symbo.ls/${t}/package.json`);return n?m(E(n),...o):null},de=(e,t,...o)=>{if(e.isMonorepo){for(const s of["packages","plugins"]){const r=m(e.monorepoRoot,s,t,...o);if(S(r))return r}return null}const n=K(e,`@symbo.ls/${t}/package.json`);return n?m(E(n),...o):null},ve=(e,t,...o)=>{if(e.isMonorepo)return m(e.monorepoRoot,"packages","domql","packages",t,...o);const n=K(e,`@symbo.ls/${t}/package.json`);return n?m(E(n),...o):null};let ce=null;const Le="smbls/ssr",jt=async()=>{const e=await import("smbls/ssr");if(typeof e?.createDomqlElement!="function")throw new Error("smbls/ssr does not export createDomqlElement");return e},$e=()=>(ce||(ce=xt().catch(e=>{throw ce=null,e})),ce),xt=async()=>{const e=_t();let t;if(O&&e.isMonorepo)t=m(e.monorepoRoot,"packages","smbls","src","createDomql.js");else{let f;try{return await jt()}catch(i){f=i}if(!O)throw new Error(`brender: ${Le} 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&&m(e.smblsRoot,"src","createDomql.js");if(c&&S(c))t=c;else throw new Error(`brender: cannot load ${Le} (${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=Je(Xe(),`br_createDomql_${Qe(6).toString("hex")}.mjs`),s=Ve,r=e.isMonorepo?await import(Ze(m(e.monorepoRoot,"scripts","vite-workspace-source.mjs")).href):null,a=r?new Set(r.workspacePackageRoots().map(f=>r.workspacePackageAt(f).name)):null,u=/^(?:smbls(?:\/.*)?|domql|css-in-props(?:\/.*)?|@symbo\.ls\/[^/]+)$/,l={name:"workspace-resolve",setup(f){r&&(f.onResolve({filter:/^[^./]/},c=>{if(u.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 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 i=c.path.replace(/^smbls\/?/,""),p=e.isMonorepo?m(e.monorepoRoot,"packages","smbls"):e.smblsRoot;if(!p)return;if(!i){const j=s(p);return j?{path:j}:void 0}const h=m(p,i);if(S(h))return{path:h};if(S(h+".js"))return{path:h+".js"};const y=m(h,"index.js");if(S(y))return{path:y}}),f.onResolve({filter:/^domql$/},c=>{if(e.isMonorepo){const i=m(e.monorepoRoot,"packages","domql","src","index.js");if(S(i))return{path:i};const p=m(e.monorepoRoot,"packages","domql","index.js");if(S(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 h of["packages","plugins"]){const y=m(e.monorepoRoot,h,i,"src","index.js");if(S(y))return{path:y};const j=m(e.monorepoRoot,h,i,"index.js");if(S(j))return{path:j}}const p=m(e.monorepoRoot,"packages","default-config","blank","index.js");if(i==="default-config"&&S(p))return{path:p}}else{const p=de(e,i,"src","index.js");if(p&&S(p))return{path:p};const h=de(e,i,"index.js");if(h&&S(h))return{path:h};if(i==="default-config"){const y=de(e,"default-config","blank","index.js");if(y&&S(y))return{path:y}}}}),f.onResolve({filter:/^@domql\//},c=>{const i=c.path.replace("@symbo.ls/","");if(e.isMonorepo){const p=m(e.monorepoRoot,"packages","domql","packages",i,"src","index.js");if(S(p))return{path:p};const h=m(e.monorepoRoot,"packages","domql","packages",i,"index.js");if(S(h))return{path:h}}else{const p=ve(e,i,"src","index.js");if(p&&S(p))return{path:p};const h=ve(e,i,"index.js");if(h&&S(h))return{path:h}}}),f.onResolve({filter:/^css-in-props/},c=>{let i;if(e.isMonorepo)i=m(e.monorepoRoot,"packages","css-in-props");else{const y=K(e,"css-in-props/package.json");y&&(i=E(y))}if(!i)return;const p=c.path.replace(/^css-in-props\/?/,"");if(p){const y=m(i,p),j=m(y,"index.js");if(S(j))return{path:j};if(S(y+".js"))return{path:y+".js"};if(S(y))return{path:y}}const h=s(i);if(h)return{path:h}}),f.onResolve({filter:/^@emotion\//},c=>{let i;if(e.isMonorepo)i=m(e.monorepoRoot,"node_modules",c.path);else{const p=K(e,`${c.path}/package.json`);if(!p)return;i=E(p)}if(S(i)){const p=m(i,"package.json");if(S(p))try{const h=JSON.parse(X(p,"utf8")),y=h.module||h.main||"dist/emotion-css.esm.js";return{path:m(i,y)}}catch{}return{path:i}}}),f.onResolve({filter:/\.json$/},c=>{if(c.resolveDir){const i=m(c.resolveDir,c.path);if(S(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=X(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=X(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=X(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=X(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?[m(e.monorepoRoot,"node_modules")]:[...e.smblsRoot?[m(e.smblsRoot,"node_modules")]:[],...e.projectRoot?[m(e.projectRoot,"node_modules")]:[],...e.smblsRoot?[m(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{Ye(n)}catch{}return g},Tt={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"}},Et=(e,t)=>{const o={};return De(e,(n,s)=>{if(!s.__brKey)return;const r=t(n);o[r===""?"__root":r]=s.__brKey}),o},De=(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)},vt=new Set(["app","config","pages","components","state","snippets","designSystem","dependencies","functions","methods","files","sharedLibraries"]);let G=0;const Ae=Symbol.for("brender.fetchTracked"),ge=()=>G,Lt=256*1024,$t=1024*1024,Dt=new Set(["accept-language","accept"]),Ce={renders:0,record:null},At=50,he={renders:0,record:null},qe=(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)}}},ye=e=>e.record&&!e.record.tainted?e.record:null,Ct=e=>{try{const t=new URL(e);return t.origin+t.pathname}catch{return String(e).split("?")[0]}},be=(e,t,o)=>{!e||e.tainted||e.failures.length>=At||e.failures.push({url:Ct(t),reason:o})},Me=e=>e&&typeof e=="object"&&typeof e.url=="string"?e.url:String(e),qt=e=>{const[t,o]=e,n=t&&typeof t=="object"&&typeof t.url=="string",s=Me(t),r=String(o&&o.method||n&&t.method||"GET").toUpperCase(),a={},u=l=>{if(!l)return;const g=!Array.isArray(l)&&typeof l.entries!="function";for(const[f,c]of it(l))g&&c==null||(a[String(f).toLowerCase()]=String(c))};return n&&u(t.headers),o&&u(o.headers),{url:s,method:r,headers:a}},Mt=(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>Lt||e.bytes+a.length>$t||(e.bytes+=a.length,e.entries.push({url:t.url,lang:t.headers["accept-language"]||"",status:o.status,type:s,body:a}))},()=>{}))},Ot=()=>{const e=globalThis.fetch;if(typeof e!="function"||e[Ae])return;const t=function(...o){G++;let n=!1;const s=()=>{n||(n=!0,G--)},r=c=>{G++,c.then(()=>{G--},()=>{G--})},a=ye(Ce),u=ye(he);let l=null;if(a)try{l=qt(o),(l.method!=="GET"||!/^https?:/i.test(l.url)||Object.keys(l.headers).some(c=>!Dt.has(c)))&&(l=null)}catch{l=null}const g=()=>Me(o[0]);let f;try{f=e.apply(this,o)}catch(c){throw be(u,g(),c&&c.message||"failed"),s(),c}return f&&typeof f.then=="function"?f.then(c=>{l&&Mt(a,l,c,r),u&&c&&c.ok===!1&&be(u,g(),"HTTP "+c.status),s()},c=>{be(u,g(),c&&c.message||"network error"),s()}):s(),f};t[Ae]=!0,globalThis.fetch=t},Go=({brRegistry:e,fetchSeed:t,hydrateFlag:o=!0}={})=>{if(!e||!Object.keys(e).length)return"";const n=pt(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 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
|
+
`+gt(t):""):`<script>window.__BR_REGISTRY__=${n}<\/script>`},Ut=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=Rt(e())}catch{return}for(const[r]of s)!o.has(r)&&Object.prototype.hasOwnProperty.call(n,r)&&Reflect.deleteProperty(n,r)}},Vt=3e4,Jt=2147483647;let Be=Promise.resolve();const Ke=()=>{},Yt=e=>{const t=Be.then(e);return Be=t.then(Ke,Ke),t},Xt=e=>{if(e===void 0)return Vt;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,Jt)},Qt=(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},Zt=e=>{e.closed=!0;for(const t of e.undo.splice(0))t()},eo=e=>{let t=!1;return()=>{t||(t=!0,e())}},Ne=(e,t={})=>{let o;try{o=Xt(t.timeout)}catch(n){return Promise.reject(n)}return Yt(()=>to(e,t,o))},to=async(e,t,o)=>{await Oe();const n={closed:!1,undo:[]},s=oo(e,t,n);if(!o)return s;let r;const i=new Promise((u,a)=>{r=setTimeout(()=>{Zt(n),a(Qt(t,o))},o)});try{return await Promise.race([s,i])}finally{clearTimeout(r)}},oo=async(e,t,o)=>{const{route:n="/",pathname:s,state:r,context:i,prefetch:u=!1,lazyGroups:a}=t,g=s||n;Wt();const f=Fe(Re,()=>({failures:[],labelKeys:new Set,tainted:!1}));try{return await no(At(e),{route:n,locationPath:g,stateOverrides:r,contextOverrides:i,prefetch:u,lazyGroups:a},f.record,o)}finally{f.close()}},no=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 dt(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),Ut(()=>h&&h.sharedLibraries)].map(eo);a.undo.push(...R);try{const{createDomqlElement:k,PRERENDER_REGISTRY_FORMATS:X}=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 fo)e[m]!=null&&_[m]==null&&(_[m]=e[m]);const E=_.polyglot?{..._.polyglot,prefetch:!1}:void 0,Q=kt(o,E);Q&&c.localStorage.setItem(wt(E),Q),f&&E&&(E.translations={...E.translations||{},...f});const Z=L(e.state||{});$t();const P={};for(const m in e){if(It.has(m))continue;const b=e[m];P[m]=b&&typeof b=="object"?L(b):b}const x={...P,state:Z,...n?{state:{...Z,...n}}:{},dependencies:L(e.dependencies||{}),components:L(e.components||{}),snippets:L(e.snippets||{}),pages:L(e.pages||{}),functions:{...e.functions||{},polyglot:Dt,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,...so(i),...s||{},ssr:!0};h=x;const ee=await Tt();g(),ee&&(x.plugins=x.plugins||[],x.plugins.some(m=>m.name==="funcql")||x.plugins.push(ee)),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 ro(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 $=Le(w),{elementPath:K}=await import("@symbo.ls/element");g();const U=Pt(w,K),te=W.length?Lt(w&&w.context||x,K):{},V=w?.context?.routerOptions?.contentElementKey||"content",I=w&&w[V],C=I&&typeof I=="object"&&I.__ref?I:void 0,Ee=he(e,t,w,w?.state,C),M=[],J=l.head||l.querySelector("head");if(J)for(const m of J.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}=yo(M);let v=We(p.innerHTML);const F=w?.state?.root,G=le(F,E),O=co(f,F);O&&(v=ze(v,He(O,G,E)));const N=u&&!u.tainted?{failedRequests:u.failures.slice(),rawLabelKeys:ao(u.labelKeys,v,G,E,F)}:null,A=(w&&w.context||x).__lazyFirstViews,se=A&&typeof A.forEach=="function"?[...A]:[];return{html:v,metadata:Ee,registry:$,brRegistry:U,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(X)?X.slice():["paths"]}}finally{for(const k of R)k()}},so=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]})},ro=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,co=(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),io=/<script\b[^>]*\btype\s*=\s*["']?application\/json["']?[^>]*>[\s\S]*?<\/script\s*>/gi,lo=(e,t)=>new RegExp(`(?:^|[^\\w.-])${bt(t)}(?![\\w-]|\\.\\w)`).test(e),ao=(e,t,o,n,s)=>{const r=new Set,i=t.replace(io,"");for(const u of e)/\s/.test(u)||Ae(u,o,n,s)===void 0&&lo(i,u)&&r.add(u);for(const u of i.matchAll(xe))r.add(u[1].trim());return[...r]},nn=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={...qt,...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}),fo=["globalTheme","themeStorageKey","themeRoot","useReset","useVariable","useFontImport","useIconSprite","useSvgSprite","useDocumentTheme","useDefaultConfig","useDefaultIcons","useThemeSuffixedVars","verbose","semanticIcons"],po=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||po.has(s)?null:s},uo=e=>String(e).replace(/&/g,"&").replace(/"/g,""").replace(/</g,"<"),mo=/\.[A-Za-z_\\-]/,go=e=>{if(typeof e!="string"||!e)return!1;const t=e.replace(/"(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*'/g,'""').replace(/\[[^\]]*\]/g,"[]");return!mo.test(t)},ho=/^\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 go(e.selectorText)?ho.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),yo=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
|
|
6
|
-
`),
|
|
5
|
+
`),fontFaceCSS:""},classRules:o}};let pe=new Set;const sn=()=>{pe=new Set},rn=()=>Array.from(pe).join(`
|
|
6
|
+
`),cn=(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
|
-
${
|
|
8
|
+
</style>`:""),ln=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=bo(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}},bo=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},an=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:X="all",cssClasses:me="all",cssVariables:_="all",registryFormat:E="paths",lazyGroups:Q,paint:Z}=o,P=So(Z),x=To(p),ee=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:Q});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 $=_o(y.html,c);X==="page"&&($=ft($,typeof a=="string"?a:"").html);const K=Array.from(pe),U=K.filter(Je),te=U.length?K.filter(m=>!Je(m)):K,V=me==="page"?pt(te,ut($,typeof a=="string"?a:"")).join(`
|
|
10
|
+
`):te.join(`
|
|
11
|
+
`),I=U.length?`<style data-smbls-faces>
|
|
12
|
+
${U.join(`
|
|
13
13
|
`)}
|
|
14
|
-
</style>`:"",
|
|
15
|
-
${(
|
|
16
|
-
${
|
|
17
|
-
${
|
|
18
|
-
</style>`,
|
|
19
|
-
${
|
|
20
|
-
</style>`:"",
|
|
21
|
-
`:"",
|
|
22
|
-
`:"";
|
|
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)||""}
|
|
16
|
+
${C.resetRules||""}
|
|
17
|
+
${C.keyframeRules||""}
|
|
18
|
+
</style>`,J=V?`<style data-emotion="smbls">
|
|
19
|
+
${V}
|
|
20
|
+
</style>`:"",oe=et(e.designSystem||{}),ne=Object.keys(y.registry).length,de=E==="tree"||E==="auto"&&(y.registryFormats||[]).includes("tree"),v=Eo({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=xt({polyglot:e.polyglot||e.config?.polyglot,translations:y.ssrTranslations,lang:y.activeLang,pathname:f||t,attrs:x}),D=T?T+`
|
|
21
|
+
`:"",re=v.blocks?v.blocks+`
|
|
22
|
+
`:"";F=`${D}${re}<script type="module" src="${b}${r.clientScript}"${x}><\/script>`}else F=`<script type="module"${x}>
|
|
23
23
|
{
|
|
24
24
|
const brEls = document.querySelectorAll('body > :not(script):not(style)')
|
|
25
25
|
const observer = new MutationObserver((mutations) => {
|
|
@@ -36,23 +36,29 @@ ${W}
|
|
|
36
36
|
observer.observe(document.body, { childList: true })
|
|
37
37
|
}
|
|
38
38
|
<\/script>
|
|
39
|
-
<script type="module" src="${b}${r.clientScript}"${x}><\/script>`}const
|
|
40
|
-
${
|
|
41
|
-
`),body:
|
|
42
|
-
|
|
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 A=y.diagnostics;if(A)for(const m of N.matchAll(xe)){const b=m[1].trim();A.rawLabelKeys.includes(b)||A.rawLabelKeys.push(b)}if(typeof a=="string"){const m=at(a,{headTags:`${N}
|
|
40
|
+
${v.meta}`,headEnd:[oe,I,M,J].filter(Boolean).join(`
|
|
41
|
+
`),body:P?$+`
|
|
42
|
+
`+Xe:$,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:A,preparedDesignSystem:y.preparedDesignSystem||null,lazyGroups:y.lazyGroups||[]}}const se=`<!DOCTYPE html>
|
|
43
|
+
<html lang="${ee}"${B?` data-theme="${uo(B)}"`:""}>
|
|
43
44
|
<head>
|
|
44
|
-
${
|
|
45
|
-
${
|
|
46
|
-
${
|
|
47
|
-
${
|
|
48
|
-
${
|
|
49
|
-
${
|
|
45
|
+
${N}${r&&r.clientScript&&i?`
|
|
46
|
+
${v.meta}`:""}
|
|
47
|
+
${oe}
|
|
48
|
+
${I}
|
|
49
|
+
${M}
|
|
50
|
+
${J}
|
|
50
51
|
</head>
|
|
51
52
|
<body>
|
|
52
|
-
${
|
|
53
|
-
${
|
|
53
|
+
${$}${P?`
|
|
54
|
+
${Xe}`:""}
|
|
55
|
+
${F}
|
|
54
56
|
</body>
|
|
55
|
-
</html
|
|
56
|
-
|
|
57
|
-
`+
|
|
58
|
-
|
|
57
|
+
</html>`;return{html:P?Qe(se):se,route:t,brKeyCount:ne,diagnostics:A,preparedDesignSystem:y.preparedDesignSystem||null,lazyGroups:y.lazyGroups||[]}},Ye="smbls-whole",Xe=`<template id="${Ye}"></template>`,ue=`<link rel="expect" href="#${Ye}" blocking="render">`,So=e=>!!(e&&typeof e=="object"&&e.whole===!0),Ro=/<meta\b[^>]*\bcharset\s*=[^>]*>/i,wo=[/<head\b[^>]*>/i,/<html\b[^>]*>/i,/^\s*<!doctype\b[^>]*>/i],Qe=e=>{if(typeof e!="string"||e.includes(ue))return e;const t=Ro.exec(e.slice(0,1024));if(t){const o=t.index+t[0].length;return e.slice(0,o)+`
|
|
58
|
+
`+ue+e.slice(o)}for(const o of wo){const n=o.exec(e);if(n)return e.slice(0,n.index+n[0].length)+`
|
|
59
|
+
`+ue+e.slice(n.index+n[0].length)}return ue+`
|
|
60
|
+
`+e},Ze=2,ko=/<img\b(?:[^>"']|"[^"]*"|'[^']*')*>/gi,xo=/\sloading\s*=/i,_o=(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(ko,s=>n++<o||xo.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(`
|
|
61
|
+
`):""},Eo=({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"&&Et(e)||_t(e):"",f=vt(t,o);return{meta:`<meta name="brender" content="${g?"hydrate":"replace"}">
|
|
62
|
+
`+yt(jo(s,r))+($e(u)?`
|
|
63
|
+
`+$e(u):""),blocks:[g,f].filter(Boolean).join(`
|
|
64
|
+
`)}},jo=(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}},To=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,on as adoptionScripts,jo as buildStamp,Ue as fixedThemeOf,rn as getAccumulatedEmotionCSS,_o as markLazyImages,So as paintsWhole,Eo as prerenderParts,Ne as render,nn as renderElement,an as renderPage,ln as renderRoute,cn as replaceEmotionCSS,sn as resetGlobalCSSCache,yo as splitCapturedCSS,Qe as withWholePaint};
|
package/index.js
CHANGED
|
@@ -7,6 +7,11 @@ import { collectBrNodes, hydrate } from './hydrate.js'
|
|
|
7
7
|
import { generateSitemap } from './sitemap.js'
|
|
8
8
|
import { prefetchPageData, injectPrefetchedState } from './prefetch.js'
|
|
9
9
|
import { composeIntoShell } from './shell.js'
|
|
10
|
+
// The page-weight trims (XMA round 8), public for a caller that stores pages
|
|
11
|
+
// itself — @symbo.ls/cli's publish-time pre-render (helpers/ssrCache.js) —
|
|
12
|
+
// or trims a stored page as it serves it (stylesheetRules reads a stored
|
|
13
|
+
// stylesheet text back into the rules classRulesForPage takes).
|
|
14
|
+
import { trimSpriteToPage, classRulesForPage, markupClasses, trimVariablesToPage, stylesheetRules } from './pageWeight.js'
|
|
10
15
|
|
|
11
16
|
export {
|
|
12
17
|
createEnv,
|
|
@@ -32,7 +37,12 @@ export {
|
|
|
32
37
|
composeIntoShell,
|
|
33
38
|
adoptionScripts,
|
|
34
39
|
prerenderParts,
|
|
35
|
-
buildStamp
|
|
40
|
+
buildStamp,
|
|
41
|
+
trimSpriteToPage,
|
|
42
|
+
classRulesForPage,
|
|
43
|
+
markupClasses,
|
|
44
|
+
trimVariablesToPage,
|
|
45
|
+
stylesheetRules
|
|
36
46
|
}
|
|
37
47
|
|
|
38
48
|
export default {
|
|
@@ -59,5 +69,10 @@ export default {
|
|
|
59
69
|
composeIntoShell,
|
|
60
70
|
adoptionScripts,
|
|
61
71
|
prerenderParts,
|
|
62
|
-
buildStamp
|
|
72
|
+
buildStamp,
|
|
73
|
+
trimSpriteToPage,
|
|
74
|
+
classRulesForPage,
|
|
75
|
+
markupClasses,
|
|
76
|
+
trimVariablesToPage,
|
|
77
|
+
stylesheetRules
|
|
63
78
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@symbo.ls/brender",
|
|
3
|
-
"version": "3.14.
|
|
3
|
+
"version": "3.14.676",
|
|
4
4
|
"license": "CC-BY-NC-4.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"module": "./dist/esm/index.js",
|
|
@@ -48,16 +48,16 @@
|
|
|
48
48
|
"dependencies": {
|
|
49
49
|
"@symbo.ls/css": "^3.14.782",
|
|
50
50
|
"@symbo.ls/default-config": "^3.14.789",
|
|
51
|
-
"@symbo.ls/element": "^3.14.
|
|
52
|
-
"@symbo.ls/fetch": "^3.14.
|
|
53
|
-
"@symbo.ls/helmet": "^3.14.
|
|
51
|
+
"@symbo.ls/element": "^3.14.818",
|
|
52
|
+
"@symbo.ls/fetch": "^3.14.673",
|
|
53
|
+
"@symbo.ls/helmet": "^3.14.672",
|
|
54
54
|
"@symbo.ls/scratch": "^3.14.795",
|
|
55
|
-
"@symbo.ls/utils": "^3.14.
|
|
55
|
+
"@symbo.ls/utils": "^3.14.853",
|
|
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.859"
|
|
61
61
|
},
|
|
62
62
|
"optionalDependencies": {
|
|
63
63
|
"@symbo.ls/funcql": "^3.14.0"
|
package/pageWeight.js
CHANGED
|
@@ -25,6 +25,12 @@ const SPRITE_OPEN = /<svg\b[^>]*\sid="svgSprite"[^>]*>/
|
|
|
25
25
|
const SYMBOL_AT = /\s*<symbol\b[^>]*>[\s\S]*?<\/symbol>/y
|
|
26
26
|
const SPRITE_CLOSE_AT = /\s*<\/svg>/y
|
|
27
27
|
const SYMBOL_ID = /^\s*<symbol\b[^>]*?\sid="([^"]+)"/
|
|
28
|
+
// Where the next symbol, or the sprite's end, starts.
|
|
29
|
+
const NEXT_PART = /<symbol\b|<\/svg>/g
|
|
30
|
+
// An element tag (a comment aside) — the sign of a sprite holding more than
|
|
31
|
+
// symbols and text, which the trim leaves alone.
|
|
32
|
+
const ELEMENT_TAG = /<\/?[A-Za-z]/
|
|
33
|
+
const COMMENT = /<!--[\s\S]*?-->/g
|
|
28
34
|
// A fragment reference — `href="#x"`, `xlink:href="#x"`, `url(#x)` — or any
|
|
29
35
|
// `#token`: a hex colour that happens to spell an icon's name keeps it, which
|
|
30
36
|
// is the safe direction.
|
|
@@ -43,8 +49,11 @@ const fragmentsIn = (text, into) => {
|
|
|
43
49
|
* symbols those reference). The client's design-system init adds every other
|
|
44
50
|
* icon of the design system to the same sprite before the app is created, so
|
|
45
51
|
* an icon the page shows later (after a click, on new data) is there when it
|
|
46
|
-
* renders.
|
|
47
|
-
*
|
|
52
|
+
* renders. Text between the symbols (a library icon whose value is not an
|
|
53
|
+
* <svg> leaves it there — system/brand's stray `<>`) and comments are kept as
|
|
54
|
+
* they are and never end the walk: it stopped there, and system/landing's
|
|
55
|
+
* pages kept all 710 symbols (W7 B0). Returns `html` unchanged when it has no
|
|
56
|
+
* sprite or the sprite holds an element other than a symbol.
|
|
48
57
|
* @param {string} html - the page's body markup
|
|
49
58
|
* @param {string} [context] - other markup of the same document (the shell)
|
|
50
59
|
* @returns {{ html: string, kept: number, dropped: number }}
|
|
@@ -57,18 +66,30 @@ export const trimSpriteToPage = (html, context = '') => {
|
|
|
57
66
|
const start = open.index
|
|
58
67
|
let at = start + open[0].length
|
|
59
68
|
const symbols = []
|
|
69
|
+
// The sprite's content in order: each symbol, and the text between them.
|
|
70
|
+
const parts = []
|
|
60
71
|
for (;;) {
|
|
61
72
|
SYMBOL_AT.lastIndex = at
|
|
62
73
|
const m = SYMBOL_AT.exec(html)
|
|
63
|
-
if (
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
74
|
+
if (m) {
|
|
75
|
+
const id = SYMBOL_ID.exec(m[0])
|
|
76
|
+
const symbol = { text: m[0], id: id ? id[1] : null }
|
|
77
|
+
symbols.push(symbol)
|
|
78
|
+
parts.push(symbol)
|
|
79
|
+
at = SYMBOL_AT.lastIndex
|
|
80
|
+
continue
|
|
81
|
+
}
|
|
82
|
+
SPRITE_CLOSE_AT.lastIndex = at
|
|
83
|
+
if (SPRITE_CLOSE_AT.exec(html)) break
|
|
84
|
+
NEXT_PART.lastIndex = at
|
|
85
|
+
const next = NEXT_PART.exec(html)
|
|
86
|
+
// No end, or an element other than a symbol inside the sprite: left alone.
|
|
87
|
+
if (!next) return none
|
|
88
|
+
const text = html.slice(at, next.index)
|
|
89
|
+
if (ELEMENT_TAG.test(text.replace(COMMENT, ''))) return none
|
|
90
|
+
parts.push(text)
|
|
91
|
+
at = next.index
|
|
67
92
|
}
|
|
68
|
-
SPRITE_CLOSE_AT.lastIndex = at
|
|
69
|
-
const close = SPRITE_CLOSE_AT.exec(html)
|
|
70
|
-
// Something other than a symbol inside the sprite: left alone.
|
|
71
|
-
if (!close) return none
|
|
72
93
|
const end = SPRITE_CLOSE_AT.lastIndex
|
|
73
94
|
|
|
74
95
|
const outside = html.slice(0, start) + html.slice(end)
|
|
@@ -87,9 +108,11 @@ export const trimSpriteToPage = (html, context = '') => {
|
|
|
87
108
|
}
|
|
88
109
|
// An anonymous symbol is kept: nothing can name it, but nothing proves it
|
|
89
110
|
// unused either.
|
|
90
|
-
const
|
|
111
|
+
const isKept = (s) => !s.id || keep.has(s)
|
|
112
|
+
const kept = symbols.filter(isKept)
|
|
91
113
|
if (kept.length === symbols.length) return { html, kept: kept.length, dropped: 0 }
|
|
92
|
-
const
|
|
114
|
+
const content = parts.map((part) => typeof part === 'string' ? part : isKept(part) ? part.text : '').join('')
|
|
115
|
+
const sprite = open[0] + content + html.slice(at, end)
|
|
93
116
|
return { html: html.slice(0, start) + sprite + html.slice(end), kept: kept.length, dropped: symbols.length - kept.length }
|
|
94
117
|
}
|
|
95
118
|
|
|
@@ -185,6 +208,69 @@ export const classRulesForPage = (rules, classes) => {
|
|
|
185
208
|
return out
|
|
186
209
|
}
|
|
187
210
|
|
|
211
|
+
/**
|
|
212
|
+
* The rules of a stylesheet text, each as its own cssText — what
|
|
213
|
+
* classRulesForPage takes (a string there would be read a character at a
|
|
214
|
+
* time). A stored pre-render keeps a version's atomic rules as one text
|
|
215
|
+
* (@symbo.ls/cli helpers/ssrCache.js `__styles.css`, one rule per line), and
|
|
216
|
+
* the platform trims it to each route as it serves the route. Any layout
|
|
217
|
+
* reads alike, a rule over several lines or several rules on one line:
|
|
218
|
+
* strings, comments, escapes and parentheses are skipped, a comment between
|
|
219
|
+
* rules is dropped, and a statement at-rule (`@import …;`) is a rule of its
|
|
220
|
+
* own.
|
|
221
|
+
* @param {string} css
|
|
222
|
+
* @returns {string[]|null} the rules, in order; null for a text it cannot
|
|
223
|
+
* read whole (an unclosed block, string, comment or parenthesis, a stray
|
|
224
|
+
* `}` or `)`), so the caller keeps that text as it is
|
|
225
|
+
*/
|
|
226
|
+
export const stylesheetRules = (css) => {
|
|
227
|
+
if (typeof css !== 'string') return null
|
|
228
|
+
const rules = []
|
|
229
|
+
let start = 0
|
|
230
|
+
let depth = 0
|
|
231
|
+
let parens = 0
|
|
232
|
+
let quote = null
|
|
233
|
+
const take = (end) => {
|
|
234
|
+
const rule = css.slice(start, end).trim()
|
|
235
|
+
if (rule) rules.push(rule)
|
|
236
|
+
start = end
|
|
237
|
+
}
|
|
238
|
+
for (let i = 0; i < css.length; i++) {
|
|
239
|
+
const ch = css[i]
|
|
240
|
+
if (quote) {
|
|
241
|
+
if (ch === '\\') i++
|
|
242
|
+
else if (ch === quote) quote = null
|
|
243
|
+
continue
|
|
244
|
+
}
|
|
245
|
+
if (ch === '\\') {
|
|
246
|
+
i++
|
|
247
|
+
continue
|
|
248
|
+
}
|
|
249
|
+
if (ch === '/' && css[i + 1] === '*') {
|
|
250
|
+
const close = css.indexOf('*/', i + 2)
|
|
251
|
+
if (close === -1) return null
|
|
252
|
+
// Between rules the comment is dropped; inside a rule it stays.
|
|
253
|
+
if (!depth && !parens && !css.slice(start, i).trim()) start = close + 2
|
|
254
|
+
i = close + 1
|
|
255
|
+
continue
|
|
256
|
+
}
|
|
257
|
+
if (ch === '"' || ch === "'") quote = ch
|
|
258
|
+
else if (ch === '(') parens++
|
|
259
|
+
else if (ch === ')') {
|
|
260
|
+
if (!parens) return null
|
|
261
|
+
parens--
|
|
262
|
+
} else if (parens) continue
|
|
263
|
+
else if (ch === '{') depth++
|
|
264
|
+
else if (ch === '}') {
|
|
265
|
+
if (!depth) return null
|
|
266
|
+
if (!--depth) take(i + 1)
|
|
267
|
+
} else if (ch === ';' && !depth) take(i + 1)
|
|
268
|
+
}
|
|
269
|
+
if (quote || depth || parens) return null
|
|
270
|
+
take(css.length)
|
|
271
|
+
return rules
|
|
272
|
+
}
|
|
273
|
+
|
|
188
274
|
// ── The CSS variables (E7) ──────────────────────────────────────────────────
|
|
189
275
|
|
|
190
276
|
const VAR_REF = /var\(\s*(--[\w-]+)/g
|
package/prefetch.js
CHANGED
|
@@ -36,6 +36,14 @@ const resolveParams = (params, mockState) => {
|
|
|
36
36
|
return params
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
+
// Does a config run on create? `on` as @symbo.ls/fetch reads it: no value or
|
|
40
|
+
// an empty list is 'create'; a string is that trigger; a list runs on every
|
|
41
|
+
// trigger it names (`on: ['create', 'stateChange']` loads on create too).
|
|
42
|
+
const runsOnCreate = (on) => {
|
|
43
|
+
if (isArray(on)) return on.length === 0 || on.includes('create') || !on.some((t) => typeof t === 'string' && t)
|
|
44
|
+
return !on || on === 'create'
|
|
45
|
+
}
|
|
46
|
+
|
|
39
47
|
/**
|
|
40
48
|
* Normalize a single fetch declaration to a standard config object.
|
|
41
49
|
*/
|
|
@@ -58,7 +66,7 @@ const normalizeFetchConfig = (cfg, elementState) => {
|
|
|
58
66
|
const isMutation = resolved.method === 'insert' || resolved.method === 'update' ||
|
|
59
67
|
resolved.method === 'upsert' || resolved.method === 'delete'
|
|
60
68
|
if (isMutation) return null
|
|
61
|
-
if (resolved.on
|
|
69
|
+
if (!runsOnCreate(resolved.on)) return null
|
|
62
70
|
|
|
63
71
|
return resolved
|
|
64
72
|
}
|
|
@@ -270,8 +278,10 @@ export const fetchSSRTranslations = async (data) => {
|
|
|
270
278
|
const fetchConfig = polyglot.fetch
|
|
271
279
|
const rpcName = fetchConfig.rpc || fetchConfig.from || 'get_translations_if_changed'
|
|
272
280
|
// `languages` may be a function (utils polyglotLanguages); none: the default
|
|
273
|
-
const
|
|
274
|
-
const
|
|
281
|
+
const utils = await import('@symbo.ls/utils')
|
|
282
|
+
const listed = utils.polyglotLanguages(polyglot, false)
|
|
283
|
+
// `defaultLang` may be a function too (utils polyglotDefaultLang)
|
|
284
|
+
const languages = listed.length ? listed : [utils.polyglotFallbackLang(undefined, polyglot)]
|
|
275
285
|
|
|
276
286
|
const translations = {}
|
|
277
287
|
|
package/render.js
CHANGED
|
@@ -14,8 +14,12 @@ import { trimSpriteToPage, classRulesForPage, markupClasses, trimVariablesToPage
|
|
|
14
14
|
import { fetchSSRTranslations } from './prefetch.js'
|
|
15
15
|
import { createEarlySeedScript } from '@symbo.ls/fetch'
|
|
16
16
|
import { headerEntries } from '@symbo.ls/fetch/rest'
|
|
17
|
-
import { brenderStampMeta, escapeRegExp, jsonForScript, libraryPackageSpecs, polyglotFallbackLang, polyglotLangStorageKey, polyglotAddressLang, polyglotStartupScript } from '@symbo.ls/utils'
|
|
17
|
+
import { brenderStampMeta, brenderLazyMeta, escapeRegExp, jsonForScript, libraryPackageSpecs, polyglotFallbackLang, polyglotLangStorageKey, polyglotAddressLang, polyglotStartupScript } from '@symbo.ls/utils'
|
|
18
18
|
import { brenderRegistryBlock, brenderRegistryTreeBlock } from '@symbo.ls/utils/server'
|
|
19
|
+
// Namespace import, read at call time: revivePolyglotDefaultLang is newer
|
|
20
|
+
// than this package's utils floor (a named import of a missing export fails
|
|
21
|
+
// the module at link time).
|
|
22
|
+
import * as utilsServer from '@symbo.ls/utils/server'
|
|
19
23
|
|
|
20
24
|
// funcql plugin — enables evaluation of funcql schemas as property values
|
|
21
25
|
// during SSR. Async-imported to avoid polluting non-funcql codepaths.
|
|
@@ -56,6 +60,27 @@ const ssrResolve = (map, key) => {
|
|
|
56
60
|
// SSR/hydrate text mismatches.
|
|
57
61
|
const activeLangOf = (root, poly) => polyglotFallbackLang(root?.lang, poly)
|
|
58
62
|
|
|
63
|
+
// The payload as a server may hold it (frank JSON — mermaid's live render,
|
|
64
|
+
// any caller that does not revive it first): `polyglot.defaultLang` as a
|
|
65
|
+
// function's source TEXT. Revived once per render, before anything asks —
|
|
66
|
+
// the start language (smbls prepareState), activeLangOf, the labels, the
|
|
67
|
+
// prefetch — so the render is in the language the page starts in
|
|
68
|
+
// (@symbo.ls/utils/server revivePolyglotDefaultLang; text that cannot be
|
|
69
|
+
// compiled here stays text and counts as none, as before). The shared
|
|
70
|
+
// language helpers compile no text: they ride in every browser bundle.
|
|
71
|
+
const withRevivedDefaultLang = (data) => {
|
|
72
|
+
const revive = utilsServer.revivePolyglotDefaultLang
|
|
73
|
+
if (typeof revive !== 'function' || !data || typeof data !== 'object') return data
|
|
74
|
+
const top = data.polyglot ? revive(data.polyglot) : data.polyglot
|
|
75
|
+
const stored = data.config && data.config.polyglot
|
|
76
|
+
const inConfig = stored ? revive(stored) : stored
|
|
77
|
+
if (top === data.polyglot && inConfig === stored) return data
|
|
78
|
+
const out = { ...data }
|
|
79
|
+
if (top !== data.polyglot) out.polyglot = top
|
|
80
|
+
if (inConfig !== stored) out.config = { ...data.config, polyglot: inConfig }
|
|
81
|
+
return out
|
|
82
|
+
}
|
|
83
|
+
|
|
59
84
|
// One label lookup, the order the client's translate() reads: static
|
|
60
85
|
// translations, then the labels loaded into the root state, then the
|
|
61
86
|
// default language's static ones. `undefined` when no table has the key.
|
|
@@ -1119,7 +1144,7 @@ const renderInTurn = async (data, options, timeout) => {
|
|
|
1119
1144
|
}
|
|
1120
1145
|
|
|
1121
1146
|
const renderOnce = async (data, options, turn) => {
|
|
1122
|
-
const { route = '/', pathname, state: stateOverrides, context: contextOverrides, prefetch = false } = options
|
|
1147
|
+
const { route = '/', pathname, state: stateOverrides, context: contextOverrides, prefetch = false, lazyGroups } = options
|
|
1123
1148
|
// pathname is the actual URL path (e.g. /podcast/abc-123), route is the page pattern (e.g. /podcast/:id)
|
|
1124
1149
|
const locationPath = pathname || route
|
|
1125
1150
|
|
|
@@ -1127,13 +1152,13 @@ const renderOnce = async (data, options, turn) => {
|
|
|
1127
1152
|
trackFetches()
|
|
1128
1153
|
const diag = openRenderRecord(DIAG, () => ({ failures: [], labelKeys: new Set(), tainted: false }))
|
|
1129
1154
|
try {
|
|
1130
|
-
return await renderWithDiagnostics(data, { route, locationPath, stateOverrides, contextOverrides, prefetch }, diag.record, turn)
|
|
1155
|
+
return await renderWithDiagnostics(withRevivedDefaultLang(data), { route, locationPath, stateOverrides, contextOverrides, prefetch, lazyGroups }, diag.record, turn)
|
|
1131
1156
|
} finally {
|
|
1132
1157
|
diag.close()
|
|
1133
1158
|
}
|
|
1134
1159
|
}
|
|
1135
1160
|
|
|
1136
|
-
const renderWithDiagnostics = async (data, { route, locationPath, stateOverrides, contextOverrides, prefetch }, diag, turn) => {
|
|
1161
|
+
const renderWithDiagnostics = async (data, { route, locationPath, stateOverrides, contextOverrides, prefetch, lazyGroups }, diag, turn) => {
|
|
1137
1162
|
// After every wait: a render abandoned at its timeout stops here (see
|
|
1138
1163
|
// "One render at a time").
|
|
1139
1164
|
const live = () => {
|
|
@@ -1217,7 +1242,9 @@ const renderWithDiagnostics = async (data, { route, locationPath, stateOverrides
|
|
|
1217
1242
|
}
|
|
1218
1243
|
|
|
1219
1244
|
// Inject SSR translations into polyglot config and root state
|
|
1220
|
-
|
|
1245
|
+
// `prefetch` (warm the other languages at idle) is for a visitor's
|
|
1246
|
+
// browser: a render loads its page's language only.
|
|
1247
|
+
const polyglotConfig = config.polyglot ? { ...config.polyglot, prefetch: false } : undefined
|
|
1221
1248
|
|
|
1222
1249
|
// The address's language in this render's storage before the app starts,
|
|
1223
1250
|
// as the browser has it (see "The address's language").
|
|
@@ -1231,20 +1258,14 @@ const renderWithDiagnostics = async (data, { route, locationPath, stateOverrides
|
|
|
1231
1258
|
}
|
|
1232
1259
|
}
|
|
1233
1260
|
|
|
1261
|
+
// The project's state as declared. No `root` key of the render's own: the
|
|
1262
|
+
// language comes from polyglot's start language (smbls prepareState) and
|
|
1263
|
+
// the loaded labels are in `polyglotConfig.translations` (above). A
|
|
1264
|
+
// `root: { lang, translations }` written here never reached the state —
|
|
1265
|
+
// smbls merges state with mergeStateData, which keeps the store's
|
|
1266
|
+
// bookkeeping slots out — and made every build print the
|
|
1267
|
+
// `[smbls/state] … "root"` warning (XMA 3.14.857 audit, c7).
|
|
1234
1268
|
const baseState = structuredCloneDeep(data.state || {})
|
|
1235
|
-
// Ensure root state has lang and translations for polyglot resolution
|
|
1236
|
-
if (ssrTranslations || polyglotConfig) {
|
|
1237
|
-
if (!baseState.root) baseState.root = {}
|
|
1238
|
-
if (polyglotConfig) {
|
|
1239
|
-
baseState.root.lang = polyglotFallbackLang(baseState.root.lang, polyglotConfig)
|
|
1240
|
-
}
|
|
1241
|
-
if (ssrTranslations) {
|
|
1242
|
-
baseState.root.translations = {
|
|
1243
|
-
...(baseState.root.translations || {}),
|
|
1244
|
-
...ssrTranslations
|
|
1245
|
-
}
|
|
1246
|
-
}
|
|
1247
|
-
}
|
|
1248
1269
|
|
|
1249
1270
|
// Reset the atomic CSS engine for this render pass
|
|
1250
1271
|
resetCss()
|
|
@@ -1309,6 +1330,9 @@ const renderWithDiagnostics = async (data, { route, locationPath, stateOverrides
|
|
|
1309
1330
|
sync: false,
|
|
1310
1331
|
inspect: false,
|
|
1311
1332
|
notifications: false,
|
|
1333
|
+
// The app's `afterFirstView` (smbls create option) is for the visitor's
|
|
1334
|
+
// first view — an analytics beacon, a page view — never for a build.
|
|
1335
|
+
afterFirstView: null,
|
|
1312
1336
|
// Every server render processes the design system the way the page's
|
|
1313
1337
|
// own single boot does, on a config of its own — the first render of a
|
|
1314
1338
|
// process used to share the global config and every later one took the
|
|
@@ -1328,8 +1352,22 @@ const renderWithDiagnostics = async (data, { route, locationPath, stateOverrides
|
|
|
1328
1352
|
// render returns it as `preparedDesignSystem`, and `smbls build` writes
|
|
1329
1353
|
// it into one file each page preloads).
|
|
1330
1354
|
captureDesignSystem: true,
|
|
1355
|
+
// `lazyGroups` (XMA question 6): a lazy route renders as its group's
|
|
1356
|
+
// real page, not its placeholder — @symbo.ls/router lazy.js "The first
|
|
1357
|
+
// view" (see lazyFirstViewOf).
|
|
1358
|
+
...lazyFirstViewOf(lazyGroups),
|
|
1331
1359
|
// Caller overrides
|
|
1332
|
-
...(contextOverrides || {})
|
|
1360
|
+
...(contextOverrides || {}),
|
|
1361
|
+
// For app code: this code runs inside a server render. The server
|
|
1362
|
+
// document lays nothing out and observes nothing (no getComputedStyle,
|
|
1363
|
+
// no ResizeObserver / IntersectionObserver): an entrance motion that
|
|
1364
|
+
// hides an element until it scrolls into view stored the hidden state in
|
|
1365
|
+
// the page (W7 B0, system/landing: 34 blocks at `opacity: 0` on `/`).
|
|
1366
|
+
// App code reads `el.context.ssr === true` and renders the final state.
|
|
1367
|
+
// A browser has no such key; a client boot over a pre-rendered page has
|
|
1368
|
+
// `el.context.prerendered` (smbls prerender.js). Last, so no payload key
|
|
1369
|
+
// and no caller override makes a server render look like a browser.
|
|
1370
|
+
ssr: true
|
|
1333
1371
|
}
|
|
1334
1372
|
renderContext = ctx
|
|
1335
1373
|
|
|
@@ -1353,6 +1391,16 @@ const renderWithDiagnostics = async (data, { route, locationPath, stateOverrides
|
|
|
1353
1391
|
try {
|
|
1354
1392
|
element = await createDomqlElement(app, ctx)
|
|
1355
1393
|
live()
|
|
1394
|
+
// `lazyGroups`: the first navigation awaits its group before it renders
|
|
1395
|
+
// the route (@symbo.ls/router lazy.js "The first view") — the page is
|
|
1396
|
+
// in once that navigation is. Bounded by the render's own time limit.
|
|
1397
|
+
if (ctx.lazyFirstView) {
|
|
1398
|
+
const navigation = ((element && element.context) || ctx).__firstNavigation
|
|
1399
|
+
if (navigation && typeof navigation.then === 'function') {
|
|
1400
|
+
try { await navigation } catch (err) { /* the router reports its own errors */ }
|
|
1401
|
+
live()
|
|
1402
|
+
}
|
|
1403
|
+
}
|
|
1356
1404
|
|
|
1357
1405
|
// Allow async operations (fetch callbacks, state updates, re-renders) to flush.
|
|
1358
1406
|
// DOMQL's fetch plugin fires on element creation and updates state
|
|
@@ -1469,7 +1517,13 @@ const renderWithDiagnostics = async (data, { route, locationPath, stateOverrides
|
|
|
1469
1517
|
}
|
|
1470
1518
|
: null
|
|
1471
1519
|
|
|
1472
|
-
|
|
1520
|
+
// The lazy groups whose page this render's first view shows — awaited
|
|
1521
|
+
// (`lazyGroups`) or loaded during the render — as the router records them:
|
|
1522
|
+
// the page names them and `smbls build` preloads their chunks.
|
|
1523
|
+
const firstViews = ((element && element.context) || ctx).__lazyFirstViews
|
|
1524
|
+
const lazyGroupsShown = firstViews && typeof firstViews.forEach === 'function' ? [...firstViews] : []
|
|
1525
|
+
|
|
1526
|
+
return { html, metadata, registry, brRegistry, element, emotionCSS, lazyGroups: lazyGroupsShown, globalCSS, classRules, fetchSeed, fetchRuns, document, window, ssrTranslations: labels || undefined, activeLang, diagnostics, preparedDesignSystem: ctx.preparedDesignSystem || null,
|
|
1473
1527
|
// the registry encodings the client of this smbls reads (smbls
|
|
1474
1528
|
// prerender.js PRERENDER_REGISTRY_FORMATS); an older smbls reads paths
|
|
1475
1529
|
registryFormats: Array.isArray(PRERENDER_REGISTRY_FORMATS) ? PRERENDER_REGISTRY_FORMATS.slice() : ['paths'] }
|
|
@@ -1478,6 +1532,25 @@ const renderWithDiagnostics = async (data, { route, locationPath, stateOverrides
|
|
|
1478
1532
|
}
|
|
1479
1533
|
}
|
|
1480
1534
|
|
|
1535
|
+
// ── Lazy routes built as their pages (XMA question 6) ──────────────────────
|
|
1536
|
+
// `lazyGroups: 'load'` — every lazy group a pre-rendered route names is
|
|
1537
|
+
// loaded in the render and the route renders as the group's page; an array
|
|
1538
|
+
// of group names does that for those groups only. Unset: the render does not
|
|
1539
|
+
// wait for a group — the route shows its group's page when the group loaded
|
|
1540
|
+
// during the render (its loader settled in time: a context that declares
|
|
1541
|
+
// `lazy`, `brender.context`), else its placeholder. The group's `when` is
|
|
1542
|
+
// asked as in the browser: false builds the placeholder. Either way a page
|
|
1543
|
+
// names the groups whose page it shows (`<meta name="brender-lazy">`), and
|
|
1544
|
+
// the browser's first navigation loads them before it adopts the page.
|
|
1545
|
+
const lazyFirstViewOf = (lazyGroups) => {
|
|
1546
|
+
if (lazyGroups === 'load' || lazyGroups === true) return { lazyFirstView: true }
|
|
1547
|
+
if (Array.isArray(lazyGroups)) {
|
|
1548
|
+
const names = lazyGroups.filter((g) => typeof g === 'string' && g)
|
|
1549
|
+
return names.length ? { lazyFirstView: names } : {}
|
|
1550
|
+
}
|
|
1551
|
+
return {}
|
|
1552
|
+
}
|
|
1553
|
+
|
|
1481
1554
|
// Put `value` on globalThis[name] for a render and hand back what puts the
|
|
1482
1555
|
// old property back, descriptor and all. Never reads the old value — a host
|
|
1483
1556
|
// global can be a lazy accessor that warns or throws when read.
|
|
@@ -1621,7 +1694,8 @@ export const renderElement = async (elementDef, options = {}) => {
|
|
|
1621
1694
|
let element
|
|
1622
1695
|
try {
|
|
1623
1696
|
element = create(elementDef, { node: body }, 'root', {
|
|
1624
|
-
|
|
1697
|
+
// `ssr: true` — a server render, as in render() above
|
|
1698
|
+
context: { document, window, ...context, components, utils, ssr: true }
|
|
1625
1699
|
})
|
|
1626
1700
|
} catch (err) {
|
|
1627
1701
|
// Lifecycle events (onRender, onDone, etc.) may throw in SSR
|
|
@@ -1959,10 +2033,13 @@ const collectFetchedState = (element) => {
|
|
|
1959
2033
|
* `adoptDom`: the element-path registry as paths, as a tree (the same data
|
|
1960
2034
|
* in a third of the bytes — @symbo.ls/utils/server encodeBrenderRegistry), or the
|
|
1961
2035
|
* tree when the smbls the page renders with reads it
|
|
2036
|
+
* @param {{ whole?: boolean }} [options.paint] - `{ whole: true }`: the first
|
|
2037
|
+
* paint waits until the page's markup is in (see withWholePaint)
|
|
1962
2038
|
* @returns {Promise<{ html: string, route: string, brKeyCount: number, shell?: true }>}
|
|
1963
2039
|
*/
|
|
1964
2040
|
export const renderPage = async (data, route = '/', options = {}) => {
|
|
1965
|
-
const { lang, themeColor, isr, hydrate = true, prefetch = true, shell, adoptDom = false, pathname, eagerImages = EAGER_IMAGES_DEFAULT, preloadScripts = true, nonce, timeout, stamp, time, spriteIcons = 'all', cssClasses = 'all', cssVariables = 'all', registryFormat = 'paths' } = options
|
|
2041
|
+
const { lang, themeColor, isr, hydrate = true, prefetch = true, shell, adoptDom = false, pathname, eagerImages = EAGER_IMAGES_DEFAULT, preloadScripts = true, nonce, timeout, stamp, time, spriteIcons = 'all', cssClasses = 'all', cssVariables = 'all', registryFormat = 'paths', lazyGroups, paint } = options
|
|
2042
|
+
const whole = paintsWhole(paint)
|
|
1966
2043
|
const nonceAttr = nonceAttribute(nonce)
|
|
1967
2044
|
|
|
1968
2045
|
// Detect lang from project config, app metadata, or default
|
|
@@ -1976,7 +2053,7 @@ export const renderPage = async (data, route = '/', options = {}) => {
|
|
|
1976
2053
|
// canonical/og:url built from it read it. Without it a route was rendered
|
|
1977
2054
|
// at its own pattern: the `/*` page wrote a `/*` canonical into 404.html
|
|
1978
2055
|
// (xma.info report, brender build item 7).
|
|
1979
|
-
const result = await render(data, { route, pathname, prefetch, timeout })
|
|
2056
|
+
const result = await render(data, { route, pathname, prefetch, timeout, lazyGroups })
|
|
1980
2057
|
if (!result) return null
|
|
1981
2058
|
|
|
1982
2059
|
const metadata = { ...result.metadata }
|
|
@@ -2063,7 +2140,7 @@ export const renderPage = async (data, route = '/', options = {}) => {
|
|
|
2063
2140
|
// `registryFormat: 'auto'` — the tree when the smbls this page renders
|
|
2064
2141
|
// with (the page's own client) reads it, else paths (XMA round 8, E13).
|
|
2065
2142
|
const treeRegistry = registryFormat === 'tree' || (registryFormat === 'auto' && (result.registryFormats || []).includes('tree'))
|
|
2066
|
-
const prerender = prerenderParts({ brRegistry: result.brRegistry, fetchSeed: result.fetchSeed, fetchRuns: result.fetchRuns, adoptDom, stamp, time, registryFormat: treeRegistry ? 'tree' : 'paths' })
|
|
2143
|
+
const prerender = prerenderParts({ brRegistry: result.brRegistry, fetchSeed: result.fetchSeed, fetchRuns: result.fetchRuns, adoptDom, stamp, time, registryFormat: treeRegistry ? 'tree' : 'paths', lazyGroups: result.lazyGroups })
|
|
2067
2144
|
|
|
2068
2145
|
// ISR: include client SPA bundle for hydration + data fetching
|
|
2069
2146
|
let isrBody = ''
|
|
@@ -2159,7 +2236,7 @@ export const renderPage = async (data, route = '/', options = {}) => {
|
|
|
2159
2236
|
const html = composeIntoShell(shell, {
|
|
2160
2237
|
headTags: `${resolvedHeadTags}\n${prerender.meta}`,
|
|
2161
2238
|
headEnd: [fontLinks, facesStyle, brenderStyle, emotionStyle].filter(Boolean).join('\n'),
|
|
2162
|
-
body: bodyHtml,
|
|
2239
|
+
body: whole ? bodyHtml + '\n' + WHOLE_MARKER : bodyHtml,
|
|
2163
2240
|
depth: Number.isInteger(options.depth) ? options.depth : 0,
|
|
2164
2241
|
bootScripts: prerender.blocks,
|
|
2165
2242
|
// The page's language on <html>, as the client's polyglot sets it at
|
|
@@ -2169,7 +2246,7 @@ export const renderPage = async (data, route = '/', options = {}) => {
|
|
|
2169
2246
|
theme: htmlTheme,
|
|
2170
2247
|
preloadScripts
|
|
2171
2248
|
})
|
|
2172
|
-
return { html, route, brKeyCount, shell: true, diagnostics, preparedDesignSystem: result.preparedDesignSystem || null }
|
|
2249
|
+
return { html: whole ? withWholePaint(html) : html, route, brKeyCount, shell: true, diagnostics, preparedDesignSystem: result.preparedDesignSystem || null, lazyGroups: result.lazyGroups || [] }
|
|
2173
2250
|
}
|
|
2174
2251
|
|
|
2175
2252
|
// The line after the font links holds the page's @font-face rules in a
|
|
@@ -2184,12 +2261,49 @@ ${brenderStyle}
|
|
|
2184
2261
|
${emotionStyle}
|
|
2185
2262
|
</head>
|
|
2186
2263
|
<body>
|
|
2187
|
-
${bodyHtml}
|
|
2264
|
+
${bodyHtml}${whole ? `\n${WHOLE_MARKER}` : ''}
|
|
2188
2265
|
${isrBody}
|
|
2189
2266
|
</body>
|
|
2190
2267
|
</html>`
|
|
2191
2268
|
|
|
2192
|
-
return { html, route, brKeyCount, diagnostics, preparedDesignSystem: result.preparedDesignSystem || null }
|
|
2269
|
+
return { html: whole ? withWholePaint(html) : html, route, brKeyCount, diagnostics, preparedDesignSystem: result.preparedDesignSystem || null, lazyGroups: result.lazyGroups || [] }
|
|
2270
|
+
}
|
|
2271
|
+
|
|
2272
|
+
// ── The first paint waits for the whole markup (`paint: { whole: true }`) ───
|
|
2273
|
+
// A long page built ahead arrives in parts on a slow connection, and the
|
|
2274
|
+
// browser paints what it has parsed so far: the header and a part of the
|
|
2275
|
+
// first view, then the rest, which moves the parts below it in. With
|
|
2276
|
+
// `paint.whole` the page holds its first paint until its markup is in: a
|
|
2277
|
+
// `<link rel="expect" href="#smbls-whole" blocking="render">` right after the
|
|
2278
|
+
// charset declaration names an empty `<template id="smbls-whole">` that
|
|
2279
|
+
// follows the markup (before the page's data blocks and the app's scripts).
|
|
2280
|
+
// No script: a strict Content-Security-Policy does not block it. A browser
|
|
2281
|
+
// stops the hold when the element is parsed, and when the parser finishes
|
|
2282
|
+
// the document without one (HTML "expect" links: "unblock rendering" at the
|
|
2283
|
+
// end of parsing), so a page never stays blank. A browser without
|
|
2284
|
+
// `rel=expect` (Firefox, Safari in 2026) paints as before.
|
|
2285
|
+
export const WHOLE_MARKER_ID = 'smbls-whole'
|
|
2286
|
+
const WHOLE_MARKER = `<template id="${WHOLE_MARKER_ID}"></template>`
|
|
2287
|
+
const WHOLE_LINK = `<link rel="expect" href="#${WHOLE_MARKER_ID}" blocking="render">`
|
|
2288
|
+
// `paint: { whole: true }` (symbols.json / config.js `brender.paint`).
|
|
2289
|
+
export const paintsWhole = (paint) => !!(paint && typeof paint === 'object' && paint.whole === true)
|
|
2290
|
+
// The link after the charset declaration (a parser reads the charset in the
|
|
2291
|
+
// first 1024 bytes, so nothing goes ahead of it), else after the head's
|
|
2292
|
+
// opener, the html's, or the doctype.
|
|
2293
|
+
const CHARSET_META = /<meta\b[^>]*\bcharset\s*=[^>]*>/i
|
|
2294
|
+
const PAINT_OPENERS = [/<head\b[^>]*>/i, /<html\b[^>]*>/i, /^\s*<!doctype\b[^>]*>/i]
|
|
2295
|
+
export const withWholePaint = (html) => {
|
|
2296
|
+
if (typeof html !== 'string' || html.includes(WHOLE_LINK)) return html
|
|
2297
|
+
const charset = CHARSET_META.exec(html.slice(0, 1024))
|
|
2298
|
+
if (charset) {
|
|
2299
|
+
const end = charset.index + charset[0].length
|
|
2300
|
+
return html.slice(0, end) + '\n' + WHOLE_LINK + html.slice(end)
|
|
2301
|
+
}
|
|
2302
|
+
for (const opener of PAINT_OPENERS) {
|
|
2303
|
+
const m = opener.exec(html)
|
|
2304
|
+
if (m) return html.slice(0, m.index + m[0].length) + '\n' + WHOLE_LINK + html.slice(m.index + m[0].length)
|
|
2305
|
+
}
|
|
2306
|
+
return WHOLE_LINK + '\n' + html
|
|
2193
2307
|
}
|
|
2194
2308
|
|
|
2195
2309
|
// ── Images below the fold ───────────────────────────────────────────────────
|
|
@@ -2269,14 +2383,16 @@ const generateFontLinks = (ds) => {
|
|
|
2269
2383
|
// bytes — in its own block; a registry that cannot be one stays paths.
|
|
2270
2384
|
// `fetchRuns` — which queries each element ran (@symbo.ls/fetch
|
|
2271
2385
|
// fetchRunsOf), carried in the seed block with the answers.
|
|
2272
|
-
export const prerenderParts = ({ brRegistry, fetchSeed, fetchRuns, adoptDom = false, stamp, time, registryFormat = 'paths' } = {}) => {
|
|
2386
|
+
export const prerenderParts = ({ brRegistry, fetchSeed, fetchRuns, adoptDom = false, stamp, time, registryFormat = 'paths', lazyGroups } = {}) => {
|
|
2273
2387
|
const has = adoptDom && brRegistry && Object.keys(brRegistry).length
|
|
2274
2388
|
const registry = has
|
|
2275
2389
|
? (registryFormat === 'tree' && brenderRegistryTreeBlock(brRegistry)) || brenderRegistryBlock(brRegistry)
|
|
2276
2390
|
: ''
|
|
2277
2391
|
const seed = createEarlySeedBlock(fetchSeed, fetchRuns)
|
|
2278
2392
|
return {
|
|
2279
|
-
|
|
2393
|
+
// `lazyGroups`: the groups whose pages the page was built with
|
|
2394
|
+
// (@symbo.ls/utils brenderLazyMeta) — none, no tag
|
|
2395
|
+
meta: `<meta name="brender" content="${registry ? 'hydrate' : 'replace'}">\n` + brenderStampMeta(buildStamp(stamp, time)) + (brenderLazyMeta(lazyGroups) ? '\n' + brenderLazyMeta(lazyGroups) : ''),
|
|
2280
2396
|
blocks: [registry, seed].filter(Boolean).join('\n')
|
|
2281
2397
|
}
|
|
2282
2398
|
}
|