@rsc-kit/core 0.16.2 → 0.17.0
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/appAssets.d.ts +4 -1
- package/dist/appAssets.js +55 -19
- package/dist/appAssets.js.map +1 -1
- package/dist/buildReport.d.ts +6 -1
- package/dist/buildReport.js +16 -18
- package/dist/buildReport.js.map +1 -1
- package/dist/cache.js +8 -4
- package/dist/cache.js.map +1 -1
- package/dist/clientEntries.d.ts +32 -0
- package/dist/clientEntries.js +107 -0
- package/dist/clientEntries.js.map +1 -0
- package/dist/clientImports.d.ts +36 -0
- package/dist/clientImports.js +36 -0
- package/dist/clientImports.js.map +1 -0
- package/dist/devReload.d.ts +19 -0
- package/dist/devReload.js +32 -0
- package/dist/devReload.js.map +1 -0
- package/dist/js/Form.js +54 -6
- package/dist/js/Form.js.map +1 -1
- package/dist/js/Link.d.ts +0 -3
- package/dist/js/Link.js +3 -6
- package/dist/js/Link.js.map +1 -1
- package/dist/js/SegmentBoundary.d.ts +38 -2
- package/dist/js/SegmentBoundary.js +51 -13
- package/dist/js/SegmentBoundary.js.map +1 -1
- package/dist/js/createViteRscApp.js +72 -11
- package/dist/js/createViteRscApp.js.map +1 -1
- package/dist/js/devNotice.d.ts +13 -0
- package/dist/js/devNotice.js +55 -0
- package/dist/js/devNotice.js.map +1 -0
- package/dist/js/fallbackReport.d.ts +2 -0
- package/dist/js/fallbackReport.js +51 -0
- package/dist/js/fallbackReport.js.map +1 -0
- package/dist/js/navigate.d.ts +1 -1
- package/dist/js/navigate.js +55 -23
- package/dist/js/navigate.js.map +1 -1
- package/dist/js/segmentStore.d.ts +2 -0
- package/dist/js/segmentStore.js +21 -1
- package/dist/js/segmentStore.js.map +1 -1
- package/dist/js/staleAssets.d.ts +17 -0
- package/dist/js/staleAssets.js +48 -0
- package/dist/js/staleAssets.js.map +1 -0
- package/dist/js/standardSchema.d.ts +6 -0
- package/dist/js/standardSchema.js +19 -1
- package/dist/js/standardSchema.js.map +1 -1
- package/dist/js/useLinkStatus.d.ts +14 -1
- package/dist/js/useLinkStatus.js +15 -1
- package/dist/js/useLinkStatus.js.map +1 -1
- 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/metadata.d.ts +94 -15
- package/dist/metadata.js.map +1 -1
- package/dist/metadataRoutes.d.ts +41 -0
- package/dist/metadataRoutes.js +138 -0
- package/dist/metadataRoutes.js.map +1 -0
- package/dist/prerender.js +43 -10
- package/dist/prerender.js.map +1 -1
- package/dist/reactCache.d.ts +1 -0
- package/dist/reactCache.js +52 -0
- package/dist/reactCache.js.map +1 -0
- package/dist/request.d.ts +18 -1
- package/dist/request.js +91 -5
- package/dist/request.js.map +1 -1
- package/dist/useSsr.d.ts +19 -0
- package/dist/useSsr.js +104 -0
- package/dist/useSsr.js.map +1 -0
- package/dist/vite.d.ts +48 -11
- package/dist/vite.js +526 -26
- package/dist/vite.js.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SegmentBoundary.js","sourceRoot":"","sources":["../../src/js/SegmentBoundary.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAC;;AAEb;;;;;;;;;;;;GAYG;AAEH,OAAO,
|
|
1
|
+
{"version":3,"file":"SegmentBoundary.js","sourceRoot":"","sources":["../../src/js/SegmentBoundary.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAC;;AAEb;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAC/B,OAAO,EAAE,QAAQ,EAAE,eAAe,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAEvE;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,CAAC,MAAM,0BAA0B,GAAG,gBAAgB,CAAC;AAE3D,4EAA4E;AAC5E,yEAAyE;AACzE,uDAAuD;AACvD,MAAM,iBAAiB,GACpB,KAAwD,CAAC,iBAAiB;IAC1E,KAAiE;SAC/D,0BAA0B,CAAC;AAEhC,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,EACL,eAAe,EACf,aAAa,EACb,WAAW,EACX,kBAAkB,GACnB,MAAM,gBAAgB,CAAC;AAExB,MAAM,UAAU,eAAe,CAAC,EAC9B,KAAK,EACL,OAAO,EACP,QAAQ,GAMT;IACC,2EAA2E;IAC3E,2EAA2E;IAC3E,+DAA+D;IAC/D,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG,QAAQ,CAAqC,GAAG,EAAE,CAC1E,OAAO,MAAM,KAAK,WAAW,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,eAAe,CAAC,KAAK,CAAC,CAC9D,CAAC;IAEF,SAAS,CACP,GAAG,EAAE,CACH,kBAAkB,CAAC,KAAK,EAAE,GAAG,EAAE;QAC7B,eAAe,CAAC,GAAG,EAAE;YACnB,+DAA+D;YAC/D,gEAAgE;YAChE,sBAAsB;YACtB,IAAI,aAAa,EAAE;gBAAE,iBAAiB,EAAE,CAAC,0BAA0B,CAAC,CAAC;YAErE,QAAQ,CAAC,eAAe,CAAC,KAAK,CAAC,CAAC,CAAC;QACnC,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,EACJ,CAAC,KAAK,CAAC,CACR,CAAC;IAEF,yEAAyE;IACzE,6EAA6E;IAC7E,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,OAAO;YAAE,WAAW,CAAC,KAAK,EAAE,OAAO,EAAE,QAAQ,CAAC,CAAC;IACrD,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,QAAQ,CAAC,CAAC,CAAC;IAE/B,4EAA4E;IAC5E,yEAAyE;IACzE,4EAA4E;IAC5E,mEAAmE;IACnE,wEAAwE;IACxE,sEAAsE;IACtE,2EAA2E;IAC3E,4EAA4E;IAC5E,qEAAqE;IACrE,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,OAAO,CACL,KAAC,gBAAgB,cACd,OAAO,CAAC,CAAC,CAAC,CACT,KAAC,QAAQ,IAAe,IAAI,EAAC,SAAS,YACnC,QAAQ,IADI,OAAO,CAEX,CACZ,CAAC,CAAC,CAAC,CACF,QAAQ,CACT,GACgB,CACpB,CAAC;IACJ,CAAC;IAED,OAAO,CACL,KAAC,gBAAgB,cACd,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAC5B,KAAC,QAAQ,IAEP,IAAI,EAAE,KAAK,CAAC,GAAG,KAAK,KAAK,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,QAAQ,YAEzD,KAAK,CAAC,IAAiB,IAHnB,KAAK,CAAC,GAAG,CAIL,CACZ,CAAC,GACe,CACpB,CAAC;AACJ,CAAC","sourcesContent":["\"use client\";\n\n/**\n * The point in the tree a navigation can replace on its own.\n *\n * Sits between a layout and its children, so showing a different segment\n * re-renders from here down and leaves the layouts above it mounted. Server\n * components cannot be re-rendered on the client, which is why the seam has to\n * be a client component reading from a store rather than the layout itself.\n *\n * Pages it has already shown stay mounted behind <Activity mode=\"hidden\">.\n * Hidden is not unmounted: effects are torn down but state survives, so\n * returning to a page brings back the form you were filling in. Rendering only\n * the current one would throw that away, which is what replacing the root did.\n */\n\nimport * as React from \"react\";\nimport { Activity, startTransition, useEffect, useState } from \"react\";\n\n/**\n * The transition type a navigation carries.\n *\n * The engine does not animate a navigation itself; React's own\n * <ViewTransition> does, wherever an app puts one. What the app cannot do\n * from outside is tell a navigation from the seed after hydration - the\n * boundary taking over its own server-rendered children, which animated\n * would be the page fading into itself on every load. So a navigation's\n * update carries this type, and the seed's does not: a boundary keyed on it,\n * `default={{ \"rsc-navigation\": \"page\", default: \"none\" }}`, animates\n * navigations and nothing else.\n *\n * An earlier shape wrapped the segment in a boundary of the engine's own,\n * behind a flag. React names every top-level element under a boundary and\n * morphs each from where it was to where it is, so the stylesheet pulled\n * them back into one root fade - and fought React over the root, which it\n * cancels when nothing outside a boundary changed: a captured root with a\n * hidden group, a dark viewport on the first navigation. One wrapper element\n * under the app's own boundary is one snapshot pair, which is the dissolve\n * wanted, with nothing to fight.\n */\nexport const NAVIGATION_TRANSITION_TYPE = \"rsc-navigation\";\n\n// Exported by React 19.3; `unstable_` before it. Read at call time from the\n// namespace rather than imported by name, so a React that lacks it still\n// loads this module and simply does not mark the type.\nconst addTransitionType: ((type: string) => void) | undefined =\n (React as { addTransitionType?: (type: string) => void }).addTransitionType ??\n (React as { unstable_addTransitionType?: (type: string) => void })\n .unstable_addTransitionType;\nimport type { ReactNode } from \"react\";\nimport { RedirectBoundary } from \"./RedirectBoundary\";\nimport {\n getSegmentState,\n navigatedOnce,\n seedSegment,\n subscribeToSegment,\n} from \"./segmentStore\";\n\nexport function SegmentBoundary({\n depth,\n pageKey,\n children,\n}: {\n depth: number;\n /** The page these server-rendered children belong to. */\n pageKey?: string;\n children: ReactNode;\n}) {\n // The store still addresses the boundary; the render reads React state, so\n // the update can be a transition. Initialised from the store on the client\n // and null on the server — exactly what getServerSnapshot did.\n const [state, setState] = useState<ReturnType<typeof getSegmentState>>(() =>\n typeof window === \"undefined\" ? null : getSegmentState(depth),\n );\n\n useEffect(\n () =>\n subscribeToSegment(depth, () => {\n startTransition(() => {\n // A navigation, not the seed: the seed is the page taking over\n // its own server-rendered children, and fading that is the page\n // fading into itself.\n if (navigatedOnce()) addTransitionType?.(NAVIGATION_TRANSITION_TYPE);\n\n setState(getSegmentState(depth));\n });\n }),\n [depth],\n );\n\n // Record the page we arrived on, so a later navigation away and back can\n // return to it. Without this the first page is the one page you cannot keep.\n useEffect(() => {\n if (pageKey) seedSegment(depth, pageKey, children);\n }, [depth, pageKey, children]);\n\n // Wrapped here rather than around the whole app because this is the closest\n // client component above a page: a redirect thrown inside the page's own\n // Suspense boundary surfaces at the nearest error boundary, and catching it\n // here leaves the layouts above mounted while the navigation runs.\n // The same shape before and after the seed. The server's render and the\n // first client render already place the children inside the retention\n // wrapper, keyed by the page, so when the store takes over after hydration\n // the tree changes state but not shape - and React keeps the DOM instead of\n // remounting the page, which was blank, then content, on every load.\n if (!state) {\n return (\n <RedirectBoundary>\n {pageKey ? (\n <Activity key={pageKey} mode=\"visible\">\n {children}\n </Activity>\n ) : (\n children\n )}\n </RedirectBoundary>\n );\n }\n\n return (\n <RedirectBoundary>\n {state.entries.map((entry) => (\n <Activity\n key={entry.key}\n mode={entry.key === state.activeKey ? \"visible\" : \"hidden\"}\n >\n {entry.tree as ReactNode}\n </Activity>\n ))}\n </RedirectBoundary>\n );\n}\n"]}
|
|
@@ -1,5 +1,15 @@
|
|
|
1
|
+
// Vite-engine client bootstrap. Uses @vitejs/plugin-rsc's browser runtime as
|
|
2
|
+
// the Flight deserializer + action encoder, and drives the package's engine-agnostic
|
|
3
|
+
// navigate.ts SPA engine (Link, prefetch, popstate) through it. This replaces
|
|
4
|
+
// the bun engine's createRscApp + the hand-rolled webpack shim — the plugin
|
|
5
|
+
// resolves client references itself.
|
|
6
|
+
import { SEARCH_PARAMS_FALLBACK } from "./useSearchParams";
|
|
7
|
+
import { recoverFromStaleAssets } from "./staleAssets";
|
|
8
|
+
import { showDevNotice } from "./devNotice";
|
|
9
|
+
import { caughtByLoading } from "./fallbackReport";
|
|
10
|
+
import { noteNavigation } from "./segmentStore";
|
|
1
11
|
import { isSafeRedirect } from "../safeUrl.js";
|
|
2
|
-
import { createFromReadableStream, encodeReply, setServerCallback } from "@vitejs/plugin-rsc/browser";
|
|
12
|
+
import { createFromReadableStream, encodeReply, setServerCallback, } from "@vitejs/plugin-rsc/browser";
|
|
3
13
|
import { createElement } from "react";
|
|
4
14
|
import { hydrateRoot } from "react-dom/client";
|
|
5
15
|
import { ActivityRoot } from "./ActivityRouter";
|
|
@@ -39,7 +49,8 @@ export async function createViteRscApp(container = document, interceptEntries =
|
|
|
39
49
|
if (encoded instanceof FormData) {
|
|
40
50
|
const serialized = new Response(encoded);
|
|
41
51
|
body = await serialized.arrayBuffer();
|
|
42
|
-
realContentType =
|
|
52
|
+
realContentType =
|
|
53
|
+
serialized.headers.get("content-type") ?? "multipart/form-data";
|
|
43
54
|
}
|
|
44
55
|
else {
|
|
45
56
|
body = encoded;
|
|
@@ -90,7 +101,9 @@ export async function createViteRscApp(container = document, interceptEntries =
|
|
|
90
101
|
* was updated around it.
|
|
91
102
|
*/
|
|
92
103
|
function unwrapRevalidated(answer) {
|
|
93
|
-
if (answer === null ||
|
|
104
|
+
if (answer === null ||
|
|
105
|
+
typeof answer !== "object" ||
|
|
106
|
+
!("__rscRevalidated" in answer)) {
|
|
94
107
|
return answer;
|
|
95
108
|
}
|
|
96
109
|
const envelope = answer;
|
|
@@ -118,13 +131,14 @@ export async function createViteRscApp(container = document, interceptEntries =
|
|
|
118
131
|
setServerCallback(callServer);
|
|
119
132
|
// Link / Form / router live in a separate build graph and reach the SPA
|
|
120
133
|
// engine through these globals.
|
|
121
|
-
window.__rsc_navigate =
|
|
122
|
-
|
|
123
|
-
window.
|
|
124
|
-
|
|
125
|
-
window.
|
|
126
|
-
|
|
127
|
-
window.__rsc_refresh =
|
|
134
|
+
window.__rsc_navigate =
|
|
135
|
+
navigate;
|
|
136
|
+
window.__rsc_prefetch =
|
|
137
|
+
prefetch;
|
|
138
|
+
window.__rsc_cancel_prefetch = cancelPrefetch;
|
|
139
|
+
window.__rsc_is_prefetched = isPrefetched;
|
|
140
|
+
window.__rsc_refresh =
|
|
141
|
+
refresh;
|
|
128
142
|
// Hydrate from the RSC endpoint (same url + X-RSC, no version header).
|
|
129
143
|
//
|
|
130
144
|
// Everything from here to the decode is failure handling, because this is
|
|
@@ -169,14 +183,59 @@ export async function createViteRscApp(container = document, interceptEntries =
|
|
|
169
183
|
: tree;
|
|
170
184
|
const root = hydrateRoot(container, shell, {
|
|
171
185
|
onRecoverableError(error, errorInfo) {
|
|
186
|
+
// A client component whose chunk the server no longer has fails while
|
|
187
|
+
// hydrating, and React reports that as recoverable: the page is loaded
|
|
188
|
+
// again, with the current names.
|
|
189
|
+
if (recoverFromStaleAssets(error))
|
|
190
|
+
return;
|
|
172
191
|
// A PPR shell is served with its Suspense boundaries deliberately
|
|
173
192
|
// unfinished — the build aborts the render once the static part is out.
|
|
174
193
|
// React reports that as #419 and client-renders the boundary from the
|
|
175
194
|
// Flight payload, which is the intended path, not a fault to report.
|
|
176
195
|
const message = String(error?.message ?? error);
|
|
177
|
-
if (message.includes("419") ||
|
|
196
|
+
if (message.includes("419") ||
|
|
197
|
+
message.includes("did not finish this Suspense boundary")) {
|
|
178
198
|
return;
|
|
179
199
|
}
|
|
200
|
+
// A view transition the browser cancelled because the tab was not
|
|
201
|
+
// visible. React reports it here; the update still landed, and a tab
|
|
202
|
+
// in the background is not a fault in the page.
|
|
203
|
+
if (message.includes("Transition was aborted"))
|
|
204
|
+
return;
|
|
205
|
+
// A page whose query string was read under a boundary carries the
|
|
206
|
+
// fallback there; React reports the recovery on hydration. Under a
|
|
207
|
+
// boundary the developer wrote, that is the designed path and nothing
|
|
208
|
+
// is said. With nothing closer than a loading.tsx, the whole segment
|
|
209
|
+
// showed the fallback until the query arrived - said here, in the
|
|
210
|
+
// page and the console, for whoever is looking at either. Production
|
|
211
|
+
// has no stack to tell the two apart; the build's note on the route is
|
|
212
|
+
// the record there.
|
|
213
|
+
if (error?.digest === SEARCH_PARAMS_FALLBACK ||
|
|
214
|
+
message.includes("useSearchParams()")) {
|
|
215
|
+
const stack = errorInfo
|
|
216
|
+
?.componentStack;
|
|
217
|
+
if (!caughtByLoading(stack))
|
|
218
|
+
return;
|
|
219
|
+
const hint = "useSearchParams() was read on the server with nothing closer than a loading.tsx, so the " +
|
|
220
|
+
"whole segment showed that fallback until the query arrived. A <Suspense> around the " +
|
|
221
|
+
"component that reads keeps the rest of the page painted.";
|
|
222
|
+
showDevNotice(hint, stack);
|
|
223
|
+
console.error(new Error(hint), errorInfo);
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
console.error(error, errorInfo);
|
|
227
|
+
},
|
|
228
|
+
// A chunk the deploy no longer serves is not a fault in the page; the
|
|
229
|
+
// document is loaded again, and the new names come with it. Anything
|
|
230
|
+
// else is reported the way React would have.
|
|
231
|
+
onUncaughtError(error, errorInfo) {
|
|
232
|
+
if (recoverFromStaleAssets(error))
|
|
233
|
+
return;
|
|
234
|
+
console.error(error, errorInfo);
|
|
235
|
+
},
|
|
236
|
+
onCaughtError(error, errorInfo) {
|
|
237
|
+
if (recoverFromStaleAssets(error))
|
|
238
|
+
return;
|
|
180
239
|
console.error(error, errorInfo);
|
|
181
240
|
},
|
|
182
241
|
});
|
|
@@ -185,6 +244,8 @@ export async function createViteRscApp(container = document, interceptEntries =
|
|
|
185
244
|
// it mounted, which is the point of asking for a partial render at all.
|
|
186
245
|
setNavigateHandler((tree, key, segmentDepth) => {
|
|
187
246
|
const newTree = tree;
|
|
247
|
+
// From here on the boundary animates; the seed commit before this did not.
|
|
248
|
+
noteNavigation();
|
|
188
249
|
if (segmentDepth > 0) {
|
|
189
250
|
setSegment(segmentDepth, key, newTree);
|
|
190
251
|
return;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"createViteRscApp.js","sourceRoot":"","sources":["../../src/js/createViteRscApp.ts"],"names":[],"mappings":"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","sourcesContent":["// Vite-engine client bootstrap. Uses @vitejs/plugin-rsc's browser runtime as\n// the Flight deserializer + action encoder, and drives the package's engine-agnostic\n// navigate.ts SPA engine (Link, prefetch, popstate) through it. This replaces\n// the bun engine's createRscApp + the hand-rolled webpack shim — the plugin\n// resolves client references itself.\nimport type { Href } from \"../routes.js\";\nimport { isSafeRedirect } from \"../safeUrl.js\";\nimport { createFromReadableStream, encodeReply, setServerCallback } from \"@vitejs/plugin-rsc/browser\";\nimport { createElement } from \"react\";\nimport { hydrateRoot } from \"react-dom/client\";\nimport { ActivityRoot } from \"./ActivityRouter\";\nimport { ServerRedirectError, throwForFailedAction } from \"./errors\";\nimport { fetchPagePayload } from \"./pagePayload\";\nimport { claimRead, setQueryCodec } from \"./queryClient\";\nimport { clearSegments, restoreSegments, setSegment } from \"./segmentStore\";\nimport type { ReactNode } from \"react\";\nimport {\n cancelPrefetch,\n isPrefetched,\n navigate,\n refresh,\n applyRevalidated,\n prefetch,\n retentionKey,\n setCallServer,\n setHeldLayouts,\n setStaticPayloads,\n setStaticRoutes,\n seedStaticChain,\n payloadUrl,\n setDeserializer,\n setInterceptManifest,\n setNavigateHandler,\n setRestoreHandler,\n setVersion,\n} from \"./navigate\";\n\nexport async function createViteRscApp(\n container: Document | Element = document,\n interceptEntries: { urlPattern: string; slot: string }[] = [],\n options: { staticPayloads?: string | null; routes?: unknown[] | null } = {},\n): Promise<void> {\n // An exported build has no server to negotiate with, so payloads live at\n // their own urls rather than behind a header on the page's url.\n setStaticPayloads(options.staticPayloads ?? null);\n // Only an exported build ships this: with no server to negotiate with, the\n // client works out for itself how much of a page it still holds.\n if (options.routes) setStaticRoutes(options.routes as never);\n // The router has to recognise an intercepted link before it asks the server,\n // so the patterns are baked into the generated browser entry. Without them\n // every intercepted route falls through to a full-page navigation.\n setInterceptManifest(interceptEntries);\n\n async function callServer(id: string, args: unknown[]): Promise<unknown> {\n // A read opened a slot before calling the reference, and this is the first\n // point at which the id exists on the client — React keeps it private and\n // hands it over only here. Claiming the slot enqueues the read for the\n // batched GET instead of posting it.\n const read = claimRead(id, args);\n\n if (read) return await read;\n\n const encoded = await encodeReply(args);\n\n // encodeReply returns FormData as soon as an argument contains a File.\n // Sending that as multipart would make PHP consume php://input while\n // parsing it, leaving the action with an empty body — so serialize it to\n // raw bytes under an opaque content-type and send the real one in\n // X-RSC-Content-Type for the worker to rebuild FormData from.\n let body: BodyInit;\n let realContentType: string;\n\n if (encoded instanceof FormData) {\n const serialized = new Response(encoded);\n body = await serialized.arrayBuffer();\n realContentType = serialized.headers.get(\"content-type\") ?? \"multipart/form-data\";\n } else {\n body = encoded as BodyInit;\n realContentType = \"text/plain;charset=UTF-8\";\n }\n\n const res = await fetch(\"/_rsc/action\", {\n method: \"POST\",\n headers: {\n \"X-RSC-Action\": id,\n // Where the action was invoked from. The host resolves it to the\n // components that render the page, so anything the action says it\n // invalidated can come back with the answer instead of being fetched\n // afterwards.\n \"X-RSC-Referer\": window.location.pathname + window.location.search,\n \"X-RSC-Content-Type\": realContentType,\n \"Content-Type\": \"application/octet-stream\",\n \"X-XSRF-TOKEN\": decodeURIComponent(\n document.cookie.match(/XSRF-TOKEN=([^;]+)/)?.[1] ?? \"\",\n ),\n },\n body,\n });\n\n // A failed action does not answer with a Flight stream, and the decoder\n // cannot tell — it reports its own parse failure instead of what the\n // server said.\n try {\n await throwForFailedAction(res);\n } catch (err) {\n // A redirect is an instruction, not something for a form to display:\n // an expired session answers this way, and the destination is the login\n // page rather than a message about one.\n if (err instanceof ServerRedirectError) {\n // The destination came off a response header, so it is checked here\n // too — the engine refuses these at the source, but a proxy or a host\n // in front of it can write whatever it likes.\n if (isSafeRedirect(err.location)) window.location.href = err.location;\n }\n\n throw err;\n }\n\n const answer = await createFromReadableStream(res.body!, { callServer });\n\n return unwrapRevalidated(answer);\n }\n\n /**\n * Put anything the action re-rendered on screen, and hand back its result.\n *\n * The trees travel with the answer rather than being fetched afterwards, so\n * the caller sees only what its action returned and never knows the page\n * was updated around it.\n */\n function unwrapRevalidated(answer: unknown): unknown {\n if (answer === null || typeof answer !== \"object\" || !(\"__rscRevalidated\" in answer)) {\n return answer;\n }\n\n const envelope = answer as { __rscRevalidated: Record<string, unknown>; result: unknown };\n\n for (const [target, tree] of Object.entries(envelope.__rscRevalidated)) {\n applyRevalidated(target, tree);\n }\n\n return envelope.result;\n }\n\n setDeserializer(createFromReadableStream as never);\n // The query transport speaks Flight too, and reaches the runtime through here\n // rather than importing it: a second import of the browser runtime from the\n // client-component graph is a second client-reference registry, and the\n // symptom is components that render as undefined with nothing logged.\n setQueryCodec({\n deserialize: (stream) =>\n createFromReadableStream(stream, { callServer }) as Promise<unknown>,\n encode: (args) => encodeReply(args) as Promise<string | FormData>,\n // The way back when a read cannot ride in a url. Re-entering callServer is\n // safe: the slot was cleared the moment the reference returned, so this\n // takes the ordinary POST path rather than claiming a read again.\n asAction: (id, args) => callServer(id, args),\n });\n setCallServer(callServer);\n // The plugin's own \"use server\" client stubs route through its registered\n // server callback — register the same transport there too.\n setServerCallback(callServer as never);\n\n // Link / Form / router live in a separate build graph and reach the SPA\n // engine through these globals.\n (window as unknown as { __rsc_navigate: typeof navigate }).__rsc_navigate = navigate;\n (window as unknown as { __rsc_prefetch: typeof prefetch }).__rsc_prefetch = prefetch;\n (window as unknown as { __rsc_cancel_prefetch: typeof cancelPrefetch }).__rsc_cancel_prefetch =\n cancelPrefetch;\n (window as unknown as { __rsc_is_prefetched: typeof isPrefetched }).__rsc_is_prefetched =\n isPrefetched;\n (window as unknown as { __rsc_refresh: typeof refresh }).__rsc_refresh = refresh;\n\n // Hydrate from the RSC endpoint (same url + X-RSC, no version header).\n //\n // Everything from here to the decode is failure handling, because this is\n // the one request with nothing watching it. A page whose shell is already\n // rendered looks fine while this fails, and its fallbacks stay on screen\n // indefinitely with nothing reported anywhere.\n const res = await fetchPagePayload(payloadUrl(window.location.href));\n\n // Seed the SPA engine with the build this page was served from, so a\n // redeploy mid-session is caught on the next navigation. This matters most\n // behind a CDN, where the shell may be cached from an older build.\n const servedVersion = res.headers.get(\"X-RSC-Version\");\n\n if (servedVersion) {\n setVersion(servedVersion);\n }\n\n // The chain this page is built from, so the next navigation can say what is\n // already mounted and be sent only what changed.\n // A server says what this page is built from; a file server says nothing,\n // so an exported build works it out from the table it was given.\n const servedLayouts = res.headers.get(\"X-RSC-Layouts\");\n\n if (servedLayouts !== null) {\n setHeldLayouts(servedLayouts.split(\",\"));\n } else if (!seedStaticChain(window.location.href)) {\n setHeldLayouts([]);\n }\n const tree = await createFromReadableStream(res.body!, { callServer });\n\n // Retaining the previous page behind <Activity> needs a wrapper above the\n // page, and React will not hydrate a *document* container through one: the\n // root child of a document has to be <html>, and wrapping it hangs the\n // renderer outright (React 19.2.7). An app whose root layout owns <html>\n // therefore hydrates the tree directly and navigations replace it, as before.\n //\n // Apps that hydrate into an element get retention. Giving it to document-\n // rooted apps means SPA navigation returning only the changed segment rather\n // than a whole document, which is an engine change, not a client one.\n const retains = container !== document;\n\n const shell = retains\n ? createElement(ActivityRoot, {\n initialKey: retentionKey(window.location.href, null),\n initialTree: tree as ReactNode,\n })\n : (tree as ReactNode);\n\n const root = hydrateRoot(container, shell, {\n onRecoverableError(error: unknown, errorInfo: unknown) {\n // A PPR shell is served with its Suspense boundaries deliberately\n // unfinished — the build aborts the render once the static part is out.\n // React reports that as #419 and client-renders the boundary from the\n // Flight payload, which is the intended path, not a fault to report.\n const message = String((error as { message?: string })?.message ?? error);\n\n if (message.includes(\"419\") || message.includes(\"did not finish this Suspense boundary\")) {\n return;\n }\n\n console.error(error, errorInfo);\n },\n });\n\n // Depth 0 is a whole document and replaces the root. Anything deeper is one\n // segment: handing it to the boundary at that depth leaves the layouts above\n // it mounted, which is the point of asking for a partial render at all.\n setNavigateHandler((tree: unknown, key: string, segmentDepth: number) => {\n const newTree = tree as ReactNode;\n\n if (segmentDepth > 0) {\n setSegment(segmentDepth, key, newTree);\n\n return;\n }\n\n clearSegments();\n root.render(newTree);\n });\n\n // Back and forward reveal a page the boundaries are still holding, with the\n // form you were filling in still filled in, and without asking the server.\n setRestoreHandler((key: string, maxAge?: number) => restoreSegments(key, maxAge));\n\n window.addEventListener(\"popstate\", () => {\n // restore: back and forward reveal the page you were on, with its state.\n // Wherever the browser just went; not a literal this app wrote.\n navigate(window.location.href as Href, { replace: true, restore: true });\n });\n\n history.replaceState({ rscUrl: window.location.href }, \"\", window.location.href);\n}\n"]}
|
|
1
|
+
{"version":3,"file":"createViteRscApp.js","sourceRoot":"","sources":["../../src/js/createViteRscApp.ts"],"names":[],"mappings":"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","sourcesContent":["// Vite-engine client bootstrap. Uses @vitejs/plugin-rsc's browser runtime as\n// the Flight deserializer + action encoder, and drives the package's engine-agnostic\n// navigate.ts SPA engine (Link, prefetch, popstate) through it. This replaces\n// the bun engine's createRscApp + the hand-rolled webpack shim — the plugin\n// resolves client references itself.\nimport { SEARCH_PARAMS_FALLBACK } from \"./useSearchParams\";\nimport { recoverFromStaleAssets } from \"./staleAssets\";\nimport { showDevNotice } from \"./devNotice\";\nimport { caughtByLoading } from \"./fallbackReport\";\nimport { noteNavigation } from \"./segmentStore\";\nimport type { Href } from \"../routes.js\";\nimport { isSafeRedirect } from \"../safeUrl.js\";\nimport {\n createFromReadableStream,\n encodeReply,\n setServerCallback,\n} from \"@vitejs/plugin-rsc/browser\";\nimport { createElement } from \"react\";\nimport { hydrateRoot } from \"react-dom/client\";\nimport { ActivityRoot } from \"./ActivityRouter\";\nimport { ServerRedirectError, throwForFailedAction } from \"./errors\";\nimport { fetchPagePayload } from \"./pagePayload\";\nimport { claimRead, setQueryCodec } from \"./queryClient\";\nimport { clearSegments, restoreSegments, setSegment } from \"./segmentStore\";\nimport type { ReactNode } from \"react\";\nimport {\n cancelPrefetch,\n isPrefetched,\n navigate,\n refresh,\n applyRevalidated,\n prefetch,\n retentionKey,\n setCallServer,\n setHeldLayouts,\n setStaticPayloads,\n setStaticRoutes,\n seedStaticChain,\n payloadUrl,\n setDeserializer,\n setInterceptManifest,\n setNavigateHandler,\n setRestoreHandler,\n setVersion,\n} from \"./navigate\";\n\nexport async function createViteRscApp(\n container: Document | Element = document,\n interceptEntries: { urlPattern: string; slot: string }[] = [],\n options: { staticPayloads?: string | null; routes?: unknown[] | null } = {},\n): Promise<void> {\n // An exported build has no server to negotiate with, so payloads live at\n // their own urls rather than behind a header on the page's url.\n setStaticPayloads(options.staticPayloads ?? null);\n // Only an exported build ships this: with no server to negotiate with, the\n // client works out for itself how much of a page it still holds.\n if (options.routes) setStaticRoutes(options.routes as never);\n // The router has to recognise an intercepted link before it asks the server,\n // so the patterns are baked into the generated browser entry. Without them\n // every intercepted route falls through to a full-page navigation.\n setInterceptManifest(interceptEntries);\n\n async function callServer(id: string, args: unknown[]): Promise<unknown> {\n // A read opened a slot before calling the reference, and this is the first\n // point at which the id exists on the client — React keeps it private and\n // hands it over only here. Claiming the slot enqueues the read for the\n // batched GET instead of posting it.\n const read = claimRead(id, args);\n\n if (read) return await read;\n\n const encoded = await encodeReply(args);\n\n // encodeReply returns FormData as soon as an argument contains a File.\n // Sending that as multipart would make PHP consume php://input while\n // parsing it, leaving the action with an empty body — so serialize it to\n // raw bytes under an opaque content-type and send the real one in\n // X-RSC-Content-Type for the worker to rebuild FormData from.\n let body: BodyInit;\n let realContentType: string;\n\n if (encoded instanceof FormData) {\n const serialized = new Response(encoded);\n body = await serialized.arrayBuffer();\n realContentType =\n serialized.headers.get(\"content-type\") ?? \"multipart/form-data\";\n } else {\n body = encoded as BodyInit;\n realContentType = \"text/plain;charset=UTF-8\";\n }\n\n const res = await fetch(\"/_rsc/action\", {\n method: \"POST\",\n headers: {\n \"X-RSC-Action\": id,\n // Where the action was invoked from. The host resolves it to the\n // components that render the page, so anything the action says it\n // invalidated can come back with the answer instead of being fetched\n // afterwards.\n \"X-RSC-Referer\": window.location.pathname + window.location.search,\n \"X-RSC-Content-Type\": realContentType,\n \"Content-Type\": \"application/octet-stream\",\n \"X-XSRF-TOKEN\": decodeURIComponent(\n document.cookie.match(/XSRF-TOKEN=([^;]+)/)?.[1] ?? \"\",\n ),\n },\n body,\n });\n\n // A failed action does not answer with a Flight stream, and the decoder\n // cannot tell — it reports its own parse failure instead of what the\n // server said.\n try {\n await throwForFailedAction(res);\n } catch (err) {\n // A redirect is an instruction, not something for a form to display:\n // an expired session answers this way, and the destination is the login\n // page rather than a message about one.\n if (err instanceof ServerRedirectError) {\n // The destination came off a response header, so it is checked here\n // too — the engine refuses these at the source, but a proxy or a host\n // in front of it can write whatever it likes.\n if (isSafeRedirect(err.location)) window.location.href = err.location;\n }\n\n throw err;\n }\n\n const answer = await createFromReadableStream(res.body!, { callServer });\n\n return unwrapRevalidated(answer);\n }\n\n /**\n * Put anything the action re-rendered on screen, and hand back its result.\n *\n * The trees travel with the answer rather than being fetched afterwards, so\n * the caller sees only what its action returned and never knows the page\n * was updated around it.\n */\n function unwrapRevalidated(answer: unknown): unknown {\n if (\n answer === null ||\n typeof answer !== \"object\" ||\n !(\"__rscRevalidated\" in answer)\n ) {\n return answer;\n }\n\n const envelope = answer as {\n __rscRevalidated: Record<string, unknown>;\n result: unknown;\n };\n\n for (const [target, tree] of Object.entries(envelope.__rscRevalidated)) {\n applyRevalidated(target, tree);\n }\n\n return envelope.result;\n }\n\n setDeserializer(createFromReadableStream as never);\n // The query transport speaks Flight too, and reaches the runtime through here\n // rather than importing it: a second import of the browser runtime from the\n // client-component graph is a second client-reference registry, and the\n // symptom is components that render as undefined with nothing logged.\n setQueryCodec({\n deserialize: (stream) =>\n createFromReadableStream(stream, { callServer }) as Promise<unknown>,\n encode: (args) => encodeReply(args) as Promise<string | FormData>,\n // The way back when a read cannot ride in a url. Re-entering callServer is\n // safe: the slot was cleared the moment the reference returned, so this\n // takes the ordinary POST path rather than claiming a read again.\n asAction: (id, args) => callServer(id, args),\n });\n setCallServer(callServer);\n // The plugin's own \"use server\" client stubs route through its registered\n // server callback — register the same transport there too.\n setServerCallback(callServer as never);\n\n // Link / Form / router live in a separate build graph and reach the SPA\n // engine through these globals.\n (window as unknown as { __rsc_navigate: typeof navigate }).__rsc_navigate =\n navigate;\n (window as unknown as { __rsc_prefetch: typeof prefetch }).__rsc_prefetch =\n prefetch;\n (\n window as unknown as { __rsc_cancel_prefetch: typeof cancelPrefetch }\n ).__rsc_cancel_prefetch = cancelPrefetch;\n (\n window as unknown as { __rsc_is_prefetched: typeof isPrefetched }\n ).__rsc_is_prefetched = isPrefetched;\n (window as unknown as { __rsc_refresh: typeof refresh }).__rsc_refresh =\n refresh;\n\n // Hydrate from the RSC endpoint (same url + X-RSC, no version header).\n //\n // Everything from here to the decode is failure handling, because this is\n // the one request with nothing watching it. A page whose shell is already\n // rendered looks fine while this fails, and its fallbacks stay on screen\n // indefinitely with nothing reported anywhere.\n const res = await fetchPagePayload(payloadUrl(window.location.href));\n\n // Seed the SPA engine with the build this page was served from, so a\n // redeploy mid-session is caught on the next navigation. This matters most\n // behind a CDN, where the shell may be cached from an older build.\n const servedVersion = res.headers.get(\"X-RSC-Version\");\n\n if (servedVersion) {\n setVersion(servedVersion);\n }\n\n // The chain this page is built from, so the next navigation can say what is\n // already mounted and be sent only what changed.\n // A server says what this page is built from; a file server says nothing,\n // so an exported build works it out from the table it was given.\n const servedLayouts = res.headers.get(\"X-RSC-Layouts\");\n\n if (servedLayouts !== null) {\n setHeldLayouts(servedLayouts.split(\",\"));\n } else if (!seedStaticChain(window.location.href)) {\n setHeldLayouts([]);\n }\n const tree = await createFromReadableStream(res.body!, { callServer });\n\n // Retaining the previous page behind <Activity> needs a wrapper above the\n // page, and React will not hydrate a *document* container through one: the\n // root child of a document has to be <html>, and wrapping it hangs the\n // renderer outright (React 19.2.7). An app whose root layout owns <html>\n // therefore hydrates the tree directly and navigations replace it, as before.\n //\n // Apps that hydrate into an element get retention. Giving it to document-\n // rooted apps means SPA navigation returning only the changed segment rather\n // than a whole document, which is an engine change, not a client one.\n const retains = container !== document;\n\n const shell = retains\n ? createElement(ActivityRoot, {\n initialKey: retentionKey(window.location.href, null),\n initialTree: tree as ReactNode,\n })\n : (tree as ReactNode);\n\n const root = hydrateRoot(container, shell, {\n onRecoverableError(error: unknown, errorInfo: unknown) {\n // A client component whose chunk the server no longer has fails while\n // hydrating, and React reports that as recoverable: the page is loaded\n // again, with the current names.\n if (recoverFromStaleAssets(error)) return;\n\n // A PPR shell is served with its Suspense boundaries deliberately\n // unfinished — the build aborts the render once the static part is out.\n // React reports that as #419 and client-renders the boundary from the\n // Flight payload, which is the intended path, not a fault to report.\n const message = String((error as { message?: string })?.message ?? error);\n\n if (\n message.includes(\"419\") ||\n message.includes(\"did not finish this Suspense boundary\")\n ) {\n return;\n }\n\n // A view transition the browser cancelled because the tab was not\n // visible. React reports it here; the update still landed, and a tab\n // in the background is not a fault in the page.\n if (message.includes(\"Transition was aborted\")) return;\n // A page whose query string was read under a boundary carries the\n // fallback there; React reports the recovery on hydration. Under a\n // boundary the developer wrote, that is the designed path and nothing\n // is said. With nothing closer than a loading.tsx, the whole segment\n // showed the fallback until the query arrived - said here, in the\n // page and the console, for whoever is looking at either. Production\n // has no stack to tell the two apart; the build's note on the route is\n // the record there.\n if (\n (error as { digest?: string })?.digest === SEARCH_PARAMS_FALLBACK ||\n message.includes(\"useSearchParams()\")\n ) {\n const stack = (errorInfo as { componentStack?: string } | null)\n ?.componentStack;\n\n if (!caughtByLoading(stack)) return;\n\n const hint =\n \"useSearchParams() was read on the server with nothing closer than a loading.tsx, so the \" +\n \"whole segment showed that fallback until the query arrived. A <Suspense> around the \" +\n \"component that reads keeps the rest of the page painted.\";\n\n showDevNotice(hint, stack);\n console.error(new Error(hint), errorInfo);\n\n return;\n }\n\n console.error(error, errorInfo);\n },\n // A chunk the deploy no longer serves is not a fault in the page; the\n // document is loaded again, and the new names come with it. Anything\n // else is reported the way React would have.\n onUncaughtError(error: unknown, errorInfo: unknown) {\n if (recoverFromStaleAssets(error)) return;\n\n console.error(error, errorInfo);\n },\n onCaughtError(error: unknown, errorInfo: unknown) {\n if (recoverFromStaleAssets(error)) return;\n\n console.error(error, errorInfo);\n },\n });\n\n // Depth 0 is a whole document and replaces the root. Anything deeper is one\n // segment: handing it to the boundary at that depth leaves the layouts above\n // it mounted, which is the point of asking for a partial render at all.\n setNavigateHandler((tree: unknown, key: string, segmentDepth: number) => {\n const newTree = tree as ReactNode;\n\n // From here on the boundary animates; the seed commit before this did not.\n noteNavigation();\n\n if (segmentDepth > 0) {\n setSegment(segmentDepth, key, newTree);\n\n return;\n }\n\n clearSegments();\n root.render(newTree);\n });\n\n // Back and forward reveal a page the boundaries are still holding, with the\n // form you were filling in still filled in, and without asking the server.\n setRestoreHandler((key: string, maxAge?: number) =>\n restoreSegments(key, maxAge),\n );\n\n window.addEventListener(\"popstate\", () => {\n // restore: back and forward reveal the page you were on, with its state.\n // Wherever the browser just went; not a literal this app wrote.\n navigate(window.location.href as Href, { replace: true, restore: true });\n });\n\n history.replaceState(\n { rscUrl: window.location.href },\n \"\",\n window.location.href,\n );\n}\n"]}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A line at the bottom of the page, in development only.
|
|
3
|
+
*
|
|
4
|
+
* The dev server prints the boundary the build will want, and the browser
|
|
5
|
+
* console reports it; the person looking at the page sees neither. Next.js
|
|
6
|
+
* shows a small badge for the same reason. This is that: fixed to the bottom
|
|
7
|
+
* of the viewport, the message and the component it came from, with a
|
|
8
|
+
* button to dismiss it. Nothing here reaches a production bundle - the DEV check is a
|
|
9
|
+
* constant at build time and the code behind it is dropped.
|
|
10
|
+
*/
|
|
11
|
+
/** The first component in a React component stack: `at AuthTrigger (...)` → `AuthTrigger`. */
|
|
12
|
+
export declare function componentIn(componentStack: string | undefined | null): string | null;
|
|
13
|
+
export declare function showDevNotice(message: string, componentStack?: string | null): void;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A line at the bottom of the page, in development only.
|
|
3
|
+
*
|
|
4
|
+
* The dev server prints the boundary the build will want, and the browser
|
|
5
|
+
* console reports it; the person looking at the page sees neither. Next.js
|
|
6
|
+
* shows a small badge for the same reason. This is that: fixed to the bottom
|
|
7
|
+
* of the viewport, the message and the component it came from, with a
|
|
8
|
+
* button to dismiss it. Nothing here reaches a production bundle - the DEV check is a
|
|
9
|
+
* constant at build time and the code behind it is dropped.
|
|
10
|
+
*/
|
|
11
|
+
const ID = "rsc-kit-dev-notice";
|
|
12
|
+
/** The first component in a React component stack: `at AuthTrigger (...)` → `AuthTrigger`. */
|
|
13
|
+
export function componentIn(componentStack) {
|
|
14
|
+
const match = /at ([A-Z][\w$]*)/.exec(componentStack ?? "");
|
|
15
|
+
return match ? match[1] : null;
|
|
16
|
+
}
|
|
17
|
+
export function showDevNotice(message, componentStack) {
|
|
18
|
+
if (!import.meta.env?.DEV || typeof document === "undefined")
|
|
19
|
+
return;
|
|
20
|
+
const where = componentIn(componentStack);
|
|
21
|
+
const text = (where ? `${where}: ` : "") + message;
|
|
22
|
+
let box = document.getElementById(ID);
|
|
23
|
+
if (!box) {
|
|
24
|
+
box = document.createElement("div");
|
|
25
|
+
box.id = ID;
|
|
26
|
+
box.setAttribute("role", "status");
|
|
27
|
+
box.style.cssText =
|
|
28
|
+
"position:fixed;left:12px;right:12px;bottom:12px;z-index:2147483647;" +
|
|
29
|
+
"font:12px/1.5 ui-monospace,SFMono-Regular,Menlo,monospace;color:#fff;background:#b91c1c;" +
|
|
30
|
+
"border-radius:6px;padding:10px 40px 10px 14px;box-shadow:0 4px 16px rgba(0,0,0,.35);" +
|
|
31
|
+
"max-height:40vh;overflow:auto;white-space:pre-wrap;user-select:text";
|
|
32
|
+
// A button, not a click anywhere: the text is there to be selected and
|
|
33
|
+
// copied, and a box that vanishes on the first click cannot be.
|
|
34
|
+
const close = document.createElement("button");
|
|
35
|
+
close.type = "button";
|
|
36
|
+
close.textContent = "\u00d7";
|
|
37
|
+
close.setAttribute("aria-label", "Dismiss");
|
|
38
|
+
close.title = "Dismiss";
|
|
39
|
+
close.style.cssText =
|
|
40
|
+
"position:absolute;top:6px;right:8px;border:0;background:transparent;color:#fff;" +
|
|
41
|
+
"font:16px/1 ui-monospace,SFMono-Regular,Menlo,monospace;cursor:pointer;padding:4px 6px";
|
|
42
|
+
close.addEventListener("click", () => box?.remove());
|
|
43
|
+
box.appendChild(close);
|
|
44
|
+
document.body.appendChild(box);
|
|
45
|
+
}
|
|
46
|
+
// The same line twice is one problem, not two.
|
|
47
|
+
for (const line of Array.from(box.children)) {
|
|
48
|
+
if (line.tagName !== "BUTTON" && line.textContent === text)
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
const line = document.createElement("div");
|
|
52
|
+
line.textContent = text;
|
|
53
|
+
box.appendChild(line);
|
|
54
|
+
}
|
|
55
|
+
//# sourceMappingURL=devNotice.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"devNotice.js","sourceRoot":"","sources":["../../src/js/devNotice.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,MAAM,EAAE,GAAG,oBAAoB,CAAC;AAEhC,8FAA8F;AAC9F,MAAM,UAAU,WAAW,CACzB,cAAyC;IAEzC,MAAM,KAAK,GAAG,kBAAkB,CAAC,IAAI,CAAC,cAAc,IAAI,EAAE,CAAC,CAAC;IAE5D,OAAO,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAE,CAAC,CAAC,CAAC,IAAI,CAAC;AAClC,CAAC;AAED,MAAM,UAAU,aAAa,CAC3B,OAAe,EACf,cAA8B;IAE9B,IAAI,CAAC,OAAO,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,OAAO,QAAQ,KAAK,WAAW;QAAE,OAAO;IAErE,MAAM,KAAK,GAAG,WAAW,CAAC,cAAc,CAAC,CAAC;IAC1C,MAAM,IAAI,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,KAAK,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,GAAG,OAAO,CAAC;IAEnD,IAAI,GAAG,GAAG,QAAQ,CAAC,cAAc,CAAC,EAAE,CAAC,CAAC;IAEtC,IAAI,CAAC,GAAG,EAAE,CAAC;QACT,GAAG,GAAG,QAAQ,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC;QACpC,GAAG,CAAC,EAAE,GAAG,EAAE,CAAC;QACZ,GAAG,CAAC,YAAY,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;QACnC,GAAG,CAAC,KAAK,CAAC,OAAO;YACf,qEAAqE;gBACrE,0FAA0F;gBAC1F,sFAAsF;gBACtF,qEAAqE,CAAC;QAExE,uEAAuE;QACvE,gEAAgE;QAChE,MAAM,KAAK,GAAG,QAAQ,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC;QAE/C,KAAK,CAAC,IAAI,GAAG,QAAQ,CAAC;QACtB,KAAK,CAAC,WAAW,GAAG,QAAQ,CAAC;QAC7B,KAAK,CAAC,YAAY,CAAC,YAAY,EAAE,SAAS,CAAC,CAAC;QAC5C,KAAK,CAAC,KAAK,GAAG,SAAS,CAAC;QACxB,KAAK,CAAC,KAAK,CAAC,OAAO;YACjB,iFAAiF;gBACjF,wFAAwF,CAAC;QAC3F,KAAK,CAAC,gBAAgB,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,CAAC,CAAC;QACrD,GAAG,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC;QACvB,QAAQ,CAAC,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC;IACjC,CAAC;IAED,+CAA+C;IAC/C,KAAK,MAAM,IAAI,IAAI,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC5C,IAAI,IAAI,CAAC,OAAO,KAAK,QAAQ,IAAI,IAAI,CAAC,WAAW,KAAK,IAAI;YAAE,OAAO;IACrE,CAAC;IAED,MAAM,IAAI,GAAG,QAAQ,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC;IAE3C,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC;IACxB,GAAG,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC;AACxB,CAAC","sourcesContent":["/**\n * A line at the bottom of the page, in development only.\n *\n * The dev server prints the boundary the build will want, and the browser\n * console reports it; the person looking at the page sees neither. Next.js\n * shows a small badge for the same reason. This is that: fixed to the bottom\n * of the viewport, the message and the component it came from, with a\n * button to dismiss it. Nothing here reaches a production bundle - the DEV check is a\n * constant at build time and the code behind it is dropped.\n */\n\nconst ID = \"rsc-kit-dev-notice\";\n\n/** The first component in a React component stack: `at AuthTrigger (...)` → `AuthTrigger`. */\nexport function componentIn(\n componentStack: string | undefined | null,\n): string | null {\n const match = /at ([A-Z][\\w$]*)/.exec(componentStack ?? \"\");\n\n return match ? match[1]! : null;\n}\n\nexport function showDevNotice(\n message: string,\n componentStack?: string | null,\n): void {\n if (!import.meta.env?.DEV || typeof document === \"undefined\") return;\n\n const where = componentIn(componentStack);\n const text = (where ? `${where}: ` : \"\") + message;\n\n let box = document.getElementById(ID);\n\n if (!box) {\n box = document.createElement(\"div\");\n box.id = ID;\n box.setAttribute(\"role\", \"status\");\n box.style.cssText =\n \"position:fixed;left:12px;right:12px;bottom:12px;z-index:2147483647;\" +\n \"font:12px/1.5 ui-monospace,SFMono-Regular,Menlo,monospace;color:#fff;background:#b91c1c;\" +\n \"border-radius:6px;padding:10px 40px 10px 14px;box-shadow:0 4px 16px rgba(0,0,0,.35);\" +\n \"max-height:40vh;overflow:auto;white-space:pre-wrap;user-select:text\";\n\n // A button, not a click anywhere: the text is there to be selected and\n // copied, and a box that vanishes on the first click cannot be.\n const close = document.createElement(\"button\");\n\n close.type = \"button\";\n close.textContent = \"\\u00d7\";\n close.setAttribute(\"aria-label\", \"Dismiss\");\n close.title = \"Dismiss\";\n close.style.cssText =\n \"position:absolute;top:6px;right:8px;border:0;background:transparent;color:#fff;\" +\n \"font:16px/1 ui-monospace,SFMono-Regular,Menlo,monospace;cursor:pointer;padding:4px 6px\";\n close.addEventListener(\"click\", () => box?.remove());\n box.appendChild(close);\n document.body.appendChild(box);\n }\n\n // The same line twice is one problem, not two.\n for (const line of Array.from(box.children)) {\n if (line.tagName !== \"BUTTON\" && line.textContent === text) return;\n }\n\n const line = document.createElement(\"div\");\n\n line.textContent = text;\n box.appendChild(line);\n}\n"]}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Whether a read React caught at a boundary fell through to a loading.tsx.
|
|
3
|
+
*
|
|
4
|
+
* A component that reads the query string under a <Suspense> the developer
|
|
5
|
+
* wrote is the designed path: the fallback is stored, the browser fills it,
|
|
6
|
+
* and nothing needs saying. The same read with nothing closer than a
|
|
7
|
+
* loading.tsx - a boundary the engine put there for the whole segment - is
|
|
8
|
+
* worth a line, because the whole segment shows the fallback until the query
|
|
9
|
+
* arrives, and a boundary closer to the read would keep the rest painted.
|
|
10
|
+
*
|
|
11
|
+
* The component stack says which: frames run from the component outward,
|
|
12
|
+
* and the engine's segment boundaries render through SegmentBoundary or
|
|
13
|
+
* SlotBoundary, so the frame just outside the nearest Suspense tells whose
|
|
14
|
+
* it is. Only development has the stack; production reports nothing here,
|
|
15
|
+
* and the build's own note or warning on the route is the record.
|
|
16
|
+
*/
|
|
17
|
+
const ENGINE_BOUNDARY = /^\s*at (SegmentBoundary|SlotBoundary)\b/;
|
|
18
|
+
export function caughtByLoading(componentStack) {
|
|
19
|
+
const frames = (componentStack ?? "")
|
|
20
|
+
.split("\n")
|
|
21
|
+
.filter((line) => /^\s*at /.test(line));
|
|
22
|
+
const at = frames.findIndex((line) => /^\s*at Suspense\b/.test(line));
|
|
23
|
+
if (at === -1)
|
|
24
|
+
return false;
|
|
25
|
+
return frames
|
|
26
|
+
.slice(at + 1, at + 3)
|
|
27
|
+
.some((line) => ENGINE_BOUNDARY.test(line));
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Whether a render error is the consumer cancelling, not the app failing.
|
|
31
|
+
*
|
|
32
|
+
* React's server renderer reports an abort as an error, and the reason it
|
|
33
|
+
* gives when the stream was simply cancelled - a browser that left the page
|
|
34
|
+
* mid-stream, a prefetch abandoned, a proxy that closed - is its own fixed
|
|
35
|
+
* message. Logged, it reads as a fault in the page, and the page had none:
|
|
36
|
+
* "[rsc-kit:ssr] Error: The render was aborted by the server without a
|
|
37
|
+
* reason." on a dev console, every so often, for nothing. The same message
|
|
38
|
+
* from the payload renderer means the same thing.
|
|
39
|
+
*/
|
|
40
|
+
const CANCELLED = /^The render was aborted by the server (?:without a reason|with a promise)\.$/;
|
|
41
|
+
export function cancelledByConsumer(error) {
|
|
42
|
+
if (error instanceof DOMException)
|
|
43
|
+
return error.name === "AbortError";
|
|
44
|
+
const message = typeof error === "object" && error !== null && "message" in error
|
|
45
|
+
? String(error.message)
|
|
46
|
+
: typeof error === "string"
|
|
47
|
+
? error
|
|
48
|
+
: "";
|
|
49
|
+
return CANCELLED.test(message);
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=fallbackReport.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fallbackReport.js","sourceRoot":"","sources":["../../src/js/fallbackReport.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,MAAM,eAAe,GAAG,yCAAyC,CAAC;AAElE,MAAM,UAAU,eAAe,CAC7B,cAAyC;IAEzC,MAAM,MAAM,GAAG,CAAC,cAAc,IAAI,EAAE,CAAC;SAClC,KAAK,CAAC,IAAI,CAAC;SACX,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;IAC1C,MAAM,EAAE,GAAG,MAAM,CAAC,SAAS,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,mBAAmB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;IAEtE,IAAI,EAAE,KAAK,CAAC,CAAC;QAAE,OAAO,KAAK,CAAC;IAE5B,OAAO,MAAM;SACV,KAAK,CAAC,EAAE,GAAG,CAAC,EAAE,EAAE,GAAG,CAAC,CAAC;SACrB,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,eAAe,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;AAChD,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,SAAS,GACb,8EAA8E,CAAC;AAEjF,MAAM,UAAU,mBAAmB,CAAC,KAAc;IAChD,IAAI,KAAK,YAAY,YAAY;QAAE,OAAO,KAAK,CAAC,IAAI,KAAK,YAAY,CAAC;IAEtE,MAAM,OAAO,GACX,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,SAAS,IAAI,KAAK;QAC/D,CAAC,CAAC,MAAM,CAAE,KAA8B,CAAC,OAAO,CAAC;QACjD,CAAC,CAAC,OAAO,KAAK,KAAK,QAAQ;YACzB,CAAC,CAAC,KAAK;YACP,CAAC,CAAC,EAAE,CAAC;IAEX,OAAO,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;AACjC,CAAC","sourcesContent":["/**\n * Whether a read React caught at a boundary fell through to a loading.tsx.\n *\n * A component that reads the query string under a <Suspense> the developer\n * wrote is the designed path: the fallback is stored, the browser fills it,\n * and nothing needs saying. The same read with nothing closer than a\n * loading.tsx - a boundary the engine put there for the whole segment - is\n * worth a line, because the whole segment shows the fallback until the query\n * arrives, and a boundary closer to the read would keep the rest painted.\n *\n * The component stack says which: frames run from the component outward,\n * and the engine's segment boundaries render through SegmentBoundary or\n * SlotBoundary, so the frame just outside the nearest Suspense tells whose\n * it is. Only development has the stack; production reports nothing here,\n * and the build's own note or warning on the route is the record.\n */\nconst ENGINE_BOUNDARY = /^\\s*at (SegmentBoundary|SlotBoundary)\\b/;\n\nexport function caughtByLoading(\n componentStack: string | null | undefined,\n): boolean {\n const frames = (componentStack ?? \"\")\n .split(\"\\n\")\n .filter((line) => /^\\s*at /.test(line));\n const at = frames.findIndex((line) => /^\\s*at Suspense\\b/.test(line));\n\n if (at === -1) return false;\n\n return frames\n .slice(at + 1, at + 3)\n .some((line) => ENGINE_BOUNDARY.test(line));\n}\n\n/**\n * Whether a render error is the consumer cancelling, not the app failing.\n *\n * React's server renderer reports an abort as an error, and the reason it\n * gives when the stream was simply cancelled - a browser that left the page\n * mid-stream, a prefetch abandoned, a proxy that closed - is its own fixed\n * message. Logged, it reads as a fault in the page, and the page had none:\n * \"[rsc-kit:ssr] Error: The render was aborted by the server without a\n * reason.\" on a dev console, every so often, for nothing. The same message\n * from the payload renderer means the same thing.\n */\nconst CANCELLED =\n /^The render was aborted by the server (?:without a reason|with a promise)\\.$/;\n\nexport function cancelledByConsumer(error: unknown): boolean {\n if (error instanceof DOMException) return error.name === \"AbortError\";\n\n const message =\n typeof error === \"object\" && error !== null && \"message\" in error\n ? String((error as { message: unknown }).message)\n : typeof error === \"string\"\n ? error\n : \"\";\n\n return CANCELLED.test(message);\n}\n"]}
|
package/dist/js/navigate.d.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* The Flight deserializer is injected by createViteRscApp to avoid
|
|
6
6
|
* duplicate bundling of react-server-dom-webpack.
|
|
7
7
|
*/
|
|
8
|
-
import type { Href } from
|
|
8
|
+
import type { Href } from "../routes.js";
|
|
9
9
|
import type { ManifestRoute } from "../manifest";
|
|
10
10
|
type ReactNode = unknown;
|
|
11
11
|
type Deserializer = (stream: ReadableStream, options: Record<string, unknown>) => Promise<ReactNode>;
|
package/dist/js/navigate.js
CHANGED
|
@@ -5,12 +5,13 @@
|
|
|
5
5
|
* The Flight deserializer is injected by createViteRscApp to avoid
|
|
6
6
|
* duplicate bundling of react-server-dom-webpack.
|
|
7
7
|
*/
|
|
8
|
-
import {
|
|
8
|
+
import { isStaleAssetError } from "./staleAssets";
|
|
9
|
+
import { isSafeRedirect } from "../safeUrl.js";
|
|
9
10
|
import { reportReachable } from "./onlineStore";
|
|
10
11
|
import { clearSlots, setSlot } from "./slotStore";
|
|
11
12
|
// Shared with the host: it stores a page under this key and the client looks
|
|
12
13
|
// under it, so the format cannot live in two places.
|
|
13
|
-
import { matchRoute, retentionKey as retentionKeyFor, sharedDepth } from "../routing";
|
|
14
|
+
import { matchRoute, retentionKey as retentionKeyFor, sharedDepth, } from "../routing";
|
|
14
15
|
let version = "";
|
|
15
16
|
let onNavigate = null;
|
|
16
17
|
let onRestore = null;
|
|
@@ -278,7 +279,11 @@ priority = "high") {
|
|
|
278
279
|
}
|
|
279
280
|
// `priority` is not in every lib.dom yet; browsers without it ignore it.
|
|
280
281
|
const asked = staticSegments(url, chain);
|
|
281
|
-
const request = fetch(payloadUrl(url, chain), {
|
|
282
|
+
const request = fetch(payloadUrl(url, chain), {
|
|
283
|
+
headers,
|
|
284
|
+
signal,
|
|
285
|
+
priority,
|
|
286
|
+
}).catch((err) => {
|
|
282
287
|
// Nothing answered at all. An abort is our own doing, not the network's —
|
|
283
288
|
// leaving a link cancels its prefetch, and that must not read as offline.
|
|
284
289
|
if (!(err instanceof DOMException && err.name === "AbortError")) {
|
|
@@ -304,7 +309,9 @@ priority = "high") {
|
|
|
304
309
|
const location = response.headers.get("X-RSC-Location");
|
|
305
310
|
// Server-chosen, so checked again here: the engine refuses these at the
|
|
306
311
|
// source, but a host in front of it can put anything on the header.
|
|
307
|
-
window.location.href = isSafeRedirect(location ?? url)
|
|
312
|
+
window.location.href = isSafeRedirect(location ?? url)
|
|
313
|
+
? (location ?? url)
|
|
314
|
+
: url;
|
|
308
315
|
throw new Error("Version mismatch — full reload triggered");
|
|
309
316
|
}
|
|
310
317
|
return response;
|
|
@@ -320,14 +327,15 @@ priority = "high") {
|
|
|
320
327
|
*/
|
|
321
328
|
function deserializeResponse(response) {
|
|
322
329
|
return flightDeserializer(response.body, {
|
|
323
|
-
callServer: callServerFn ??
|
|
324
|
-
|
|
325
|
-
|
|
330
|
+
callServer: callServerFn ??
|
|
331
|
+
(async () => {
|
|
332
|
+
throw new Error("Server actions not initialized");
|
|
333
|
+
}),
|
|
326
334
|
});
|
|
327
335
|
}
|
|
328
336
|
function isExternalUrl(url) {
|
|
329
337
|
try {
|
|
330
|
-
return new URL(url, window.location.origin).origin !== window.location.origin;
|
|
338
|
+
return (new URL(url, window.location.origin).origin !== window.location.origin);
|
|
331
339
|
}
|
|
332
340
|
catch {
|
|
333
341
|
return false;
|
|
@@ -458,9 +466,13 @@ export async function navigate(url, opts) {
|
|
|
458
466
|
// revealing the page underneath instead leaves the modal sitting over a page
|
|
459
467
|
// whose url has already changed.
|
|
460
468
|
const mayReveal = opts?.restore === true ||
|
|
461
|
-
(!askingForThisPage &&
|
|
469
|
+
(!askingForThisPage &&
|
|
470
|
+
!interceptSlot &&
|
|
471
|
+
interceptedOver === null &&
|
|
472
|
+
interceptedAtDepth === null);
|
|
462
473
|
// Unbounded for the back button, bounded for a link.
|
|
463
|
-
if (mayReveal &&
|
|
474
|
+
if (mayReveal &&
|
|
475
|
+
onRestore?.(activityKey, opts?.restore ? undefined : revealWithin)) {
|
|
464
476
|
// A restored tree carries its own slot contents, so the flag only has to
|
|
465
477
|
// reflect whether what is now showing is an intercepted view.
|
|
466
478
|
if (!interceptSlot)
|
|
@@ -525,18 +537,23 @@ export async function navigate(url, opts) {
|
|
|
525
537
|
// replace: the url that redirected never became a page the user was
|
|
526
538
|
// on, so Back must not return to it and redirect again.
|
|
527
539
|
// Chosen by the server, not written here.
|
|
528
|
-
await navigate(redirectTo, {
|
|
540
|
+
await navigate(redirectTo, {
|
|
541
|
+
replace: true,
|
|
542
|
+
redirectsFollowed: redirectsFollowed + 1,
|
|
543
|
+
});
|
|
529
544
|
return;
|
|
530
545
|
}
|
|
531
546
|
const contentType = response.headers.get("Content-Type") ?? "";
|
|
532
|
-
if (staticPayloadSuffix === null &&
|
|
547
|
+
if (staticPayloadSuffix === null &&
|
|
548
|
+
!contentType.includes("text/x-component")) {
|
|
533
549
|
window.location.href = url;
|
|
534
550
|
return;
|
|
535
551
|
}
|
|
536
552
|
// Headers on a server; worked out locally on a static host, where there
|
|
537
553
|
// is nothing to send them.
|
|
538
554
|
const served = staticFetches.get(response) ?? null;
|
|
539
|
-
segmentDepth =
|
|
555
|
+
segmentDepth =
|
|
556
|
+
Number(response.headers.get("X-RSC-Segment-Depth") ?? served?.depth ?? 0) || 0;
|
|
540
557
|
// Named region rather than a segment — see the apply below.
|
|
541
558
|
slotPayload = response.headers.get("X-RSC-Revalidate");
|
|
542
559
|
const servedLayouts = response.headers.get("X-RSC-Layouts");
|
|
@@ -592,6 +609,12 @@ export async function navigate(url, opts) {
|
|
|
592
609
|
catch (err) {
|
|
593
610
|
if (err instanceof DOMException && err.name === "AbortError")
|
|
594
611
|
return;
|
|
612
|
+
// A chunk the deploy no longer serves: the browser would have loaded the
|
|
613
|
+
// document, and the new names with it. Do what it would have done.
|
|
614
|
+
if (isStaleAssetError(err)) {
|
|
615
|
+
window.location.href = url;
|
|
616
|
+
return;
|
|
617
|
+
}
|
|
595
618
|
// Without this a navigation that fails does nothing observable: the click
|
|
596
619
|
// clears its own pending state and the page stays as it was, with no
|
|
597
620
|
// error, no fallback and nothing for an app to react to. Dispatched before
|
|
@@ -615,12 +638,12 @@ export async function navigate(url, opts) {
|
|
|
615
638
|
export function applyRevalidated(target, tree) {
|
|
616
639
|
const url = window.location.pathname + window.location.search;
|
|
617
640
|
const key = retentionKey(url, null);
|
|
618
|
-
if (target ===
|
|
641
|
+
if (target === "all") {
|
|
619
642
|
// Depth 0 replaces the root, which is what re-rendering the layouts means.
|
|
620
643
|
onNavigate?.(tree, key, 0);
|
|
621
644
|
return;
|
|
622
645
|
}
|
|
623
|
-
if (target ===
|
|
646
|
+
if (target === "page") {
|
|
624
647
|
onNavigate?.(tree, key, heldLayouts.length);
|
|
625
648
|
return;
|
|
626
649
|
}
|
|
@@ -643,11 +666,15 @@ export function applyRevalidated(target, tree) {
|
|
|
643
666
|
* form leaves the layouts alone, which is what makes it cheap and also why a
|
|
644
667
|
* count living in a layout will not move until you ask for 'all'.
|
|
645
668
|
*/
|
|
646
|
-
export async function refresh(target =
|
|
669
|
+
export async function refresh(target = "page") {
|
|
647
670
|
const url = window.location.pathname + window.location.search;
|
|
648
|
-
if (target !==
|
|
671
|
+
if (target !== "page" && target !== "all") {
|
|
649
672
|
const response = await fetch(payloadUrl(url), {
|
|
650
|
-
headers: {
|
|
673
|
+
headers: {
|
|
674
|
+
"X-RSC": "true",
|
|
675
|
+
"X-RSC-Version": version,
|
|
676
|
+
"X-RSC-Revalidate": target,
|
|
677
|
+
},
|
|
651
678
|
});
|
|
652
679
|
if (!response.ok) {
|
|
653
680
|
throw new Error(`Could not revalidate ${target}: the server answered ${response.status}`);
|
|
@@ -660,7 +687,7 @@ export async function refresh(target = 'page') {
|
|
|
660
687
|
// Never from the cache: refreshing asks what the server says now, not what
|
|
661
688
|
// it said a moment ago.
|
|
662
689
|
cache.delete(cacheKey);
|
|
663
|
-
if (target ===
|
|
690
|
+
if (target === "all") {
|
|
664
691
|
heldLayouts = [];
|
|
665
692
|
}
|
|
666
693
|
// A refresh is not a navigation: the same page stays under the reader, so
|
|
@@ -679,7 +706,7 @@ export async function refresh(target = 'page') {
|
|
|
679
706
|
}
|
|
680
707
|
/** Every element that can scroll, whether or not it currently is. */
|
|
681
708
|
function scrollables() {
|
|
682
|
-
return [...document.querySelectorAll(
|
|
709
|
+
return [...document.querySelectorAll("*")].filter((el) => el.scrollHeight > el.clientHeight || el.scrollWidth > el.clientWidth);
|
|
683
710
|
}
|
|
684
711
|
/**
|
|
685
712
|
* Where everything on the page is scrolled to, in document order.
|
|
@@ -697,8 +724,12 @@ function scrollables() {
|
|
|
697
724
|
*/
|
|
698
725
|
function scrollPositions() {
|
|
699
726
|
return [
|
|
700
|
-
{ tag:
|
|
701
|
-
...scrollables().map((el) => ({
|
|
727
|
+
{ tag: "window", top: window.scrollY, left: window.scrollX },
|
|
728
|
+
...scrollables().map((el) => ({
|
|
729
|
+
tag: el.tagName,
|
|
730
|
+
top: el.scrollTop,
|
|
731
|
+
left: el.scrollLeft,
|
|
732
|
+
})),
|
|
702
733
|
];
|
|
703
734
|
}
|
|
704
735
|
/**
|
|
@@ -789,7 +820,8 @@ function prefetchUrl(cacheKey, url, ttl, interceptSlot, refererUrl) {
|
|
|
789
820
|
}
|
|
790
821
|
entry.slot = response.headers.get("X-RSC-Revalidate");
|
|
791
822
|
const local = staticFetches.get(response) ?? null;
|
|
792
|
-
entry.segmentDepth =
|
|
823
|
+
entry.segmentDepth =
|
|
824
|
+
Number(response.headers.get("X-RSC-Segment-Depth") ?? local?.depth ?? 0) || 0;
|
|
793
825
|
const served = response.headers.get("X-RSC-Layouts");
|
|
794
826
|
if (served !== null) {
|
|
795
827
|
entry.layouts = served === "" ? [] : served.split(",");
|