@rsc-kit/core 0.20.3 → 0.20.5
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/earlyHints.d.ts +17 -0
- package/dist/earlyHints.js +65 -0
- package/dist/earlyHints.js.map +1 -0
- package/dist/host.d.ts +3 -0
- package/dist/host.js +31 -1
- package/dist/host.js.map +1 -1
- package/dist/js/Link.js +1 -1
- package/dist/js/Link.js.map +1 -1
- package/dist/js/PathnameProvider.js +14 -1
- package/dist/js/PathnameProvider.js.map +1 -1
- package/dist/js/createViteRscApp.js +6 -0
- package/dist/js/createViteRscApp.js.map +1 -1
- package/dist/js/earlyClicks.d.ts +35 -0
- package/dist/js/earlyClicks.js +67 -0
- package/dist/js/earlyClicks.js.map +1 -0
- package/dist/js/navigate.js +95 -11
- package/dist/js/navigate.js.map +1 -1
- package/dist/js/updateStore.d.ts +6 -0
- package/dist/js/updateStore.js +48 -2
- package/dist/js/updateStore.js.map +1 -1
- package/dist/vite.js +113 -11
- package/dist/vite.js.map +1 -1
- package/package.json +1 -1
package/dist/js/Link.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Link.js","sourceRoot":"","sources":["../../src/js/Link.tsx"],"names":[],"mappings":"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","sourcesContent":["\"use client\";\n\nimport { LinkStatusContext } from \"./useLinkStatus\";\nimport { prefetchWhenVisible } from \"./viewportPrefetch\";\nimport { type Route, type SearchProp, withSearch } from \"../routes.js\";\nimport {\n type AnchorHTMLAttributes,\n type MouseEvent,\n type Ref,\n useCallback,\n useEffect,\n useRef,\n useState,\n} from \"react\";\n\ntype PrefetchStrategy = \"hover\" | \"mount\" | \"click\" | \"none\" | boolean;\n\ninterface LinkBaseProps<H extends Route> extends Omit<\n AnchorHTMLAttributes<HTMLAnchorElement>,\n \"href\"\n> {\n /**\n * Where this goes. Typed to the routes the build found, so a link to a page\n * that does not exist stops compiling; `path as Route` when it is computed.\n */\n href: H;\n /**\n * The underlying anchor.\n *\n * Spread onto it like any other prop — React 19 passes ref through a\n * function component without forwardRef — but declared here because it is\n * not part of AnchorHTMLAttributes. A link that cannot be focused is a link\n * a dialog cannot move focus to when it opens.\n */\n ref?: Ref<HTMLAnchorElement>;\n prefetch?: PrefetchStrategy;\n cacheFor?: number;\n replace?: boolean;\n preserveScroll?: boolean;\n}\n\n/**\n * `search` is typed to the page's own `searchParams` schema when it exports\n * one — the same schema the page parses with, so a key it never reads or a\n * number written as text does not compile, and a key it requires is required\n * here. With no schema, any scalars. See SearchFor in routes.ts.\n */\ntype LinkProps<H extends Route> = LinkBaseProps<H> & SearchProp<H>;\n\nfunction isExternalUrl(url: string): boolean {\n try {\n return (\n new URL(url, window.location.origin).origin !== window.location.origin\n );\n } catch {\n return false;\n }\n}\n\nfunction shouldInterceptClick(e: MouseEvent<HTMLAnchorElement>): boolean {\n return (\n !e.defaultPrevented &&\n e.button === 0 &&\n !e.metaKey &&\n !e.ctrlKey &&\n !e.shiftKey &&\n !e.altKey\n );\n}\n\n/**\n * How long a pointer has to settle on a link before it prefetches.\n *\n * A pointer crossing a nav bar enters every link on the way, and each one used\n * to fire a request immediately — enough to fill the browser's per-origin\n * connection limit with pages the user never meant to visit. Waiting is close\n * to free: the prefetch only has to beat the click, and a click that follows a\n * hover this short would not have had its payload back anyway.\n */\nconst HOVER_PREFETCH_DELAY_MS = 100;\n\nexport default function Link<H extends Route>({\n href: path,\n search,\n prefetch: prefetchProp = \"hover\",\n cacheFor,\n replace = false,\n preserveScroll = false,\n children,\n onClick,\n onMouseEnter,\n onMouseLeave,\n ref: callerRef,\n ...rest\n}: LinkProps<H>) {\n // The string the anchor and the router both use: the path, with the typed\n // search params serialised onto it.\n const href = (search ? withSearch(path, search as object) : path) as Route;\n const [pending, setPending] = useState(false);\n const hoverTimer = useRef<ReturnType<typeof setTimeout> | null>(null);\n\n const prefetchStrategy =\n prefetchProp === true\n ? \"hover\"\n : prefetchProp === false\n ? \"none\"\n : prefetchProp;\n\n const doPrefetch = useCallback(() => {\n if (isExternalUrl(href)) return;\n const fn = (window as any).__rsc_prefetch;\n fn?.(href, cacheFor);\n }, [href, cacheFor]);\n\n // Only useEffect needed: prefetch on mount strategy\n useEffect(() => {\n if (prefetchStrategy === \"mount\") {\n doPrefetch();\n }\n }, [prefetchStrategy, doPrefetch]);\n\n // Where nothing can hover, the link prefetches as it comes into view -\n // the head start a touch gives is a round trip short. See viewportPrefetch.\n const anchor = useRef<HTMLAnchorElement | null>(null);\n\n useEffect(() => {\n if (prefetchStrategy !== \"hover\") return;\n\n return prefetchWhenVisible(anchor.current, doPrefetch);\n }, [prefetchStrategy, doPrefetch]);\n\n const handleClick = useCallback(\n (e: MouseEvent<HTMLAnchorElement>) => {\n onClick?.(e);\n\n if (e.defaultPrevented) return;\n\n const target = (e.currentTarget as HTMLAnchorElement).target;\n if (target && target !== \"_self\") return;\n if (!shouldInterceptClick(e) || isExternalUrl(href)) return;\n\n // Hash-only links (#section) — let the browser scroll natively\n if (href.startsWith(\"#\")) return;\n\n e.preventDefault();\n setPending(true);\n\n // A hover that had not yet prefetched: the click is the navigation, and\n // a prefetch of the page being left for, firing after it, is a request\n // for nothing - seen as a stray 204 for a guarded link after landing\n // on the login page.\n if (hoverTimer.current !== null) {\n clearTimeout(hoverTimer.current);\n hoverTimer.current = null;\n }\n\n // navigate() returns a Promise — clear pending when it resolves or rejects\n const nav = (window as any).__rsc_navigate;\n const promise = nav?.(href, { replace, preserveScroll });\n promise?.then(\n () => setPending(false),\n () => setPending(false),\n );\n },\n [href, replace, preserveScroll, onClick],\n );\n\n const handleMouseEnter = useCallback(\n (e: MouseEvent<HTMLAnchorElement>) => {\n onMouseEnter?.(e);\n\n if (prefetchStrategy !== \"hover\" && prefetchStrategy !== \"click\") return;\n\n if (hoverTimer.current !== null) clearTimeout(hoverTimer.current);\n\n hoverTimer.current = setTimeout(() => {\n hoverTimer.current = null;\n doPrefetch();\n }, HOVER_PREFETCH_DELAY_MS);\n },\n [prefetchStrategy, doPrefetch, onMouseEnter],\n );\n\n const handleMouseLeave = useCallback(\n (e: MouseEvent<HTMLAnchorElement>) => {\n onMouseLeave?.(e);\n\n // Never fired: the pointer passed over on its way somewhere else.\n if (hoverTimer.current !== null) {\n clearTimeout(hoverTimer.current);\n hoverTimer.current = null;\n }\n\n // Already in flight: give the connection back to whatever the pointer is\n // heading for. A prefetch that has landed is kept — see cancelPrefetch.\n if (isExternalUrl(href)) return;\n (window as any).__rsc_cancel_prefetch?.(href);\n },\n [href, onMouseLeave],\n );\n\n // Touch gets no delay. There is no hovering to disambiguate — a touch is\n // already the start of a tap — and touchstart leads the click by little\n // enough that spending any of it waiting would waste the head start.\n const handleTouchStart = useCallback(() => {\n if (prefetchStrategy === \"hover\" || prefetchStrategy === \"click\") {\n doPrefetch();\n }\n }, [prefetchStrategy, doPrefetch]);\n\n // A link unmounted mid-hover (navigating away) must not prefetch afterwards.\n useEffect(\n () => () => {\n if (hoverTimer.current !== null) clearTimeout(hoverTimer.current);\n },\n [],\n );\n\n return (\n <LinkStatusContext.Provider value={{ pending }}>\n <a\n // The caller's ref still fills: a dialog moving focus to its link\n // needs the element as much as the observer above does.\n ref={(node) => {\n anchor.current = node;\n\n if (typeof callerRef === \"function\") callerRef(node);\n else if (callerRef) (callerRef as { current: HTMLAnchorElement | null }).current = node;\n }}\n href={href}\n onClick={handleClick}\n onMouseEnter={handleMouseEnter}\n onMouseLeave={handleMouseLeave}\n onTouchStart={handleTouchStart}\n data-pending={pending ? \"\" : undefined}\n {...rest}\n >\n {children}\n </a>\n </LinkStatusContext.Provider>\n );\n}\n"]}
|
|
1
|
+
{"version":3,"file":"Link.js","sourceRoot":"","sources":["../../src/js/Link.tsx"],"names":[],"mappings":"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","sourcesContent":["\"use client\";\n\nimport { LinkStatusContext } from \"./useLinkStatus\";\nimport { prefetchWhenVisible } from \"./viewportPrefetch\";\nimport { type Route, type SearchProp, withSearch } from \"../routes.js\";\nimport {\n type AnchorHTMLAttributes,\n type MouseEvent,\n type Ref,\n useCallback,\n useEffect,\n useRef,\n useState,\n} from \"react\";\n\ntype PrefetchStrategy = \"hover\" | \"mount\" | \"click\" | \"none\" | boolean;\n\ninterface LinkBaseProps<H extends Route> extends Omit<\n AnchorHTMLAttributes<HTMLAnchorElement>,\n \"href\"\n> {\n /**\n * Where this goes. Typed to the routes the build found, so a link to a page\n * that does not exist stops compiling; `path as Route` when it is computed.\n */\n href: H;\n /**\n * The underlying anchor.\n *\n * Spread onto it like any other prop — React 19 passes ref through a\n * function component without forwardRef — but declared here because it is\n * not part of AnchorHTMLAttributes. A link that cannot be focused is a link\n * a dialog cannot move focus to when it opens.\n */\n ref?: Ref<HTMLAnchorElement>;\n prefetch?: PrefetchStrategy;\n cacheFor?: number;\n replace?: boolean;\n preserveScroll?: boolean;\n}\n\n/**\n * `search` is typed to the page's own `searchParams` schema when it exports\n * one — the same schema the page parses with, so a key it never reads or a\n * number written as text does not compile, and a key it requires is required\n * here. With no schema, any scalars. See SearchFor in routes.ts.\n */\ntype LinkProps<H extends Route> = LinkBaseProps<H> & SearchProp<H>;\n\nfunction isExternalUrl(url: string): boolean {\n try {\n return (\n new URL(url, window.location.origin).origin !== window.location.origin\n );\n } catch {\n return false;\n }\n}\n\nfunction shouldInterceptClick(e: MouseEvent<HTMLAnchorElement>): boolean {\n return (\n !e.defaultPrevented &&\n e.button === 0 &&\n !e.metaKey &&\n !e.ctrlKey &&\n !e.shiftKey &&\n !e.altKey\n );\n}\n\n/**\n * How long a pointer has to settle on a link before it prefetches.\n *\n * A pointer crossing a nav bar enters every link on the way, and each one used\n * to fire a request immediately — enough to fill the browser's per-origin\n * connection limit with pages the user never meant to visit. Waiting is close\n * to free: the prefetch only has to beat the click, and a click that follows a\n * hover this short would not have had its payload back anyway.\n */\nconst HOVER_PREFETCH_DELAY_MS = 100;\n\nexport default function Link<H extends Route>({\n href: path,\n search,\n prefetch: prefetchProp = \"hover\",\n cacheFor,\n replace = false,\n preserveScroll = false,\n children,\n onClick,\n onMouseEnter,\n onMouseLeave,\n ref: callerRef,\n ...rest\n}: LinkProps<H>) {\n // The string the anchor and the router both use: the path, with the typed\n // search params serialised onto it.\n const href = (search ? withSearch(path, search as object) : path) as Route;\n const [pending, setPending] = useState(false);\n const hoverTimer = useRef<ReturnType<typeof setTimeout> | null>(null);\n\n const prefetchStrategy =\n prefetchProp === true\n ? \"hover\"\n : prefetchProp === false\n ? \"none\"\n : prefetchProp;\n\n const doPrefetch = useCallback(() => {\n if (isExternalUrl(href)) return;\n const fn = (window as any).__rsc_prefetch;\n fn?.(href, cacheFor);\n }, [href, cacheFor]);\n\n // Only useEffect needed: prefetch on mount strategy\n useEffect(() => {\n if (prefetchStrategy === \"mount\") {\n doPrefetch();\n }\n }, [prefetchStrategy, doPrefetch]);\n\n // Where nothing can hover, the link prefetches as it comes into view -\n // the head start a touch gives is a round trip short. See viewportPrefetch.\n const anchor = useRef<HTMLAnchorElement | null>(null);\n\n useEffect(() => {\n if (prefetchStrategy !== \"hover\") return;\n\n return prefetchWhenVisible(anchor.current, doPrefetch);\n }, [prefetchStrategy, doPrefetch]);\n\n const handleClick = useCallback(\n (e: MouseEvent<HTMLAnchorElement>) => {\n onClick?.(e);\n\n if (e.defaultPrevented) return;\n\n const target = (e.currentTarget as HTMLAnchorElement).target;\n if (target && target !== \"_self\") return;\n if (!shouldInterceptClick(e) || isExternalUrl(href)) return;\n\n // Hash-only links (#section) — let the browser scroll natively\n if (href.startsWith(\"#\")) return;\n\n e.preventDefault();\n setPending(true);\n\n // A hover that had not yet prefetched: the click is the navigation, and\n // a prefetch of the page being left for, firing after it, is a request\n // for nothing - seen as a stray 204 for a guarded link after landing\n // on the login page.\n if (hoverTimer.current !== null) {\n clearTimeout(hoverTimer.current);\n hoverTimer.current = null;\n }\n\n // navigate() returns a Promise — clear pending when it resolves or rejects\n const nav = (window as any).__rsc_navigate;\n const promise = nav?.(href, { replace, preserveScroll });\n promise?.then(\n () => setPending(false),\n () => setPending(false),\n );\n },\n [href, replace, preserveScroll, onClick],\n );\n\n const handleMouseEnter = useCallback(\n (e: MouseEvent<HTMLAnchorElement>) => {\n onMouseEnter?.(e);\n\n if (prefetchStrategy !== \"hover\" && prefetchStrategy !== \"click\") return;\n\n if (hoverTimer.current !== null) clearTimeout(hoverTimer.current);\n\n hoverTimer.current = setTimeout(() => {\n hoverTimer.current = null;\n doPrefetch();\n }, HOVER_PREFETCH_DELAY_MS);\n },\n [prefetchStrategy, doPrefetch, onMouseEnter],\n );\n\n const handleMouseLeave = useCallback(\n (e: MouseEvent<HTMLAnchorElement>) => {\n onMouseLeave?.(e);\n\n // Never fired: the pointer passed over on its way somewhere else.\n if (hoverTimer.current !== null) {\n clearTimeout(hoverTimer.current);\n hoverTimer.current = null;\n }\n\n // Already in flight: give the connection back to whatever the pointer is\n // heading for. A prefetch that has landed is kept — see cancelPrefetch.\n if (isExternalUrl(href)) return;\n (window as any).__rsc_cancel_prefetch?.(href);\n },\n [href, onMouseLeave],\n );\n\n // Touch gets no delay. There is no hovering to disambiguate — a touch is\n // already the start of a tap — and touchstart leads the click by little\n // enough that spending any of it waiting would waste the head start.\n const handleTouchStart = useCallback(() => {\n if (prefetchStrategy === \"hover\" || prefetchStrategy === \"click\") {\n doPrefetch();\n }\n }, [prefetchStrategy, doPrefetch]);\n\n // A link unmounted mid-hover (navigating away) must not prefetch afterwards.\n useEffect(\n () => () => {\n if (hoverTimer.current !== null) clearTimeout(hoverTimer.current);\n },\n [],\n );\n\n return (\n <LinkStatusContext.Provider value={{ pending }}>\n <a\n // The caller's ref still fills: a dialog moving focus to its link\n // needs the element as much as the observer above does.\n ref={(node) => {\n anchor.current = node;\n\n if (typeof callerRef === \"function\") callerRef(node);\n else if (callerRef) (callerRef as { current: HTMLAnchorElement | null }).current = node;\n }}\n href={href}\n // The mark the bootstrap script's click listener looks for: a tap\n // on this before the router exists is held for it. See earlyClicks.\n data-rsc=\"\"\n onClick={handleClick}\n onMouseEnter={handleMouseEnter}\n onMouseLeave={handleMouseLeave}\n onTouchStart={handleTouchStart}\n data-pending={pending ? \"\" : undefined}\n {...rest}\n >\n {children}\n </a>\n </LinkStatusContext.Provider>\n );\n}\n"]}
|
|
@@ -12,9 +12,22 @@ import { jsx as _jsx } from "react/jsx-runtime";
|
|
|
12
12
|
* A context rather than a module-level variable: two requests render at once
|
|
13
13
|
* in the same process, and a variable would hand one page the other's url.
|
|
14
14
|
*/
|
|
15
|
-
import { createContext, useContext } from "react";
|
|
15
|
+
import { createContext, useContext, useEffect } from "react";
|
|
16
16
|
const PathnameContext = createContext("/");
|
|
17
17
|
export function PathnameProvider({ value, children }) {
|
|
18
|
+
// The outermost client component on every page with a runtime, so its
|
|
19
|
+
// first effect is the moment hydration has committed - the moment a
|
|
20
|
+
// click on a <Link> is React's to handle. Until then the bootstrap
|
|
21
|
+
// script's listener holds the taps; see earlyClicks. Announced through a
|
|
22
|
+
// global rather than a module, because this component and the runtime
|
|
23
|
+
// are bundled apart.
|
|
24
|
+
useEffect(() => {
|
|
25
|
+
const w = window;
|
|
26
|
+
if (w.__rsc_hydrated)
|
|
27
|
+
return;
|
|
28
|
+
w.__rsc_hydrated = true;
|
|
29
|
+
w.__rsc_on_hydrated?.();
|
|
30
|
+
}, []);
|
|
18
31
|
return _jsx(PathnameContext.Provider, { value: value, children: children });
|
|
19
32
|
}
|
|
20
33
|
/** What the server rendered. On the client this is the url it was hydrated with. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PathnameProvider.js","sourceRoot":"","sources":["../../src/js/PathnameProvider.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAC;;AAEb;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"PathnameProvider.js","sourceRoot":"","sources":["../../src/js/PathnameProvider.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAC;;AAEb;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAG7D,MAAM,eAAe,GAAG,aAAa,CAAS,GAAG,CAAC,CAAC;AAEnD,MAAM,UAAU,gBAAgB,CAAC,EAAE,KAAK,EAAE,QAAQ,EAA0C;IAC1F,sEAAsE;IACtE,oEAAoE;IACpE,mEAAmE;IACnE,yEAAyE;IACzE,sEAAsE;IACtE,qBAAqB;IACrB,SAAS,CAAC,GAAG,EAAE;QACb,MAAM,CAAC,GAAG,MAAsE,CAAC;QAEjF,IAAI,CAAC,CAAC,cAAc;YAAE,OAAO;QAE7B,CAAC,CAAC,cAAc,GAAG,IAAI,CAAC;QACxB,CAAC,CAAC,iBAAiB,EAAE,EAAE,CAAC;IAC1B,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,OAAO,KAAC,eAAe,CAAC,QAAQ,IAAC,KAAK,EAAE,KAAK,YAAG,QAAQ,GAA4B,CAAC;AACvF,CAAC;AAED,oFAAoF;AACpF,MAAM,UAAU,mBAAmB;IACjC,OAAO,UAAU,CAAC,eAAe,CAAC,CAAC;AACrC,CAAC","sourcesContent":["\"use client\";\n\n/**\n * The url the server rendered, handed to the client hooks.\n *\n * usePathname used to answer \"/\" during a server render, because a hook has no\n * props and the module had nothing else to go on. Every prerendered page\n * therefore shipped with the wrong link marked active, corrected only once\n * hydration ran — a visible flash, and simply wrong on a route that ships no\n * client runtime at all.\n *\n * A context rather than a module-level variable: two requests render at once\n * in the same process, and a variable would hand one page the other's url.\n */\n\nimport { createContext, useContext, useEffect } from \"react\";\nimport type { ReactNode } from \"react\";\n\nconst PathnameContext = createContext<string>(\"/\");\n\nexport function PathnameProvider({ value, children }: { value: string; children: ReactNode }) {\n // The outermost client component on every page with a runtime, so its\n // first effect is the moment hydration has committed - the moment a\n // click on a <Link> is React's to handle. Until then the bootstrap\n // script's listener holds the taps; see earlyClicks. Announced through a\n // global rather than a module, because this component and the runtime\n // are bundled apart.\n useEffect(() => {\n const w = window as { __rsc_hydrated?: boolean; __rsc_on_hydrated?: () => void };\n\n if (w.__rsc_hydrated) return;\n\n w.__rsc_hydrated = true;\n w.__rsc_on_hydrated?.();\n }, []);\n\n return <PathnameContext.Provider value={value}>{children}</PathnameContext.Provider>;\n}\n\n/** What the server rendered. On the client this is the url it was hydrated with. */\nexport function useRenderedPathname(): string {\n return useContext(PathnameContext);\n}\n"]}
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
// resolves client references itself.
|
|
6
6
|
import { SEARCH_PARAMS_FALLBACK } from "./useSearchParams";
|
|
7
7
|
import { recoverFromStaleAssets } from "./staleAssets";
|
|
8
|
+
import { afterHydration, replayEarlyClicks } from "./earlyClicks";
|
|
8
9
|
import { parseRedirectDigest } from "../redirectDigest.js";
|
|
9
10
|
import { showDevNotice } from "./devNotice";
|
|
10
11
|
import { caughtByLoading } from "./fallbackReport";
|
|
@@ -316,5 +317,10 @@ export async function createViteRscApp(container = document, interceptEntries =
|
|
|
316
317
|
navigate(window.location.href, { replace: true, restore: true });
|
|
317
318
|
});
|
|
318
319
|
history.replaceState({ rscUrl: window.location.href }, "", window.location.href);
|
|
320
|
+
// A tap held by the bootstrap script, navigated to once hydration has
|
|
321
|
+
// committed - not now: the runtime's script running is seconds before
|
|
322
|
+
// React can dispatch a click on a phone still downloading chunks, and
|
|
323
|
+
// a tap in that window would be the browser's. See earlyClicks.ts.
|
|
324
|
+
afterHydration(() => replayEarlyClicks((url) => navigate(url)));
|
|
319
325
|
}
|
|
320
326
|
//# sourceMappingURL=createViteRscApp.js.map
|
|
@@ -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 { SEARCH_PARAMS_FALLBACK } from \"./useSearchParams\";\nimport { recoverFromStaleAssets } from \"./staleAssets\";\nimport { parseRedirectDigest } from \"../redirectDigest.js\";\nimport { showDevNotice } from \"./devNotice\";\nimport { caughtByLoading } from \"./fallbackReport\";\nimport { noteNavigation } from \"./segmentStore\";\nimport type { Route } 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, noteRedirected, 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 setApiRoutes,\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\n\n/**\n * A redirect that travelled as an error: the digest of the error itself, or\n * of the cause React wraps it in when it reports client-rendering a boundary\n * the server had already failed.\n */\nfunction isRedirect(error: unknown): boolean {\n const digest = (error as { digest?: unknown } | null)?.digest;\n const cause = (error as { cause?: { digest?: unknown } } | null)?.cause;\n\n return parseRedirectDigest(digest) !== null || parseRedirectDigest(cause?.digest) !== null;\n}\n\nexport async function createViteRscApp(\n container: Document | Element = document,\n interceptEntries: { urlPattern: string; slot: string }[] = [],\n options: { staticPayloads?: string | null; routes?: unknown[] | null; apiRoutes?: string[] } = {},\n): Promise<void> {\n // The urls a route.ts answers: a link to one is an anchor, never a\n // prefetch, never a payload fetch.\n if (options.apiRoutes) setApiRoutes(options.apiRoutes);\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. A navigation, not a\n // document load: the layouts stay mounted and the history entry the\n // form was on is replaced, the way a redirect after a POST should\n // be - Back does not return to the submitted form.\n if (isSafeRedirect(err.location)) {\n void navigate(err.location as Route, { replace: true });\n }\n\n // Performed, so resolved - not thrown. The call's answer is \"you are\n // being taken somewhere\": an object saying so, the shape an action's\n // result already has, so a caller reading result.validationErrors\n // reads undefined rather than throwing. A caller that awaited the\n // action in a plain startTransition had no catch for a throw, the\n // rejection reached React, and the root unmounted to a white page\n // on every logout.\n noteRedirected(err.location);\n\n return { redirected: 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 router with the build this DOCUMENT is, so a redeploy is caught\n // on the next navigation: the host answers 409 to a client of another\n // build, and the document is loaded again. From the document's own meta\n // first - a document a service worker served from its cache is the last\n // build's, while the payload just fetched is this one's, and seeding from\n // the payload would have the old client claim to be new. The header is\n // the fallback for a document with no meta.\n const documentBuild = document.querySelector('meta[name=\"rsc-kit:build\"]')?.getAttribute(\"content\");\n const servedVersion = documentBuild || 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 redirect decided inside a boundary: the server's row carried it as\n // the digest, React client-renders the boundary and reports that it\n // did, and the RedirectBoundary is already performing it. Nothing to\n // read in the console about a page doing what it was told.\n if (isRedirect(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\n // A navigation another one overtook: its request was aborted, and a\n // row of its payload that a component was still reading rejects with\n // the abort. React recovers by rendering the root again synchronously\n // and reports the recovery here as #520, with the abort as the cause.\n // The page it recovered to is the one the second navigation asked for;\n // the abort was the design, not a fault.\n const cause = (error as { cause?: { name?: string; message?: string } } | null)?.cause;\n\n if (\n cause?.name === \"AbortError\" ||\n /\\baborted\\b/i.test(cause?.message ?? \"\") ||\n /\\baborted\\b/i.test(message)\n ) {\n return;\n }\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 // Caught by the RedirectBoundary, which is performing it.\n if (isRedirect(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 Route, { replace: true, restore: true });\n });\n\n history.replaceState(\n { rscUrl: window.location.href },\n \"\",\n window.location.href,\n );\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 { afterHydration, replayEarlyClicks } from \"./earlyClicks\";\nimport { parseRedirectDigest } from \"../redirectDigest.js\";\nimport { showDevNotice } from \"./devNotice\";\nimport { caughtByLoading } from \"./fallbackReport\";\nimport { noteNavigation } from \"./segmentStore\";\nimport type { Route } 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, noteRedirected, 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 setApiRoutes,\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\n\n/**\n * A redirect that travelled as an error: the digest of the error itself, or\n * of the cause React wraps it in when it reports client-rendering a boundary\n * the server had already failed.\n */\nfunction isRedirect(error: unknown): boolean {\n const digest = (error as { digest?: unknown } | null)?.digest;\n const cause = (error as { cause?: { digest?: unknown } } | null)?.cause;\n\n return parseRedirectDigest(digest) !== null || parseRedirectDigest(cause?.digest) !== null;\n}\n\nexport async function createViteRscApp(\n container: Document | Element = document,\n interceptEntries: { urlPattern: string; slot: string }[] = [],\n options: { staticPayloads?: string | null; routes?: unknown[] | null; apiRoutes?: string[] } = {},\n): Promise<void> {\n // The urls a route.ts answers: a link to one is an anchor, never a\n // prefetch, never a payload fetch.\n if (options.apiRoutes) setApiRoutes(options.apiRoutes);\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. A navigation, not a\n // document load: the layouts stay mounted and the history entry the\n // form was on is replaced, the way a redirect after a POST should\n // be - Back does not return to the submitted form.\n if (isSafeRedirect(err.location)) {\n void navigate(err.location as Route, { replace: true });\n }\n\n // Performed, so resolved - not thrown. The call's answer is \"you are\n // being taken somewhere\": an object saying so, the shape an action's\n // result already has, so a caller reading result.validationErrors\n // reads undefined rather than throwing. A caller that awaited the\n // action in a plain startTransition had no catch for a throw, the\n // rejection reached React, and the root unmounted to a white page\n // on every logout.\n noteRedirected(err.location);\n\n return { redirected: 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 router with the build this DOCUMENT is, so a redeploy is caught\n // on the next navigation: the host answers 409 to a client of another\n // build, and the document is loaded again. From the document's own meta\n // first - a document a service worker served from its cache is the last\n // build's, while the payload just fetched is this one's, and seeding from\n // the payload would have the old client claim to be new. The header is\n // the fallback for a document with no meta.\n const documentBuild = document.querySelector('meta[name=\"rsc-kit:build\"]')?.getAttribute(\"content\");\n const servedVersion = documentBuild || 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 redirect decided inside a boundary: the server's row carried it as\n // the digest, React client-renders the boundary and reports that it\n // did, and the RedirectBoundary is already performing it. Nothing to\n // read in the console about a page doing what it was told.\n if (isRedirect(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\n // A navigation another one overtook: its request was aborted, and a\n // row of its payload that a component was still reading rejects with\n // the abort. React recovers by rendering the root again synchronously\n // and reports the recovery here as #520, with the abort as the cause.\n // The page it recovered to is the one the second navigation asked for;\n // the abort was the design, not a fault.\n const cause = (error as { cause?: { name?: string; message?: string } } | null)?.cause;\n\n if (\n cause?.name === \"AbortError\" ||\n /\\baborted\\b/i.test(cause?.message ?? \"\") ||\n /\\baborted\\b/i.test(message)\n ) {\n return;\n }\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 // Caught by the RedirectBoundary, which is performing it.\n if (isRedirect(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 Route, { replace: true, restore: true });\n });\n\n history.replaceState(\n { rscUrl: window.location.href },\n \"\",\n window.location.href,\n );\n\n // A tap held by the bootstrap script, navigated to once hydration has\n // committed - not now: the runtime's script running is seconds before\n // React can dispatch a click on a phone still downloading chunks, and\n // a tap in that window would be the browser's. See earlyClicks.ts.\n afterHydration(() => replayEarlyClicks((url) => navigate(url as Route)));\n}\n"]}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A tap before the page can navigate.
|
|
3
|
+
*
|
|
4
|
+
* The document arrives, the bootstrap script starts the runtime, and on a
|
|
5
|
+
* phone the runtime takes a while to arrive and hydrate - seconds, with a
|
|
6
|
+
* tag manager or two on the main thread. A tap on a link in that window is
|
|
7
|
+
* the browser's: a document load, of a page the router would have swapped
|
|
8
|
+
* in. The port's "Sign in is slow from the home page, and sometimes a full
|
|
9
|
+
* reload" was this, and only on a phone, where the window is wide.
|
|
10
|
+
*
|
|
11
|
+
* The first thing the bootstrap script does is listen: a click on one of
|
|
12
|
+
* this package's links, before the page has hydrated, is held. When
|
|
13
|
+
* hydration commits - not when the runtime's script runs, which is
|
|
14
|
+
* seconds earlier on a phone whose chunks are still downloading, and where
|
|
15
|
+
* a click is nobody's yet - the last one held is navigated to. A click
|
|
16
|
+
* held for three seconds without hydration is given to the browser as a
|
|
17
|
+
* document load, so nothing is lost if the runtime never comes.
|
|
18
|
+
*
|
|
19
|
+
* Only this package's links, marked `data-rsc`: a plain anchor to a file, a
|
|
20
|
+
* route.ts, a mailto or a foreign origin is not a page the router swaps in.
|
|
21
|
+
*/
|
|
22
|
+
/** Written into the bootstrap script, ahead of the runtime's import. No backticks: it lives in a template. */
|
|
23
|
+
export declare const EARLY_CLICKS: string;
|
|
24
|
+
/**
|
|
25
|
+
* Hand the clicks held before hydration to the router. The last one is the
|
|
26
|
+
* page the visitor meant; the ones before it were on the way there.
|
|
27
|
+
*/
|
|
28
|
+
export declare function replayEarlyClicks(navigate: (url: string) => Promise<void>): string | null;
|
|
29
|
+
/**
|
|
30
|
+
* Run once hydration has committed - now, if it already has, or when the
|
|
31
|
+
* outermost client component's first effect says so. Before that a click
|
|
32
|
+
* is nobody's: the listener above is still holding them, and React has no
|
|
33
|
+
* fibers to dispatch to.
|
|
34
|
+
*/
|
|
35
|
+
export declare function afterHydration(fn: () => void): void;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A tap before the page can navigate.
|
|
3
|
+
*
|
|
4
|
+
* The document arrives, the bootstrap script starts the runtime, and on a
|
|
5
|
+
* phone the runtime takes a while to arrive and hydrate - seconds, with a
|
|
6
|
+
* tag manager or two on the main thread. A tap on a link in that window is
|
|
7
|
+
* the browser's: a document load, of a page the router would have swapped
|
|
8
|
+
* in. The port's "Sign in is slow from the home page, and sometimes a full
|
|
9
|
+
* reload" was this, and only on a phone, where the window is wide.
|
|
10
|
+
*
|
|
11
|
+
* The first thing the bootstrap script does is listen: a click on one of
|
|
12
|
+
* this package's links, before the page has hydrated, is held. When
|
|
13
|
+
* hydration commits - not when the runtime's script runs, which is
|
|
14
|
+
* seconds earlier on a phone whose chunks are still downloading, and where
|
|
15
|
+
* a click is nobody's yet - the last one held is navigated to. A click
|
|
16
|
+
* held for three seconds without hydration is given to the browser as a
|
|
17
|
+
* document load, so nothing is lost if the runtime never comes.
|
|
18
|
+
*
|
|
19
|
+
* Only this package's links, marked `data-rsc`: a plain anchor to a file, a
|
|
20
|
+
* route.ts, a mailto or a foreign origin is not a page the router swaps in.
|
|
21
|
+
*/
|
|
22
|
+
/** Written into the bootstrap script, ahead of the runtime's import. No backticks: it lives in a template. */
|
|
23
|
+
export const EARLY_CLICKS = "(function(){var q=[];function h(e){" +
|
|
24
|
+
"if(e.defaultPrevented||e.button!==0||e.metaKey||e.ctrlKey||e.shiftKey||e.altKey)return;" +
|
|
25
|
+
"var t=e.target;var a=t&&t.closest?t.closest('a[data-rsc]'):null;" +
|
|
26
|
+
"if(!a||(a.target&&a.target!=='_self'))return;" +
|
|
27
|
+
"var u;try{u=new URL(a.getAttribute('href'),location.href)}catch(x){return}" +
|
|
28
|
+
"if(u.origin!==location.origin)return;" +
|
|
29
|
+
"e.preventDefault();q.push(u.pathname+u.search+u.hash);a.setAttribute('data-pending','');" +
|
|
30
|
+
// A tap held for longer than a page should take to hydrate is given to
|
|
31
|
+
// the browser: three seconds from the tap, not from the script.
|
|
32
|
+
"setTimeout(function(){if(q.length&&!w.__rsc_hydrated)location.href=q[q.length-1]},3000)}" +
|
|
33
|
+
"var w=window;document.addEventListener('click',h,true);" +
|
|
34
|
+
"w.__rsc_early={q:q,stop:function(){document.removeEventListener('click',h,true)}}})();";
|
|
35
|
+
/**
|
|
36
|
+
* Hand the clicks held before hydration to the router. The last one is the
|
|
37
|
+
* page the visitor meant; the ones before it were on the way there.
|
|
38
|
+
*/
|
|
39
|
+
export function replayEarlyClicks(navigate) {
|
|
40
|
+
const early = window.__rsc_early;
|
|
41
|
+
if (!early)
|
|
42
|
+
return null;
|
|
43
|
+
early.stop();
|
|
44
|
+
const last = early.q[early.q.length - 1] ?? null;
|
|
45
|
+
early.q.length = 0;
|
|
46
|
+
if (last)
|
|
47
|
+
void navigate(last);
|
|
48
|
+
return last;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Run once hydration has committed - now, if it already has, or when the
|
|
52
|
+
* outermost client component's first effect says so. Before that a click
|
|
53
|
+
* is nobody's: the listener above is still holding them, and React has no
|
|
54
|
+
* fibers to dispatch to.
|
|
55
|
+
*/
|
|
56
|
+
export function afterHydration(fn) {
|
|
57
|
+
const w = window;
|
|
58
|
+
if (w.__rsc_hydrated) {
|
|
59
|
+
fn();
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
w.__rsc_on_hydrated = () => {
|
|
63
|
+
w.__rsc_on_hydrated = undefined;
|
|
64
|
+
fn();
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
//# sourceMappingURL=earlyClicks.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"earlyClicks.js","sourceRoot":"","sources":["../../src/js/earlyClicks.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,8GAA8G;AAC9G,MAAM,CAAC,MAAM,YAAY,GACvB,qCAAqC;IACrC,yFAAyF;IACzF,kEAAkE;IAClE,+CAA+C;IAC/C,4EAA4E;IAC5E,uCAAuC;IACvC,0FAA0F;IAC1F,uEAAuE;IACvE,gEAAgE;IAChE,0FAA0F;IAC1F,yDAAyD;IACzD,wFAAwF,CAAC;AAO3F;;;GAGG;AACH,MAAM,UAAU,iBAAiB,CAAC,QAAwC;IACxE,MAAM,KAAK,GAAI,MAAkC,CAAC,WAAW,CAAC;IAE9D,IAAI,CAAC,KAAK;QAAE,OAAO,IAAI,CAAC;IAExB,KAAK,CAAC,IAAI,EAAE,CAAC;IAEb,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,IAAI,IAAI,CAAC;IAEjD,KAAK,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC;IAEnB,IAAI,IAAI;QAAE,KAAK,QAAQ,CAAC,IAAI,CAAC,CAAC;IAE9B,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,cAAc,CAAC,EAAc;IAC3C,MAAM,CAAC,GAAG,MAAsE,CAAC;IAEjF,IAAI,CAAC,CAAC,cAAc,EAAE,CAAC;QACrB,EAAE,EAAE,CAAC;QAEL,OAAO;IACT,CAAC;IAED,CAAC,CAAC,iBAAiB,GAAG,GAAG,EAAE;QACzB,CAAC,CAAC,iBAAiB,GAAG,SAAS,CAAC;QAChC,EAAE,EAAE,CAAC;IACP,CAAC,CAAC;AACJ,CAAC","sourcesContent":["/**\n * A tap before the page can navigate.\n *\n * The document arrives, the bootstrap script starts the runtime, and on a\n * phone the runtime takes a while to arrive and hydrate - seconds, with a\n * tag manager or two on the main thread. A tap on a link in that window is\n * the browser's: a document load, of a page the router would have swapped\n * in. The port's \"Sign in is slow from the home page, and sometimes a full\n * reload\" was this, and only on a phone, where the window is wide.\n *\n * The first thing the bootstrap script does is listen: a click on one of\n * this package's links, before the page has hydrated, is held. When\n * hydration commits - not when the runtime's script runs, which is\n * seconds earlier on a phone whose chunks are still downloading, and where\n * a click is nobody's yet - the last one held is navigated to. A click\n * held for three seconds without hydration is given to the browser as a\n * document load, so nothing is lost if the runtime never comes.\n *\n * Only this package's links, marked `data-rsc`: a plain anchor to a file, a\n * route.ts, a mailto or a foreign origin is not a page the router swaps in.\n */\n\n/** Written into the bootstrap script, ahead of the runtime's import. No backticks: it lives in a template. */\nexport const EARLY_CLICKS =\n \"(function(){var q=[];function h(e){\" +\n \"if(e.defaultPrevented||e.button!==0||e.metaKey||e.ctrlKey||e.shiftKey||e.altKey)return;\" +\n \"var t=e.target;var a=t&&t.closest?t.closest('a[data-rsc]'):null;\" +\n \"if(!a||(a.target&&a.target!=='_self'))return;\" +\n \"var u;try{u=new URL(a.getAttribute('href'),location.href)}catch(x){return}\" +\n \"if(u.origin!==location.origin)return;\" +\n \"e.preventDefault();q.push(u.pathname+u.search+u.hash);a.setAttribute('data-pending','');\" +\n // A tap held for longer than a page should take to hydrate is given to\n // the browser: three seconds from the tap, not from the script.\n \"setTimeout(function(){if(q.length&&!w.__rsc_hydrated)location.href=q[q.length-1]},3000)}\" +\n \"var w=window;document.addEventListener('click',h,true);\" +\n \"w.__rsc_early={q:q,stop:function(){document.removeEventListener('click',h,true)}}})();\";\n\ninterface Early {\n q: string[];\n stop(): void;\n}\n\n/**\n * Hand the clicks held before hydration to the router. The last one is the\n * page the visitor meant; the ones before it were on the way there.\n */\nexport function replayEarlyClicks(navigate: (url: string) => Promise<void>): string | null {\n const early = (window as { __rsc_early?: Early }).__rsc_early;\n\n if (!early) return null;\n\n early.stop();\n\n const last = early.q[early.q.length - 1] ?? null;\n\n early.q.length = 0;\n\n if (last) void navigate(last);\n\n return last;\n}\n\n/**\n * Run once hydration has committed - now, if it already has, or when the\n * outermost client component's first effect says so. Before that a click\n * is nobody's: the listener above is still holding them, and React has no\n * fibers to dispatch to.\n */\nexport function afterHydration(fn: () => void): void {\n const w = window as { __rsc_hydrated?: boolean; __rsc_on_hydrated?: () => void };\n\n if (w.__rsc_hydrated) {\n fn();\n\n return;\n }\n\n w.__rsc_on_hydrated = () => {\n w.__rsc_on_hydrated = undefined;\n fn();\n };\n}\n"]}
|
package/dist/js/navigate.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* duplicate bundling of react-server-dom-webpack.
|
|
7
7
|
*/
|
|
8
8
|
import { isStaleAssetError, loadDocumentOnce } from "./staleAssets";
|
|
9
|
-
import { isUpdated } from "./updateStore";
|
|
9
|
+
import { isUpdated, markStale } from "./updateStore";
|
|
10
10
|
import { isSafeRedirect } from "../safeUrl.js";
|
|
11
11
|
import { reportReachable } from "./onlineStore";
|
|
12
12
|
import { clearSlots, setSlot } from "./slotStore";
|
|
@@ -14,6 +14,8 @@ import { clearSlots, setSlot } from "./slotStore";
|
|
|
14
14
|
// under it, so the format cannot live in two places.
|
|
15
15
|
import { matchRoute, retentionKey as retentionKeyFor, sharedDepth, } from "../routing";
|
|
16
16
|
let version = "";
|
|
17
|
+
/** Pages a navigation is fetching right now, so a prefetch does not ask again. */
|
|
18
|
+
const navigating = new Set();
|
|
17
19
|
let onNavigate = null;
|
|
18
20
|
let onRestore = null;
|
|
19
21
|
/**
|
|
@@ -172,6 +174,31 @@ const staticFetches = new WeakMap();
|
|
|
172
174
|
export function setVersion(v) {
|
|
173
175
|
version = v;
|
|
174
176
|
}
|
|
177
|
+
/**
|
|
178
|
+
* Tell the worker which build the server is on, and wait to be heard - a
|
|
179
|
+
* document load follows, and the message has to land before the request
|
|
180
|
+
* the load makes. Bounded: a worker that does not answer is not worth a
|
|
181
|
+
* visitor's wait, and a page with no worker has nothing to tell.
|
|
182
|
+
*/
|
|
183
|
+
async function tellWorkerServerBuild(build) {
|
|
184
|
+
if (!build || typeof navigator === "undefined" || !("serviceWorker" in navigator))
|
|
185
|
+
return;
|
|
186
|
+
const worker = navigator.serviceWorker.controller;
|
|
187
|
+
if (!worker)
|
|
188
|
+
return;
|
|
189
|
+
await new Promise((resolve) => {
|
|
190
|
+
const channel = new MessageChannel();
|
|
191
|
+
const done = () => {
|
|
192
|
+
clearTimeout(timer);
|
|
193
|
+
resolve();
|
|
194
|
+
};
|
|
195
|
+
const timer = setTimeout(done, 300);
|
|
196
|
+
channel.port1.onmessage = done;
|
|
197
|
+
worker.postMessage({ type: "rsc-kit:server-build", version: build }, [channel.port2]);
|
|
198
|
+
});
|
|
199
|
+
// And look for the successor now rather than on the next navigation.
|
|
200
|
+
void navigator.serviceWorker.getRegistration().then((registration) => registration?.update()).catch(() => { });
|
|
201
|
+
}
|
|
175
202
|
/**
|
|
176
203
|
* The layout chain the client is holding.
|
|
177
204
|
*
|
|
@@ -291,7 +318,16 @@ function fetchRscPayload(url, signal, interceptSlot, refererUrl, chain = heldLay
|
|
|
291
318
|
// were identical — so a click could queue behind several prefetches the user
|
|
292
319
|
// had already moved past. Over HTTP/1.1 a browser opens ~6 connections per
|
|
293
320
|
// origin, which a sweep across a nav bar fills on its own.
|
|
294
|
-
priority = "high"
|
|
321
|
+
priority = "high",
|
|
322
|
+
/**
|
|
323
|
+
* A prefetch is speculative: a 409 on one must not load a document the
|
|
324
|
+
* visitor never asked for. A viewport prefetch of the sign-in link, sent
|
|
325
|
+
* from an old page after a deploy, took the visitor reading the home page
|
|
326
|
+
* to /login half a second after it appeared - and a hover over a sidebar
|
|
327
|
+
* link did the same to a half-filled form. The session is marked stale
|
|
328
|
+
* instead, so the next navigation the visitor makes is a document load.
|
|
329
|
+
*/
|
|
330
|
+
speculative = false) {
|
|
295
331
|
const headers = {
|
|
296
332
|
"X-RSC": "true",
|
|
297
333
|
"X-RSC-Version": version,
|
|
@@ -340,6 +376,16 @@ priority = "high") {
|
|
|
340
376
|
// Once per url: a worker still serving the last build's document would
|
|
341
377
|
// answer the load with it, and the next click would be here again.
|
|
342
378
|
const to = isSafeRedirect(location ?? url) ? (location ?? url) : url;
|
|
379
|
+
// The worker first. It serves a stored document from its cache, and
|
|
380
|
+
// its cache is the build this page came from - the document it would
|
|
381
|
+
// answer the load with is the one being left. Told which build the
|
|
382
|
+
// server is on, it serves the network for a copy from any other, and
|
|
383
|
+
// is asked to look for its successor. Then the load.
|
|
384
|
+
await tellWorkerServerBuild(served);
|
|
385
|
+
if (speculative) {
|
|
386
|
+
markStale();
|
|
387
|
+
throw new Error("This page is from an earlier build; the next navigation loads the document");
|
|
388
|
+
}
|
|
343
389
|
throw new Error(loadDocumentOnce(to)
|
|
344
390
|
? "This page is from an earlier build; loading the document from the current one"
|
|
345
391
|
: "This page is from an earlier build, and loading the document did not bring the current one");
|
|
@@ -568,7 +614,17 @@ export async function navigate(url, opts) {
|
|
|
568
614
|
}
|
|
569
615
|
else {
|
|
570
616
|
cache.delete(cacheKey);
|
|
571
|
-
|
|
617
|
+
// Marked while in flight, so a prefetch of the same page - a held
|
|
618
|
+
// tap's replay landing beside the idle viewport prefetch of the link
|
|
619
|
+
// it came from - does not ask for it a second time.
|
|
620
|
+
navigating.add(cacheKey);
|
|
621
|
+
let response;
|
|
622
|
+
try {
|
|
623
|
+
response = await fetchRscPayload(url, controller.signal, interceptSlot ?? undefined, currentUrl, chain);
|
|
624
|
+
}
|
|
625
|
+
finally {
|
|
626
|
+
navigating.delete(cacheKey);
|
|
627
|
+
}
|
|
572
628
|
// The check is for a host that answered the page instead of the
|
|
573
629
|
// payload, which is what a server does when it does not recognise the
|
|
574
630
|
// header. An exported build asks a url that only ever holds a payload,
|
|
@@ -581,11 +637,13 @@ export async function navigate(url, opts) {
|
|
|
581
637
|
// as an error digest and RedirectBoundary performs it.
|
|
582
638
|
const redirectTo = response.headers.get("X-RSC-Redirect");
|
|
583
639
|
if (redirectTo) {
|
|
584
|
-
//
|
|
585
|
-
//
|
|
640
|
+
// The url that redirected was never pushed - the history entry is
|
|
641
|
+
// written after the tree arrives, below - so the destination takes
|
|
642
|
+
// this navigation's own mode. `replace: true` here replaced the page
|
|
643
|
+
// the visitor was ON, and Back from the login page skipped home.
|
|
586
644
|
// Chosen by the server, not written here.
|
|
587
645
|
await navigate(redirectTo, {
|
|
588
|
-
replace:
|
|
646
|
+
replace: opts?.replace,
|
|
589
647
|
redirectsFollowed: redirectsFollowed + 1,
|
|
590
648
|
});
|
|
591
649
|
return;
|
|
@@ -633,7 +691,7 @@ export async function navigate(url, opts) {
|
|
|
633
691
|
// asked again, from a cache that no longer holds the entry.
|
|
634
692
|
if (reused.redirectTo) {
|
|
635
693
|
await navigate(reused.redirectTo, {
|
|
636
|
-
replace:
|
|
694
|
+
replace: opts?.replace,
|
|
637
695
|
redirectsFollowed: redirectsFollowed + 1,
|
|
638
696
|
});
|
|
639
697
|
return;
|
|
@@ -849,6 +907,15 @@ export function prefetch(url, cacheForMs) {
|
|
|
849
907
|
// Never a route.ts: fetching one runs it, and a hover is not a click.
|
|
850
908
|
if (isApiRoute(url))
|
|
851
909
|
return;
|
|
910
|
+
// Never the page the visitor is on. A logo link to / on the home page,
|
|
911
|
+
// in view, was a 14 KB payload for the page already on screen.
|
|
912
|
+
if (retentionKey(url, null) === retentionKey(window.location.href, null))
|
|
913
|
+
return;
|
|
914
|
+
// Nor anything, once this page is known to be the previous build: the
|
|
915
|
+
// next navigation is a document load, and a payload it would not use is
|
|
916
|
+
// a 409 for nothing - one per tap, measured.
|
|
917
|
+
if (isUpdated())
|
|
918
|
+
return;
|
|
852
919
|
if (isExternalUrl(url))
|
|
853
920
|
return;
|
|
854
921
|
const ttl = cacheForMs ?? DEFAULT_PREFETCH_TTL;
|
|
@@ -869,12 +936,19 @@ function prefetchUrl(cacheKey, url, ttl, interceptSlot, refererUrl) {
|
|
|
869
936
|
if (existing && existing.expiresAt > Date.now()) {
|
|
870
937
|
return;
|
|
871
938
|
}
|
|
939
|
+
// A navigation is already fetching it; the answer is on its way.
|
|
940
|
+
if (navigating.has(cacheKey))
|
|
941
|
+
return;
|
|
872
942
|
cache.delete(cacheKey);
|
|
873
943
|
makeRoom();
|
|
874
944
|
const controller = new AbortController();
|
|
875
945
|
prefetchControllers.set(cacheKey, controller);
|
|
946
|
+
let decoded = null;
|
|
876
947
|
const entry = {
|
|
877
|
-
|
|
948
|
+
body: Promise.resolve(null),
|
|
949
|
+
get tree() {
|
|
950
|
+
return (decoded ??= this.body.then((text) => (text === null ? null : deserializeResponse(new Response(text)))));
|
|
951
|
+
},
|
|
878
952
|
expiresAt: Date.now() + ttl,
|
|
879
953
|
segmentDepth: 0,
|
|
880
954
|
layouts: null,
|
|
@@ -885,7 +959,7 @@ function prefetchUrl(cacheKey, url, ttl, interceptSlot, refererUrl) {
|
|
|
885
959
|
};
|
|
886
960
|
// Low priority: the browser then lets a real navigation overtake a queue of
|
|
887
961
|
// speculative requests instead of serving them in the order they were made.
|
|
888
|
-
entry.
|
|
962
|
+
entry.body = fetchRscPayload(url, controller.signal, interceptSlot, refererUrl, chain, "low", true)
|
|
889
963
|
.then((response) => {
|
|
890
964
|
// On a static host there are no headers to read, and dropping the depth
|
|
891
965
|
// is not a small loss: the entry then claims a segment is a whole
|
|
@@ -896,8 +970,17 @@ function prefetchUrl(cacheKey, url, ttl, interceptSlot, refererUrl) {
|
|
|
896
970
|
// body to deserialize. The click re-requests and redirects properly.
|
|
897
971
|
const redirectTo = response.headers.get("X-RSC-Redirect");
|
|
898
972
|
if (redirectTo) {
|
|
973
|
+
// The guard's answer is kept, for the entry's TTL, and the
|
|
974
|
+
// destination is prefetched now. A tap on the link then goes
|
|
975
|
+
// straight to a payload already here: no request for the page the
|
|
976
|
+
// visitor may not see, none for the one they will. Before this,
|
|
977
|
+
// every tap on a guarded link paid the guard's round trip and the
|
|
978
|
+
// destination's, in sequence - on a phone, the two slowest things a
|
|
979
|
+
// navigation can do, and the port's "Sign in is slow from home".
|
|
980
|
+
// Next's prefetch follows the redirect the same way.
|
|
899
981
|
entry.redirectTo = redirectTo;
|
|
900
|
-
|
|
982
|
+
if (!isExternalUrl(redirectTo) && isSafeRedirect(redirectTo))
|
|
983
|
+
prefetch(redirectTo, ttl);
|
|
901
984
|
return null;
|
|
902
985
|
}
|
|
903
986
|
entry.slot = response.headers.get("X-RSC-Revalidate");
|
|
@@ -911,7 +994,8 @@ function prefetchUrl(cacheKey, url, ttl, interceptSlot, refererUrl) {
|
|
|
911
994
|
else if (local) {
|
|
912
995
|
entry.layouts = local.chain;
|
|
913
996
|
}
|
|
914
|
-
|
|
997
|
+
// The bytes, not the page: decoding is the navigation's, see CacheEntry.
|
|
998
|
+
return response.text();
|
|
915
999
|
})
|
|
916
1000
|
.catch(() => {
|
|
917
1001
|
entry.failed = true;
|