@rsc-kit/core 0.16.1 → 0.16.3
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/apiPrerender.d.ts +2 -0
- package/dist/apiPrerender.js +111 -81
- package/dist/apiPrerender.js.map +1 -1
- package/dist/appAssets.d.ts +4 -1
- package/dist/appAssets.js +55 -19
- package/dist/appAssets.js.map +1 -1
- package/dist/buildReport.d.ts +2 -0
- package/dist/buildReport.js.map +1 -1
- package/dist/files.js +29 -5
- package/dist/files.js.map +1 -1
- package/dist/host.d.ts +11 -0
- package/dist/host.js +61 -5
- package/dist/host.js.map +1 -1
- package/dist/js/createViteRscApp.js +57 -11
- package/dist/js/createViteRscApp.js.map +1 -1
- package/dist/js/devNotice.d.ts +13 -0
- package/dist/js/devNotice.js +45 -0
- package/dist/js/devNotice.js.map +1 -0
- package/dist/js/errors.js +4 -0
- package/dist/js/errors.js.map +1 -1
- package/dist/js/navigate.js +23 -0
- package/dist/js/navigate.js.map +1 -1
- package/dist/js/staleAssets.d.ts +17 -0
- package/dist/js/staleAssets.js +39 -0
- package/dist/js/staleAssets.js.map +1 -0
- package/dist/js/useSearchParams.d.ts +6 -0
- package/dist/js/useSearchParams.js +16 -3
- package/dist/js/useSearchParams.js.map +1 -1
- package/dist/prerender.d.ts +2 -2
- package/dist/prerender.js +190 -124
- package/dist/prerender.js.map +1 -1
- package/dist/request.d.ts +3 -1
- package/dist/request.js +131 -45
- package/dist/request.js.map +1 -1
- package/dist/routing.d.ts +0 -7
- package/dist/routing.js +54 -20
- package/dist/routing.js.map +1 -1
- package/dist/vite.d.ts +13 -12
- package/dist/vite.js +554 -324
- package/dist/vite.js.map +1 -1
- package/package.json +2 -2
package/dist/prerender.js
CHANGED
|
@@ -14,10 +14,10 @@
|
|
|
14
14
|
//
|
|
15
15
|
// The file layout matches the one Laravel writes, so both hosts serve the same
|
|
16
16
|
// shapes and anything that reads them works for either.
|
|
17
|
-
import { withRedirect } from
|
|
18
|
-
import { withCache } from
|
|
19
|
-
import { requestReadBy, requestWasRead, withRequest } from
|
|
20
|
-
import { watchNondeterminism, whileRendering } from
|
|
17
|
+
import { withRedirect } from "./redirect.js";
|
|
18
|
+
import { withCache } from "./cache.js";
|
|
19
|
+
import { requestReadBy, requestReadWhere, requestWasRead, withRequest, } from "./request.js";
|
|
20
|
+
import { watchNondeterminism, whileRendering } from "./nondeterminism.js";
|
|
21
21
|
/** What a prerenderer needs from the built bundle, beyond serving a request. */
|
|
22
22
|
/**
|
|
23
23
|
* How long the root-fallback diagnostic waits before answering.
|
|
@@ -49,11 +49,11 @@ const DEFAULT_PRERENDER_CONCURRENCY = 4;
|
|
|
49
49
|
* else in the list is the app's, and needs the runtime.
|
|
50
50
|
*/
|
|
51
51
|
const RUNTIME_OWN = new Set([
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
52
|
+
"DocumentTitle",
|
|
53
|
+
"PathnameProvider",
|
|
54
|
+
"RouteErrorBoundary",
|
|
55
|
+
"SegmentBoundary",
|
|
56
|
+
"SlotBoundary",
|
|
57
57
|
]);
|
|
58
58
|
/**
|
|
59
59
|
* The one line the bootstrap would have run for the worker, for a document
|
|
@@ -75,8 +75,10 @@ export function withInlineStylesheets(html, read) {
|
|
|
75
75
|
});
|
|
76
76
|
}
|
|
77
77
|
export function withWorkerRegistration(html) {
|
|
78
|
-
const at = html.lastIndexOf(
|
|
79
|
-
return at === -1
|
|
78
|
+
const at = html.lastIndexOf("</body>");
|
|
79
|
+
return at === -1
|
|
80
|
+
? html
|
|
81
|
+
: html.slice(0, at) + WORKER_REGISTRATION + html.slice(at);
|
|
80
82
|
}
|
|
81
83
|
/**
|
|
82
84
|
* The build's refusal to store a route it cannot answer ahead of time.
|
|
@@ -91,33 +93,35 @@ export class NotPrerenderable extends Error {
|
|
|
91
93
|
// Two different problems arrive here, and the advice for one is useless for
|
|
92
94
|
// the other. A page that reads the request above every boundary needs a
|
|
93
95
|
// boundary; a page whose render threw needs whatever it was reaching for.
|
|
94
|
-
const threw = routes.filter((r) => r.reason?.startsWith(
|
|
95
|
-
const unpaintable = routes.filter((r) => !r.reason?.startsWith(
|
|
96
|
+
const threw = routes.filter((r) => r.reason?.startsWith("could not be rendered"));
|
|
97
|
+
const unpaintable = routes.filter((r) => !r.reason?.startsWith("could not be rendered"));
|
|
96
98
|
const advice = [];
|
|
97
99
|
if (unpaintable.length > 0) {
|
|
98
|
-
advice.push(
|
|
99
|
-
|
|
100
|
-
unpaintable
|
|
101
|
-
|
|
102
|
-
|
|
100
|
+
advice.push("These read request data — params, headers, cookies, or the host — above\n" +
|
|
101
|
+
"every Suspense boundary, so nothing can paint without it:\n\n" +
|
|
102
|
+
unpaintable
|
|
103
|
+
.map((r) => ` ${r.url} — ${r.reason ?? "nothing to paint"}`)
|
|
104
|
+
.join("\n") +
|
|
105
|
+
"\n\nPut the part that waits inside <Suspense>, or add a loading.tsx beside\n" +
|
|
106
|
+
"the page, so there is something to store while the rest arrives.");
|
|
103
107
|
}
|
|
104
108
|
if (threw.length > 0) {
|
|
105
|
-
advice.push(
|
|
106
|
-
threw.map((r) => ` ${r.url} — ${r.reason}`).join(
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
109
|
+
advice.push("These failed while rendering:\n\n" +
|
|
110
|
+
threw.map((r) => ` ${r.url} — ${r.reason}`).join("\n") +
|
|
111
|
+
"\n\nPrerendering runs your application code, so it needs whatever that code\n" +
|
|
112
|
+
"needs. If the page is fine and this machine simply cannot reach a\n" +
|
|
113
|
+
"database or an API, either give the build access or put `await connection()`\n" +
|
|
114
|
+
"before the read: the page then renders per request and the rest is still stored.\n\n" +
|
|
115
|
+
"Reaching for data through the host — `await rpc(...)` — avoids this\n" +
|
|
116
|
+
"entirely: the build stubs that call, so the page freezes a shell\n" +
|
|
117
|
+
"without the data being available.");
|
|
114
118
|
}
|
|
115
|
-
super(
|
|
116
|
-
this.name =
|
|
119
|
+
super("Some routes could not be prerendered.\n\n" + advice.join("\n\n") + "\n");
|
|
120
|
+
this.name = "NotPrerenderable";
|
|
117
121
|
this.routes = routes;
|
|
118
122
|
// Not enumerable: Vite prints an error's own properties under its stack,
|
|
119
123
|
// and this list is the message again, as an object dump.
|
|
120
|
-
Object.defineProperty(this,
|
|
124
|
+
Object.defineProperty(this, "routes", { value: routes, enumerable: false });
|
|
121
125
|
}
|
|
122
126
|
}
|
|
123
127
|
/**
|
|
@@ -129,16 +133,16 @@ export class NotPrerenderable extends Error {
|
|
|
129
133
|
* route matches, including the ones the app never listed.
|
|
130
134
|
*/
|
|
131
135
|
export function patternKey(route) {
|
|
132
|
-
const parts = route.segments.map((s) =>
|
|
133
|
-
return parts.join(
|
|
136
|
+
const parts = route.segments.map((s) => s.type === "static" ? s.value : `_${s.value}_`);
|
|
137
|
+
return parts.join("/") || "index";
|
|
134
138
|
}
|
|
135
139
|
/** Close a document the render was aborted in the middle of. */
|
|
136
140
|
export function closeDocument(html) {
|
|
137
141
|
let out = html;
|
|
138
142
|
if (!/<\/body>/i.test(out) && /<body/i.test(out))
|
|
139
|
-
out +=
|
|
143
|
+
out += "</body>";
|
|
140
144
|
if (!/<\/html>/i.test(out) && /<html/i.test(out))
|
|
141
|
-
out +=
|
|
145
|
+
out += "</html>";
|
|
142
146
|
return out;
|
|
143
147
|
}
|
|
144
148
|
/**
|
|
@@ -163,19 +167,19 @@ export function closeDocument(html) {
|
|
|
163
167
|
export function legend(results) {
|
|
164
168
|
const has = (type) => results.some((r) => r.type === type);
|
|
165
169
|
const lines = [];
|
|
166
|
-
if (has(
|
|
167
|
-
lines.push(
|
|
170
|
+
if (has("frozen")) {
|
|
171
|
+
lines.push(" \u25CB (Static) prerendered as static content");
|
|
168
172
|
}
|
|
169
|
-
if (has(
|
|
170
|
-
lines.push(
|
|
173
|
+
if (has("shell")) {
|
|
174
|
+
lines.push(" \u25D0 (Partial Prerender) prerendered as static HTML with dynamic server-streamed content");
|
|
171
175
|
}
|
|
172
|
-
if (has(
|
|
173
|
-
lines.push(
|
|
176
|
+
if (has("dynamic")) {
|
|
177
|
+
lines.push(" \u0192 (Dynamic) server-rendered on demand");
|
|
174
178
|
}
|
|
175
|
-
if (has(
|
|
176
|
-
lines.push(
|
|
179
|
+
if (has("error") || has("blocked")) {
|
|
180
|
+
lines.push(" \u2717 (Failed) did not render, or painted nothing — the build stops here");
|
|
177
181
|
}
|
|
178
|
-
return lines.join(
|
|
182
|
+
return lines.join("\n");
|
|
179
183
|
}
|
|
180
184
|
/**
|
|
181
185
|
* How much JavaScript a route makes the browser download.
|
|
@@ -190,7 +194,7 @@ export function legend(results) {
|
|
|
190
194
|
*/
|
|
191
195
|
export function clientJsSize(bytes) {
|
|
192
196
|
if (bytes === 0)
|
|
193
|
-
return
|
|
197
|
+
return "no js";
|
|
194
198
|
const kb = bytes / 1000;
|
|
195
199
|
return `${kb < 10 ? kb.toFixed(1) : Math.round(kb)} kB`;
|
|
196
200
|
}
|
|
@@ -213,27 +217,29 @@ export function notes(results) {
|
|
|
213
217
|
// root layout leaves: one cookies() in a header component and nothing on
|
|
214
218
|
// the site can be stored. The build cannot see which component read it -
|
|
215
219
|
// only that every page did - so it says what that usually means.
|
|
216
|
-
const pages = results.filter((r) => r.type && r.type !==
|
|
217
|
-
const reasons = new Set(pages.map((r) => r.reason ??
|
|
218
|
-
if (pages.length > 1 &&
|
|
220
|
+
const pages = results.filter((r) => r.type && r.type !== "error" && r.type !== "blocked");
|
|
221
|
+
const reasons = new Set(pages.map((r) => r.reason ?? ""));
|
|
222
|
+
if (pages.length > 1 &&
|
|
223
|
+
!pages.some((r) => r.type === "frozen") &&
|
|
224
|
+
reasons.size === 1) {
|
|
219
225
|
const [reason] = reasons;
|
|
220
226
|
parts.push(` Every route ${reason}. When one read makes every page dynamic it is usually\n` +
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
227
|
+
" in the root layout - a header reading the session, a locale from a cookie.\n" +
|
|
228
|
+
" Move that read into the component that needs it, under a <Suspense>, and\n" +
|
|
229
|
+
" the rest of the site can freeze around it.");
|
|
224
230
|
}
|
|
225
|
-
if (said(
|
|
231
|
+
if (said("rpc("))
|
|
226
232
|
parts.push(rpcNote());
|
|
227
|
-
return parts.join(
|
|
233
|
+
return parts.join("\n\n");
|
|
228
234
|
}
|
|
229
235
|
function rpcNote() {
|
|
230
|
-
return (
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
236
|
+
return (" A build has no backend to call. rpc() suspends instead of answering, so a page\n" +
|
|
237
|
+
" that reads through one ships a shell and finishes for whoever asks — which is\n" +
|
|
238
|
+
" almost always what you want, since that data is rarely the same for everyone.\n" +
|
|
239
|
+
"\n" +
|
|
240
|
+
" If a page really should be frozen, move the read out of rpc(). If it really\n" +
|
|
241
|
+
" should be per visitor, say so with await connection() and the intent is on the\n" +
|
|
242
|
+
" page rather than inferred from a call that happened to suspend.");
|
|
237
243
|
}
|
|
238
244
|
/**
|
|
239
245
|
* The one-line tally under the legend, in the legend's own words.
|
|
@@ -241,20 +247,22 @@ function rpcNote() {
|
|
|
241
247
|
export function summary(results) {
|
|
242
248
|
const count = (type) => results.filter((r) => r.type === type).length;
|
|
243
249
|
const parts = [];
|
|
244
|
-
if (count(
|
|
245
|
-
parts.push(`${count(
|
|
246
|
-
if (count(
|
|
247
|
-
parts.push(`${count(
|
|
248
|
-
if (count(
|
|
249
|
-
parts.push(`${count(
|
|
250
|
-
if (count(
|
|
251
|
-
parts.push(`${count(
|
|
252
|
-
return parts.join(
|
|
250
|
+
if (count("frozen"))
|
|
251
|
+
parts.push(`${count("frozen")} static`);
|
|
252
|
+
if (count("shell"))
|
|
253
|
+
parts.push(`${count("shell")} partial prerender`);
|
|
254
|
+
if (count("dynamic"))
|
|
255
|
+
parts.push(`${count("dynamic")} dynamic`);
|
|
256
|
+
if (count("error") + count("blocked"))
|
|
257
|
+
parts.push(`${count("error") + count("blocked")} failed`);
|
|
258
|
+
return parts.join(", ") || "nothing to store";
|
|
253
259
|
}
|
|
254
260
|
export function pathKey(url) {
|
|
255
|
-
const key = url.replace(/^\/+|\/+$/g,
|
|
256
|
-
if (key.split(
|
|
257
|
-
throw new Error(
|
|
261
|
+
const key = url.replace(/^\/+|\/+$/g, "") || "index";
|
|
262
|
+
if (key.split("/").some((segment) => segment === ".." || segment === ".")) {
|
|
263
|
+
throw new Error("Refusing to store " +
|
|
264
|
+
JSON.stringify(url) +
|
|
265
|
+
": the path leaves the output directory. " +
|
|
258
266
|
'A url segment came from generateStaticParams() or a route param and contains "..".');
|
|
259
267
|
}
|
|
260
268
|
return key;
|
|
@@ -268,16 +276,20 @@ export function pathKey(url) {
|
|
|
268
276
|
*/
|
|
269
277
|
export function urlFor(route, params) {
|
|
270
278
|
const parts = route.segments.map((segment) => {
|
|
271
|
-
if (segment.type ===
|
|
279
|
+
if (segment.type === "static")
|
|
272
280
|
return segment.value;
|
|
273
|
-
const value = params[segment.value] ??
|
|
274
|
-
if (segment.type !==
|
|
275
|
-
throw new Error(
|
|
276
|
-
|
|
281
|
+
const value = params[segment.value] ?? "";
|
|
282
|
+
if (segment.type !== "catchAll" && /[/\\]/.test(value)) {
|
|
283
|
+
throw new Error("The " +
|
|
284
|
+
segment.value +
|
|
285
|
+
" param is " +
|
|
286
|
+
JSON.stringify(value) +
|
|
287
|
+
", which is not one url " +
|
|
288
|
+
"segment. A value with a slash in it has to be a catch-all route, or be encoded.");
|
|
277
289
|
}
|
|
278
290
|
return value;
|
|
279
291
|
});
|
|
280
|
-
return
|
|
292
|
+
return "/" + parts.filter((p) => p !== "").join("/");
|
|
281
293
|
}
|
|
282
294
|
/**
|
|
283
295
|
* Every url this build should attempt, and the params each was built from.
|
|
@@ -290,15 +302,15 @@ export function urlFor(route, params) {
|
|
|
290
302
|
function placeholders(route) {
|
|
291
303
|
const params = {};
|
|
292
304
|
for (const segment of route.segments) {
|
|
293
|
-
if (segment.type !==
|
|
294
|
-
params[segment.value] =
|
|
305
|
+
if (segment.type !== "static")
|
|
306
|
+
params[segment.value] = "_";
|
|
295
307
|
}
|
|
296
308
|
return params;
|
|
297
309
|
}
|
|
298
310
|
export async function urlsToBuild(manifest, engine) {
|
|
299
311
|
const entries = [];
|
|
300
312
|
for (const route of manifest.routes) {
|
|
301
|
-
const parameterised = route.segments.some((s) => s.type !==
|
|
313
|
+
const parameterised = route.segments.some((s) => s.type !== "static");
|
|
302
314
|
if (!parameterised) {
|
|
303
315
|
entries.push({ route, params: {}, url: urlFor(route, {}) });
|
|
304
316
|
continue;
|
|
@@ -306,7 +318,11 @@ export async function urlsToBuild(manifest, engine) {
|
|
|
306
318
|
// A route that lists no urls still gets one attempt, with placeholder
|
|
307
319
|
// params, so it can ship a shell. Nothing in a shell varies by param.
|
|
308
320
|
if (!route.staticParams) {
|
|
309
|
-
entries.push({
|
|
321
|
+
entries.push({
|
|
322
|
+
route,
|
|
323
|
+
params: placeholders(route),
|
|
324
|
+
url: urlFor(route, placeholders(route)),
|
|
325
|
+
});
|
|
310
326
|
continue;
|
|
311
327
|
}
|
|
312
328
|
const sets = (await engine.getStaticParams?.(route.component)) ?? null;
|
|
@@ -325,7 +341,7 @@ export async function prerender(options) {
|
|
|
325
341
|
const { engine, write, version } = options;
|
|
326
342
|
const manifest = options.manifest ?? engine.manifest?.();
|
|
327
343
|
if (!manifest) {
|
|
328
|
-
throw new Error(
|
|
344
|
+
throw new Error("No route table. Pass `manifest`, or build with a plugin version that embeds one.");
|
|
329
345
|
}
|
|
330
346
|
const entries = await urlsToBuild(manifest, engine);
|
|
331
347
|
const results = new Array(entries.length);
|
|
@@ -362,15 +378,19 @@ export async function prerender(options) {
|
|
|
362
378
|
finally {
|
|
363
379
|
unwatch();
|
|
364
380
|
}
|
|
365
|
-
const refused = results.filter((r) => r.type ===
|
|
381
|
+
const refused = results.filter((r) => r.type === "blocked");
|
|
366
382
|
if (refused.length > 0) {
|
|
367
383
|
throw new NotPrerenderable(refused);
|
|
368
384
|
}
|
|
369
385
|
return results;
|
|
370
386
|
async function prerenderOne(route, params, url) {
|
|
371
387
|
const props = options.props ? options.props(route, params) : params;
|
|
372
|
-
const layouts = route.layouts.map((component) => ({
|
|
373
|
-
|
|
388
|
+
const layouts = route.layouts.map((component) => ({
|
|
389
|
+
component,
|
|
390
|
+
props: {},
|
|
391
|
+
}));
|
|
392
|
+
const unlistedNow = route.segments.some((seg) => seg.type !== "static") &&
|
|
393
|
+
!route.staticParams;
|
|
374
394
|
/**
|
|
375
395
|
* Why this route ships a shell rather than a whole page.
|
|
376
396
|
*
|
|
@@ -380,26 +400,27 @@ export async function prerender(options) {
|
|
|
380
400
|
*/
|
|
381
401
|
const shellReason = (calls) => {
|
|
382
402
|
if (calls.length)
|
|
383
|
-
return
|
|
403
|
+
return "dynamic — called " + calls.join(", ");
|
|
384
404
|
// One shell serving every url the route matches. generateStaticParams is
|
|
385
405
|
// what turns it into a page per url.
|
|
386
406
|
if (unlistedNow)
|
|
387
|
-
return
|
|
407
|
+
return "one shell for every url — add generateStaticParams to store each";
|
|
388
408
|
// Nothing reached for the request; the data simply took too long. The
|
|
389
409
|
// page is fine, it just finishes per visitor.
|
|
390
410
|
if (shell?.timedOut)
|
|
391
|
-
return
|
|
411
|
+
return "data took longer than the build budget";
|
|
392
412
|
return null;
|
|
393
413
|
};
|
|
394
414
|
const said = (type, reason) => ({
|
|
395
415
|
// A route standing in for many urls reports the pattern. Reporting the
|
|
396
416
|
// placeholder url instead prints `/posts/_`, which looks like a page.
|
|
397
|
-
url: unlistedNow ?
|
|
417
|
+
url: unlistedNow ? "/" + patternKey(route) : url,
|
|
398
418
|
component: route.component,
|
|
399
419
|
type,
|
|
400
420
|
reason,
|
|
401
421
|
});
|
|
402
|
-
const unlisted = route.segments.some((seg) => seg.type !==
|
|
422
|
+
const unlisted = route.segments.some((seg) => seg.type !== "static") &&
|
|
423
|
+
!route.staticParams;
|
|
403
424
|
// Classify by rendering, never by asking. The probe is cheap and cannot
|
|
404
425
|
// hang: anything still suspended when its budget expires is the answer.
|
|
405
426
|
//
|
|
@@ -413,19 +434,22 @@ export async function prerender(options) {
|
|
|
413
434
|
// must not share an answer just because they were built in the same run.
|
|
414
435
|
// No request, deliberately: a page that reads one is caught below rather
|
|
415
436
|
// than frozen holding whatever the build machine happened to send.
|
|
416
|
-
const [{ shell, redirected, readRequest, readBy }, nondeterministic] = await whileRendering(() => withRequest(null, () => withCache(() => withRedirect(async (taken) => {
|
|
437
|
+
const [{ shell, redirected, readRequest, readBy, readWhere }, nondeterministic,] = await whileRendering(() => withRequest(null, () => withCache(() => withRedirect(async (taken) => {
|
|
417
438
|
try {
|
|
418
439
|
return {
|
|
419
440
|
shell: await engine.handleRscPprShell(route.component, props, layouts, route.loadings, route.slots,
|
|
420
441
|
// Only when this shell serves one url. A parameterised route's
|
|
421
442
|
// shell is shared, so baking a url into it would put the wrong one
|
|
422
443
|
// on every page but the one that happened to be built.
|
|
423
|
-
unlistedNow ?
|
|
444
|
+
unlistedNow ? "" : url),
|
|
424
445
|
redirected: taken(),
|
|
425
446
|
readRequest: requestWasRead(),
|
|
426
447
|
// What reached for it, so the build can name the call rather than
|
|
427
448
|
// only report that the page is dynamic.
|
|
428
449
|
readBy: requestReadBy(),
|
|
450
|
+
// And from which component, so the message is a line to open
|
|
451
|
+
// rather than a category to search for.
|
|
452
|
+
readWhere: requestReadWhere(),
|
|
429
453
|
};
|
|
430
454
|
}
|
|
431
455
|
catch (error) {
|
|
@@ -436,7 +460,13 @@ export async function prerender(options) {
|
|
|
436
460
|
const refused = taken();
|
|
437
461
|
if (!refused)
|
|
438
462
|
throw error;
|
|
439
|
-
return {
|
|
463
|
+
return {
|
|
464
|
+
shell: null,
|
|
465
|
+
redirected: refused,
|
|
466
|
+
readRequest: requestWasRead(),
|
|
467
|
+
readBy: requestReadBy(),
|
|
468
|
+
readWhere: requestReadWhere(),
|
|
469
|
+
};
|
|
440
470
|
}
|
|
441
471
|
}))));
|
|
442
472
|
// A page that leaves rather than renders is not a build failure, and it is
|
|
@@ -464,9 +494,21 @@ export async function prerender(options) {
|
|
|
464
494
|
* the prerender.
|
|
465
495
|
*/
|
|
466
496
|
async function withRootFallbackChecked(result) {
|
|
467
|
-
if (result.type !==
|
|
497
|
+
if (result.type !== "shell")
|
|
468
498
|
return result;
|
|
469
|
-
if (
|
|
499
|
+
if (!engine.handleRscPprShell)
|
|
500
|
+
return result;
|
|
501
|
+
// Only when the one fallback in the chain is the root layout's. A page
|
|
502
|
+
// with its own loading.tsx beside it has put the boundary exactly where
|
|
503
|
+
// the waiting is, and this used to tell it otherwise - "exactly one
|
|
504
|
+
// loading" is not the same as "only the root one".
|
|
505
|
+
const [only] = route.loadings;
|
|
506
|
+
const root = route.layouts[0];
|
|
507
|
+
const dirOf = (name) => name.slice(0, name.lastIndexOf("/"));
|
|
508
|
+
if (route.loadings.length !== 1 ||
|
|
509
|
+
!only ||
|
|
510
|
+
!root ||
|
|
511
|
+
dirOf(only) !== dirOf(root))
|
|
470
512
|
return result;
|
|
471
513
|
// A tenth of the shell budget, because this is a different question.
|
|
472
514
|
//
|
|
@@ -477,10 +519,10 @@ export async function prerender(options) {
|
|
|
477
519
|
// to learn what the first millisecond already said, which on this
|
|
478
520
|
// example was 40% of the entire prerender.
|
|
479
521
|
const withoutRoot = await withRequest(null, () => withCache(() => withRedirect(async () => engine.handleRscPprShell(route.component, props, layouts, [], route.slots, url, ROOT_FALLBACK_BUDGET_MS)))).catch(() => null);
|
|
480
|
-
if (withoutRoot && closeDocument(withoutRoot.shellHtml.trim()) ===
|
|
522
|
+
if (withoutRoot && closeDocument(withoutRoot.shellHtml.trim()) === "") {
|
|
481
523
|
result.warning =
|
|
482
|
-
|
|
483
|
-
|
|
524
|
+
"nothing painted without the root loading.tsx — the fallback the whole app shares " +
|
|
525
|
+
"is standing in for this page. Put a boundary where the waiting is.";
|
|
484
526
|
}
|
|
485
527
|
return result;
|
|
486
528
|
}
|
|
@@ -488,13 +530,16 @@ export async function prerender(options) {
|
|
|
488
530
|
// is the redirect, not a page. Rendering it per request re-derives a
|
|
489
531
|
// constant, and on a static host there would be nothing to derive it with.
|
|
490
532
|
if (redirected) {
|
|
491
|
-
await write(`${pathKey(url)}.redirect.json`, JSON.stringify({
|
|
492
|
-
|
|
533
|
+
await write(`${pathKey(url)}.redirect.json`, JSON.stringify({
|
|
534
|
+
status: redirected.status,
|
|
535
|
+
location: redirected.location,
|
|
536
|
+
}));
|
|
537
|
+
return said("frozen", `redirects to ${redirected.location}`);
|
|
493
538
|
}
|
|
494
539
|
if (!shell)
|
|
495
|
-
return said(
|
|
540
|
+
return said("error", "refused before it rendered");
|
|
496
541
|
if (shell.error)
|
|
497
|
-
return said(
|
|
542
|
+
return said("error", shell.error);
|
|
498
543
|
// Something failed while rendering. Not necessarily the page's fault — a
|
|
499
544
|
// build machine that cannot reach the database produces this, and so does a
|
|
500
545
|
// genuinely broken component — so it is not fatal. It is simply not
|
|
@@ -504,7 +549,7 @@ export async function prerender(options) {
|
|
|
504
549
|
// Rendering per request is the honest answer. It works as soon as whatever
|
|
505
550
|
// was missing is reachable, which at runtime it usually is.
|
|
506
551
|
if (shell.renderFailure) {
|
|
507
|
-
return said(
|
|
552
|
+
return said("blocked", `could not be rendered at build time: ${shell.renderFailure}`);
|
|
508
553
|
}
|
|
509
554
|
// A timeout is the ordinary path for a page that streams, not a failure:
|
|
510
555
|
// the probe hands the page a host global that never resolves, React
|
|
@@ -518,21 +563,29 @@ export async function prerender(options) {
|
|
|
518
563
|
// ship — a page that blocks above every boundary can only be rendered on
|
|
519
564
|
// demand. `reaches for the host` is the specific reason when both are
|
|
520
565
|
// true; a page that merely ran long says the other thing.
|
|
521
|
-
if (body ===
|
|
522
|
-
return said(
|
|
523
|
-
?
|
|
566
|
+
if (body === "") {
|
|
567
|
+
return said("blocked", (readRequest
|
|
568
|
+
? "reads " +
|
|
569
|
+
(readWhere.length
|
|
570
|
+
? readWhere
|
|
571
|
+
: readBy.length
|
|
572
|
+
? readBy
|
|
573
|
+
: ["the request"]).join(", ")
|
|
524
574
|
: shell.usedDynamicApis
|
|
525
|
-
?
|
|
526
|
-
:
|
|
527
|
-
|
|
528
|
-
|
|
575
|
+
? "reaches for the host"
|
|
576
|
+
: "blocks") +
|
|
577
|
+
" before anything can paint. Add a loading.tsx beside it, or put a " +
|
|
578
|
+
"<Suspense> above the waiting, and it has a skeleton to store.");
|
|
529
579
|
}
|
|
530
580
|
await writeShell(route, url, body, shell.postponed);
|
|
531
581
|
// Why it is a shell rather than a whole page. "◐ /orders" on its own
|
|
532
582
|
// leaves someone reading the build output to guess what did it, which is
|
|
533
583
|
// the question this line exists to answer.
|
|
534
|
-
const calls = [
|
|
535
|
-
|
|
584
|
+
const calls = [
|
|
585
|
+
...(shell.dynamicBecause ?? []),
|
|
586
|
+
...(readWhere.length ? readWhere : readBy),
|
|
587
|
+
];
|
|
588
|
+
return await withRootFallbackChecked(said("shell", shellReason(calls)));
|
|
536
589
|
}
|
|
537
590
|
// Warned, not refused.
|
|
538
591
|
//
|
|
@@ -553,10 +606,10 @@ export async function prerender(options) {
|
|
|
553
606
|
if (nondeterministic.length === 0)
|
|
554
607
|
return result;
|
|
555
608
|
result.warning =
|
|
556
|
-
`froze ${nondeterministic.join(
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
609
|
+
`froze ${nondeterministic.join(" and ")} — a stored page keeps whatever that ` +
|
|
610
|
+
"returned at build time. If it should differ per visitor, await connection() so " +
|
|
611
|
+
"the page renders per request; if only the browser needs it, use(browser()) keeps " +
|
|
612
|
+
"it out of the build entirely.";
|
|
560
613
|
return result;
|
|
561
614
|
};
|
|
562
615
|
// Rendered whole, with params that were invented because the route listed
|
|
@@ -566,7 +619,7 @@ export async function prerender(options) {
|
|
|
566
619
|
// before it can paint, which is exactly the shape that cannot be shared
|
|
567
620
|
// across urls.
|
|
568
621
|
if (unlisted) {
|
|
569
|
-
return said(
|
|
622
|
+
return said("blocked", "renders its params before it can paint, and lists no urls to build");
|
|
570
623
|
}
|
|
571
624
|
const rendered = await engine.handleRsc(route.component, props, null, layouts,
|
|
572
625
|
// Not empty: a page whose layout declares a parallel slot renders without
|
|
@@ -582,7 +635,7 @@ export async function prerender(options) {
|
|
|
582
635
|
// everyone and there is no build-time answer for it. Not frozen: a shell
|
|
583
636
|
// is shipped instead and the page finishes for whoever asks.
|
|
584
637
|
if (rendered.usedDynamicApis) {
|
|
585
|
-
return said(
|
|
638
|
+
return said("shell", "reaches the host for data");
|
|
586
639
|
}
|
|
587
640
|
// Nothing for a runtime to do. No client component means nothing to
|
|
588
641
|
// hydrate, and no server reference means no form that would post through
|
|
@@ -601,14 +654,18 @@ export async function prerender(options) {
|
|
|
601
654
|
// a Link elsewhere navigating here fetches it and expects the segment
|
|
602
655
|
// and title wrappers that render puts in, which this one leaves out.
|
|
603
656
|
const bare = await engine.handleRsc(route.component, props, null, layouts, route.loadings, route.slots, 0, url, false, false);
|
|
604
|
-
body = options.serviceWorker
|
|
657
|
+
body = options.serviceWorker
|
|
658
|
+
? withWorkerRegistration(bare.body)
|
|
659
|
+
: bare.body;
|
|
605
660
|
let inlined = false;
|
|
606
661
|
if (options.stylesheet) {
|
|
607
662
|
const before = body;
|
|
608
663
|
body = withInlineStylesheets(body, options.stylesheet);
|
|
609
664
|
inlined = body !== before;
|
|
610
665
|
}
|
|
611
|
-
note =
|
|
666
|
+
note =
|
|
667
|
+
"no client components, so ships no javascript" +
|
|
668
|
+
(inlined ? "; stylesheet inlined" : "");
|
|
612
669
|
}
|
|
613
670
|
const key = pathKey(url);
|
|
614
671
|
await write(`${key}.html`, body);
|
|
@@ -622,8 +679,12 @@ export async function prerender(options) {
|
|
|
622
679
|
const { rscPayload } = await engine.handleRscPayload(route.component, props, layouts, route.loadings, route.slots, depth, url);
|
|
623
680
|
await write(`${key}.seg${depth}.flight`, rscPayload);
|
|
624
681
|
}
|
|
625
|
-
await write(`${key}.meta.json`, JSON.stringify({
|
|
626
|
-
|
|
682
|
+
await write(`${key}.meta.json`, JSON.stringify({
|
|
683
|
+
layouts: route.layouts,
|
|
684
|
+
component: route.component,
|
|
685
|
+
version: version ?? null,
|
|
686
|
+
}, null, 2));
|
|
687
|
+
const frozen = noteNondeterminism(await withRootFallbackChecked(said("frozen", null)));
|
|
627
688
|
return note ? { ...frozen, note } : frozen;
|
|
628
689
|
}
|
|
629
690
|
/**
|
|
@@ -636,7 +697,7 @@ export async function prerender(options) {
|
|
|
636
697
|
* ones you could already freeze whole.
|
|
637
698
|
*/
|
|
638
699
|
async function writeShell(route, url, body, postponed) {
|
|
639
|
-
const parameterised = route.segments.some((s) => s.type !==
|
|
700
|
+
const parameterised = route.segments.some((s) => s.type !== "static");
|
|
640
701
|
const key = parameterised && !route.staticParams ? patternKey(route) : pathKey(url);
|
|
641
702
|
await write(`${key}.ppr.html`, body);
|
|
642
703
|
// Written only when there is something to resume from. Its absence is
|
|
@@ -646,7 +707,12 @@ export async function prerender(options) {
|
|
|
646
707
|
if (postponed != null) {
|
|
647
708
|
await write(`${key}.postponed.json`, JSON.stringify(postponed));
|
|
648
709
|
}
|
|
649
|
-
await write(`${key}.ppr-meta.json`, JSON.stringify({
|
|
710
|
+
await write(`${key}.ppr-meta.json`, JSON.stringify({
|
|
711
|
+
layouts: route.layouts,
|
|
712
|
+
component: route.component,
|
|
713
|
+
parameterised,
|
|
714
|
+
version: version ?? null,
|
|
715
|
+
}, null, 2));
|
|
650
716
|
}
|
|
651
717
|
}
|
|
652
718
|
//# sourceMappingURL=prerender.js.map
|