@uniweb/runtime 1.1.20 → 1.2.1
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 +138 -148
- 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/collect-records.js +7 -1
- package/src/components/Background.jsx +19 -294
- package/src/components/BlockRenderer.jsx +10 -2
- package/src/components/Layout.jsx +3 -2
- package/src/components/PageRenderer.jsx +4 -2
- package/src/default-fetcher.js +4 -1
- package/src/hooks/useHeadMeta.js +42 -7
- package/src/load-page-data.js +6 -1
- package/src/page-renderer.js +21 -40
- package/src/prepare-props.js +31 -19
- package/src/require-transport.js +35 -0
- package/src/setup.js +2 -1
- package/src/ssr-renderer.js +86 -161
- package/src/sticky-check.js +106 -33
- package/src/sticky.js +5 -3
package/src/hooks/useHeadMeta.js
CHANGED
|
@@ -3,6 +3,13 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Manages document head meta tags for SEO and social sharing.
|
|
5
5
|
* Updates meta tags when page changes in SPA navigation.
|
|
6
|
+
*
|
|
7
|
+
* ⭐ The browser twin of `injectPageContent`'s head tags, from the same source
|
|
8
|
+
* (`Page#getHeadMeta`), so a crawler that runs the page's script reads what one that
|
|
9
|
+
* does not read. These tags are the framework's: each is set when the page has a value
|
|
10
|
+
* and removed when it has none — as it must be, or a tag of the page before survives a
|
|
11
|
+
* navigation. ⛔ Until 2026-10-25 this removed the canonical of every page that did not
|
|
12
|
+
* author one, after the prerendered HTML had stated it.
|
|
6
13
|
*/
|
|
7
14
|
|
|
8
15
|
import { useEffect, useRef } from 'react'
|
|
@@ -20,7 +27,9 @@ const META_TAGS = {
|
|
|
20
27
|
'og:image': { property: 'og:image' },
|
|
21
28
|
'og:url': { property: 'og:url' },
|
|
22
29
|
'og:type': { property: 'og:type' },
|
|
30
|
+
'og:site_name': { property: 'og:site_name' },
|
|
23
31
|
'twitter:card': { name: 'twitter:card' },
|
|
32
|
+
'twitter:site': { name: 'twitter:site' },
|
|
24
33
|
'twitter:title': { name: 'twitter:title' },
|
|
25
34
|
'twitter:description': { name: 'twitter:description' },
|
|
26
35
|
'twitter:image': { name: 'twitter:image' }
|
|
@@ -72,13 +81,17 @@ function getOrCreateLinkTag(rel) {
|
|
|
72
81
|
}
|
|
73
82
|
|
|
74
83
|
/**
|
|
75
|
-
* Set or remove a meta tag's content
|
|
84
|
+
* Set or remove a meta tag's content — one tag of each name, whoever wrote the others
|
|
76
85
|
* @param {string} key - Meta tag key
|
|
77
86
|
* @param {string|null} content - Content value (null to remove)
|
|
78
87
|
*/
|
|
79
88
|
function setMetaContent(key, content) {
|
|
80
89
|
const element = getOrCreateMetaTag(key)
|
|
81
90
|
if (!element) return
|
|
91
|
+
const config = META_TAGS[key]
|
|
92
|
+
for (const extra of document.querySelectorAll(`meta[${config.property ? 'property' : 'name'}="${config.property || config.name}"]`)) {
|
|
93
|
+
if (extra !== element) extra.remove()
|
|
94
|
+
}
|
|
82
95
|
|
|
83
96
|
if (content) {
|
|
84
97
|
element.setAttribute('content', content)
|
|
@@ -88,6 +101,21 @@ function setMetaContent(key, content) {
|
|
|
88
101
|
}
|
|
89
102
|
}
|
|
90
103
|
|
|
104
|
+
/**
|
|
105
|
+
* The page's URL in each locale (`hreflang`), replacing whatever the document carried
|
|
106
|
+
* @param {Array<{hreflang: string, url: string}>} alternates
|
|
107
|
+
*/
|
|
108
|
+
function setAlternates(alternates) {
|
|
109
|
+
for (const link of document.querySelectorAll('link[rel="alternate"][hreflang]')) link.remove()
|
|
110
|
+
for (const { hreflang, url } of alternates || []) {
|
|
111
|
+
const link = document.createElement('link')
|
|
112
|
+
link.setAttribute('rel', 'alternate')
|
|
113
|
+
link.setAttribute('hreflang', hreflang)
|
|
114
|
+
link.setAttribute('href', url)
|
|
115
|
+
document.head.appendChild(link)
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
91
119
|
/**
|
|
92
120
|
* useHeadMeta hook
|
|
93
121
|
*
|
|
@@ -138,26 +166,30 @@ export function useHeadMeta(meta, options = {}) {
|
|
|
138
166
|
setMetaContent('og:image', meta.og.image || null)
|
|
139
167
|
setMetaContent('og:url', meta.og.url || null)
|
|
140
168
|
setMetaContent('og:type', 'website')
|
|
169
|
+
setMetaContent('og:site_name', meta.og.siteName || null)
|
|
141
170
|
|
|
142
171
|
// Twitter cards (fallback to OG values)
|
|
143
172
|
setMetaContent('twitter:card', meta.og.image ? 'summary_large_image' : 'summary')
|
|
173
|
+
setMetaContent('twitter:site', meta.twitter?.site || null)
|
|
144
174
|
setMetaContent('twitter:title', meta.og.title || meta.title || null)
|
|
145
175
|
setMetaContent('twitter:description', meta.og.description || meta.description || null)
|
|
146
176
|
setMetaContent('twitter:image', meta.og.image || null)
|
|
147
177
|
}
|
|
148
178
|
|
|
149
|
-
// Update canonical link
|
|
179
|
+
// Update canonical link — the page's own URL unless it authors one, and none when the site's
|
|
180
|
+
// address is unknown (`Page#getHeadMeta`)
|
|
150
181
|
if (meta.canonical) {
|
|
151
182
|
const canonicalLink = getOrCreateLinkTag('canonical')
|
|
152
183
|
canonicalLink.setAttribute('href', meta.canonical)
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
const existingCanonical = document.querySelector('link[rel="canonical"]')
|
|
156
|
-
if (existingCanonical) {
|
|
157
|
-
existingCanonical.remove()
|
|
184
|
+
for (const extra of document.querySelectorAll('link[rel="canonical"]')) {
|
|
185
|
+
if (extra !== canonicalLink) extra.remove()
|
|
158
186
|
}
|
|
187
|
+
} else {
|
|
188
|
+
for (const existing of document.querySelectorAll('link[rel="canonical"]')) existing.remove()
|
|
159
189
|
}
|
|
160
190
|
|
|
191
|
+
setAlternates(meta.alternates)
|
|
192
|
+
|
|
161
193
|
// Cleanup function - reset to defaults on unmount
|
|
162
194
|
return () => {
|
|
163
195
|
// We don't remove tags on cleanup because another page will set them
|
|
@@ -174,6 +206,9 @@ export function useHeadMeta(meta, options = {}) {
|
|
|
174
206
|
meta?.og?.description,
|
|
175
207
|
meta?.og?.image,
|
|
176
208
|
meta?.og?.url,
|
|
209
|
+
meta?.og?.siteName,
|
|
210
|
+
meta?.twitter?.site,
|
|
211
|
+
JSON.stringify(meta?.alternates || []),
|
|
177
212
|
siteName
|
|
178
213
|
])
|
|
179
214
|
}
|
package/src/load-page-data.js
CHANGED
|
@@ -26,6 +26,7 @@ import DataStore, { deriveCacheKey } from '@uniweb/core/datastore'
|
|
|
26
26
|
import { resolvePage } from './resolve-page.js'
|
|
27
27
|
import { getComponentMeta } from './prepare-props.js'
|
|
28
28
|
import { createDefaultFetcher } from './default-fetcher.js'
|
|
29
|
+
import { requireTransport } from './require-transport.js'
|
|
29
30
|
|
|
30
31
|
/**
|
|
31
32
|
* Every block the page's render can reach: its body and the layout areas it uses (the runtime's own
|
|
@@ -53,7 +54,8 @@ function blocksOf(page) {
|
|
|
53
54
|
* @param {Object} options
|
|
54
55
|
* @param {Object} options.website - the initialized Website the render will use (`initPrerender`)
|
|
55
56
|
* @param {string|Object} options.route - the route, or an already-resolved Page
|
|
56
|
-
* @param {Function} options.fetch - the transport, per request
|
|
57
|
+
* @param {Function} options.fetch - the transport, per request. Required: a missing one is refused,
|
|
58
|
+
* never replaced by the ambient `fetch` (`require-transport.js`)
|
|
57
59
|
* @param {boolean} [options.dev]
|
|
58
60
|
* @param {string|null} [options.locale] - ⛔ NOT a locale to fetch in: the Website is already
|
|
59
61
|
* locale-sliced (`initPrerenderForLocale`) and every address is resolved in ITS locale. Passing
|
|
@@ -74,6 +76,9 @@ export async function loadPageData({ website, route, fetch, dev = false, locale
|
|
|
74
76
|
if (prerender !== 'author' && prerender !== 'always') {
|
|
75
77
|
throw new Error(`loadPageData: prerender must be 'author' or 'always', got ${JSON.stringify(prerender)}`)
|
|
76
78
|
}
|
|
79
|
+
// Before the route is resolved: a lost transport is the caller's wiring, and an unknown route
|
|
80
|
+
// must not hide it.
|
|
81
|
+
requireTransport(fetch, 'loadPageData')
|
|
77
82
|
const active = website?.getActiveLocale?.() ?? null
|
|
78
83
|
if (locale && active && locale !== active) {
|
|
79
84
|
throw new Error(
|
package/src/page-renderer.js
CHANGED
|
@@ -22,17 +22,19 @@
|
|
|
22
22
|
* modelling a deployment it cannot see (the host drew this line itself,
|
|
23
23
|
* 2026-09-03: a serve location is read from the payload, never constructed here).
|
|
24
24
|
* - **Init and hydration.** The two lanes differ REALLY here, not incidentally: a
|
|
25
|
-
* build initializes once
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
* `
|
|
25
|
+
* build initializes once per locale, hydrates its executor's answers up front and
|
|
26
|
+
* then runs the data step for each page over one cache for the whole run; an
|
|
27
|
+
* isolate initializes per locale and runs the step fresh for each request. Folding
|
|
28
|
+
* either in would fit one caller and lie to the other. `initPrerender` /
|
|
29
|
+
* `initPrerenderForLocale` / `loadPageData` / `hydrateDataStore` stay separate
|
|
30
|
+
* exports, and `prefetchAndHydrate` below is the isolate's two-step, not a general one.
|
|
30
31
|
* - **Anything build-only.** `injectBuildData` stays in the build lane; it is the
|
|
31
32
|
* other half of the head seam and has its own parity guard.
|
|
32
33
|
*/
|
|
33
34
|
import { resolvePage, renderPage, classifyRenderError, injectPageContent } from './ssr-renderer.js'
|
|
34
35
|
import { hydrateDataStore } from './wire-foundation.js'
|
|
35
36
|
import { loadPageData } from './load-page-data.js'
|
|
37
|
+
import { requireTransport } from './require-transport.js'
|
|
36
38
|
|
|
37
39
|
/**
|
|
38
40
|
* A renderer bound to one initialized Website and one shell.
|
|
@@ -55,8 +57,8 @@ export function createPageRenderer({ website, shell }) {
|
|
|
55
57
|
* ⭐ Returns an OUTCOME rather than throwing or returning a bare string, because
|
|
56
58
|
* the two callers branch differently on the same three cases and neither wants an
|
|
57
59
|
* exception: a build logs and keeps going so one broken section cannot fail a whole
|
|
58
|
-
* site, an isolate decides a status code and a cache policy. Same reasoning as
|
|
59
|
-
*
|
|
60
|
+
* site, an isolate decides a status code and a cache policy. Same reasoning as the
|
|
61
|
+
* per-entry outcome `loadPageData` returns.
|
|
60
62
|
*
|
|
61
63
|
* @param {string|Object} target - a route (`/blog/1`, resolved through the same
|
|
62
64
|
* matcher the browser uses, so a dynamic route works) or an already-resolved
|
|
@@ -143,13 +145,14 @@ export function createPageRenderer({ website, shell }) {
|
|
|
143
145
|
* on the Website it is handed: the keys each component declares, the layout the page actually
|
|
144
146
|
* draws, and the transport the site chose for a key. `content` is no longer read — the graph
|
|
145
147
|
* carries everything — and stays in the signature for callers that pass it.
|
|
146
|
-
* ⛔ `prefetchPageData
|
|
147
|
-
* every fetch on every level
|
|
148
|
+
* ⛔ `prefetchPageData`, which it replaced, worked from the payload alone and could know none of
|
|
149
|
+
* those: it asked for every fetch on every level and still got four cases wrong. It was removed
|
|
150
|
+
* on 2026-09-20.
|
|
148
151
|
*
|
|
149
|
-
* ⛔ The build lane does NOT call this: it
|
|
150
|
-
*
|
|
151
|
-
* That difference is why this is a named isolate
|
|
152
|
-
* `render`.
|
|
152
|
+
* ⛔ The build lane does NOT call this: it runs `loadPageData` itself for each page, with one
|
|
153
|
+
* cache for the whole run (which this entry does not take) and `prerender: 'author'`, and keeps
|
|
154
|
+
* the entries to embed in that page's HTML. That difference is why this is a named isolate
|
|
155
|
+
* helper and not a step inside `render`.
|
|
153
156
|
*
|
|
154
157
|
* @returns {Promise<Array<{config: Object, outcome: string, data: any, error?: string}>>}
|
|
155
158
|
*/
|
|
@@ -162,33 +165,11 @@ export async function prefetchAndHydrate({ website, content, route, locale = nul
|
|
|
162
165
|
throw new Error('prefetchAndHydrate: `website` must be an initialized Website with a dataStore')
|
|
163
166
|
}
|
|
164
167
|
|
|
165
|
-
// ⛔ THE TRANSPORT IS REQUIRED
|
|
166
|
-
// one
|
|
167
|
-
//
|
|
168
|
-
//
|
|
169
|
-
|
|
170
|
-
// entrypoint's `fetch(Request)` with a JSON body the transport arrives
|
|
171
|
-
// **`undefined`**; passed as an argument to an RPC method it arrives as a callable
|
|
172
|
-
// function and the isolate invokes it. Only the RPC shape carries it.
|
|
173
|
-
//
|
|
174
|
-
// ⚠️ And `undefined` is not where it stops, which is the part their measurement
|
|
175
|
-
// could not see from outside our code. `createDefaultFetcher` resolves the
|
|
176
|
-
// transport as `fetchImpl || globalThis.fetch`, so a transport that failed to cross
|
|
177
|
-
// silently becomes THE ISOLATE'S OWN NETWORK — outside the host's timeout, byte
|
|
178
|
-
// budget and site-relative address resolution. With an absolute address it does not
|
|
179
|
-
// even fail: the request goes out from the wrong place and comes back
|
|
180
|
-
// `outcome: 'fetched'`. A wiring mistake wearing a success.
|
|
181
|
-
//
|
|
182
|
-
// ⇒ So the entry whose only caller crosses that boundary demands a real function
|
|
183
|
-
// rather than defaulting. A Node or browser caller that genuinely wants the global
|
|
184
|
-
// passes `fetch: globalThis.fetch` — one word, and it says so.
|
|
185
|
-
if (typeof fetch !== 'function') {
|
|
186
|
-
throw new Error(
|
|
187
|
-
'prefetchAndHydrate: `fetch` must be a function. A transport does not survive a ' +
|
|
188
|
-
'JSON-serialized isolate boundary — pass it as an RPC method argument. ' +
|
|
189
|
-
'To use the ambient fetch deliberately, pass `fetch: globalThis.fetch`.'
|
|
190
|
-
)
|
|
191
|
-
}
|
|
168
|
+
// ⛔ THE TRANSPORT IS REQUIRED, as on every entry a host calls to have the runtime fetch for
|
|
169
|
+
// it: one that did not cross the isolate boundary is refused here, never replaced by the
|
|
170
|
+
// isolate's own network (`require-transport.js`, which records the measurement). Checked here
|
|
171
|
+
// as well as in `loadPageData` so the message names the entry the caller called.
|
|
172
|
+
requireTransport(fetch, 'prefetchAndHydrate')
|
|
192
173
|
// ⛔ `locale` is forwarded to be CHECKED, not applied: the Website is locale-sliced already, so a
|
|
193
174
|
// caller whose two disagree gets an error rather than a page prerendered in one language with the
|
|
194
175
|
// data of another.
|
package/src/prepare-props.js
CHANGED
|
@@ -133,9 +133,10 @@ export function applyDefaults(params, defaults) {
|
|
|
133
133
|
*
|
|
134
134
|
* A key declared `single: true` then holds one record, or `null` (`holdOneRecord`).
|
|
135
135
|
*
|
|
136
|
-
* A tagged
|
|
137
|
-
* in dev (`warnUndeclaredBlocks`); it stays in `content.sequence`.
|
|
138
|
-
*
|
|
136
|
+
* A tagged block the section holds under a key the component does not declare is left out, and
|
|
137
|
+
* said so in dev (`warnUndeclaredBlocks`); it stays in `content.sequence`. A block inside an item
|
|
138
|
+
* is that item's (`content.items[n].data`), never held here. The data object is rebuilt on each
|
|
139
|
+
* render from those sources, never from the last render's.
|
|
139
140
|
*
|
|
140
141
|
* ⛔ Until 2026-09-14 this merged every delivered key into what the section held, so a
|
|
141
142
|
* section received every fetch that reached it and every tagged block it had.
|
|
@@ -149,7 +150,8 @@ function assembleData(block, meta, entityData) {
|
|
|
149
150
|
else if (entityData && entityData[key] !== undefined) data[key] = entityData[key]
|
|
150
151
|
else data[key] = null
|
|
151
152
|
}
|
|
152
|
-
|
|
153
|
+
// What the section holds and this leaves out — the keys the dev warning reports.
|
|
154
|
+
warnUndeclaredBlocks(block, Object.keys(held).filter((key) => !Object.hasOwn(data, key)))
|
|
153
155
|
block.parsedContent.data = data
|
|
154
156
|
return declared
|
|
155
157
|
}
|
|
@@ -177,26 +179,36 @@ function holdOneRecord(block, declared) {
|
|
|
177
179
|
}
|
|
178
180
|
|
|
179
181
|
/**
|
|
180
|
-
* Say once, in dev, that a tagged block — a data block or a concept block — did
|
|
181
|
-
*
|
|
182
|
-
*
|
|
182
|
+
* Say once, in dev, that a tagged block the section holds — a data block or a concept block — did
|
|
183
|
+
* not reach its component: its key is not declared. `assembleData` passes the keys it left out, so
|
|
184
|
+
* this names exactly what the component did not receive. The block is still in `content.sequence`,
|
|
185
|
+
* where a component that renders the sequence finds it.
|
|
186
|
+
*
|
|
187
|
+
* ⭐ AN ITEM'S BLOCK IS NEVER ONE OF THEM. The parser keeps a block inside an item with that item
|
|
188
|
+
* (`content.items[n].data`), which asks no declaration, and only the section's own blocks reach
|
|
189
|
+
* `block.heldData`. ⛔ Until 2026-10-25 this walked `content.sequence`, which lists every element of
|
|
190
|
+
* the section, its items' included, and so reported each card's `meta` block in a card grid as left
|
|
191
|
+
* out of `content.data`, where it never belonged.
|
|
183
192
|
*/
|
|
184
193
|
const warnedBlocks = new Set()
|
|
185
|
-
function warnUndeclaredBlocks(block,
|
|
186
|
-
if (!block.website?.entityStore?.dev) return
|
|
187
|
-
const
|
|
188
|
-
for (const
|
|
189
|
-
|
|
190
|
-
// both need the key declared. ⛔ Until 2026-09-29 only a data block was reported, and an
|
|
191
|
-
// undeclared concept block left content.data without a word.
|
|
192
|
-
const kind = item?.type === 'dataBlock' ? 'data block' : item?.type === 'concept_block' ? 'concept block' : null
|
|
193
|
-
if (!kind || typeof item.tag !== 'string' || keys.has(item.tag)) continue
|
|
194
|
-
const memo = `${block.type}::${item.tag}`
|
|
194
|
+
function warnUndeclaredBlocks(block, leftOut) {
|
|
195
|
+
if (!leftOut.length || !block.website?.entityStore?.dev) return
|
|
196
|
+
const sequence = block.parsedContent?.sequence || []
|
|
197
|
+
for (const tag of leftOut) {
|
|
198
|
+
const memo = `${block.type}::${tag}`
|
|
195
199
|
if (warnedBlocks.has(memo)) continue
|
|
200
|
+
// The section's own block under the tag is the first in the sequence: the parser cuts the
|
|
201
|
+
// sequence into slices, and the section's own content is the first (`groups.js`). None means
|
|
202
|
+
// the key holds a fetch's prerendered answer, which is not a block.
|
|
203
|
+
// A data block (```yaml:tag) and a concept block (```md:tag) both land under their tag, so
|
|
204
|
+
// both need the key declared. ⛔ Until 2026-09-29 only a data block was reported.
|
|
205
|
+
const element = sequence.find((e) => (e?.type === 'dataBlock' || e?.type === 'concept_block') && e.tag === tag)
|
|
206
|
+
if (!element) continue
|
|
196
207
|
warnedBlocks.add(memo)
|
|
208
|
+
const kind = element.type === 'dataBlock' ? 'data block' : 'concept block'
|
|
197
209
|
console.warn(
|
|
198
|
-
`[uniweb] ${block.type}: the \`${
|
|
199
|
-
`declare \`${
|
|
210
|
+
`[uniweb] ${block.type}: the \`${tag}\` ${kind} is not in content.data — ${block.type} does not ` +
|
|
211
|
+
`declare \`${tag}\` in its meta.js \`data:\`. It stays in content.sequence.`
|
|
200
212
|
)
|
|
201
213
|
}
|
|
202
214
|
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* THE HOST'S TRANSPORT, REQUIRED — on every entry a host calls to have the runtime fetch on its
|
|
3
|
+
* behalf: `loadPageData`, `prefetchAndHydrate` and `collectSiteRecords`.
|
|
4
|
+
*
|
|
5
|
+
* ⛔ The failure this prevents is not "no transport". `createDefaultFetcher` resolves its transport
|
|
6
|
+
* as `fetchImpl || globalThis.fetch`, so a missing one becomes the ambient network — in an isolate,
|
|
7
|
+
* the isolate's own, outside the host's timeout and byte budget and outside its resolution of
|
|
8
|
+
* site-relative addresses. With an absolute address the request does not even fail: it leaves from
|
|
9
|
+
* the wrong place and comes back `fetched`. A wiring mistake wearing a success.
|
|
10
|
+
*
|
|
11
|
+
* Reachable, not hypothetical: a host measured against a real isolate loader (2026-09-03) that a
|
|
12
|
+
* function passed through an entrypoint's `fetch(Request)` with a JSON body arrives `undefined`,
|
|
13
|
+
* and survives only as an RPC method argument.
|
|
14
|
+
*
|
|
15
|
+
* ⛔ Until 2026-10-25 only `prefetchAndHydrate` refused one, while the entries a host's isolate
|
|
16
|
+
* calls — the data step and the corpus entry — fell back to the ambient network silently.
|
|
17
|
+
*
|
|
18
|
+
* The default stays where it is right: in the browser, which has one network, and in a harness
|
|
19
|
+
* that builds a default fetcher itself. A caller of these entries that wants the ambient fetch
|
|
20
|
+
* passes `fetch: globalThis.fetch`, which is one word and says so.
|
|
21
|
+
*
|
|
22
|
+
* Zero-dependency leaf, so the data step's graph stays renderer-free (`tests/data-step-graph.test.js`).
|
|
23
|
+
*
|
|
24
|
+
* @param {*} fetch - what the caller passed as `fetch`
|
|
25
|
+
* @param {string} entry - the entry's name, for the message
|
|
26
|
+
* @returns {Function} the transport
|
|
27
|
+
*/
|
|
28
|
+
export function requireTransport(fetch, entry) {
|
|
29
|
+
if (typeof fetch === 'function') return fetch
|
|
30
|
+
throw new Error(
|
|
31
|
+
`${entry}: \`fetch\` must be a function. A transport does not survive a ` +
|
|
32
|
+
'JSON-serialized isolate boundary — pass it as an RPC method argument. ' +
|
|
33
|
+
'To use the ambient fetch deliberately, pass `fetch: globalThis.fetch`.'
|
|
34
|
+
)
|
|
35
|
+
}
|
package/src/setup.js
CHANGED
|
@@ -291,7 +291,8 @@ export function initUniweb({ content, foundation, extensions = [], routingCompon
|
|
|
291
291
|
|
|
292
292
|
uniweb.childBlockRenderer = ChildBlocks
|
|
293
293
|
// The same background a section gets, for kit's `SectionBackground` — a component that
|
|
294
|
-
// paints its own (`background: 'self'`). The
|
|
294
|
+
// paints its own (`background: 'self'`). The prerender sets the same one (`ssr-renderer.js`):
|
|
295
|
+
// one implementation (`background.js`), of which this is the browser's, which plays a video.
|
|
295
296
|
uniweb.backgroundRenderer = Background
|
|
296
297
|
|
|
297
298
|
// L2 (singleton wiring): defaultInsets, xref.build(), and any future
|