@real-router/vue 0.15.2 → 0.15.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/index.js +1 -1
- package/dist/cjs/index.js.map +1 -1
- package/dist/esm/index.mjs +1 -1
- package/dist/esm/index.mjs.map +1 -1
- package/package.json +7 -8
- package/src/RouterProvider.ts +0 -162
- package/src/components/Await.ts +0 -47
- package/src/components/ClientOnly.ts +0 -16
- package/src/components/HttpStatusCode.ts +0 -74
- package/src/components/HttpStatusProvider.ts +0 -22
- package/src/components/Link.ts +0 -226
- package/src/components/RouteView/RouteView.ts +0 -233
- package/src/components/RouteView/components.ts +0 -53
- package/src/components/RouteView/helpers.ts +0 -207
- package/src/components/RouteView/index.ts +0 -8
- package/src/components/RouteView/types.ts +0 -20
- package/src/components/RouterErrorBoundary.ts +0 -61
- package/src/components/ServerOnly.ts +0 -16
- package/src/components/Streamed.ts +0 -31
- package/src/composables/useDeferred.ts +0 -37
- package/src/composables/useIsActiveRoute.ts +0 -61
- package/src/composables/useNavigator.ts +0 -15
- package/src/composables/useRoute.ts +0 -34
- package/src/composables/useRouteEnter.ts +0 -120
- package/src/composables/useRouteExit.ts +0 -116
- package/src/composables/useRouteNode.ts +0 -31
- package/src/composables/useRouteUtils.ts +0 -12
- package/src/composables/useRouter.ts +0 -15
- package/src/composables/useRouterTransition.ts +0 -14
- package/src/constants.ts +0 -9
- package/src/context.ts +0 -13
- package/src/createRouterPlugin.ts +0 -31
- package/src/directives/vLink.ts +0 -208
- package/src/index.ts +0 -64
- package/src/setupRouteProvision.ts +0 -42
- package/src/ssr.ts +0 -39
- package/src/types.ts +0 -40
- package/src/useRefFromSource.ts +0 -16
- package/src/utils/createHttpStatusSink.ts +0 -31
|
@@ -1,233 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
Fragment,
|
|
3
|
-
defineComponent,
|
|
4
|
-
h,
|
|
5
|
-
KeepAlive,
|
|
6
|
-
markRaw,
|
|
7
|
-
Suspense,
|
|
8
|
-
} from "vue";
|
|
9
|
-
|
|
10
|
-
import { Match, NotFound, Self } from "./components";
|
|
11
|
-
import {
|
|
12
|
-
buildRenderList,
|
|
13
|
-
collectElements,
|
|
14
|
-
isKeepAliveEnabled,
|
|
15
|
-
} from "./helpers";
|
|
16
|
-
import { useRouteNode } from "../../composables/useRouteNode";
|
|
17
|
-
|
|
18
|
-
import type { Component, VNode } from "vue";
|
|
19
|
-
|
|
20
|
-
type SlotChildren = Record<string, (() => VNode[]) | undefined> | null;
|
|
21
|
-
|
|
22
|
-
function getSlotContent(vnode: VNode): VNode[] | null {
|
|
23
|
-
const slots = vnode.children as SlotChildren;
|
|
24
|
-
|
|
25
|
-
return slots?.default?.() ?? null;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
function getOrCreateWrapper(
|
|
29
|
-
cache: Map<string, Component>,
|
|
30
|
-
segment: string,
|
|
31
|
-
): Component {
|
|
32
|
-
const existing = cache.get(segment);
|
|
33
|
-
|
|
34
|
-
if (existing) {
|
|
35
|
-
return existing;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
const wrapper = markRaw(
|
|
39
|
-
defineComponent({
|
|
40
|
-
name: `KeepAlive-${segment}`,
|
|
41
|
-
setup(_wrapperProps, wrapperCtx) {
|
|
42
|
-
return () => wrapperCtx.slots.default?.();
|
|
43
|
-
},
|
|
44
|
-
}),
|
|
45
|
-
);
|
|
46
|
-
|
|
47
|
-
cache.set(segment, wrapper);
|
|
48
|
-
|
|
49
|
-
return wrapper;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
function wrapWithSuspense(content: VNode, fallback: unknown): VNode {
|
|
53
|
-
if (fallback === undefined) {
|
|
54
|
-
return content;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
const fallbackContent =
|
|
58
|
-
typeof fallback === "function" ? (fallback as () => VNode)() : fallback;
|
|
59
|
-
|
|
60
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-assignment
|
|
61
|
-
const suspenseComponent = Suspense as any;
|
|
62
|
-
|
|
63
|
-
return h(
|
|
64
|
-
suspenseComponent,
|
|
65
|
-
{},
|
|
66
|
-
{
|
|
67
|
-
default: () => content,
|
|
68
|
-
fallback: () => fallbackContent,
|
|
69
|
-
},
|
|
70
|
-
);
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
// Lazy-initialised — only allocated when a per-Match keepAlive path needs to
|
|
74
|
-
// keep the cache slot "occupied" with a no-render placeholder. Apps without
|
|
75
|
-
// keepAlive never pay the markRaw + defineComponent allocation at import.
|
|
76
|
-
let emptyKeepAlivePlaceholderInstance: Component | null = null;
|
|
77
|
-
|
|
78
|
-
function getEmptyKeepAlivePlaceholder(): Component {
|
|
79
|
-
emptyKeepAlivePlaceholderInstance ??= markRaw(
|
|
80
|
-
defineComponent({
|
|
81
|
-
name: "KeepAlive-placeholder",
|
|
82
|
-
render() {
|
|
83
|
-
return null;
|
|
84
|
-
},
|
|
85
|
-
}),
|
|
86
|
-
);
|
|
87
|
-
|
|
88
|
-
return emptyKeepAlivePlaceholderInstance;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
function renderWithRootKA(
|
|
92
|
-
activeChild: VNode,
|
|
93
|
-
wrapperCache: Map<string, Component>,
|
|
94
|
-
fallback: unknown,
|
|
95
|
-
): VNode {
|
|
96
|
-
const activeProps = activeChild.props as { segment?: string } | null;
|
|
97
|
-
const segment = activeProps?.segment ?? "__not-found__";
|
|
98
|
-
const WrapperComponent = getOrCreateWrapper(wrapperCache, segment);
|
|
99
|
-
const slotContent = getSlotContent(activeChild) ?? [];
|
|
100
|
-
const keepAliveContent = h(KeepAlive, null, {
|
|
101
|
-
default: () =>
|
|
102
|
-
h(WrapperComponent, { key: segment }, { default: () => slotContent }),
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
return wrapWithSuspense(keepAliveContent, fallback);
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
function renderWithPerMatchKA(
|
|
109
|
-
activeChild: VNode,
|
|
110
|
-
wrapperCache: Map<string, Component>,
|
|
111
|
-
fallback: unknown,
|
|
112
|
-
): VNode | null {
|
|
113
|
-
const matchProps = activeChild.props as {
|
|
114
|
-
segment?: string;
|
|
115
|
-
keepAlive?: unknown;
|
|
116
|
-
} | null;
|
|
117
|
-
|
|
118
|
-
if (isKeepAliveEnabled(matchProps?.keepAlive) && activeChild.type === Match) {
|
|
119
|
-
/* v8 ignore start */
|
|
120
|
-
const segment = matchProps?.segment ?? "__not-found__";
|
|
121
|
-
/* v8 ignore stop */
|
|
122
|
-
const WrapperComponent = getOrCreateWrapper(wrapperCache, segment);
|
|
123
|
-
const slotContent = getSlotContent(activeChild) ?? [];
|
|
124
|
-
|
|
125
|
-
return h(Fragment, [
|
|
126
|
-
h(KeepAlive, null, {
|
|
127
|
-
default: () =>
|
|
128
|
-
h(WrapperComponent, { key: segment }, { default: () => slotContent }),
|
|
129
|
-
}),
|
|
130
|
-
]);
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
const content = getSlotContent(activeChild);
|
|
134
|
-
|
|
135
|
-
/* v8 ignore start */
|
|
136
|
-
if (!content) {
|
|
137
|
-
return null;
|
|
138
|
-
}
|
|
139
|
-
/* v8 ignore stop */
|
|
140
|
-
|
|
141
|
-
return h(Fragment, [
|
|
142
|
-
h(KeepAlive, null, {
|
|
143
|
-
default: () => h(getEmptyKeepAlivePlaceholder()),
|
|
144
|
-
}),
|
|
145
|
-
wrapWithSuspense(h(Fragment, content), fallback),
|
|
146
|
-
]);
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
const RouteViewComponent = defineComponent({
|
|
150
|
-
name: "RouteView",
|
|
151
|
-
props: {
|
|
152
|
-
nodeName: {
|
|
153
|
-
type: String,
|
|
154
|
-
required: true,
|
|
155
|
-
},
|
|
156
|
-
keepAlive: {
|
|
157
|
-
type: Boolean,
|
|
158
|
-
default: false,
|
|
159
|
-
},
|
|
160
|
-
},
|
|
161
|
-
setup(props, { slots }) {
|
|
162
|
-
const routeContext = useRouteNode(props.nodeName);
|
|
163
|
-
const wrapperCache = new Map<string, Component>();
|
|
164
|
-
|
|
165
|
-
return (): VNode | null => {
|
|
166
|
-
const route = routeContext.route.value;
|
|
167
|
-
|
|
168
|
-
if (!route) {
|
|
169
|
-
return null;
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
const slotOutput = slots.default?.();
|
|
173
|
-
const elements: VNode[] = [];
|
|
174
|
-
|
|
175
|
-
collectElements(slotOutput, elements);
|
|
176
|
-
|
|
177
|
-
// `hasPerMatchKA` is a side-channel produced by the same pipeline pass
|
|
178
|
-
// that builds `rendered` — closes the audit §8.1 "double iteration"
|
|
179
|
-
// finding. The previous identity-cache on `slotOutput` is no longer
|
|
180
|
-
// needed: per-render cost is one O(n) walk instead of two.
|
|
181
|
-
const { rendered, fallback, hasPerMatchKA } = buildRenderList(
|
|
182
|
-
elements,
|
|
183
|
-
route.name,
|
|
184
|
-
props.nodeName,
|
|
185
|
-
);
|
|
186
|
-
|
|
187
|
-
if (rendered.length === 0) {
|
|
188
|
-
return null;
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
const activeChild = rendered[0];
|
|
192
|
-
|
|
193
|
-
if (props.keepAlive) {
|
|
194
|
-
return renderWithRootKA(activeChild, wrapperCache, fallback);
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
/* v8 ignore start */
|
|
198
|
-
if (
|
|
199
|
-
activeChild.type !== Match &&
|
|
200
|
-
activeChild.type !== Self &&
|
|
201
|
-
activeChild.type !== NotFound
|
|
202
|
-
) {
|
|
203
|
-
return null;
|
|
204
|
-
}
|
|
205
|
-
/* v8 ignore stop */
|
|
206
|
-
|
|
207
|
-
if (hasPerMatchKA) {
|
|
208
|
-
return renderWithPerMatchKA(activeChild, wrapperCache, fallback);
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
const content = getSlotContent(activeChild);
|
|
212
|
-
|
|
213
|
-
if (!content) {
|
|
214
|
-
return null;
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
return wrapWithSuspense(h(Fragment, content), fallback);
|
|
218
|
-
};
|
|
219
|
-
},
|
|
220
|
-
});
|
|
221
|
-
|
|
222
|
-
export const RouteView = Object.assign(RouteViewComponent, {
|
|
223
|
-
Match,
|
|
224
|
-
Self,
|
|
225
|
-
NotFound,
|
|
226
|
-
});
|
|
227
|
-
|
|
228
|
-
export type {
|
|
229
|
-
RouteViewProps,
|
|
230
|
-
MatchProps as RouteViewMatchProps,
|
|
231
|
-
SelfProps as RouteViewSelfProps,
|
|
232
|
-
NotFoundProps as RouteViewNotFoundProps,
|
|
233
|
-
} from "./types";
|
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
import { defineComponent } from "vue";
|
|
2
|
-
|
|
3
|
-
import type { SelfProps } from "./types";
|
|
4
|
-
import type { FunctionalComponent, PropType, VNode } from "vue";
|
|
5
|
-
|
|
6
|
-
function renderNull() {
|
|
7
|
-
return null;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
export const Match = defineComponent({
|
|
11
|
-
name: "RouteView.Match",
|
|
12
|
-
props: {
|
|
13
|
-
segment: {
|
|
14
|
-
type: String as PropType<string>,
|
|
15
|
-
required: true,
|
|
16
|
-
},
|
|
17
|
-
exact: {
|
|
18
|
-
type: Boolean,
|
|
19
|
-
default: false,
|
|
20
|
-
},
|
|
21
|
-
fallback: {
|
|
22
|
-
type: [Object, Function] as PropType<VNode | (() => VNode)>,
|
|
23
|
-
default: undefined,
|
|
24
|
-
},
|
|
25
|
-
keepAlive: {
|
|
26
|
-
type: Boolean,
|
|
27
|
-
default: false,
|
|
28
|
-
},
|
|
29
|
-
},
|
|
30
|
-
render: renderNull,
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
// Type Self via FunctionalComponent<SelfProps> so the SelfProps interface
|
|
34
|
-
// is anchored to the component contract — knip otherwise flags SelfProps
|
|
35
|
-
// as unused even though it's re-exported as RouteViewSelfProps for
|
|
36
|
-
// consumers wrapping Self in custom HOCs.
|
|
37
|
-
const SelfImpl = defineComponent({
|
|
38
|
-
name: "RouteView.Self",
|
|
39
|
-
props: {
|
|
40
|
-
fallback: {
|
|
41
|
-
type: [Object, Function] as PropType<VNode | (() => VNode)>,
|
|
42
|
-
default: undefined,
|
|
43
|
-
},
|
|
44
|
-
},
|
|
45
|
-
render: renderNull,
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
export const Self = SelfImpl as unknown as FunctionalComponent<SelfProps>;
|
|
49
|
-
|
|
50
|
-
export const NotFound = defineComponent({
|
|
51
|
-
name: "RouteView.NotFound",
|
|
52
|
-
render: renderNull,
|
|
53
|
-
});
|
|
@@ -1,207 +0,0 @@
|
|
|
1
|
-
import { UNKNOWN_ROUTE } from "@real-router/core";
|
|
2
|
-
import { startsWithSegment } from "@real-router/route-utils";
|
|
3
|
-
import { Fragment, isVNode } from "vue";
|
|
4
|
-
|
|
5
|
-
import { Match, NotFound, Self } from "./components";
|
|
6
|
-
|
|
7
|
-
import type { VNode } from "vue";
|
|
8
|
-
|
|
9
|
-
const MARKER_TYPES: ReadonlySet<unknown> = new Set([Match, Self, NotFound]);
|
|
10
|
-
const KEEP_ALIVE_VALUES: ReadonlySet<unknown> = new Set([
|
|
11
|
-
true,
|
|
12
|
-
"",
|
|
13
|
-
"keep-alive",
|
|
14
|
-
]);
|
|
15
|
-
|
|
16
|
-
type FallbackType = VNode | (() => VNode) | undefined;
|
|
17
|
-
|
|
18
|
-
interface FallbackSlots {
|
|
19
|
-
selfVNode: VNode | null;
|
|
20
|
-
selfFallback: FallbackType;
|
|
21
|
-
notFoundChildren: unknown;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export function isSegmentMatch(
|
|
25
|
-
routeName: string,
|
|
26
|
-
fullSegmentName: string,
|
|
27
|
-
exact: boolean,
|
|
28
|
-
): boolean {
|
|
29
|
-
if (exact) {
|
|
30
|
-
return routeName === fullSegmentName;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
return startsWithSegment(routeName, fullSegmentName);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
// Vue compiles boolean-shorthand template attributes (`<Match keepAlive>`) to
|
|
37
|
-
// an empty string instead of `true`, and converts them to `true` only when the
|
|
38
|
-
// receiving component's prop is declared with `type: Boolean`. `Match` is a
|
|
39
|
-
// marker component (`render: null`) — its props are inspected on the VNode
|
|
40
|
-
// without ever going through Vue's prop-casting pipeline, so the raw `""` (or
|
|
41
|
-
// the hyphenated attribute name) reaches us here. Accept the same trio Vue's
|
|
42
|
-
// runtime does.
|
|
43
|
-
export function isKeepAliveEnabled(value: unknown): boolean {
|
|
44
|
-
return KEEP_ALIVE_VALUES.has(value);
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
function normalizeChildren(children: unknown): VNode[] {
|
|
48
|
-
if (Array.isArray(children)) {
|
|
49
|
-
const result: VNode[] = [];
|
|
50
|
-
|
|
51
|
-
for (const child of children) {
|
|
52
|
-
if (Array.isArray(child)) {
|
|
53
|
-
result.push(...normalizeChildren(child));
|
|
54
|
-
} else if (isVNode(child)) {
|
|
55
|
-
result.push(child);
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
return result;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
if (isVNode(children)) {
|
|
63
|
-
return [children];
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
return [];
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
export function collectElements(children: unknown, result: VNode[]): void {
|
|
70
|
-
const vnodes = normalizeChildren(children);
|
|
71
|
-
|
|
72
|
-
for (const child of vnodes) {
|
|
73
|
-
if (MARKER_TYPES.has(child.type)) {
|
|
74
|
-
result.push(child);
|
|
75
|
-
} else if (child.type === Fragment) {
|
|
76
|
-
collectElements(child.children, result);
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
function recordFallback(child: VNode, slots: FallbackSlots): boolean {
|
|
82
|
-
if (child.type === NotFound) {
|
|
83
|
-
slots.notFoundChildren = child.children;
|
|
84
|
-
|
|
85
|
-
return true;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
if (child.type === Self) {
|
|
89
|
-
if (slots.selfVNode === null) {
|
|
90
|
-
slots.selfVNode = child;
|
|
91
|
-
const props = child.props as { fallback?: FallbackType } | null;
|
|
92
|
-
|
|
93
|
-
slots.selfFallback = props?.fallback;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
return true;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
return false;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
function evaluateMatch(
|
|
103
|
-
child: VNode,
|
|
104
|
-
routeName: string,
|
|
105
|
-
nodeName: string,
|
|
106
|
-
): { isActive: boolean; fallback: FallbackType } {
|
|
107
|
-
const props = child.props as {
|
|
108
|
-
segment: string;
|
|
109
|
-
exact?: boolean;
|
|
110
|
-
fallback?: FallbackType;
|
|
111
|
-
} | null;
|
|
112
|
-
const segment = props?.segment ?? "";
|
|
113
|
-
const exact = props?.exact ?? false;
|
|
114
|
-
const fullSegmentName = nodeName ? `${nodeName}.${segment}` : segment;
|
|
115
|
-
const isActive = isSegmentMatch(routeName, fullSegmentName, exact);
|
|
116
|
-
|
|
117
|
-
return { isActive, fallback: props?.fallback };
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
function appendFallback(
|
|
121
|
-
rendered: VNode[],
|
|
122
|
-
routeName: string,
|
|
123
|
-
nodeName: string,
|
|
124
|
-
slots: FallbackSlots,
|
|
125
|
-
elements: VNode[],
|
|
126
|
-
): FallbackType {
|
|
127
|
-
if (slots.selfVNode !== null && routeName === nodeName) {
|
|
128
|
-
rendered.push(slots.selfVNode);
|
|
129
|
-
|
|
130
|
-
return slots.selfFallback;
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
if (routeName === UNKNOWN_ROUTE && slots.notFoundChildren !== null) {
|
|
134
|
-
const nfElements = elements.filter((element) => element.type === NotFound);
|
|
135
|
-
/* v8 ignore next 3 */
|
|
136
|
-
const lastNf = nfElements.at(-1);
|
|
137
|
-
|
|
138
|
-
if (lastNf) {
|
|
139
|
-
rendered.push(lastNf);
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
return undefined;
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
export function buildRenderList(
|
|
147
|
-
elements: VNode[],
|
|
148
|
-
routeName: string,
|
|
149
|
-
nodeName: string,
|
|
150
|
-
): {
|
|
151
|
-
rendered: VNode[];
|
|
152
|
-
activeMatchFound: boolean;
|
|
153
|
-
fallback?: FallbackType;
|
|
154
|
-
/**
|
|
155
|
-
* True iff any `<Match>` child in the input has its `keepAlive` prop set
|
|
156
|
-
* to one of Vue's accepted boolean-shorthand forms. Surfaced as a
|
|
157
|
-
* side-channel from the single pipeline pass so the caller doesn't have
|
|
158
|
-
* to re-iterate `elements` after `buildRenderList` returns — closes a MED
|
|
159
|
-
* code-quality finding (audit §8.1).
|
|
160
|
-
*/
|
|
161
|
-
hasPerMatchKA: boolean;
|
|
162
|
-
} {
|
|
163
|
-
const slots: FallbackSlots = {
|
|
164
|
-
selfVNode: null,
|
|
165
|
-
selfFallback: undefined,
|
|
166
|
-
notFoundChildren: null,
|
|
167
|
-
};
|
|
168
|
-
let activeMatchFound = false;
|
|
169
|
-
let fallback: FallbackType = undefined;
|
|
170
|
-
let hasPerMatchKA = false;
|
|
171
|
-
const rendered: VNode[] = [];
|
|
172
|
-
|
|
173
|
-
for (const child of elements) {
|
|
174
|
-
// Match-only side-channel: scan for the keepAlive shorthand in the same
|
|
175
|
-
// pass that already inspects every child. Short-circuits once a positive
|
|
176
|
-
// is found to avoid redundant prop reads in big slot trees.
|
|
177
|
-
if (!hasPerMatchKA && child.type === Match) {
|
|
178
|
-
const matchProps = child.props as { keepAlive?: unknown } | null;
|
|
179
|
-
|
|
180
|
-
if (isKeepAliveEnabled(matchProps?.keepAlive)) {
|
|
181
|
-
hasPerMatchKA = true;
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
if (recordFallback(child, slots)) {
|
|
186
|
-
continue;
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
if (activeMatchFound) {
|
|
190
|
-
continue;
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
const result = evaluateMatch(child, routeName, nodeName);
|
|
194
|
-
|
|
195
|
-
if (result.isActive) {
|
|
196
|
-
activeMatchFound = true;
|
|
197
|
-
fallback = result.fallback;
|
|
198
|
-
rendered.push(child);
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
if (!activeMatchFound) {
|
|
203
|
-
fallback = appendFallback(rendered, routeName, nodeName, slots, elements);
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
return { rendered, activeMatchFound, fallback, hasPerMatchKA };
|
|
207
|
-
}
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import type { VNode } from "vue";
|
|
2
|
-
|
|
3
|
-
export interface RouteViewProps {
|
|
4
|
-
readonly nodeName: string;
|
|
5
|
-
readonly keepAlive?: boolean;
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
export interface MatchProps {
|
|
9
|
-
readonly segment: string;
|
|
10
|
-
readonly exact?: boolean;
|
|
11
|
-
readonly fallback?: VNode | (() => VNode);
|
|
12
|
-
readonly keepAlive?: boolean;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export interface SelfProps {
|
|
16
|
-
/** Fallback content while children are suspended. */
|
|
17
|
-
readonly fallback?: VNode | (() => VNode);
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export type NotFoundProps = Record<string, never>;
|
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
import { createDismissableError } from "@real-router/sources";
|
|
2
|
-
import { defineComponent, h, watch, Fragment } from "vue";
|
|
3
|
-
|
|
4
|
-
import { useRouter } from "../composables/useRouter";
|
|
5
|
-
import { useRefFromSource } from "../useRefFromSource";
|
|
6
|
-
|
|
7
|
-
import type { RouterError, State } from "@real-router/core";
|
|
8
|
-
import type { VNode, PropType } from "vue";
|
|
9
|
-
|
|
10
|
-
export const RouterErrorBoundary = defineComponent({
|
|
11
|
-
name: "RouterErrorBoundary",
|
|
12
|
-
props: {
|
|
13
|
-
fallback: {
|
|
14
|
-
type: Function as PropType<
|
|
15
|
-
(error: RouterError, resetError: () => void) => VNode
|
|
16
|
-
>,
|
|
17
|
-
required: true,
|
|
18
|
-
},
|
|
19
|
-
onError: {
|
|
20
|
-
type: Function as PropType<
|
|
21
|
-
(
|
|
22
|
-
error: RouterError,
|
|
23
|
-
toRoute: State | null,
|
|
24
|
-
fromRoute: State | null,
|
|
25
|
-
) => void
|
|
26
|
-
>,
|
|
27
|
-
default: undefined,
|
|
28
|
-
},
|
|
29
|
-
},
|
|
30
|
-
setup(props, { slots }) {
|
|
31
|
-
const router = useRouter();
|
|
32
|
-
const snapshot = useRefFromSource(createDismissableError(router));
|
|
33
|
-
|
|
34
|
-
watch(
|
|
35
|
-
() => snapshot.value.version,
|
|
36
|
-
() => {
|
|
37
|
-
if (snapshot.value.error) {
|
|
38
|
-
props.onError?.(
|
|
39
|
-
snapshot.value.error,
|
|
40
|
-
snapshot.value.toRoute,
|
|
41
|
-
snapshot.value.fromRoute,
|
|
42
|
-
);
|
|
43
|
-
}
|
|
44
|
-
},
|
|
45
|
-
{ immediate: true },
|
|
46
|
-
);
|
|
47
|
-
|
|
48
|
-
return () => {
|
|
49
|
-
const children = slots.default?.() ?? [];
|
|
50
|
-
const errorVNode = snapshot.value.error
|
|
51
|
-
? props.fallback(snapshot.value.error, snapshot.value.resetError)
|
|
52
|
-
: null;
|
|
53
|
-
|
|
54
|
-
return h(Fragment, null, [...children, errorVNode]);
|
|
55
|
-
};
|
|
56
|
-
},
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
export type RouterErrorBoundaryProps = InstanceType<
|
|
60
|
-
typeof RouterErrorBoundary
|
|
61
|
-
>["$props"];
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import { defineComponent, onMounted, ref } from "vue";
|
|
2
|
-
|
|
3
|
-
export const ServerOnly = defineComponent({
|
|
4
|
-
name: "ServerOnly",
|
|
5
|
-
setup(_, { slots }) {
|
|
6
|
-
const mounted = ref(false);
|
|
7
|
-
|
|
8
|
-
onMounted(() => {
|
|
9
|
-
mounted.value = true;
|
|
10
|
-
});
|
|
11
|
-
|
|
12
|
-
return () => (mounted.value ? slots.fallback?.() : slots.default?.());
|
|
13
|
-
},
|
|
14
|
-
});
|
|
15
|
-
|
|
16
|
-
export type ServerOnlyProps = InstanceType<typeof ServerOnly>["$props"];
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import { defineComponent, h, Suspense } from "vue";
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Cross-adapter alias for Vue's native `<Suspense>`. Symmetric naming with
|
|
5
|
-
* the React/Preact/Solid/Svelte/Angular `<Streamed>` components.
|
|
6
|
-
*
|
|
7
|
-
* Slots:
|
|
8
|
-
* - `default` — content (may contain `<Await>` or `async setup()` children).
|
|
9
|
-
* - `fallback` — shown while any descendant suspends.
|
|
10
|
-
*
|
|
11
|
-
* Vue's `<Suspense>` is **blocking** under SSR (no out-of-order placeholder
|
|
12
|
-
* resolution) — render of HTML after `<Streamed>` waits for every
|
|
13
|
-
* `async setup()` inside. This matches Vue 3's stable streaming behaviour
|
|
14
|
-
* (vs React 19 / Solid which support OOO resolution).
|
|
15
|
-
*/
|
|
16
|
-
export const Streamed = defineComponent({
|
|
17
|
-
name: "Streamed",
|
|
18
|
-
setup(_, { slots }) {
|
|
19
|
-
return () =>
|
|
20
|
-
h(
|
|
21
|
-
Suspense,
|
|
22
|
-
{},
|
|
23
|
-
{
|
|
24
|
-
default: () => slots.default?.(),
|
|
25
|
-
fallback: () => slots.fallback?.(),
|
|
26
|
-
},
|
|
27
|
-
);
|
|
28
|
-
},
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
export type StreamedProps = InstanceType<typeof Streamed>["$props"];
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
import { useRoute } from "./useRoute";
|
|
2
|
-
|
|
3
|
-
interface DeferredContext {
|
|
4
|
-
ssrDataDeferred?: Record<string, Promise<unknown>>;
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
const NEVER_PROMISE = new Promise<never>(() => {
|
|
8
|
-
// Intentionally never resolves — surfaces a forever-pending Suspense boundary
|
|
9
|
-
// when a key is requested that the loader never declared.
|
|
10
|
-
});
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* Read a deferred promise published by `defer({ deferred: { <key>: Promise } })`
|
|
14
|
-
* inside an SSR data loader. Returns the Promise for use inside `async setup()`
|
|
15
|
-
* (Vue's native Suspense pattern) or paired with `<Await name="key">`.
|
|
16
|
-
*
|
|
17
|
-
* ```ts
|
|
18
|
-
* // Vue async setup pattern
|
|
19
|
-
* export default defineComponent({
|
|
20
|
-
* async setup() {
|
|
21
|
-
* const reviews = await useDeferred<Review[]>("reviews");
|
|
22
|
-
* return () => h("div", reviews.map(...));
|
|
23
|
-
* },
|
|
24
|
-
* });
|
|
25
|
-
* ```
|
|
26
|
-
*
|
|
27
|
-
* Returns a forever-pending promise when the key is missing — surfaces
|
|
28
|
-
* loader/consumer key drift as a visible Suspense fallback rather than a
|
|
29
|
-
* silent runtime error.
|
|
30
|
-
*/
|
|
31
|
-
export function useDeferred<T = unknown>(key: string): Promise<T> {
|
|
32
|
-
const { route } = useRoute();
|
|
33
|
-
const context = route.value.context as DeferredContext;
|
|
34
|
-
const deferred = context.ssrDataDeferred;
|
|
35
|
-
|
|
36
|
-
return (deferred?.[key] ?? NEVER_PROMISE) as Promise<T>;
|
|
37
|
-
}
|