@void/react 1.4.0 → 1.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{action-4EvQppia.mjs → action-CyCVdW12.mjs} +2 -3
- package/dist/{context-D2ctU4yT.mjs → context-BQpawTZc.mjs} +1 -2
- package/dist/{deferred-shell-DOuP2YAo.mjs → deferred-shell-D5T32zXz.mjs} +1 -2
- package/dist/index.d.mts +13 -76
- package/dist/index.mjs +61 -83
- package/dist/plugin.d.mts +2 -10
- package/dist/plugin.mjs +45 -107
- package/dist/runtime/pages-client.d.mts +2 -3
- package/dist/runtime/pages-client.mjs +13 -10
- package/dist/runtime/pages-server.d.mts +2 -3
- package/dist/runtime/pages-server.mjs +6 -8
- package/package.json +6 -6
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { createActionClient } from "void/pages-client";
|
|
2
|
-
|
|
3
|
-
const client = createActionClient();
|
|
2
|
+
// src/runtime/action.ts
|
|
3
|
+
const client = createActionClient({ _resolveOnShell: true });
|
|
4
4
|
const action = client.action;
|
|
5
5
|
const setActionRouter = client.setActionRouter;
|
|
6
|
-
//#endregion
|
|
7
6
|
export { setActionRouter as n, action as t };
|
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
import { createContext } from "react";
|
|
2
2
|
import { idleNavigationState } from "void/pages-client";
|
|
3
|
-
|
|
3
|
+
// src/runtime/context.ts
|
|
4
4
|
const SharedContext = createContext(null);
|
|
5
5
|
const ErrorsContext = createContext(null);
|
|
6
6
|
const ParamsContext = createContext(null);
|
|
7
7
|
const RouterContext = createContext(null);
|
|
8
8
|
const NavigationContext = createContext(idleNavigationState());
|
|
9
9
|
const FrontmatterContext = createContext({});
|
|
10
|
-
//#endregion
|
|
11
10
|
export { RouterContext as a, ParamsContext as i, FrontmatterContext as n, SharedContext as o, NavigationContext as r, ErrorsContext as t };
|
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
|
|
1
|
+
// src/runtime/deferred-shell.ts
|
|
2
2
|
const DEFERRED_SHELL_END_ID = "__VOID_DEFERRED_SHELL_END__";
|
|
3
3
|
const DEFERRED_SHELL_END_MARKER = `<template id="${DEFERRED_SHELL_END_ID}"></template>`;
|
|
4
4
|
const DEFERRED_SHELL_CANCEL_REASON = "Void deferred shell complete";
|
|
5
|
-
//#endregion
|
|
6
5
|
export { DEFERRED_SHELL_END_ID as n, DEFERRED_SHELL_END_MARKER as r, DEFERRED_SHELL_CANCEL_REASON as t };
|
package/dist/index.d.mts
CHANGED
|
@@ -1,112 +1,49 @@
|
|
|
1
1
|
import { AnchorHTMLAttributes, ButtonHTMLAttributes, Context, FormHTMLAttributes, JSX, ReactElement, ReactNode } from "react";
|
|
2
|
-
import { ActionClient, FormMethod, FormModel,
|
|
2
|
+
import { ActionClient, BaseLinkProps, FormMethod, FormModel, FormState, IslandFormState, NavigationState, VoidActionError, VoidRouter } from "void/pages-client";
|
|
3
3
|
import { CloudContextVariables, InferProps } from "void";
|
|
4
4
|
import { ActionFormOptions, ActionUrl, ResolveActionBody } from "void/routes";
|
|
5
|
-
//#region src/runtime/form.d.ts
|
|
6
5
|
type FormProps = Omit<FormHTMLAttributes<HTMLFormElement>, "action" | "onSubmit" | "method" | "onError"> & {
|
|
7
6
|
form: FormModel<[FormData]>;
|
|
8
7
|
method?: FormMethod;
|
|
9
8
|
renderError?: (error: VoidActionError) => ReactNode;
|
|
10
9
|
};
|
|
11
10
|
export declare function Form({ form, method, renderError, children, ...props }: FormProps): ReactElement;
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
type PrefetchStrategy = "hover" | "click" | "mount" | "visible";
|
|
15
|
-
type LinkMethod = "GET" | "POST" | "PUT" | "PATCH" | "DELETE";
|
|
16
|
-
type CommonProps = {
|
|
17
|
-
href: string;
|
|
18
|
-
method?: LinkMethod | Lowercase<LinkMethod>;
|
|
19
|
-
data?: Record<string, unknown>;
|
|
20
|
-
preserveScroll?: boolean;
|
|
21
|
-
preserveState?: boolean;
|
|
22
|
-
replace?: boolean;
|
|
23
|
-
reloadDocument?: boolean;
|
|
24
|
-
viewTransition?: boolean;
|
|
25
|
-
prefetch?: boolean | PrefetchStrategy | ReadonlyArray<PrefetchStrategy>;
|
|
26
|
-
cacheFor?: number | string | [string, string];
|
|
27
|
-
onNavigate?: (event: {
|
|
28
|
-
preventDefault(): void;
|
|
29
|
-
}) => void;
|
|
30
|
-
};
|
|
31
|
-
type LinkProps = CommonProps & Omit<AnchorHTMLAttributes<HTMLAnchorElement>, keyof CommonProps> & Omit<ButtonHTMLAttributes<HTMLButtonElement>, keyof CommonProps>;
|
|
32
|
-
export declare function Link({ href, method, data, preserveScroll, preserveState, replace, reloadDocument, viewTransition, prefetch: prefetchProp, cacheFor, onNavigate, children, style, onClick: onClickProp, onMouseEnter: onMouseEnterProp, onMouseLeave: onMouseLeaveProp, onFocus: onFocusProp, onMouseDown: onMouseDownProp, onTouchStart: onTouchStartProp, ...rest }: LinkProps): JSX.Element;
|
|
33
|
-
//#endregion
|
|
34
|
-
//#region src/runtime/use-router.d.ts
|
|
11
|
+
type LinkProps = BaseLinkProps & Omit<AnchorHTMLAttributes<HTMLAnchorElement>, keyof BaseLinkProps> & Omit<ButtonHTMLAttributes<HTMLButtonElement>, keyof BaseLinkProps>;
|
|
12
|
+
export declare function Link({ href, method, data, preserveScroll, preserveState, replace, reloadDocument, viewTransition, prefetch, cacheFor, activeMatch, onNavigate, children, style, onClick: onClickProp, onMouseEnter: onMouseEnterProp, onMouseLeave: onMouseLeaveProp, onFocus: onFocusProp, onMouseDown: onMouseDownProp, onTouchStart: onTouchStartProp, ...rest }: LinkProps): JSX.Element;
|
|
35
13
|
export declare function useRouter(): VoidRouter;
|
|
36
|
-
//#endregion
|
|
37
|
-
//#region src/runtime/use-params.d.ts
|
|
38
14
|
export declare function useParams<T extends Record<string, string> = Record<string, string>>(): T;
|
|
39
|
-
//#endregion
|
|
40
|
-
//#region src/runtime/use-shared.d.ts
|
|
41
15
|
export declare function useShared<T = CloudContextVariables["shared"]>(): T;
|
|
42
|
-
|
|
43
|
-
|
|
16
|
+
type FormReturn<TData> = FormState<TData, "setters"> & Readonly<{
|
|
17
|
+
setData: <K extends keyof TData & string>(field: K, value: TData[K]) => void;
|
|
18
|
+
post(formData: FormData): void;
|
|
19
|
+
put(formData: FormData): void;
|
|
20
|
+
patch(formData: FormData): void;
|
|
21
|
+
delete(formData: FormData): void;
|
|
22
|
+
}>;
|
|
44
23
|
type FormOptions = {
|
|
45
24
|
params?: Record<string, string>;
|
|
46
25
|
resetOnSuccess?: boolean;
|
|
26
|
+
preserveScroll?: boolean;
|
|
47
27
|
onSuccess?: () => void | Promise<void>;
|
|
48
28
|
onError?: (error: Record<string, string> | VoidActionError) => void | Promise<void>;
|
|
49
29
|
};
|
|
50
|
-
export declare function useForm<U extends ActionUrl & string>(url: U, defaults: ResolveActionBody<U>, options?: ActionFormOptions<U> & Pick<FormOptions, "onSuccess" | "onError">):
|
|
51
|
-
data: ResolveActionBody<U>;
|
|
52
|
-
setData: <K extends keyof ResolveActionBody<U> & string>(field: K, value: ResolveActionBody<U>[K]) => void;
|
|
53
|
-
errors: Partial<Record<keyof ResolveActionBody<U> & string, string>>;
|
|
54
|
-
error: VoidActionError | null;
|
|
55
|
-
pending: boolean;
|
|
56
|
-
hasChanges: boolean;
|
|
57
|
-
wasSuccessful: boolean;
|
|
58
|
-
recentlySuccessful: boolean;
|
|
59
|
-
post(formData: FormData): void;
|
|
60
|
-
put(formData: FormData): void;
|
|
61
|
-
patch(formData: FormData): void;
|
|
62
|
-
delete(formData: FormData): void;
|
|
63
|
-
reset(options: FormResetOptions, ...fields: ReadonlyArray<keyof ResolveActionBody<U> & string>): void;
|
|
64
|
-
reset(...fields: ReadonlyArray<keyof ResolveActionBody<U> & string>): void;
|
|
65
|
-
setError(field: keyof ResolveActionBody<U> & string, message: string): void;
|
|
66
|
-
clearErrors(...fields: ReadonlyArray<keyof ResolveActionBody<U> & string>): void;
|
|
67
|
-
clearError(): void;
|
|
68
|
-
};
|
|
69
|
-
//#endregion
|
|
70
|
-
//#region src/runtime/use-navigation.d.ts
|
|
30
|
+
export declare function useForm<U extends ActionUrl & string>(url: U, defaults: ResolveActionBody<U>, options?: ActionFormOptions<U> & Pick<FormOptions, "onSuccess" | "onError" | "preserveScroll">): FormReturn<ResolveActionBody<U>>;
|
|
71
31
|
export declare function useNavigation(): NavigationState;
|
|
72
|
-
|
|
73
|
-
//#region src/runtime/use-island-form.d.ts
|
|
74
|
-
type IslandFormReturn<T extends Record<string, unknown>> = Readonly<{
|
|
75
|
-
data: T;
|
|
32
|
+
type IslandFormReturn<T extends Record<string, unknown>> = IslandFormState<T, "setters"> & Readonly<{
|
|
76
33
|
setData: <K extends keyof T & string>(field: K, value: T[K]) => void;
|
|
77
|
-
errors: Record<string, string>;
|
|
78
|
-
error: VoidActionError | null;
|
|
79
|
-
pending: boolean;
|
|
80
|
-
hasChanges: boolean;
|
|
81
|
-
wasSuccessful: boolean;
|
|
82
|
-
recentlySuccessful: boolean;
|
|
83
|
-
reset: (...fields: ReadonlyArray<keyof T & string>) => void;
|
|
84
|
-
clearErrors: (...fields: ReadonlyArray<string>) => void;
|
|
85
|
-
clearError: () => void;
|
|
86
|
-
post: (url: string) => Promise<void>;
|
|
87
|
-
put: (url: string) => Promise<void>;
|
|
88
|
-
patch: (url: string) => Promise<void>;
|
|
89
|
-
delete: (url: string) => Promise<void>;
|
|
90
34
|
}>;
|
|
91
35
|
/**
|
|
92
36
|
* Form hook for island pages. Uses fetch + page reload instead of the
|
|
93
37
|
* Void Router (which is not available in island mode).
|
|
94
38
|
*/
|
|
95
39
|
export declare function useIslandForm<T extends Record<string, unknown>>(defaults: T): IslandFormReturn<T>;
|
|
96
|
-
//#endregion
|
|
97
|
-
//#region src/runtime/action.d.ts
|
|
98
40
|
export declare const action: ActionClient["action"];
|
|
99
41
|
export declare const setActionRouter: ActionClient["setActionRouter"];
|
|
100
|
-
//#endregion
|
|
101
|
-
//#region src/runtime/context.d.ts
|
|
102
42
|
export declare const SharedContext: Context<Record<string, unknown> | null>;
|
|
103
43
|
export declare const ErrorsContext: Context<Record<string, string> | null>;
|
|
104
44
|
export declare const ParamsContext: Context<Record<string, string> | null>;
|
|
105
45
|
export declare const RouterContext: Context<VoidRouter | null>;
|
|
106
46
|
export declare const NavigationContext: Context<NavigationState>;
|
|
107
47
|
export declare const FrontmatterContext: Context<Record<string, unknown>>;
|
|
108
|
-
//#endregion
|
|
109
|
-
//#region src/runtime/index.d.ts
|
|
110
48
|
export type Deferred<T> = Promise<T>;
|
|
111
|
-
//#endregion
|
|
112
49
|
export type { InferProps };
|
package/dist/index.mjs
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { a as RouterContext, i as ParamsContext, n as FrontmatterContext, o as SharedContext, r as NavigationContext, t as ErrorsContext } from "./context-
|
|
2
|
-
import { n as setActionRouter, t as action } from "./action-
|
|
1
|
+
import { a as RouterContext, i as ParamsContext, n as FrontmatterContext, o as SharedContext, r as NavigationContext, t as ErrorsContext } from "./context-BQpawTZc.mjs";
|
|
2
|
+
import { n as setActionRouter, t as action } from "./action-CyCVdW12.mjs";
|
|
3
3
|
import { useActionState, useCallback, useContext, useEffect, useMemo, useRef, useState } from "react";
|
|
4
|
-
import { cloneFormValue, formErrorMessage, formRetryMessage, formSubmissionBlocked, getValidationErrors, interpolateRouteParams, isAbortError, isEqualFormValue,
|
|
4
|
+
import { cloneFormValue, formErrorMessage, formRetryMessage, formSubmissionBlocked, getValidationErrors, handleLinkClick, interpolateRouteParams, isAbortError, isEqualFormValue, linkActiveAttributes, linkPendingAttributes, linkPrefetchStrategies, observeLinkVisible, parseFormResetArgs, prefetchLink, resolveLink, scheduleFormRetry, ssrProxy, submitAction, submitIslandAction } from "void/pages-client";
|
|
5
5
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
6
|
-
|
|
6
|
+
// src/runtime/form.tsx
|
|
7
7
|
function Form({ form, method = "post", renderError, children, ...props }) {
|
|
8
8
|
const [now, setNow] = useState(Date.now);
|
|
9
9
|
useEffect(() => scheduleFormRetry(form.error, () => setNow(Date.now())), [form.error]);
|
|
@@ -24,82 +24,73 @@ function Form({ form, method = "post", renderError, children, ...props }) {
|
|
|
24
24
|
}), children]
|
|
25
25
|
});
|
|
26
26
|
}
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
function Link({ href, method = "GET", data, preserveScroll = false, preserveState = false, replace = false, reloadDocument = false, viewTransition, prefetch: prefetchProp = false, cacheFor, onNavigate, children, style, onClick: onClickProp, onMouseEnter: onMouseEnterProp, onMouseLeave: onMouseLeaveProp, onFocus: onFocusProp, onMouseDown: onMouseDownProp, onTouchStart: onTouchStartProp, ...rest }) {
|
|
27
|
+
// src/runtime/link.tsx
|
|
28
|
+
function Link({ href, method = "GET", data, preserveScroll = false, preserveState = false, replace = false, reloadDocument = false, viewTransition, prefetch, cacheFor, activeMatch, onNavigate, children, style, onClick: onClickProp, onMouseEnter: onMouseEnterProp, onMouseLeave: onMouseLeaveProp, onFocus: onFocusProp, onMouseDown: onMouseDownProp, onTouchStart: onTouchStartProp, ...rest }) {
|
|
30
29
|
const router = useContext(RouterContext);
|
|
30
|
+
const navigation = useContext(NavigationContext);
|
|
31
31
|
const elementRef = useRef(null);
|
|
32
32
|
const hoverTimerRef = useRef(null);
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
|
|
33
|
+
const link = useMemo(() => resolveLink({
|
|
34
|
+
href,
|
|
35
|
+
method,
|
|
36
|
+
data,
|
|
37
|
+
prefetch,
|
|
38
|
+
reloadDocument
|
|
39
|
+
}), [
|
|
36
40
|
data,
|
|
37
41
|
href,
|
|
38
|
-
|
|
42
|
+
method,
|
|
43
|
+
prefetch,
|
|
44
|
+
reloadDocument
|
|
39
45
|
]);
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
},
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
if (cacheFor !== void 0) options.cacheFor = cacheFor;
|
|
55
|
-
router.prefetch(hrefWithData, options);
|
|
56
|
-
}, [
|
|
57
|
-
cacheFor,
|
|
58
|
-
hrefWithData,
|
|
46
|
+
const { isGet } = link;
|
|
47
|
+
const defaultStrategy = router?._prefetchDefaultStrategy;
|
|
48
|
+
const strategies = useMemo(() => linkPrefetchStrategies({
|
|
49
|
+
prefetch,
|
|
50
|
+
reloadDocument
|
|
51
|
+
}, { isGet }, defaultStrategy), [
|
|
52
|
+
defaultStrategy,
|
|
53
|
+
isGet,
|
|
54
|
+
prefetch,
|
|
55
|
+
reloadDocument
|
|
56
|
+
]);
|
|
57
|
+
const activeAttributes = useMemo(() => linkActiveAttributes(router, link, activeMatch), [
|
|
58
|
+
activeMatch,
|
|
59
|
+
link,
|
|
59
60
|
router
|
|
60
61
|
]);
|
|
61
|
-
const
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
62
|
+
const pendingAttributes = useMemo(() => linkPendingAttributes(router, navigation, link), [
|
|
63
|
+
link,
|
|
64
|
+
navigation,
|
|
65
|
+
router
|
|
66
|
+
]);
|
|
67
|
+
const hoverDelay = router?._hoverDelay ?? 75;
|
|
68
|
+
const doPrefetch = useCallback(() => prefetchLink(router, link.href, cacheFor), [
|
|
69
|
+
cacheFor,
|
|
70
|
+
link.href,
|
|
69
71
|
router
|
|
70
72
|
]);
|
|
71
73
|
const onClick = useCallback((event) => {
|
|
72
74
|
onClickProp?.(event);
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
onNavigate?.({ preventDefault() {
|
|
76
|
-
navigatePrevented = true;
|
|
77
|
-
} });
|
|
78
|
-
if (navigatePrevented) {
|
|
79
|
-
event.preventDefault();
|
|
80
|
-
return;
|
|
81
|
-
}
|
|
82
|
-
event.preventDefault();
|
|
83
|
-
router?.visit(hrefWithData, {
|
|
84
|
-
method: normalizedMethod,
|
|
85
|
-
data: isGet ? void 0 : data,
|
|
75
|
+
handleLinkClick(event, router, link, {
|
|
76
|
+
data,
|
|
86
77
|
preserveScroll,
|
|
87
78
|
preserveState,
|
|
88
79
|
replace,
|
|
89
|
-
|
|
80
|
+
reloadDocument,
|
|
81
|
+
viewTransition,
|
|
82
|
+
onNavigate
|
|
90
83
|
});
|
|
91
84
|
}, [
|
|
92
85
|
data,
|
|
93
|
-
|
|
94
|
-
isGet,
|
|
95
|
-
normalizedMethod,
|
|
86
|
+
link,
|
|
96
87
|
onClickProp,
|
|
97
88
|
onNavigate,
|
|
98
89
|
preserveScroll,
|
|
99
90
|
preserveState,
|
|
91
|
+
reloadDocument,
|
|
100
92
|
replace,
|
|
101
93
|
router,
|
|
102
|
-
shouldHandleClick,
|
|
103
94
|
viewTransition
|
|
104
95
|
]);
|
|
105
96
|
const onMouseEnter = useCallback((event) => {
|
|
@@ -134,23 +125,13 @@ function Link({ href, method = "GET", data, preserveScroll = false, preserveStat
|
|
|
134
125
|
useEffect(() => {
|
|
135
126
|
if (strategies.length === 0) return;
|
|
136
127
|
if (strategies.includes("mount")) doPrefetch();
|
|
137
|
-
|
|
138
|
-
if (strategies.includes("visible") && elementRef.current) {
|
|
139
|
-
observer = new IntersectionObserver((entries) => {
|
|
140
|
-
if (entries.some((e) => e.isIntersecting)) {
|
|
141
|
-
doPrefetch();
|
|
142
|
-
observer?.disconnect();
|
|
143
|
-
observer = null;
|
|
144
|
-
}
|
|
145
|
-
}, { rootMargin: "0px" });
|
|
146
|
-
observer.observe(elementRef.current);
|
|
147
|
-
}
|
|
128
|
+
const stopObserving = strategies.includes("visible") && elementRef.current ? observeLinkVisible(elementRef.current, doPrefetch) : null;
|
|
148
129
|
return () => {
|
|
149
130
|
if (hoverTimerRef.current) {
|
|
150
131
|
clearTimeout(hoverTimerRef.current);
|
|
151
132
|
hoverTimerRef.current = null;
|
|
152
133
|
}
|
|
153
|
-
|
|
134
|
+
stopObserving?.();
|
|
154
135
|
};
|
|
155
136
|
}, [doPrefetch, strategies]);
|
|
156
137
|
const eventHandlers = useMemo(() => {
|
|
@@ -191,10 +172,12 @@ function Link({ href, method = "GET", data, preserveScroll = false, preserveStat
|
|
|
191
172
|
touchAction: "manipulation",
|
|
192
173
|
...style
|
|
193
174
|
}), [style]);
|
|
194
|
-
if (isGet) return /* @__PURE__ */ jsx("a", {
|
|
175
|
+
if (link.isGet) return /* @__PURE__ */ jsx("a", {
|
|
195
176
|
ref: elementRef,
|
|
196
|
-
href:
|
|
177
|
+
href: link.href,
|
|
197
178
|
style: resolvedStyle,
|
|
179
|
+
...activeAttributes,
|
|
180
|
+
...pendingAttributes,
|
|
198
181
|
...rest,
|
|
199
182
|
...eventHandlers,
|
|
200
183
|
children
|
|
@@ -203,32 +186,29 @@ function Link({ href, method = "GET", data, preserveScroll = false, preserveStat
|
|
|
203
186
|
ref: elementRef,
|
|
204
187
|
type: "button",
|
|
205
188
|
style: resolvedStyle,
|
|
189
|
+
...pendingAttributes,
|
|
206
190
|
...rest,
|
|
207
191
|
...eventHandlers,
|
|
208
192
|
children
|
|
209
193
|
});
|
|
210
194
|
}
|
|
211
|
-
|
|
212
|
-
//#region src/runtime/use-router.ts
|
|
195
|
+
// src/runtime/use-router.ts
|
|
213
196
|
function useRouter() {
|
|
214
197
|
return useContext(RouterContext) ?? ssrProxy;
|
|
215
198
|
}
|
|
216
|
-
|
|
217
|
-
//#region src/runtime/use-params.ts
|
|
199
|
+
// src/runtime/use-params.ts
|
|
218
200
|
function useParams() {
|
|
219
201
|
const params = useContext(ParamsContext);
|
|
220
202
|
if (params === null) throw new Error("useParams(): must be used inside a Void page.");
|
|
221
203
|
return params;
|
|
222
204
|
}
|
|
223
|
-
|
|
224
|
-
//#region src/runtime/use-shared.ts
|
|
205
|
+
// src/runtime/use-shared.ts
|
|
225
206
|
function useShared() {
|
|
226
207
|
const shared = useContext(SharedContext);
|
|
227
208
|
if (!shared) throw new Error("useShared(): must be used inside a Void page.");
|
|
228
209
|
return shared;
|
|
229
210
|
}
|
|
230
|
-
|
|
231
|
-
//#region src/runtime/use-form.ts
|
|
211
|
+
// src/runtime/use-form.ts
|
|
232
212
|
function useForm(url, defaults, options) {
|
|
233
213
|
const router = useContext(RouterContext);
|
|
234
214
|
const [data, setDataState] = useState(() => cloneFormValue(defaults));
|
|
@@ -301,6 +281,7 @@ function useForm(url, defaults, options) {
|
|
|
301
281
|
result = await submitAction(router, interpolateRouteParams(url, options?.params), {
|
|
302
282
|
method,
|
|
303
283
|
data: { ...dataRef.current },
|
|
284
|
+
preserveScroll: options?.preserveScroll,
|
|
304
285
|
_resolveOnShell: true
|
|
305
286
|
});
|
|
306
287
|
} catch (submitError) {
|
|
@@ -364,13 +345,11 @@ function useForm(url, defaults, options) {
|
|
|
364
345
|
clearError
|
|
365
346
|
};
|
|
366
347
|
}
|
|
367
|
-
|
|
368
|
-
//#region src/runtime/use-navigation.ts
|
|
348
|
+
// src/runtime/use-navigation.ts
|
|
369
349
|
function useNavigation() {
|
|
370
350
|
return useContext(NavigationContext);
|
|
371
351
|
}
|
|
372
|
-
|
|
373
|
-
//#region src/runtime/use-island-form.ts
|
|
352
|
+
// src/runtime/use-island-form.ts
|
|
374
353
|
/**
|
|
375
354
|
* Form hook for island pages. Uses fetch + page reload instead of the
|
|
376
355
|
* Void Router (which is not available in island mode).
|
|
@@ -458,5 +437,4 @@ function useIslandForm(defaults) {
|
|
|
458
437
|
delete: (url) => submit(url, "DELETE")
|
|
459
438
|
};
|
|
460
439
|
}
|
|
461
|
-
//#endregion
|
|
462
440
|
export { ErrorsContext, Form, FrontmatterContext, Link, NavigationContext, ParamsContext, RouterContext, SharedContext, action, setActionRouter, useForm, useIslandForm, useNavigation, useParams, useRouter, useShared };
|
package/dist/plugin.d.mts
CHANGED
|
@@ -1,17 +1,9 @@
|
|
|
1
1
|
import react from "@vitejs/plugin-react";
|
|
2
|
+
import { PrefetchConfig } from "void/pages";
|
|
2
3
|
import { Plugin } from "vite";
|
|
3
|
-
//#region src/plugin-pages.d.ts
|
|
4
|
-
type PrefetchConfig = {
|
|
5
|
-
hoverDelay?: number;
|
|
6
|
-
cacheFor?: number | string | [string, string];
|
|
7
|
-
};
|
|
8
|
-
//#endregion
|
|
9
|
-
//#region src/plugin.d.ts
|
|
10
4
|
export type VoidReactOptions = Readonly<{
|
|
11
5
|
react?: Parameters<typeof react>[0];
|
|
12
6
|
viewTransitions?: boolean;
|
|
13
7
|
prefetch?: PrefetchConfig;
|
|
14
8
|
}>;
|
|
15
|
-
export declare function voidReact(options?: VoidReactOptions): Array<Plugin>;
|
|
16
|
-
//#endregion
|
|
17
|
-
export {};
|
|
9
|
+
export declare function voidReact(options?: VoidReactOptions): Array<Plugin>;
|
package/dist/plugin.mjs
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
import react from "@vitejs/plugin-react";
|
|
2
|
-
import { createPagesPlugin, generateComponentManifest, resolveEffectiveLayoutChain, resolvePagesDir, scanPages } from "void/pages";
|
|
2
|
+
import { createPagesPlugin, generateComponentManifest, resolveEffectiveLayoutChain, resolvePagesDir, resolvePrefetchConfig, scanPages } from "void/pages";
|
|
3
3
|
import { existsSync, readFileSync } from "node:fs";
|
|
4
4
|
import nodePath from "node:path";
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
//#region ../../node_modules/.pnpm/pathslash@0.1.0/node_modules/pathslash/dist/index.mjs
|
|
5
|
+
import { ISLANDS_CLIENT_ENTRY_ID, ISLANDS_RENDERER_ID, computePagesNeedingClientJS, createIslandManifest, editIslandSource, findIslandImports, generateIslandClientEntry, islandCssFiles, islandStylesPlugin, islandsBuildConfig, mdClientScriptIds, recordIslandStyles, recordedIslandStyles, resolveSpecifier, watchPagesForRescan } from "void/pages-islands-plugin";
|
|
6
|
+
// ../../node_modules/.pnpm/pathslash@0.1.0/node_modules/pathslash/dist/index.mjs
|
|
8
7
|
const BACKSLASH_RE = /\\/g;
|
|
9
8
|
const isWindows = process.platform === "win32";
|
|
10
9
|
/**
|
|
@@ -42,8 +41,7 @@ const posix = { ...nodePath.posix };
|
|
|
42
41
|
posix.posix = win32.posix = posix;
|
|
43
42
|
posix.win32 = win32.win32 = win32;
|
|
44
43
|
const { basename, delimiter, dirname, extname, format, isAbsolute, join, matchesGlob, normalize, parse, relative, resolve, sep, toNamespacedPath } = isWindows ? win32 : posix;
|
|
45
|
-
|
|
46
|
-
//#region src/plugin-pages.ts
|
|
44
|
+
// src/plugin-pages.ts
|
|
47
45
|
const CLIENT_ENTRY_ID = "virtual:void-pages-client";
|
|
48
46
|
const WORKER_ENVIRONMENT_NAME = "void_worker";
|
|
49
47
|
const REACT_DEDUPE = ["react", "react-dom"];
|
|
@@ -146,12 +144,13 @@ ${generateComponentManifest(scan, pagesDir)}
|
|
|
146
144
|
|
|
147
145
|
const PREAMBLE_SCRIPT = ${JSON.stringify(preambleScript)};
|
|
148
146
|
|
|
149
|
-
export async function renderPage(pageObj, assetTags) {
|
|
147
|
+
export async function renderPage(pageObj, assetTags, requestUrl) {
|
|
150
148
|
return renderReactPage({
|
|
151
149
|
pageObj,
|
|
152
150
|
assetTags,
|
|
153
151
|
components,
|
|
154
152
|
layoutTree,
|
|
153
|
+
requestUrl,
|
|
155
154
|
preambleScript: PREAMBLE_SCRIPT,
|
|
156
155
|
});
|
|
157
156
|
}
|
|
@@ -159,6 +158,7 @@ renderPage.preambleScript = PREAMBLE_SCRIPT;
|
|
|
159
158
|
`;
|
|
160
159
|
}
|
|
161
160
|
function generateClientEntry(scan, pagesDir, viewTransitions, prefetch, staticPageData) {
|
|
161
|
+
const { defaultStrategy, hoverDelay, cacheFor } = resolvePrefetchConfig(prefetch);
|
|
162
162
|
return `
|
|
163
163
|
import { startReactPages } from "@void/react/pages-client";
|
|
164
164
|
import { parseCacheFor } from "void/pages-prefetch";
|
|
@@ -173,45 +173,45 @@ function generateClientEntry(scan, pagesDir, viewTransitions, prefetch, staticPa
|
|
|
173
173
|
staticPageData: ${!!staticPageData?.enabled},
|
|
174
174
|
staticPageDataFastPath: ${!!staticPageData?.fastPath},
|
|
175
175
|
viewTransitions: ${!!viewTransitions},
|
|
176
|
-
prefetchHoverDelay: ${
|
|
177
|
-
prefetchDefaultCacheFor: parseCacheFor(${JSON.stringify(
|
|
176
|
+
prefetchHoverDelay: ${hoverDelay},
|
|
177
|
+
prefetchDefaultCacheFor: parseCacheFor(${JSON.stringify(cacheFor)}),
|
|
178
|
+
prefetchDefaultStrategy: ${JSON.stringify(defaultStrategy)},
|
|
178
179
|
});
|
|
179
180
|
export { router };
|
|
180
181
|
`;
|
|
181
182
|
}
|
|
182
|
-
|
|
183
|
-
//#region src/plugin-islands.ts
|
|
183
|
+
// src/plugin-islands.ts
|
|
184
184
|
const COMPONENT_EXTENSIONS = [
|
|
185
185
|
".tsx",
|
|
186
186
|
".jsx",
|
|
187
187
|
".ts",
|
|
188
188
|
".js"
|
|
189
189
|
];
|
|
190
|
+
const HYDRATE_ISLAND = `
|
|
191
|
+
async function hydrateIsland(el, mod, props, frontmatter) {
|
|
192
|
+
const { hydrateRoot } = await import("react-dom/client");
|
|
193
|
+
const React = await import("react");
|
|
194
|
+
const Component = mod.default || mod;
|
|
195
|
+
let island = React.createElement(Component, props);
|
|
196
|
+
if (Object.keys(frontmatter).length > 0) {
|
|
197
|
+
const { FrontmatterContext } = await import("@void/react");
|
|
198
|
+
island = React.createElement(FrontmatterContext.Provider, { value: frontmatter }, island);
|
|
199
|
+
}
|
|
200
|
+
function HydratedIsland() {
|
|
201
|
+
React.useLayoutEffect(() => {
|
|
202
|
+
el.setAttribute("data-hydrated", "true");
|
|
203
|
+
}, []);
|
|
204
|
+
return island;
|
|
205
|
+
}
|
|
206
|
+
hydrateRoot(el, React.createElement(HydratedIsland));
|
|
207
|
+
}`;
|
|
190
208
|
function islandsPlugin(pagesDir, options) {
|
|
191
209
|
let pageScan = null;
|
|
192
210
|
let isDev = false;
|
|
193
211
|
const root = options?.root ?? dirname(pagesDir);
|
|
194
|
-
const islandManifest =
|
|
195
|
-
|
|
212
|
+
const islandManifest = createIslandManifest(root, COMPONENT_EXTENSIONS);
|
|
213
|
+
let cssFiles = /* @__PURE__ */ new Set();
|
|
196
214
|
let resolvedConfig = null;
|
|
197
|
-
/** Resolve an island import and record it in the client manifest. */
|
|
198
|
-
function registerIsland(spec, importerDir, hydrate) {
|
|
199
|
-
const absPath = resolveSpecifier(spec, importerDir, COMPONENT_EXTENSIONS);
|
|
200
|
-
if (!absPath) return {
|
|
201
|
-
absPath,
|
|
202
|
-
islandId: spec
|
|
203
|
-
};
|
|
204
|
-
const islandId = normalizePath(relative(root, absPath)).replace(/\.[^.]+$/, "");
|
|
205
|
-
islandManifest.set(islandId, {
|
|
206
|
-
componentPath: absPath,
|
|
207
|
-
islandId,
|
|
208
|
-
hydrate
|
|
209
|
-
});
|
|
210
|
-
return {
|
|
211
|
-
absPath,
|
|
212
|
-
islandId
|
|
213
|
-
};
|
|
214
|
-
}
|
|
215
215
|
return [{
|
|
216
216
|
name: "void-react:islands",
|
|
217
217
|
configResolved(config) {
|
|
@@ -229,33 +229,8 @@ function islandsPlugin(pagesDir, options) {
|
|
|
229
229
|
pagesDir,
|
|
230
230
|
environment: isDev ? "dev" : "prod"
|
|
231
231
|
});
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
filePath: page.componentPath,
|
|
235
|
-
island: page.island
|
|
236
|
-
})),
|
|
237
|
-
...pageScan.layouts.map((layout) => ({
|
|
238
|
-
filePath: layout.filePath,
|
|
239
|
-
island: layout.island
|
|
240
|
-
})),
|
|
241
|
-
...pageScan.namedLayouts.map((namedLayout) => ({
|
|
242
|
-
filePath: namedLayout.filePath,
|
|
243
|
-
island: namedLayout.island
|
|
244
|
-
}))
|
|
245
|
-
];
|
|
246
|
-
for (const entry of filesToScan) {
|
|
247
|
-
if (!entry.island) continue;
|
|
248
|
-
const filePath = resolve(pagesDir, entry.filePath);
|
|
249
|
-
const code = readFileSync(filePath, "utf-8");
|
|
250
|
-
const importerDir = dirname(filePath);
|
|
251
|
-
for (const islandImport of findIslandImports(code, filePath)) registerIsland(islandImport.specifier, importerDir, islandImport.hydrate);
|
|
252
|
-
}
|
|
253
|
-
for (const page of pageScan.pages) {
|
|
254
|
-
if (!page.island) continue;
|
|
255
|
-
if (/\.(tsx|jsx)$/.test(page.componentPath)) islandCssFiles.add(normalizePath(resolve(pagesDir, page.componentPath)));
|
|
256
|
-
const layoutChain = resolveEffectiveLayoutChain(page, pageScan.layouts, pageScan.namedLayouts);
|
|
257
|
-
for (const layout of layoutChain) islandCssFiles.add(normalizePath(resolve(pagesDir, layout.filePath)));
|
|
258
|
-
}
|
|
232
|
+
islandManifest.registerPages(pageScan, pagesDir);
|
|
233
|
+
cssFiles = islandCssFiles(pageScan, pagesDir, /\.(tsx|jsx)$/);
|
|
259
234
|
}
|
|
260
235
|
},
|
|
261
236
|
buildEnd() {
|
|
@@ -272,14 +247,20 @@ function islandsPlugin(pagesDir, options) {
|
|
|
272
247
|
handler(id) {
|
|
273
248
|
if (id === "\0" + ISLANDS_RENDERER_ID) {
|
|
274
249
|
if (!pageScan) return "export function renderIslandPage() { throw new Error('No pages found'); }";
|
|
275
|
-
const
|
|
276
|
-
const mdScriptIds = new Set(mdPlugin?.api?.getClientScripts?.()?.keys() ?? []);
|
|
250
|
+
const mdScriptIds = mdClientScriptIds(resolvedConfig);
|
|
277
251
|
const needsJS = computePagesNeedingClientJS(pageScan, pagesDir, resolvedConfig);
|
|
278
252
|
return generateIslandRenderer(pageScan, pagesDir, isDev, mdScriptIds, needsJS);
|
|
279
253
|
}
|
|
280
254
|
if (id === "\0" + ISLANDS_CLIENT_ENTRY_ID) {
|
|
281
255
|
if (!pageScan) return "";
|
|
282
|
-
return generateIslandClientEntry(
|
|
256
|
+
return generateIslandClientEntry({
|
|
257
|
+
manifest: islandManifest,
|
|
258
|
+
cssFiles,
|
|
259
|
+
styles: recordedIslandStyles(pagesDir),
|
|
260
|
+
needsRuntime: islandManifest.entries.size > 0 || computePagesNeedingClientJS(pageScan, pagesDir, resolvedConfig).size > 0,
|
|
261
|
+
isDev,
|
|
262
|
+
hydrateIsland: HYDRATE_ISLAND
|
|
263
|
+
});
|
|
283
264
|
}
|
|
284
265
|
}
|
|
285
266
|
},
|
|
@@ -297,7 +278,7 @@ function islandsPlugin(pagesDir, options) {
|
|
|
297
278
|
const strategy = islandImport.hydrate;
|
|
298
279
|
const spec = islandImport.specifier;
|
|
299
280
|
const rawName = `_raw_island_${idx++}`;
|
|
300
|
-
const { islandId } =
|
|
281
|
+
const { islandId } = islandManifest.register(spec, importerDir, strategy);
|
|
301
282
|
const replacement = `import ${rawName} from ${specRaw}`;
|
|
302
283
|
edits.push({
|
|
303
284
|
start: islandImport.start,
|
|
@@ -313,10 +294,7 @@ function islandsPlugin(pagesDir, options) {
|
|
|
313
294
|
}
|
|
314
295
|
},
|
|
315
296
|
async config(_, env) {
|
|
316
|
-
if (env.command !== "serve"
|
|
317
|
-
manifest: true,
|
|
318
|
-
rollupOptions: { input: { "islands-client": ISLANDS_CLIENT_ENTRY_ID } }
|
|
319
|
-
} } } };
|
|
297
|
+
if (env.command !== "serve") return islandsBuildConfig(root, pagesDir);
|
|
320
298
|
}
|
|
321
299
|
}, islandStylesPlugin(pagesDir)];
|
|
322
300
|
}
|
|
@@ -393,46 +371,7 @@ export async function renderIslandPage(pageObj, assetTags) {
|
|
|
393
371
|
}
|
|
394
372
|
`;
|
|
395
373
|
}
|
|
396
|
-
|
|
397
|
-
const entries = [...manifest.values()];
|
|
398
|
-
const uniqueIslands = /* @__PURE__ */ new Map();
|
|
399
|
-
for (const entry of entries) uniqueIslands.set(entry.islandId, entry.componentPath);
|
|
400
|
-
const lines = [];
|
|
401
|
-
if (!isDev && styles) for (const id of styles) lines.push(`import ${JSON.stringify(id)};`);
|
|
402
|
-
else if (!isDev) for (const file of cssFiles) {
|
|
403
|
-
const content = readFileSync(file, "utf-8");
|
|
404
|
-
for (const item of findStaticCssImports(content, file)) lines.push(rewriteCssImportForVirtualModule(item.statement, item.specifier, file));
|
|
405
|
-
}
|
|
406
|
-
if (!needsRuntime && !isDev) return lines.join("\n");
|
|
407
|
-
lines.push("import { initIslands } from \"void/pages-client\";");
|
|
408
|
-
lines.push("const islands = {");
|
|
409
|
-
for (const [id, path] of uniqueIslands) lines.push(` ${JSON.stringify(id)}: () => import(${JSON.stringify(path)}),`);
|
|
410
|
-
lines.push("};");
|
|
411
|
-
lines.push(`
|
|
412
|
-
async function hydrateIsland(el, mod, props, frontmatter) {
|
|
413
|
-
const { hydrateRoot } = await import("react-dom/client");
|
|
414
|
-
const React = await import("react");
|
|
415
|
-
const Component = mod.default || mod;
|
|
416
|
-
let island = React.createElement(Component, props);
|
|
417
|
-
if (Object.keys(frontmatter).length > 0) {
|
|
418
|
-
const { FrontmatterContext } = await import("@void/react");
|
|
419
|
-
island = React.createElement(FrontmatterContext.Provider, { value: frontmatter }, island);
|
|
420
|
-
}
|
|
421
|
-
function HydratedIsland() {
|
|
422
|
-
React.useLayoutEffect(() => {
|
|
423
|
-
el.setAttribute("data-hydrated", "true");
|
|
424
|
-
}, []);
|
|
425
|
-
return island;
|
|
426
|
-
}
|
|
427
|
-
hydrateRoot(el, React.createElement(HydratedIsland));
|
|
428
|
-
}
|
|
429
|
-
|
|
430
|
-
initIslands(islands, hydrateIsland);
|
|
431
|
-
`);
|
|
432
|
-
return lines.join("\n");
|
|
433
|
-
}
|
|
434
|
-
//#endregion
|
|
435
|
-
//#region src/plugin.ts
|
|
374
|
+
// src/plugin.ts
|
|
436
375
|
function voidReact(options) {
|
|
437
376
|
const root = process.cwd();
|
|
438
377
|
const pagesDir = resolvePagesDir(root);
|
|
@@ -446,5 +385,4 @@ function voidReact(options) {
|
|
|
446
385
|
...islandsPlugin(pagesDir, { root })
|
|
447
386
|
];
|
|
448
387
|
}
|
|
449
|
-
//#endregion
|
|
450
388
|
export { voidReact };
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import { ComponentType } from "react";
|
|
2
2
|
import { VoidRouter } from "void/pages-client";
|
|
3
|
-
import { CacheForResult } from "void/pages-prefetch";
|
|
3
|
+
import { CacheForResult, LinkPrefetchValue } from "void/pages-prefetch";
|
|
4
4
|
import { PageObject } from "void/pages-protocol";
|
|
5
|
-
//#region src/runtime/pages-client.d.ts
|
|
6
5
|
type ComponentModule = {
|
|
7
6
|
default: ComponentType<any>;
|
|
8
7
|
};
|
|
@@ -19,6 +18,7 @@ export type ReactPagesConfig = Readonly<{
|
|
|
19
18
|
viewTransitions: boolean;
|
|
20
19
|
prefetchHoverDelay: number;
|
|
21
20
|
prefetchDefaultCacheFor: CacheForResult;
|
|
21
|
+
prefetchDefaultStrategy?: LinkPrefetchValue;
|
|
22
22
|
cacheVisitedPageData?: boolean;
|
|
23
23
|
/** Show this fallback for client visits whose destination remains pending at the next paint. */
|
|
24
24
|
navigationFallback?: ComponentType<{
|
|
@@ -29,5 +29,4 @@ export type ReactPagesClient = Readonly<{
|
|
|
29
29
|
router: VoidRouter;
|
|
30
30
|
}>;
|
|
31
31
|
export declare function startReactPages(config: ReactPagesConfig): Promise<ReactPagesClient>;
|
|
32
|
-
//#endregion
|
|
33
32
|
export {};
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { a as RouterContext, i as ParamsContext, o as SharedContext, r as NavigationContext, t as ErrorsContext } from "../context-
|
|
2
|
-
import { n as setActionRouter } from "../action-
|
|
3
|
-
import { n as DEFERRED_SHELL_END_ID } from "../deferred-shell-
|
|
1
|
+
import { a as RouterContext, i as ParamsContext, o as SharedContext, r as NavigationContext, t as ErrorsContext } from "../context-BQpawTZc.mjs";
|
|
2
|
+
import { n as setActionRouter } from "../action-CyCVdW12.mjs";
|
|
3
|
+
import { n as DEFERRED_SHELL_END_ID } from "../deferred-shell-D5T32zXz.mjs";
|
|
4
4
|
import React, { Suspense, startTransition, use, useCallback, useEffect, useInsertionEffect, useLayoutEffect, useMemo, useReducer, useState, useTransition } from "react";
|
|
5
|
-
import { createPageNavigationEngine, createRouterFacade, idleNavigationState, isAbortError, isCallSiteActionError, refreshPage } from "void/pages-client";
|
|
5
|
+
import { createPageNavigationEngine, createRouterFacade, idleNavigationState, isAbortError, isCallSiteActionError, refreshPage, toUrlString } from "void/pages-client";
|
|
6
6
|
import { createRoot, hydrateRoot } from "react-dom/client";
|
|
7
|
-
|
|
7
|
+
// src/runtime/pages-client.ts
|
|
8
8
|
const deferredState = /* @__PURE__ */ new WeakMap();
|
|
9
9
|
const viewTransitionWaiters = /* @__PURE__ */ new Map();
|
|
10
10
|
const pendingVisitSettlers = /* @__PURE__ */ new Map();
|
|
@@ -51,8 +51,9 @@ function currentUrl() {
|
|
|
51
51
|
return window.location.pathname + window.location.search + window.location.hash;
|
|
52
52
|
}
|
|
53
53
|
function normalizeUrl(value) {
|
|
54
|
-
|
|
55
|
-
|
|
54
|
+
const urlString = toUrlString(value);
|
|
55
|
+
if (urlString.startsWith("#")) return urlString;
|
|
56
|
+
const url = new URL(urlString, window.location.href);
|
|
56
57
|
return url.origin === window.location.origin ? url.pathname + url.search + url.hash : url.href;
|
|
57
58
|
}
|
|
58
59
|
/** Page data holds a decoded path. Compare it with browser URLs in their encoded form. */
|
|
@@ -141,6 +142,7 @@ function appReducer(state, action) {
|
|
|
141
142
|
function createRouter(engine) {
|
|
142
143
|
const router = {
|
|
143
144
|
_hoverDelay: engine._hoverDelay,
|
|
145
|
+
_prefetchDefaultStrategy: engine._prefetchDefaultStrategy,
|
|
144
146
|
get url() {
|
|
145
147
|
return new URL(currentUrl(), window.location.origin);
|
|
146
148
|
},
|
|
@@ -306,6 +308,7 @@ function App({ engine, router, viewTransitions, navigationFallback: NavigationFa
|
|
|
306
308
|
}, []);
|
|
307
309
|
const schedule = useCallback((url, options = {}) => {
|
|
308
310
|
const prepared = engine.prepareVisit(url, options);
|
|
311
|
+
const resolveOnData = prepared.options.resolveOn === "data" || prepared.options._resolveOnShell;
|
|
309
312
|
if (prepared.immediate) {
|
|
310
313
|
const navigation = navigationForResource(prepared);
|
|
311
314
|
activePendingNavigationId = prepared.id;
|
|
@@ -355,7 +358,7 @@ function App({ engine, router, viewTransitions, navigationFallback: NavigationFa
|
|
|
355
358
|
location: currentUrl()
|
|
356
359
|
});
|
|
357
360
|
resolveVisit(void 0);
|
|
358
|
-
} else if (
|
|
361
|
+
} else if (resolveOnData) {
|
|
359
362
|
pendingVisitSettlers.delete(prepared.id);
|
|
360
363
|
resolveVisit(result.pageData);
|
|
361
364
|
}
|
|
@@ -370,7 +373,7 @@ function App({ engine, router, viewTransitions, navigationFallback: NavigationFa
|
|
|
370
373
|
if (prepared.method.toUpperCase() === "GET" && !prepared.options._resolveOnShell && isAbortError(error)) resolveVisit(void 0);
|
|
371
374
|
else rejectVisit(error);
|
|
372
375
|
if (activePendingNavigationId === prepared.id) activePendingNavigationId = null;
|
|
373
|
-
if (NavigationFallback ||
|
|
376
|
+
if (NavigationFallback || resolveOnData || isAbortError(error) || isCallSiteActionError(error)) {
|
|
374
377
|
dispatch({
|
|
375
378
|
type: "abandon",
|
|
376
379
|
resource: prepared,
|
|
@@ -490,6 +493,7 @@ async function startReactPages(config) {
|
|
|
490
493
|
viewTransitions: config.viewTransitions,
|
|
491
494
|
prefetchHoverDelay: config.prefetchHoverDelay,
|
|
492
495
|
prefetchDefaultCacheFor: config.prefetchDefaultCacheFor,
|
|
496
|
+
prefetchDefaultStrategy: config.prefetchDefaultStrategy,
|
|
493
497
|
cacheVisitedPageData: config.cacheVisitedPageData
|
|
494
498
|
});
|
|
495
499
|
const router = createRouter(engine);
|
|
@@ -513,5 +517,4 @@ async function startReactPages(config) {
|
|
|
513
517
|
engine.start();
|
|
514
518
|
return { router };
|
|
515
519
|
}
|
|
516
|
-
//#endregion
|
|
517
520
|
export { startReactPages };
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { ComponentType } from "react";
|
|
2
2
|
import { PageObject } from "void/pages-protocol";
|
|
3
|
-
//#region src/runtime/pages-server.d.ts
|
|
4
3
|
type ComponentModule = {
|
|
5
4
|
default: ComponentType<any>;
|
|
6
5
|
};
|
|
@@ -13,8 +12,8 @@ export type ReactPagesServerConfig = Readonly<{
|
|
|
13
12
|
};
|
|
14
13
|
components: Record<string, () => Promise<ComponentModule>>;
|
|
15
14
|
layoutTree: Record<string, Array<string>>;
|
|
15
|
+
requestUrl?: string;
|
|
16
16
|
preambleScript?: string;
|
|
17
17
|
}>;
|
|
18
|
-
export declare function renderReactPage({ pageObj, assetTags, components, layoutTree, preambleScript }: ReactPagesServerConfig): Promise<ReadableStream<Uint8Array>>;
|
|
19
|
-
//#endregion
|
|
18
|
+
export declare function renderReactPage({ pageObj, assetTags, components, layoutTree, requestUrl, preambleScript }: ReactPagesServerConfig): Promise<ReadableStream<Uint8Array>>;
|
|
20
19
|
export {};
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import { a as RouterContext, i as ParamsContext, o as SharedContext, r as NavigationContext, t as ErrorsContext } from "../context-
|
|
2
|
-
import { n as DEFERRED_SHELL_END_ID, r as DEFERRED_SHELL_END_MARKER, t as DEFERRED_SHELL_CANCEL_REASON } from "../deferred-shell-
|
|
1
|
+
import { a as RouterContext, i as ParamsContext, o as SharedContext, r as NavigationContext, t as ErrorsContext } from "../context-BQpawTZc.mjs";
|
|
2
|
+
import { n as DEFERRED_SHELL_END_ID, r as DEFERRED_SHELL_END_MARKER, t as DEFERRED_SHELL_CANCEL_REASON } from "../deferred-shell-D5T32zXz.mjs";
|
|
3
3
|
import React from "react";
|
|
4
4
|
import { createSsrRouter, idleNavigationState } from "void/pages-client";
|
|
5
5
|
import { renderToReadableStream } from "react-dom/server";
|
|
6
6
|
import { renderBodyAttrs, renderHeadToString, renderHtmlAttrs } from "void/pages-head";
|
|
7
7
|
import { serializePageData } from "void/pages-server";
|
|
8
|
-
|
|
8
|
+
// src/runtime/page-streams.ts
|
|
9
9
|
function concatStreams(streams) {
|
|
10
10
|
let index = 0;
|
|
11
11
|
let reader;
|
|
@@ -94,8 +94,7 @@ function readDeferredShell(stream) {
|
|
|
94
94
|
}
|
|
95
95
|
}, { highWaterMark: 0 });
|
|
96
96
|
}
|
|
97
|
-
|
|
98
|
-
//#region src/runtime/pages-server.ts
|
|
97
|
+
// src/runtime/pages-server.ts
|
|
99
98
|
function createPendingDeferred() {
|
|
100
99
|
return new Promise(() => {});
|
|
101
100
|
}
|
|
@@ -112,14 +111,14 @@ function textStream(text) {
|
|
|
112
111
|
controller.close();
|
|
113
112
|
} });
|
|
114
113
|
}
|
|
115
|
-
async function renderReactPage({ pageObj, assetTags, components, layoutTree, preambleScript = "" }) {
|
|
114
|
+
async function renderReactPage({ pageObj, assetTags, components, layoutTree, requestUrl, preambleScript = "" }) {
|
|
116
115
|
const PageComponent = (await components[pageObj.component]()).default;
|
|
117
116
|
const layoutIds = layoutTree[pageObj.component] || [];
|
|
118
117
|
const layoutComponents = await Promise.all(layoutIds.map(async (id) => (await components[id]()).default));
|
|
119
118
|
const renderProps = propsForReactRender(pageObj);
|
|
120
119
|
let element = React.createElement(PageComponent, renderProps);
|
|
121
120
|
for (let i = layoutComponents.length - 1; i >= 0; i--) element = React.createElement(layoutComponents[i], null, element);
|
|
122
|
-
element = React.createElement(RouterContext.Provider, { value: createSsrRouter(pageObj.url || "/", pageObj.params ?? {}) }, React.createElement(NavigationContext.Provider, { value: idleNavigationState() }, React.createElement(React.Suspense, { fallback: null }, React.createElement(SharedContext.Provider, { value: pageObj.shared || {} }, React.createElement(ParamsContext.Provider, { value: pageObj.params ?? {} }, React.createElement(ErrorsContext.Provider, { value: pageObj.errors || {} }, element))))));
|
|
121
|
+
element = React.createElement(RouterContext.Provider, { value: createSsrRouter(pageObj.url || "/", pageObj.params ?? {}, requestUrl) }, React.createElement(NavigationContext.Provider, { value: idleNavigationState() }, React.createElement(React.Suspense, { fallback: null }, React.createElement(SharedContext.Provider, { value: pageObj.shared || {} }, React.createElement(ParamsContext.Provider, { value: pageObj.params ?? {} }, React.createElement(ErrorsContext.Provider, { value: pageObj.errors || {} }, element))))));
|
|
123
122
|
const renderElement = pageObj.deferredKeys ? React.createElement(React.Fragment, null, element, React.createElement("template", { id: DEFERRED_SHELL_END_ID })) : element;
|
|
124
123
|
let shellError;
|
|
125
124
|
let shellSent = false;
|
|
@@ -155,5 +154,4 @@ async function renderReactPage({ pageObj, assetTags, components, layoutTree, pre
|
|
|
155
154
|
</html>`)
|
|
156
155
|
]);
|
|
157
156
|
}
|
|
158
|
-
//#endregion
|
|
159
157
|
export { renderReactPage };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@void/react",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.6.0",
|
|
4
4
|
"description": "React pages, server rendering, forms, and navigation for Void.",
|
|
5
5
|
"homepage": "https://void.cloud",
|
|
6
6
|
"bugs": {
|
|
@@ -41,22 +41,22 @@
|
|
|
41
41
|
"@vitejs/plugin-react": "^6.1.2"
|
|
42
42
|
},
|
|
43
43
|
"devDependencies": {
|
|
44
|
-
"@types/node": "^26.6.
|
|
44
|
+
"@types/node": "^26.6.6",
|
|
45
45
|
"@types/react": "^19.3.0",
|
|
46
46
|
"@types/react-dom": "^19.3.0",
|
|
47
47
|
"happy-dom": "^21.0.4",
|
|
48
48
|
"pathslash": "^0.1.0",
|
|
49
49
|
"react": "^19.3.1",
|
|
50
50
|
"react-dom": "^19.3.1",
|
|
51
|
-
"vite": "npm:@voidzero-dev/vite-plus-core@1.
|
|
52
|
-
"vite-plus": "1.
|
|
53
|
-
"void": "1.
|
|
51
|
+
"vite": "npm:@voidzero-dev/vite-plus-core@1.4.0",
|
|
52
|
+
"vite-plus": "1.4.0",
|
|
53
|
+
"void": "1.6.0"
|
|
54
54
|
},
|
|
55
55
|
"peerDependencies": {
|
|
56
56
|
"react": "^19.0.0",
|
|
57
57
|
"react-dom": "^19.0.0",
|
|
58
58
|
"vite": "^8.0.0",
|
|
59
|
-
"void": "1.
|
|
59
|
+
"void": "1.6.0"
|
|
60
60
|
},
|
|
61
61
|
"scripts": {
|
|
62
62
|
"build": "vp pack",
|