@uniweb/runtime 1.1.14 → 1.1.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/ssr.js +17 -5
- package/dist/ssr.js.map +1 -1
- package/package.json +3 -3
- package/src/components/BlockRenderer.jsx +4 -1
- package/src/foundation-loader.js +5 -3
- package/src/isolate-api.js +6 -0
- package/src/setup.js +7 -1
- package/src/ssr-renderer.js +8 -13
- package/src/ssr.js +17 -0
- package/src/sticky.js +28 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@uniweb/runtime",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.16",
|
|
4
4
|
"description": "Minimal runtime for loading Uniweb foundations",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
"node": ">=22.12"
|
|
37
37
|
},
|
|
38
38
|
"dependencies": {
|
|
39
|
-
"@uniweb/core": "~1.4.
|
|
39
|
+
"@uniweb/core": "~1.4.1",
|
|
40
40
|
"@uniweb/theming": "~1.2.1"
|
|
41
41
|
},
|
|
42
42
|
"devDependencies": {
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
"esbuild": "^0.21.0 || ^0.23.0 || ^0.24.0 || ^0.25.0 || ^0.27.0 || ^0.28.0",
|
|
45
45
|
"vite": "^7.3.1",
|
|
46
46
|
"vitest": "^4.1.7",
|
|
47
|
-
"@uniweb/build": "1.
|
|
47
|
+
"@uniweb/build": "1.8.1"
|
|
48
48
|
},
|
|
49
49
|
"peerDependencies": {
|
|
50
50
|
"react": "^19.0.0",
|
|
@@ -10,7 +10,7 @@ import React, { useState, useEffect } from 'react'
|
|
|
10
10
|
import { sectionDomId } from '@uniweb/core/section-id'
|
|
11
11
|
import { prepareProps, getComponentMeta } from '../prepare-props.js'
|
|
12
12
|
import Background from './Background.jsx'
|
|
13
|
-
import { sectionStickyProps } from '../sticky.js'
|
|
13
|
+
import { sectionStickyProps, sectionScrollMargin } from '../sticky.js'
|
|
14
14
|
import { noteBodySection } from '../sticky-top.js'
|
|
15
15
|
|
|
16
16
|
/**
|
|
@@ -169,6 +169,9 @@ export default function BlockRenderer({ block, as = 'section', stickySelf = fals
|
|
|
169
169
|
wrapperProps.style = { ...wrapperProps.style, ...sticky.style }
|
|
170
170
|
wrapperProps['data-uw-sticky'] = sticky['data-uw-sticky']
|
|
171
171
|
}
|
|
172
|
+
// On a page whose layout has a sticky area, a link to this section lands below it.
|
|
173
|
+
const margin = sectionScrollMargin(block, stickySelf)
|
|
174
|
+
if (margin) wrapperProps.style = { ...wrapperProps.style, ...margin }
|
|
172
175
|
// A body section's element, for the measurement of what is stuck over the body (`../sticky-top.js`).
|
|
173
176
|
if (stickySelf === 'section' || stickySelf === true) wrapperProps.ref = (element) => noteBodySection(block, element)
|
|
174
177
|
|
package/src/foundation-loader.js
CHANGED
|
@@ -5,6 +5,8 @@
|
|
|
5
5
|
* with CSS loading in parallel.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
+
import { foundationCssUrl } from '@uniweb/core/page-shell'
|
|
9
|
+
|
|
8
10
|
/**
|
|
9
11
|
* Resolve a foundation/extension URL so that absolute-path forms
|
|
10
12
|
* (`/_module/...`) anchor to the document's origin, not to the importing
|
|
@@ -75,11 +77,11 @@ export async function loadFoundationCSS(url) {
|
|
|
75
77
|
*/
|
|
76
78
|
export async function loadFoundation(source) {
|
|
77
79
|
const rawUrl = typeof source === 'string' ? source : source.url
|
|
78
|
-
// Auto-derive CSS URL from JS URL by convention
|
|
80
|
+
// Auto-derive CSS URL from JS URL by the one convention, entry.js → assets/style.css
|
|
81
|
+
// (`foundationCssUrl`, which a page's shell links the primary's stylesheet by too).
|
|
79
82
|
// Pre-Phase-5 foundations were named foundation.js + assets/foundation.css;
|
|
80
83
|
// those keep working when an explicit `cssUrl` is passed in `source`.
|
|
81
|
-
const rawCssUrl = typeof source === 'object' ? source.cssUrl
|
|
82
|
-
: rawUrl.replace(/[^/]+\.js$/, 'assets/style.css')
|
|
84
|
+
const rawCssUrl = typeof source === 'object' ? source.cssUrl : foundationCssUrl(rawUrl)
|
|
83
85
|
|
|
84
86
|
// Phase 5: when the runtime is loaded from a different host than the
|
|
85
87
|
// document (e.g. runtime served from cdn.uniweb.app, page on
|
package/src/isolate-api.js
CHANGED
|
@@ -119,6 +119,12 @@ export const ISOLATE_API = Object.freeze({
|
|
|
119
119
|
RESOLUTION_STATUS: '0.28.6',
|
|
120
120
|
// a redirect the host answers itself, with the visitor's query and fragment on it
|
|
121
121
|
carryQuery: '1.0.1',
|
|
122
|
+
// the `__DATA__` element a host writes into a page's shell, and the escaping it uses
|
|
123
|
+
dataScript: UNRELEASED,
|
|
124
|
+
jsonForScript: UNRELEASED,
|
|
125
|
+
// the page's whole shell, and the foundation stylesheet convention it links by
|
|
126
|
+
pageShell: UNRELEASED,
|
|
127
|
+
foundationCssUrl: UNRELEASED,
|
|
122
128
|
})
|
|
123
129
|
|
|
124
130
|
/**
|
package/src/setup.js
CHANGED
|
@@ -355,10 +355,16 @@ export async function decodeData() {
|
|
|
355
355
|
const raw = el.textContent
|
|
356
356
|
|
|
357
357
|
// Plain JSON (uncompressed)
|
|
358
|
+
// ⛔ A payload that is there and does not parse is said as such: the boot then finds no
|
|
359
|
+
// configuration, and "No site configuration found" alone sent a reader looking for a payload
|
|
360
|
+
// that was present. Measured 2026-10-23: a host escaped `<!--` as `<\!--`, which JSON has no
|
|
361
|
+
// escape for, and every page stayed its server render with nothing naming the cause. JSON
|
|
362
|
+
// inside a <script> escapes `<` as `<` (`build/src/prerender.js`).
|
|
358
363
|
if (el.type === 'application/json') {
|
|
359
364
|
try {
|
|
360
365
|
return JSON.parse(raw)
|
|
361
|
-
} catch {
|
|
366
|
+
} catch (err) {
|
|
367
|
+
console.error(`[Runtime] __DATA__ is not valid JSON, so the site cannot start: ${err.message}`)
|
|
362
368
|
return null
|
|
363
369
|
}
|
|
364
370
|
}
|
package/src/ssr-renderer.js
CHANGED
|
@@ -31,10 +31,11 @@ import {
|
|
|
31
31
|
ensureThemeCss,
|
|
32
32
|
} from './wire-foundation.js'
|
|
33
33
|
import { resolveLayoutTransitions, resolveLayoutLayers, areaWrapperProps } from './area-wrappers.js'
|
|
34
|
-
import { layoutStickiness, sectionStickyProps, zoneProps } from './sticky.js'
|
|
34
|
+
import { layoutStickiness, sectionStickyProps, sectionScrollMargin, zoneProps } from './sticky.js'
|
|
35
35
|
import { DefaultLayout } from './default-layout.js'
|
|
36
36
|
import { renderAppearanceBootScript } from './appearance.js'
|
|
37
37
|
import { documentTitle } from './document-title.js'
|
|
38
|
+
import { escapeHtml } from '@uniweb/core/page-shell'
|
|
38
39
|
|
|
39
40
|
// Re-export L2 helpers so the public `@uniweb/runtime/ssr` surface
|
|
40
41
|
// carries everything an SSR consumer needs from one entry point.
|
|
@@ -274,6 +275,9 @@ export function renderBlock(block, { as = 'section', stickySelf = false } = {})
|
|
|
274
275
|
wrapperProps.style = { ...wrapperProps.style, ...sticky.style }
|
|
275
276
|
wrapperProps['data-uw-sticky'] = sticky['data-uw-sticky']
|
|
276
277
|
}
|
|
278
|
+
// On a page whose layout has a sticky area, a link to this section lands below it.
|
|
279
|
+
const margin = sectionScrollMargin(block, stickySelf)
|
|
280
|
+
if (margin) wrapperProps.style = { ...wrapperProps.style, ...margin }
|
|
277
281
|
|
|
278
282
|
// Merge Component.className (static classes declared on the component function)
|
|
279
283
|
const componentClassName = Component.className
|
|
@@ -652,18 +656,9 @@ export function renderPage(page, website) {
|
|
|
652
656
|
// HTML injection
|
|
653
657
|
// ============================================================================
|
|
654
658
|
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
export function escapeHtml(str) {
|
|
659
|
-
if (!str) return ''
|
|
660
|
-
return String(str)
|
|
661
|
-
.replace(/&/g, '&')
|
|
662
|
-
.replace(/</g, '<')
|
|
663
|
-
.replace(/>/g, '>')
|
|
664
|
-
.replace(/"/g, '"')
|
|
665
|
-
.replace(/'/g, ''')
|
|
666
|
-
}
|
|
659
|
+
// Escape HTML special characters — the one implementation, which a page's shell writes with too
|
|
660
|
+
// (`@uniweb/core/page-shell`). Still exported from here, as it has been since 0.6.14.
|
|
661
|
+
export { escapeHtml }
|
|
667
662
|
|
|
668
663
|
/**
|
|
669
664
|
* Inject prerendered content into an HTML shell.
|
package/src/ssr.js
CHANGED
|
@@ -100,3 +100,20 @@ export { collectSiteRecords } from './collect-records.js'
|
|
|
100
100
|
// Appearance. injectPageContent() already emits this for every prerendered
|
|
101
101
|
// page; exported for lanes that assemble a shell without a per-page render.
|
|
102
102
|
export { renderAppearanceBootScript } from './appearance.js'
|
|
103
|
+
|
|
104
|
+
// ⭐ THE `__DATA__` ELEMENT A HOST WRITES INTO A PAGE'S SHELL — the boot payload as JSON a
|
|
105
|
+
// `<script>` cannot end or misparse (every `<` as `<`), carrying only the served page's
|
|
106
|
+
// sections when the content names where the others are (`config.pageHashes`). The rule a static
|
|
107
|
+
// build writes its own page data by (`@uniweb/core/data-script`). ⛔ A host wrote `__DATA__` with
|
|
108
|
+
// its own escaping until 2026-10-23, escaping `<!--` as `<\!--` — a JavaScript-string escape that
|
|
109
|
+
// is invalid JSON — and no page of the site it served could start (`decodeData`).
|
|
110
|
+
// `jsonForScript` is the escaping alone, for any other JSON a host writes into a `<script>`.
|
|
111
|
+
export { dataScript, jsonForScript } from '@uniweb/core/data-script'
|
|
112
|
+
|
|
113
|
+
// ⭐ THE PAGE'S SHELL — the whole HTML a page starts as, from what only the host holds (its
|
|
114
|
+
// addresses, the runtime's manifest, the content it read): the head, an empty `#root`, and
|
|
115
|
+
// `__DATA__`. Page assembly is framework's [Diego, 2026-10-23]. In an isolate it is the `shell`
|
|
116
|
+
// `createPageRenderer` renders into, versioned with the runtime; a host links the same function
|
|
117
|
+
// (`@uniweb/core/page-shell`) for a page it serves without rendering. `foundationCssUrl` is the
|
|
118
|
+
// one convention for a foundation's stylesheet, which the loader and the shell both follow.
|
|
119
|
+
export { pageShell, foundationCssUrl } from '@uniweb/core/page-shell'
|
package/src/sticky.js
CHANGED
|
@@ -111,6 +111,34 @@ export function sectionStickyStyle(block) {
|
|
|
111
111
|
return block?.sticky ? { ...stickyStyle(block.stickyTop || STICKY_SECTION_TOP, 1), alignSelf: 'start' } : null
|
|
112
112
|
}
|
|
113
113
|
|
|
114
|
+
/**
|
|
115
|
+
* Whether any area of a page's layout sticks — its sections' components initialized, as both
|
|
116
|
+
* renderers do before they render.
|
|
117
|
+
*
|
|
118
|
+
* @param {Object} page
|
|
119
|
+
* @returns {boolean}
|
|
120
|
+
*/
|
|
121
|
+
export function hasStickyArea(page) {
|
|
122
|
+
const areas = page?.getLayoutAreas?.() || {}
|
|
123
|
+
return Object.values(areas).some((blocks) => areaStickiness(blocks) !== null)
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Where a link to a section lands — below what is stuck at the top, `--uw-sticky-top` — given to a
|
|
128
|
+
* body section's box, and a child section's, on a page whose layout has a sticky area. A link to
|
|
129
|
+
* `#section-…` (a search result, a contents entry, a pasted deep link) scrolls the section's top
|
|
130
|
+
* edge to the top of the viewport, and without the margin the stuck header covers it — measured
|
|
131
|
+
* 2026-10-20: a section's top at 0px under an 80px header, at 80px with the margin. On other pages
|
|
132
|
+
* the markup is unchanged, and unmeasured the margin is `0px`.
|
|
133
|
+
*
|
|
134
|
+
* @param {Object} block - its component initialized
|
|
135
|
+
* @param {'section'|'child'|boolean} kind - a body section's box, or a child section's; else none
|
|
136
|
+
* @returns {{ scrollMarginTop: string }|null}
|
|
137
|
+
*/
|
|
138
|
+
export function sectionScrollMargin(block, kind) {
|
|
139
|
+
return kind && hasStickyArea(block?.page) ? { scrollMarginTop: STICKY_SECTION_TOP } : null
|
|
140
|
+
}
|
|
141
|
+
|
|
114
142
|
/**
|
|
115
143
|
* What a section's wrapper adds when it sticks: its style, and which kind of box it is.
|
|
116
144
|
*
|