@rsc-kit/core 0.20.0 → 0.20.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/js/RedirectBoundary.d.ts +11 -1
- package/dist/js/RedirectBoundary.js +10 -1
- package/dist/js/RedirectBoundary.js.map +1 -1
- package/dist/js/SegmentBoundary.js +11 -4
- package/dist/js/SegmentBoundary.js.map +1 -1
- package/dist/vite.js +17 -0
- package/dist/vite.js.map +1 -1
- package/package.json +1 -1
|
@@ -4,13 +4,23 @@ interface Props {
|
|
|
4
4
|
children: ReactNode;
|
|
5
5
|
/** Shown while the navigation is in flight. Null keeps the space empty. */
|
|
6
6
|
fallback?: ReactNode;
|
|
7
|
+
/**
|
|
8
|
+
* Whether what this boundary wraps is the page on screen. A segment keeps
|
|
9
|
+
* pages alive behind the active one, each under its own boundary, and a
|
|
10
|
+
* redirect caught on a page that is not showing is not performed — it was
|
|
11
|
+
* already performed when it was. Shown again, the boundary renders the
|
|
12
|
+
* page afresh, and a refusal that still stands redirects again.
|
|
13
|
+
*/
|
|
14
|
+
active?: boolean;
|
|
7
15
|
}
|
|
8
16
|
interface State {
|
|
9
17
|
redirecting: boolean;
|
|
18
|
+
wasActive: boolean;
|
|
10
19
|
}
|
|
11
20
|
export declare class RedirectBoundary extends Component<Props, State> {
|
|
12
21
|
state: State;
|
|
13
|
-
static getDerivedStateFromError(error: unknown): State | null;
|
|
22
|
+
static getDerivedStateFromError(error: unknown): Partial<State> | null;
|
|
23
|
+
static getDerivedStateFromProps(props: Props, state: State): Partial<State> | null;
|
|
14
24
|
componentDidCatch(error: unknown, _info: ErrorInfo): void;
|
|
15
25
|
render(): ReactNode;
|
|
16
26
|
}
|
|
@@ -3,7 +3,7 @@ import { Component } from "react";
|
|
|
3
3
|
import { parseRedirectDigest } from "../redirectDigest.js";
|
|
4
4
|
import { visit } from "./router";
|
|
5
5
|
export class RedirectBoundary extends Component {
|
|
6
|
-
state = { redirecting: false };
|
|
6
|
+
state = { redirecting: false, wasActive: this.props.active !== false };
|
|
7
7
|
static getDerivedStateFromError(error) {
|
|
8
8
|
// Reading `digest` rather than the message: React replaces a server
|
|
9
9
|
// error's message in production and transmits the digest either way.
|
|
@@ -11,10 +11,19 @@ export class RedirectBoundary extends Component {
|
|
|
11
11
|
? { redirecting: true }
|
|
12
12
|
: null;
|
|
13
13
|
}
|
|
14
|
+
static getDerivedStateFromProps(props, state) {
|
|
15
|
+
const active = props.active !== false;
|
|
16
|
+
if (active === state.wasActive)
|
|
17
|
+
return null;
|
|
18
|
+
// Revealed again: render the page, not the space left by its redirect.
|
|
19
|
+
return { wasActive: active, redirecting: active ? false : state.redirecting };
|
|
20
|
+
}
|
|
14
21
|
componentDidCatch(error, _info) {
|
|
15
22
|
const target = parseRedirectDigest(error?.digest);
|
|
16
23
|
if (!target)
|
|
17
24
|
throw error;
|
|
25
|
+
if (this.props.active === false)
|
|
26
|
+
return;
|
|
18
27
|
// An SPA navigation, not a location assignment: the layouts above this
|
|
19
28
|
// boundary are already mounted and correct, so replacing the document
|
|
20
29
|
// would throw away state the user can see — including whatever they had
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RedirectBoundary.js","sourceRoot":"","sources":["../../src/js/RedirectBoundary.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAC;AAqBb,OAAO,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAElC,OAAO,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAC3D,OAAO,EAAE,KAAK,EAAE,MAAM,UAAU,CAAC;
|
|
1
|
+
{"version":3,"file":"RedirectBoundary.js","sourceRoot":"","sources":["../../src/js/RedirectBoundary.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAC;AAqBb,OAAO,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAElC,OAAO,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAC3D,OAAO,EAAE,KAAK,EAAE,MAAM,UAAU,CAAC;AAqBjC,MAAM,OAAO,gBAAiB,SAAQ,SAAuB;IAC3D,KAAK,GAAU,EAAE,WAAW,EAAE,KAAK,EAAE,SAAS,EAAE,IAAI,CAAC,KAAK,CAAC,MAAM,KAAK,KAAK,EAAE,CAAC;IAE9E,MAAM,CAAC,wBAAwB,CAAC,KAAc;QAC5C,oEAAoE;QACpE,qEAAqE;QACrE,OAAO,mBAAmB,CAAE,KAA8B,EAAE,MAAM,CAAC;YACjE,CAAC,CAAC,EAAE,WAAW,EAAE,IAAI,EAAE;YACvB,CAAC,CAAC,IAAI,CAAC;IACX,CAAC;IAED,MAAM,CAAC,wBAAwB,CAAC,KAAY,EAAE,KAAY;QACxD,MAAM,MAAM,GAAG,KAAK,CAAC,MAAM,KAAK,KAAK,CAAC;QAEtC,IAAI,MAAM,KAAK,KAAK,CAAC,SAAS;YAAE,OAAO,IAAI,CAAC;QAE5C,uEAAuE;QACvE,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,WAAW,EAAE,CAAC;IAChF,CAAC;IAED,iBAAiB,CAAC,KAAc,EAAE,KAAgB;QAChD,MAAM,MAAM,GAAG,mBAAmB,CAAE,KAA8B,EAAE,MAAM,CAAC,CAAC;QAE5E,IAAI,CAAC,MAAM;YAAE,MAAM,KAAK,CAAC;QACzB,IAAI,IAAI,CAAC,KAAK,CAAC,MAAM,KAAK,KAAK;YAAE,OAAO;QAExC,uEAAuE;QACvE,sEAAsE;QACtE,wEAAwE;QACxE,wDAAwD;QACxD,EAAE;QACF,uEAAuE;QACvE,uEAAuE;QACvE,uEAAuE;QACvE,KAAK,KAAK,CAAC,MAAM,CAAC,QAAiB,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC;IAC1D,CAAC;IAED,MAAM;QACJ,IAAI,IAAI,CAAC,KAAK,CAAC,WAAW;YAAE,OAAO,IAAI,CAAC,KAAK,CAAC,QAAQ,IAAI,IAAI,CAAC;QAE/D,OAAO,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC;IAC7B,CAAC;CACF","sourcesContent":["\"use client\";\n\n/**\n * Performs a redirect that was decided too late to be a status code.\n *\n * A redirect thrown above every Suspense boundary is answered by the host\n * before anything is written — the browser never sees this page, and this\n * component is never involved. One thrown inside a boundary arrives after the\n * shell, as an error carrying the destination in its digest, and this is what\n * turns that back into a navigation.\n *\n * An error boundary rather than a hook because that is the only thing React\n * offers for \"a child threw\": the throw happens in a server component whose\n * error is serialised into the payload, and it surfaces here when the client\n * renders it.\n *\n * Anything that is not a redirect is rethrown untouched, so an app's own error\n * boundaries still see the errors they exist for.\n */\n\nimport type { Route } from \"../routes.js\";\nimport { Component } from \"react\";\nimport type { ErrorInfo, ReactNode } from \"react\";\nimport { parseRedirectDigest } from \"../redirectDigest.js\";\nimport { visit } from \"./router\";\n\ninterface Props {\n children: ReactNode;\n /** Shown while the navigation is in flight. Null keeps the space empty. */\n fallback?: ReactNode;\n /**\n * Whether what this boundary wraps is the page on screen. A segment keeps\n * pages alive behind the active one, each under its own boundary, and a\n * redirect caught on a page that is not showing is not performed — it was\n * already performed when it was. Shown again, the boundary renders the\n * page afresh, and a refusal that still stands redirects again.\n */\n active?: boolean;\n}\n\ninterface State {\n redirecting: boolean;\n wasActive: boolean;\n}\n\nexport class RedirectBoundary extends Component<Props, State> {\n state: State = { redirecting: false, wasActive: this.props.active !== false };\n\n static getDerivedStateFromError(error: unknown): Partial<State> | null {\n // Reading `digest` rather than the message: React replaces a server\n // error's message in production and transmits the digest either way.\n return parseRedirectDigest((error as { digest?: unknown })?.digest)\n ? { redirecting: true }\n : null;\n }\n\n static getDerivedStateFromProps(props: Props, state: State): Partial<State> | null {\n const active = props.active !== false;\n\n if (active === state.wasActive) return null;\n\n // Revealed again: render the page, not the space left by its redirect.\n return { wasActive: active, redirecting: active ? false : state.redirecting };\n }\n\n componentDidCatch(error: unknown, _info: ErrorInfo): void {\n const target = parseRedirectDigest((error as { digest?: unknown })?.digest);\n\n if (!target) throw error;\n if (this.props.active === false) return;\n\n // An SPA navigation, not a location assignment: the layouts above this\n // boundary are already mounted and correct, so replacing the document\n // would throw away state the user can see — including whatever they had\n // typed into a page that is only being redirected past.\n //\n // replace, because the url being left never became a page the user was\n // on; leaving a history entry for it means Back returns to a redirect.\n // The server chose this, so it is not one of the app's authored hrefs.\n void visit(target.location as Route, { replace: true });\n }\n\n render(): ReactNode {\n if (this.state.redirecting) return this.props.fallback ?? null;\n\n return this.props.children;\n }\n}\n"]}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { Fragment as _Fragment, jsx as _jsx } from "react/jsx-runtime";
|
|
3
3
|
/**
|
|
4
4
|
* The point in the tree a navigation can replace on its own.
|
|
5
5
|
*
|
|
@@ -69,15 +69,22 @@ export function SegmentBoundary({ depth, pageKey, children, }) {
|
|
|
69
69
|
// Wrapped here rather than around the whole app because this is the closest
|
|
70
70
|
// client component above a page: a redirect thrown inside the page's own
|
|
71
71
|
// Suspense boundary surfaces at the nearest error boundary, and catching it
|
|
72
|
-
// here leaves the layouts above mounted while the navigation runs.
|
|
72
|
+
// here leaves the layouts above mounted while the navigation runs. One
|
|
73
|
+
// boundary per page, inside its Activity, not one around them all: a
|
|
74
|
+
// boundary that catches renders its fallback in place of everything it
|
|
75
|
+
// wraps, and one around them all unmounted every page being kept alive
|
|
76
|
+
// behind the one redirecting - the destination among them, when it was
|
|
77
|
+
// held - and then kept showing the fallback over the destination once it
|
|
78
|
+
// arrived. The port's report was a sidebar and a breadcrumb over an empty
|
|
79
|
+
// main.
|
|
73
80
|
// The same shape before and after the seed. The server's render and the
|
|
74
81
|
// first client render already place the children inside the retention
|
|
75
82
|
// wrapper, keyed by the page, so when the store takes over after hydration
|
|
76
83
|
// the tree changes state but not shape - and React keeps the DOM instead of
|
|
77
84
|
// remounting the page, which was blank, then content, on every load.
|
|
78
85
|
if (!state) {
|
|
79
|
-
return
|
|
86
|
+
return pageKey ? (_jsx(Activity, { mode: "visible", children: _jsx(RedirectBoundary, { children: children }) }, pageKey)) : (_jsx(RedirectBoundary, { children: children }));
|
|
80
87
|
}
|
|
81
|
-
return (_jsx(
|
|
88
|
+
return (_jsx(_Fragment, { children: state.entries.map((entry) => (_jsx(Activity, { mode: entry.key === state.activeKey ? "visible" : "hidden", children: _jsx(RedirectBoundary, { active: entry.key === state.activeKey, children: entry.tree }) }, entry.key))) }));
|
|
82
89
|
}
|
|
83
90
|
//# sourceMappingURL=SegmentBoundary.js.map
|
|
@@ -1 +1 @@
|
|
|
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,
|
|
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,uEAAuE;IACvE,qEAAqE;IACrE,uEAAuE;IACvE,uEAAuE;IACvE,uEAAuE;IACvE,yEAAyE;IACzE,0EAA0E;IAC1E,QAAQ;IACR,wEAAwE;IACxE,sEAAsE;IACtE,2EAA2E;IAC3E,4EAA4E;IAC5E,qEAAqE;IACrE,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,OAAO,OAAO,CAAC,CAAC,CAAC,CACf,KAAC,QAAQ,IAAe,IAAI,EAAC,SAAS,YACpC,KAAC,gBAAgB,cAAE,QAAQ,GAAoB,IADlC,OAAO,CAEX,CACZ,CAAC,CAAC,CAAC,CACF,KAAC,gBAAgB,cAAE,QAAQ,GAAoB,CAChD,CAAC;IACJ,CAAC;IAED,OAAO,CACL,4BACG,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,YAE1D,KAAC,gBAAgB,IAAC,MAAM,EAAE,KAAK,CAAC,GAAG,KAAK,KAAK,CAAC,SAAS,YACpD,KAAK,CAAC,IAAiB,GACP,IALd,KAAK,CAAC,GAAG,CAML,CACZ,CAAC,GACD,CACJ,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. One\n // boundary per page, inside its Activity, not one around them all: a\n // boundary that catches renders its fallback in place of everything it\n // wraps, and one around them all unmounted every page being kept alive\n // behind the one redirecting - the destination among them, when it was\n // held - and then kept showing the fallback over the destination once it\n // arrived. The port's report was a sidebar and a breadcrumb over an empty\n // main.\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 pageKey ? (\n <Activity key={pageKey} mode=\"visible\">\n <RedirectBoundary>{children}</RedirectBoundary>\n </Activity>\n ) : (\n <RedirectBoundary>{children}</RedirectBoundary>\n );\n }\n\n return (\n <>\n {state.entries.map((entry) => (\n <Activity\n key={entry.key}\n mode={entry.key === state.activeKey ? \"visible\" : \"hidden\"}\n >\n <RedirectBoundary active={entry.key === state.activeKey}>\n {entry.tree as ReactNode}\n </RedirectBoundary>\n </Activity>\n ))}\n </>\n );\n}\n"]}
|
package/dist/vite.js
CHANGED
|
@@ -5725,6 +5725,23 @@ export function rscKit(options = {}) {
|
|
|
5725
5725
|
server.watcher.add(sourceDir);
|
|
5726
5726
|
server.watcher.on("add", restart);
|
|
5727
5727
|
server.watcher.on("unlink", restart);
|
|
5728
|
+
// The host actions are read once, in config(), from the file the
|
|
5729
|
+
// backend writes; the "use server" stubs are generated from it. A
|
|
5730
|
+
// backend that writes a new action - `make:rsc-action` under a running
|
|
5731
|
+
// dev server - rewrites the file, and until the server started again
|
|
5732
|
+
// the stub was not there to import. Any event: the file appears on a
|
|
5733
|
+
// first action, changes on the next, and goes when the last is removed.
|
|
5734
|
+
const hostActionsPath = join(projectRoot, HOST_ACTIONS_FILE);
|
|
5735
|
+
const hostActionsChanged = (file) => {
|
|
5736
|
+
if (file !== hostActionsPath)
|
|
5737
|
+
return;
|
|
5738
|
+
server.config.logger.info(`[rsc-kit] ${HOST_ACTIONS_FILE} changed — restarting`);
|
|
5739
|
+
void server.restart();
|
|
5740
|
+
};
|
|
5741
|
+
server.watcher.add(hostActionsPath);
|
|
5742
|
+
server.watcher.on("add", hostActionsChanged);
|
|
5743
|
+
server.watcher.on("change", hostActionsChanged);
|
|
5744
|
+
server.watcher.on("unlink", hostActionsChanged);
|
|
5728
5745
|
},
|
|
5729
5746
|
/**
|
|
5730
5747
|
* Freeze what can be frozen, once every bundle exists.
|