@uniweb/runtime 1.2.0 → 1.2.2
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 +86 -106
- package/dist/ssr.js.map +1 -1
- package/package.json +3 -3
- package/src/background-shared.js +16 -10
- package/src/background.js +182 -0
- package/src/components/Background.jsx +19 -294
- package/src/components/PageRenderer.jsx +4 -2
- package/src/load-page-data.js +13 -2
- package/src/setup.js +2 -1
- package/src/ssr-renderer.js +26 -122
package/src/ssr-renderer.js
CHANGED
|
@@ -2,11 +2,13 @@
|
|
|
2
2
|
* SSR Renderer
|
|
3
3
|
*
|
|
4
4
|
* Hook-free rendering pipeline for SSG (build) and server-side SSR.
|
|
5
|
-
* Mirrors BlockRenderer.jsx
|
|
6
|
-
*
|
|
5
|
+
* Mirrors BlockRenderer.jsx using React.createElement directly — no hooks,
|
|
6
|
+
* no JSX, no browser APIs. A section's background and the built-in layout are
|
|
7
|
+
* not mirrored: both renderers draw the one copy (`background.js`,
|
|
8
|
+
* `default-layout.js`).
|
|
7
9
|
*
|
|
8
10
|
* This is the single source of truth for how blocks render during prerender.
|
|
9
|
-
* When modifying BlockRenderer.jsx
|
|
11
|
+
* When modifying BlockRenderer.jsx, update `renderBlock` here to match.
|
|
10
12
|
*
|
|
11
13
|
* Exports three layers:
|
|
12
14
|
* 1. Rendering functions (renderBlock, renderBlocks, renderLayout, renderBackground)
|
|
@@ -18,7 +20,7 @@ import React from 'react'
|
|
|
18
20
|
import { renderToString } from 'react-dom/server'
|
|
19
21
|
import { createUniweb, resolveDefaultLocale } from '@uniweb/core'
|
|
20
22
|
import { sectionDomId } from '@uniweb/core/section-id'
|
|
21
|
-
import {
|
|
23
|
+
import { Background } from './background.js'
|
|
22
24
|
import { routePatternToRegex, isDynamicRoute } from '@uniweb/core/route-match'
|
|
23
25
|
import { DEFAULT_ICON_BASE, iconUrl } from '@uniweb/core/icon-corpus'
|
|
24
26
|
import { buildSectionOverrides, FONT_LINKS_MARKER } from '@uniweb/theming'
|
|
@@ -95,123 +97,17 @@ export function getWrapperProps(block) {
|
|
|
95
97
|
|
|
96
98
|
|
|
97
99
|
/**
|
|
98
|
-
* Render a background
|
|
99
|
-
*
|
|
100
|
-
*
|
|
100
|
+
* Render a section's background for SSR: the one implementation both renderers draw
|
|
101
|
+
* (`background.js`). A video background is drawn as its poster — what the browser draws too,
|
|
102
|
+
* until it has mounted and starts the video.
|
|
103
|
+
* ⛔ Until 2026-10-25 this was a copy of `Background.jsx`, and drew no video at all.
|
|
104
|
+
*
|
|
105
|
+
* @param {Object} background - `block.background`, normalized by core
|
|
106
|
+
* @returns {React.ReactElement|null}
|
|
101
107
|
*/
|
|
102
108
|
export function renderBackground(background) {
|
|
103
109
|
if (!background?.mode) return null
|
|
104
|
-
|
|
105
|
-
const containerStyle = {
|
|
106
|
-
position: 'absolute',
|
|
107
|
-
inset: '0',
|
|
108
|
-
overflow: 'hidden',
|
|
109
|
-
zIndex: 0,
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
const children = []
|
|
113
|
-
|
|
114
|
-
// Color background
|
|
115
|
-
if (background.mode === 'color' && background.color) {
|
|
116
|
-
children.push(
|
|
117
|
-
React.createElement('div', {
|
|
118
|
-
key: 'bg-color',
|
|
119
|
-
className: 'background-color',
|
|
120
|
-
style: { position: 'absolute', inset: '0', backgroundColor: background.color },
|
|
121
|
-
'aria-hidden': 'true',
|
|
122
|
-
})
|
|
123
|
-
)
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
// Gradient background (supports string or object with opacity)
|
|
127
|
-
if (background.mode === 'gradient' && background.gradient) {
|
|
128
|
-
const g = background.gradient
|
|
129
|
-
|
|
130
|
-
let bgValue
|
|
131
|
-
if (typeof g === 'string') {
|
|
132
|
-
bgValue = g
|
|
133
|
-
} else {
|
|
134
|
-
const {
|
|
135
|
-
start = 'transparent',
|
|
136
|
-
end = 'transparent',
|
|
137
|
-
angle = 0,
|
|
138
|
-
startPosition = 0,
|
|
139
|
-
endPosition = 100,
|
|
140
|
-
startOpacity = 1,
|
|
141
|
-
endOpacity = 1,
|
|
142
|
-
} = g
|
|
143
|
-
const startColor = startOpacity < 1 ? withOpacity(start, startOpacity) : start
|
|
144
|
-
const endColor = endOpacity < 1 ? withOpacity(end, endOpacity) : end
|
|
145
|
-
bgValue = `linear-gradient(${angle}deg, ${startColor} ${startPosition}%, ${endColor} ${endPosition}%)`
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
children.push(
|
|
149
|
-
React.createElement('div', {
|
|
150
|
-
key: 'bg-gradient',
|
|
151
|
-
className: 'background-gradient',
|
|
152
|
-
style: { position: 'absolute', inset: '0', background: bgValue },
|
|
153
|
-
'aria-hidden': 'true',
|
|
154
|
-
})
|
|
155
|
-
)
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
// Image background
|
|
159
|
-
if (background.mode === 'image' && background.image?.src) {
|
|
160
|
-
const img = background.image
|
|
161
|
-
children.push(
|
|
162
|
-
React.createElement('div', {
|
|
163
|
-
key: 'bg-image',
|
|
164
|
-
className: 'background-image',
|
|
165
|
-
style: {
|
|
166
|
-
position: 'absolute',
|
|
167
|
-
inset: '0',
|
|
168
|
-
backgroundImage: `url(${siteUrl(img.src)})`,
|
|
169
|
-
backgroundPosition: img.position || 'center',
|
|
170
|
-
backgroundSize: img.size || 'cover',
|
|
171
|
-
backgroundRepeat: 'no-repeat',
|
|
172
|
-
},
|
|
173
|
-
'aria-hidden': 'true',
|
|
174
|
-
})
|
|
175
|
-
)
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
// Overlay (gradient or solid)
|
|
179
|
-
if (background.overlay?.enabled) {
|
|
180
|
-
const ov = background.overlay
|
|
181
|
-
let overlayStyle
|
|
182
|
-
|
|
183
|
-
if (ov.gradient) {
|
|
184
|
-
const g = ov.gradient
|
|
185
|
-
overlayStyle = {
|
|
186
|
-
position: 'absolute', inset: '0', pointerEvents: 'none',
|
|
187
|
-
background: `linear-gradient(${g.angle || 180}deg, ${g.start || 'rgba(0,0,0,0.7)'} ${g.startPosition || 0}%, ${g.end || 'rgba(0,0,0,0)'} ${g.endPosition || 100}%)`,
|
|
188
|
-
opacity: ov.opacity ?? 0.5,
|
|
189
|
-
}
|
|
190
|
-
} else {
|
|
191
|
-
const baseColor = ov.type === 'light' ? '255, 255, 255' : '0, 0, 0'
|
|
192
|
-
overlayStyle = {
|
|
193
|
-
position: 'absolute', inset: '0', pointerEvents: 'none',
|
|
194
|
-
backgroundColor: `rgba(${baseColor}, ${ov.opacity ?? 0.5})`,
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
children.push(
|
|
199
|
-
React.createElement('div', {
|
|
200
|
-
key: 'bg-overlay',
|
|
201
|
-
className: ov.gradient ? 'background-overlay background-overlay--gradient' : 'background-overlay background-overlay--solid',
|
|
202
|
-
style: overlayStyle,
|
|
203
|
-
'aria-hidden': 'true',
|
|
204
|
-
})
|
|
205
|
-
)
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
if (children.length === 0) return null
|
|
209
|
-
|
|
210
|
-
return React.createElement('div', {
|
|
211
|
-
className: `background background--${background.mode}`,
|
|
212
|
-
style: containerStyle,
|
|
213
|
-
'aria-hidden': 'true',
|
|
214
|
-
}, ...children)
|
|
110
|
+
return React.createElement(Background, background)
|
|
215
111
|
}
|
|
216
112
|
|
|
217
113
|
/**
|
|
@@ -244,11 +140,20 @@ export function renderBlock(block, { as = 'section', stickySelf = false } = {})
|
|
|
244
140
|
const meta = getComponentMeta(block.type)
|
|
245
141
|
const entityStore = block.website?.entityStore
|
|
246
142
|
let entityData = null
|
|
143
|
+
let pending = false
|
|
247
144
|
if (entityStore) {
|
|
248
145
|
const resolved = entityStore.resolve(block, meta)
|
|
249
146
|
if (resolved.status === 'ready') entityData = resolved.data
|
|
147
|
+
pending = resolved.status === 'pending'
|
|
250
148
|
}
|
|
251
149
|
|
|
150
|
+
// ⭐ A block whose data is still out is LOADING here, as in the browser, which sets the same flag
|
|
151
|
+
// before `prepareProps` (`BlockRenderer.jsx`) and fetches the data once it boots. So the page
|
|
152
|
+
// paints the component's loading state, the one the browser renders in its place.
|
|
153
|
+
// ⛔ Until 2026-10-25 this was never set, and such a section prerendered its EMPTY state —
|
|
154
|
+
// "nothing found", or an error — then switched to loading when the browser took over.
|
|
155
|
+
block.dataLoading = pending
|
|
156
|
+
|
|
252
157
|
// Build content and params with runtime guarantees.
|
|
253
158
|
// prepareProps handles the full pipeline: entity data merge,
|
|
254
159
|
// foundation content handler invocation, guaranteed content
|
|
@@ -475,10 +380,9 @@ export function initPrerender(content, foundation, extensionsOrOptions, maybeOpt
|
|
|
475
380
|
}
|
|
476
381
|
|
|
477
382
|
// The same background a section gets, for kit's `SectionBackground` — a component that
|
|
478
|
-
// paints its own (`background: 'self'`).
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
}
|
|
383
|
+
// paints its own (`background: 'self'`). The one implementation, which the browser draws
|
|
384
|
+
// too (setup.js); its `className` is the layer's. ⛔ Until 2026-10-25 this dropped it.
|
|
385
|
+
uniweb.backgroundRenderer = Background
|
|
482
386
|
|
|
483
387
|
// L2 (singleton wiring): defaultInsets, xref.build(), and any future
|
|
484
388
|
// framework-level capability bridge — shared with setup.js so both
|