@uniweb/runtime 1.2.10 → 1.2.12
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/package.json +4 -4
- package/src/hooks/useLinkInterceptor.js +2 -1
- package/src/link-intent.js +3 -3
- package/src/navigation.js +36 -12
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@uniweb/runtime",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.12",
|
|
4
4
|
"description": "Minimal runtime for loading Uniweb foundations",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -36,15 +36,15 @@
|
|
|
36
36
|
"node": ">=22.12"
|
|
37
37
|
},
|
|
38
38
|
"dependencies": {
|
|
39
|
-
"@uniweb/
|
|
40
|
-
"@uniweb/
|
|
39
|
+
"@uniweb/core": "~1.4.12",
|
|
40
|
+
"@uniweb/theming": "~1.2.1"
|
|
41
41
|
},
|
|
42
42
|
"devDependencies": {
|
|
43
43
|
"@vitejs/plugin-react": "^4.5.2",
|
|
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.10.
|
|
47
|
+
"@uniweb/build": "1.10.11"
|
|
48
48
|
},
|
|
49
49
|
"peerDependencies": {
|
|
50
50
|
"react": "^19.0.0",
|
|
@@ -110,7 +110,8 @@ export function inAppLink(anchor) {
|
|
|
110
110
|
|
|
111
111
|
/**
|
|
112
112
|
* Whether a page of `website` can have something to fetch before it is shown: content not here
|
|
113
|
-
* yet, on a site whose content is split, or data
|
|
113
|
+
* yet, on a site whose content is split, or data a page declares where it can be seen
|
|
114
|
+
* (`readsData`).
|
|
114
115
|
*/
|
|
115
116
|
function hasPagesToFetch(website) {
|
|
116
117
|
if (readsData(website)) return true
|
package/src/link-intent.js
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
* Prefetch on intent: a link's page is fetched while the reader is about to follow it.
|
|
3
3
|
*
|
|
4
4
|
* Only a site with something to fetch before a page is shown loads this module
|
|
5
|
-
* (`useLinkInterceptor`): split content not here yet, or data
|
|
6
|
-
*
|
|
7
|
-
* the decision.
|
|
5
|
+
* (`useLinkInterceptor`): split content not here yet, or data a page declares where it can be seen
|
|
6
|
+
* (`readsData`, `navigation.js`). A site with neither never downloads it, so the runtime every site
|
|
7
|
+
* carries pays only for the decision.
|
|
8
8
|
*
|
|
9
9
|
* A pointer resting on a link, or focus staying on one, for INTENT_DELAY fetches what the page
|
|
10
10
|
* the link goes to will read: its split content, then its sections' data, whatever is not held
|
package/src/navigation.js
CHANGED
|
@@ -80,29 +80,47 @@ function isReady(page) {
|
|
|
80
80
|
}
|
|
81
81
|
|
|
82
82
|
/**
|
|
83
|
-
* Whether
|
|
84
|
-
*
|
|
83
|
+
* Whether a fetch can be seen reaching `page` without building its sections: the site declares
|
|
84
|
+
* queries (`config.queries`), or a fetch sits on the page, its parent page or the site, or the page
|
|
85
|
+
* is parametric and asks for its own record. A section's own `fetch:` is read only once its block is
|
|
86
|
+
* built, so a page whose data is declared on its sections alone is not seen here.
|
|
87
|
+
*
|
|
88
|
+
* ⛔ `config.queries` alone is not the test. A hosted site's schema-less query is not projected
|
|
89
|
+
* there: its fetch carries the file's `path` (measured 2026-10-27 on a site's blog: no
|
|
90
|
+
* `config.queries`, the page's fetch `{ query, path, as }`). A gate on it skipped that blog's data.
|
|
91
|
+
*
|
|
92
|
+
* @param {Object} page
|
|
93
|
+
* @returns {boolean}
|
|
94
|
+
*/
|
|
95
|
+
function declaresData(page) {
|
|
96
|
+
const config = page.website?.config
|
|
97
|
+
const queries = config?.queries
|
|
98
|
+
if (queries && typeof queries === 'object' && Object.keys(queries).length > 0) return true
|
|
99
|
+
return !!(page.fetch || page.parent?.fetch || config?.fetch || page.dynamicContext)
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Whether a page of `website` declares data where it can be seen without building sections (see
|
|
104
|
+
* `declaresData`): what decides whether prefetching on intent is worth its module.
|
|
85
105
|
*
|
|
86
106
|
* @param {Object|null|undefined} website
|
|
87
107
|
* @returns {boolean}
|
|
88
108
|
*/
|
|
89
109
|
export function readsData(website) {
|
|
90
|
-
|
|
91
|
-
return !!queries && typeof queries === 'object' && Object.keys(queries).length > 0
|
|
110
|
+
return !!website?.pages?.some((page) => declaresData(page))
|
|
92
111
|
}
|
|
93
112
|
|
|
94
113
|
/**
|
|
95
114
|
* Ask what `page`'s render will read and the cache does not hold: off a parametric page, the
|
|
96
115
|
* page's own record, which names it; and each block's data, by the same call the renderer makes
|
|
97
|
-
* after mount (`EntityStore.fetch`).
|
|
116
|
+
* after mount (`EntityStore.fetch`). It builds the page's blocks, which its render is about to do.
|
|
98
117
|
*
|
|
99
118
|
* @param {Object} page - its content here
|
|
100
119
|
* @returns {Promise|undefined} undefined when nothing was asked
|
|
101
120
|
*/
|
|
102
121
|
function dataOf(page) {
|
|
103
|
-
const
|
|
104
|
-
|
|
105
|
-
if (!store || !readsData(website)) return
|
|
122
|
+
const store = page.website?.entityStore
|
|
123
|
+
if (!store) return
|
|
106
124
|
const asks = []
|
|
107
125
|
if (page.dynamicContext) asks.push(store.fetchPageRecord(page))
|
|
108
126
|
for (const block of blocksOf(page)) {
|
|
@@ -116,13 +134,19 @@ function dataOf(page) {
|
|
|
116
134
|
* The wait for what `page`'s render reads and is not here yet: its split content, then, its
|
|
117
135
|
* sections known, their data.
|
|
118
136
|
*
|
|
137
|
+
* ⚖️ A navigation asks for the data of every page it goes to, since building the blocks is work its
|
|
138
|
+
* render does a moment later. A prefetch is a guess the reader may not follow, so it asks only where
|
|
139
|
+
* data can be seen reaching the page (`declaresData`), and leaves sections unbuilt elsewhere.
|
|
140
|
+
*
|
|
119
141
|
* @param {Object|null} page
|
|
142
|
+
* @param {{ speculative?: boolean }} [options]
|
|
120
143
|
* @returns {Promise|undefined} undefined when it is all here
|
|
121
144
|
*/
|
|
122
|
-
function loadPage(page) {
|
|
145
|
+
function loadPage(page, { speculative = false } = {}) {
|
|
123
146
|
if (!page) return
|
|
124
|
-
|
|
125
|
-
|
|
147
|
+
const data = () => (speculative && !declaresData(page) ? undefined : dataOf(page))
|
|
148
|
+
if (!isReady(page)) return page.loadContent().then(data)
|
|
149
|
+
return data()
|
|
126
150
|
}
|
|
127
151
|
|
|
128
152
|
/** `wait`, or the end of HOLD_TIMEOUT, whichever comes first. */
|
|
@@ -152,7 +176,7 @@ export function prefetchContent(route) {
|
|
|
152
176
|
* @returns {Promise|undefined} undefined when it is all here
|
|
153
177
|
*/
|
|
154
178
|
export function prefetchPage(route) {
|
|
155
|
-
return loadPage(pageAt(route))?.catch(() => {})
|
|
179
|
+
return loadPage(pageAt(route), { speculative: true })?.catch(() => {})
|
|
156
180
|
}
|
|
157
181
|
|
|
158
182
|
// The navigation the next swap belongs to. A later navigation supersedes one still waiting for
|