@void/vue 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/index.d.mts +8 -53
- package/dist/index.mjs +85 -131
- package/dist/plugin.d.mts +2 -10
- package/dist/plugin.mjs +52 -114
- package/dist/runtime/use-deferred.d.mts +1 -3
- package/dist/runtime/use-deferred.mjs +1 -2
- package/package.json +6 -6
package/dist/index.d.mts
CHANGED
|
@@ -1,74 +1,29 @@
|
|
|
1
|
-
import { ActionClient,
|
|
2
|
-
import { Component } from "vue";
|
|
1
|
+
import { ActionClient, BaseLinkProps, FormState, FormSubmitOptions, IslandFormState, NavigationState, VoidRouter } from "void/pages-client";
|
|
2
|
+
import { AnchorHTMLAttributes, ButtonHTMLAttributes, Component, DefineSetupFnComponent } from "vue";
|
|
3
3
|
import { CloudContextVariables, DeferredState as Deferred, InferProps } from "void";
|
|
4
4
|
import { ActionFormOptions, ActionUrl, ResolveActionBody } from "void/routes";
|
|
5
|
-
//#region src/runtime/form.d.ts
|
|
6
5
|
export declare const Form: Component;
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
export declare const Link: Component;
|
|
10
|
-
//#endregion
|
|
11
|
-
//#region src/runtime/use-router.d.ts
|
|
6
|
+
type LinkProps = BaseLinkProps & Omit<AnchorHTMLAttributes, keyof BaseLinkProps> & Omit<ButtonHTMLAttributes, keyof BaseLinkProps>;
|
|
7
|
+
export declare const Link: DefineSetupFnComponent<LinkProps>;
|
|
12
8
|
export declare function useRouter(): VoidRouter;
|
|
13
|
-
//#endregion
|
|
14
|
-
//#region src/runtime/use-params.d.ts
|
|
15
9
|
export declare function useParams<T extends Record<string, string> = Record<string, string>>(): T;
|
|
16
|
-
//#endregion
|
|
17
|
-
//#region src/runtime/use-shared.d.ts
|
|
18
10
|
export declare function useShared<T = CloudContextVariables["shared"]>(): T;
|
|
19
|
-
|
|
20
|
-
//#region src/runtime/use-form.d.ts
|
|
21
|
-
type FormSubmitOptions = Omit<VisitOptions, "method" | "data"> & {
|
|
22
|
-
data?: Record<string, unknown>;
|
|
23
|
-
};
|
|
24
|
-
export declare function useForm<U extends ActionUrl & string>(url: U, defaults: ResolveActionBody<U>, options?: ActionFormOptions<U>): {
|
|
25
|
-
data: ResolveActionBody<U>;
|
|
26
|
-
errors: Partial<Record<keyof ResolveActionBody<U> & string, string>>;
|
|
27
|
-
error: VoidActionError | null;
|
|
28
|
-
pending: boolean;
|
|
29
|
-
hasChanges: boolean;
|
|
30
|
-
wasSuccessful: boolean;
|
|
31
|
-
recentlySuccessful: boolean;
|
|
11
|
+
type FormReturn<TData> = FormState<TData, "in-place"> & Readonly<{
|
|
32
12
|
post(opts?: FormSubmitOptions): Promise<void>;
|
|
33
13
|
put(opts?: FormSubmitOptions): Promise<void>;
|
|
34
14
|
patch(opts?: FormSubmitOptions): Promise<void>;
|
|
35
15
|
delete(opts?: FormSubmitOptions): Promise<void>;
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
setError(field: keyof ResolveActionBody<U> & string, message: string): void;
|
|
39
|
-
clearErrors(...fields: ReadonlyArray<keyof ResolveActionBody<U> & string>): void;
|
|
40
|
-
clearError(): void;
|
|
41
|
-
};
|
|
42
|
-
//#endregion
|
|
43
|
-
//#region src/runtime/use-navigation.d.ts
|
|
16
|
+
}>;
|
|
17
|
+
export declare function useForm<U extends ActionUrl & string>(url: U, defaults: ResolveActionBody<U>, options?: ActionFormOptions<U>): FormReturn<ResolveActionBody<U>>;
|
|
44
18
|
export declare function useNavigation(): NavigationState;
|
|
45
|
-
|
|
46
|
-
//#region src/runtime/use-island-form.d.ts
|
|
47
|
-
type IslandFormReturn<T extends Record<string, unknown>> = Readonly<{
|
|
48
|
-
data: T;
|
|
49
|
-
errors: Record<string, string>;
|
|
50
|
-
error: VoidActionError | null;
|
|
51
|
-
pending: boolean;
|
|
52
|
-
hasChanges: boolean;
|
|
53
|
-
wasSuccessful: boolean;
|
|
54
|
-
recentlySuccessful: boolean;
|
|
19
|
+
type IslandFormReturn<T extends Record<string, unknown>> = IslandFormState<T, "in-place"> & Readonly<{
|
|
55
20
|
submit(url: string, method: string): Promise<void>;
|
|
56
|
-
post(url: string): Promise<void>;
|
|
57
|
-
put(url: string): Promise<void>;
|
|
58
|
-
patch(url: string): Promise<void>;
|
|
59
|
-
delete(url: string): Promise<void>;
|
|
60
|
-
reset(...fields: ReadonlyArray<keyof T & string>): void;
|
|
61
|
-
clearErrors(...fields: ReadonlyArray<string>): void;
|
|
62
|
-
clearError(): void;
|
|
63
21
|
}>;
|
|
64
22
|
/**
|
|
65
23
|
* Form composable for island pages. Uses fetch + page reload instead of the
|
|
66
24
|
* Void Router (which is not available in island mode).
|
|
67
25
|
*/
|
|
68
26
|
export declare function useIslandForm<T extends Record<string, unknown>>(defaults: T): IslandFormReturn<T>;
|
|
69
|
-
//#endregion
|
|
70
|
-
//#region src/runtime/action.d.ts
|
|
71
27
|
export declare const action: ActionClient["action"];
|
|
72
28
|
export declare const setActionRouter: ActionClient["setActionRouter"];
|
|
73
|
-
//#endregion
|
|
74
29
|
export type { Deferred, InferProps };
|
package/dist/index.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { cloneFormValue, createActionClient, formErrorMessage, formRetryMessage, formSubmissionBlocked, getValidationErrors, idleNavigationState, interpolateRouteParams, isAbortError, isEqualFormValue,
|
|
1
|
+
import { cloneFormValue, createActionClient, formErrorMessage, formRetryMessage, formSubmissionBlocked, getValidationErrors, handleLinkClick, idleNavigationState, interpolateRouteParams, isAbortError, isEqualFormValue, linkActiveAttributes, linkPendingAttributes, linkPrefetchStrategies, observeLinkVisible, parseFormResetArgs, prefetchLink, resolveLink, scheduleFormRetry, ssrProxy, submitAction, submitIslandAction } from "void/pages-client";
|
|
2
2
|
import { computed, defineComponent, h, inject, onMounted, onUnmounted, reactive, ref, shallowRef, watch } from "vue";
|
|
3
|
-
|
|
3
|
+
// src/runtime/form.ts
|
|
4
4
|
const Form = defineComponent({
|
|
5
5
|
name: "VoidForm",
|
|
6
6
|
inheritAttrs: false,
|
|
@@ -42,8 +42,65 @@ const Form = defineComponent({
|
|
|
42
42
|
};
|
|
43
43
|
}
|
|
44
44
|
});
|
|
45
|
-
|
|
46
|
-
|
|
45
|
+
// src/runtime/link.ts
|
|
46
|
+
const linkPropOptions = {
|
|
47
|
+
href: {
|
|
48
|
+
type: String,
|
|
49
|
+
required: true
|
|
50
|
+
},
|
|
51
|
+
method: {
|
|
52
|
+
type: String,
|
|
53
|
+
default: "GET"
|
|
54
|
+
},
|
|
55
|
+
data: {
|
|
56
|
+
type: Object,
|
|
57
|
+
default: void 0
|
|
58
|
+
},
|
|
59
|
+
preserveScroll: {
|
|
60
|
+
type: Boolean,
|
|
61
|
+
default: false
|
|
62
|
+
},
|
|
63
|
+
preserveState: {
|
|
64
|
+
type: Boolean,
|
|
65
|
+
default: false
|
|
66
|
+
},
|
|
67
|
+
replace: {
|
|
68
|
+
type: Boolean,
|
|
69
|
+
default: false
|
|
70
|
+
},
|
|
71
|
+
reloadDocument: {
|
|
72
|
+
type: Boolean,
|
|
73
|
+
default: false
|
|
74
|
+
},
|
|
75
|
+
viewTransition: {
|
|
76
|
+
type: Boolean,
|
|
77
|
+
default: void 0
|
|
78
|
+
},
|
|
79
|
+
onNavigate: {
|
|
80
|
+
type: Function,
|
|
81
|
+
default: void 0
|
|
82
|
+
},
|
|
83
|
+
prefetch: {
|
|
84
|
+
type: [
|
|
85
|
+
Boolean,
|
|
86
|
+
String,
|
|
87
|
+
Array
|
|
88
|
+
],
|
|
89
|
+
default: void 0
|
|
90
|
+
},
|
|
91
|
+
cacheFor: {
|
|
92
|
+
type: [
|
|
93
|
+
Number,
|
|
94
|
+
String,
|
|
95
|
+
Array
|
|
96
|
+
],
|
|
97
|
+
default: void 0
|
|
98
|
+
},
|
|
99
|
+
activeMatch: {
|
|
100
|
+
type: String,
|
|
101
|
+
default: void 0
|
|
102
|
+
}
|
|
103
|
+
};
|
|
47
104
|
function callHandler(handler, event) {
|
|
48
105
|
if (Array.isArray(handler)) {
|
|
49
106
|
for (const fn of handler) if (typeof fn === "function") fn(event);
|
|
@@ -51,108 +108,28 @@ function callHandler(handler, event) {
|
|
|
51
108
|
}
|
|
52
109
|
const Link = defineComponent({
|
|
53
110
|
name: "Link",
|
|
54
|
-
props:
|
|
55
|
-
href: {
|
|
56
|
-
type: String,
|
|
57
|
-
required: true
|
|
58
|
-
},
|
|
59
|
-
method: {
|
|
60
|
-
type: String,
|
|
61
|
-
default: "GET"
|
|
62
|
-
},
|
|
63
|
-
data: {
|
|
64
|
-
type: Object,
|
|
65
|
-
default: void 0
|
|
66
|
-
},
|
|
67
|
-
preserveScroll: {
|
|
68
|
-
type: Boolean,
|
|
69
|
-
default: false
|
|
70
|
-
},
|
|
71
|
-
preserveState: {
|
|
72
|
-
type: Boolean,
|
|
73
|
-
default: false
|
|
74
|
-
},
|
|
75
|
-
replace: {
|
|
76
|
-
type: Boolean,
|
|
77
|
-
default: false
|
|
78
|
-
},
|
|
79
|
-
reloadDocument: {
|
|
80
|
-
type: Boolean,
|
|
81
|
-
default: false
|
|
82
|
-
},
|
|
83
|
-
viewTransition: {
|
|
84
|
-
type: Boolean,
|
|
85
|
-
default: void 0
|
|
86
|
-
},
|
|
87
|
-
onNavigate: {
|
|
88
|
-
type: Function,
|
|
89
|
-
default: void 0
|
|
90
|
-
},
|
|
91
|
-
prefetch: {
|
|
92
|
-
type: [
|
|
93
|
-
Boolean,
|
|
94
|
-
String,
|
|
95
|
-
Array
|
|
96
|
-
],
|
|
97
|
-
default: false
|
|
98
|
-
},
|
|
99
|
-
cacheFor: {
|
|
100
|
-
type: [
|
|
101
|
-
Number,
|
|
102
|
-
String,
|
|
103
|
-
Array
|
|
104
|
-
],
|
|
105
|
-
default: void 0
|
|
106
|
-
}
|
|
107
|
-
},
|
|
111
|
+
props: linkPropOptions,
|
|
108
112
|
setup(props, { slots, attrs }) {
|
|
109
113
|
const router = inject("__void_router", null);
|
|
114
|
+
const navigation = inject("__void_navigation", null);
|
|
110
115
|
const elRef = ref(null);
|
|
111
|
-
const
|
|
112
|
-
const
|
|
113
|
-
const
|
|
114
|
-
const
|
|
115
|
-
if (props.prefetch === false || props.reloadDocument) return [];
|
|
116
|
-
if (props.prefetch === true) return ["hover"];
|
|
117
|
-
if (typeof props.prefetch === "string") return [props.prefetch];
|
|
118
|
-
return props.prefetch;
|
|
119
|
-
});
|
|
116
|
+
const link = computed(() => resolveLink(props));
|
|
117
|
+
const strategies = computed(() => linkPrefetchStrategies(props, link.value, router?._prefetchDefaultStrategy));
|
|
118
|
+
const activeAttributes = computed(() => linkActiveAttributes(router, link.value, props.activeMatch));
|
|
119
|
+
const pendingAttributes = computed(() => linkPendingAttributes(router, navigation, link.value));
|
|
120
120
|
function doPrefetch() {
|
|
121
|
-
|
|
122
|
-
const current = window.location.pathname + window.location.search;
|
|
123
|
-
if (hrefWithData.value === current) return;
|
|
124
|
-
const options = { method: normalizedMethod.value };
|
|
125
|
-
if (props.cacheFor !== void 0) options.cacheFor = props.cacheFor;
|
|
126
|
-
router.prefetch(hrefWithData.value, options);
|
|
121
|
+
prefetchLink(router, link.value.href, props.cacheFor);
|
|
127
122
|
}
|
|
128
123
|
function onClick(e) {
|
|
129
124
|
callHandler(attrs.onClick, e);
|
|
130
|
-
|
|
131
|
-
if (e.currentTarget.hasAttribute("download")) return;
|
|
132
|
-
if (new URL(hrefWithData.value, window.location.origin).origin !== window.location.origin) return;
|
|
133
|
-
let navigatePrevented = false;
|
|
134
|
-
props.onNavigate?.({ preventDefault() {
|
|
135
|
-
navigatePrevented = true;
|
|
136
|
-
} });
|
|
137
|
-
if (navigatePrevented) {
|
|
138
|
-
e.preventDefault();
|
|
139
|
-
return;
|
|
140
|
-
}
|
|
141
|
-
e.preventDefault();
|
|
142
|
-
router.visit(hrefWithData.value, {
|
|
143
|
-
method: normalizedMethod.value,
|
|
144
|
-
data: isGet.value ? void 0 : props.data,
|
|
145
|
-
preserveScroll: props.preserveScroll,
|
|
146
|
-
preserveState: props.preserveState,
|
|
147
|
-
replace: props.replace,
|
|
148
|
-
viewTransition: props.viewTransition
|
|
149
|
-
});
|
|
125
|
+
handleLinkClick(e, router, link.value, props);
|
|
150
126
|
}
|
|
151
127
|
let hoverTimer = null;
|
|
152
|
-
let
|
|
128
|
+
let stopObserving = null;
|
|
153
129
|
function onMouseEnter(e) {
|
|
154
130
|
callHandler(attrs.onMouseenter ?? attrs.onMouseEnter, e);
|
|
155
131
|
if (e.defaultPrevented) return;
|
|
132
|
+
if (hoverTimer) clearTimeout(hoverTimer);
|
|
156
133
|
hoverTimer = setTimeout(doPrefetch, router?._hoverDelay ?? 75);
|
|
157
134
|
}
|
|
158
135
|
function onMouseLeave(e) {
|
|
@@ -176,28 +153,15 @@ const Link = defineComponent({
|
|
|
176
153
|
}
|
|
177
154
|
onMounted(() => {
|
|
178
155
|
if (strategies.value.includes("mount")) doPrefetch();
|
|
179
|
-
if (strategies.value.includes("visible") && elRef.value)
|
|
180
|
-
observer = new IntersectionObserver((entries) => {
|
|
181
|
-
if (entries.some((e) => e.isIntersecting)) {
|
|
182
|
-
doPrefetch();
|
|
183
|
-
observer?.disconnect();
|
|
184
|
-
observer = null;
|
|
185
|
-
}
|
|
186
|
-
}, { rootMargin: "0px" });
|
|
187
|
-
observer.observe(elRef.value);
|
|
188
|
-
}
|
|
156
|
+
if (strategies.value.includes("visible") && elRef.value) stopObserving = observeLinkVisible(elRef.value, doPrefetch);
|
|
189
157
|
});
|
|
190
158
|
onUnmounted(() => {
|
|
191
159
|
if (hoverTimer) clearTimeout(hoverTimer);
|
|
192
|
-
|
|
193
|
-
observer.disconnect();
|
|
194
|
-
observer = null;
|
|
195
|
-
}
|
|
160
|
+
stopObserving?.();
|
|
196
161
|
});
|
|
197
162
|
return () => {
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
const tag = isGet.value ? "a" : "button";
|
|
163
|
+
const { href, isGet } = link.value;
|
|
164
|
+
const tag = isGet ? "a" : "button";
|
|
201
165
|
const eventHandlers = { onClick };
|
|
202
166
|
if (strategies.value.includes("hover")) {
|
|
203
167
|
eventHandlers.onMouseenter = onMouseEnter;
|
|
@@ -210,36 +174,34 @@ const Link = defineComponent({
|
|
|
210
174
|
eventHandlers.onTouchstart = onTouchStart;
|
|
211
175
|
}
|
|
212
176
|
return h(tag, {
|
|
177
|
+
...activeAttributes.value,
|
|
178
|
+
...pendingAttributes.value,
|
|
213
179
|
...attrs,
|
|
214
180
|
ref: elRef,
|
|
215
|
-
...isGet
|
|
181
|
+
...isGet ? { href } : { type: "button" },
|
|
216
182
|
style: [{ touchAction: "manipulation" }, attrs.style],
|
|
217
183
|
...eventHandlers
|
|
218
184
|
}, slots.default?.());
|
|
219
185
|
};
|
|
220
186
|
}
|
|
221
187
|
});
|
|
222
|
-
|
|
223
|
-
//#region src/runtime/use-router.ts
|
|
188
|
+
// src/runtime/use-router.ts
|
|
224
189
|
function useRouter() {
|
|
225
190
|
return inject("__void_router", null) ?? ssrProxy;
|
|
226
191
|
}
|
|
227
|
-
|
|
228
|
-
//#region src/runtime/use-params.ts
|
|
192
|
+
// src/runtime/use-params.ts
|
|
229
193
|
function useParams() {
|
|
230
194
|
const params = inject("__void_params");
|
|
231
195
|
if (!params) throw new Error("useParams(): must be used inside a Void page.");
|
|
232
196
|
return params;
|
|
233
197
|
}
|
|
234
|
-
|
|
235
|
-
//#region src/runtime/use-shared.ts
|
|
198
|
+
// src/runtime/use-shared.ts
|
|
236
199
|
function useShared() {
|
|
237
200
|
const shared = inject("__void_shared");
|
|
238
201
|
if (!shared) throw new Error("useShared(): must be used inside a Void page.");
|
|
239
202
|
return shared;
|
|
240
203
|
}
|
|
241
|
-
|
|
242
|
-
//#region src/runtime/use-form.ts
|
|
204
|
+
// src/runtime/use-form.ts
|
|
243
205
|
function useForm(url, defaults, options) {
|
|
244
206
|
const initialDefaults = cloneFormValue(defaults);
|
|
245
207
|
const router = inject("__void_router", null);
|
|
@@ -328,17 +290,11 @@ function useForm(url, defaults, options) {
|
|
|
328
290
|
delete: (opts) => submit("DELETE", opts)
|
|
329
291
|
});
|
|
330
292
|
}
|
|
331
|
-
|
|
332
|
-
//#region src/runtime/use-navigation.ts
|
|
293
|
+
// src/runtime/use-navigation.ts
|
|
333
294
|
function useNavigation() {
|
|
334
295
|
return inject("__void_navigation", null) ?? idleNavigationState();
|
|
335
296
|
}
|
|
336
|
-
|
|
337
|
-
//#region src/runtime/use-island-form.ts
|
|
338
|
-
/**
|
|
339
|
-
* Form composable for island pages. Uses fetch + page reload instead of the
|
|
340
|
-
* Void Router (which is not available in island mode).
|
|
341
|
-
*/
|
|
297
|
+
// src/runtime/use-island-form.ts
|
|
342
298
|
function useIslandForm(defaults) {
|
|
343
299
|
const data = reactive({ ...defaults });
|
|
344
300
|
const errors = reactive({});
|
|
@@ -408,10 +364,8 @@ function useIslandForm(defaults) {
|
|
|
408
364
|
delete: (url) => submit(url, "DELETE")
|
|
409
365
|
});
|
|
410
366
|
}
|
|
411
|
-
|
|
412
|
-
//#region src/runtime/action.ts
|
|
367
|
+
// src/runtime/action.ts
|
|
413
368
|
const client = createActionClient();
|
|
414
369
|
const action = client.action;
|
|
415
370
|
const setActionRouter = client.setActionRouter;
|
|
416
|
-
//#endregion
|
|
417
371
|
export { Form, Link, action, setActionRouter, useForm, useIslandForm, useNavigation, useParams, useRouter, useShared };
|
package/dist/plugin.d.mts
CHANGED
|
@@ -1,17 +1,9 @@
|
|
|
1
1
|
import vue from "@vitejs/plugin-vue";
|
|
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 VoidVueOptions = Readonly<{
|
|
11
5
|
vue?: Parameters<typeof vue>[0];
|
|
12
6
|
viewTransitions?: boolean;
|
|
13
7
|
prefetch?: PrefetchConfig;
|
|
14
8
|
}>;
|
|
15
|
-
export declare function voidVue(options?: VoidVueOptions): Array<Plugin>;
|
|
16
|
-
//#endregion
|
|
17
|
-
export {};
|
|
9
|
+
export declare function voidVue(options?: VoidVueOptions): Array<Plugin>;
|
package/dist/plugin.mjs
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import vue from "@vitejs/plugin-vue";
|
|
2
|
-
import { createPagesPlugin, generateComponentManifest,
|
|
3
|
-
import { existsSync,
|
|
2
|
+
import { createPagesPlugin, generateComponentManifest, resolvePagesDir, resolvePrefetchConfig, scanPages } from "void/pages";
|
|
3
|
+
import { existsSync, readdirSync } from "node:fs";
|
|
4
4
|
import nodePath from "node:path";
|
|
5
|
+
import { ISLANDS_CLIENT_ENTRY_ID, ISLANDS_RENDERER_ID, computePagesNeedingClientJS, createIslandManifest, editIslandSource, findIslandImports, generateIslandClientEntry, islandCssFiles, islandStylesPlugin, islandsBuildConfig, mdClientScriptIds, recordIslandStyles, recordedIslandStyles, watchPagesForRescan } from "void/pages-islands-plugin";
|
|
5
6
|
import { normalizePath } from "vite";
|
|
6
|
-
|
|
7
|
-
//#region ../../node_modules/.pnpm/pathslash@0.1.0/node_modules/pathslash/dist/index.mjs
|
|
7
|
+
// ../../node_modules/.pnpm/pathslash@0.1.0/node_modules/pathslash/dist/index.mjs
|
|
8
8
|
const BACKSLASH_RE = /\\/g;
|
|
9
9
|
const isWindows = process.platform === "win32";
|
|
10
10
|
/**
|
|
@@ -42,8 +42,7 @@ const posix = { ...nodePath.posix };
|
|
|
42
42
|
posix.posix = win32.posix = posix;
|
|
43
43
|
posix.win32 = win32.win32 = win32;
|
|
44
44
|
const { basename, delimiter, dirname, extname, format, isAbsolute, join, matchesGlob, normalize, parse, relative, resolve, sep, toNamespacedPath } = isWindows ? win32 : posix;
|
|
45
|
-
|
|
46
|
-
//#region src/plugin-islands.ts
|
|
45
|
+
// src/plugin-islands.ts
|
|
47
46
|
const COMPONENT_EXTENSIONS = [
|
|
48
47
|
".vue",
|
|
49
48
|
".tsx",
|
|
@@ -51,30 +50,36 @@ const COMPONENT_EXTENSIONS = [
|
|
|
51
50
|
".ts",
|
|
52
51
|
".js"
|
|
53
52
|
];
|
|
53
|
+
const HYDRATE_ISLAND = `
|
|
54
|
+
async function hydrateIsland(el, mod, props, frontmatter) {
|
|
55
|
+
const { createSSRApp, h } = await import("vue");
|
|
56
|
+
const Component = mod.default || mod;
|
|
57
|
+
const app = createSSRApp({ render: () => h(Component, props) });
|
|
58
|
+
app.provide("__void_frontmatter", frontmatter);
|
|
59
|
+
app.mount(el);
|
|
60
|
+
el.setAttribute("data-hydrated", "true");
|
|
61
|
+
}`;
|
|
62
|
+
/** Imports for the `<style>` blocks of a Vue component. */
|
|
63
|
+
function styleBlockImports(file, content) {
|
|
64
|
+
const imports = [];
|
|
65
|
+
let index = 0;
|
|
66
|
+
for (const match of content.matchAll(/<style\b([^>]*)>/g)) {
|
|
67
|
+
const attrs = match[1];
|
|
68
|
+
const lang = attrs.match(/\blang\s*=\s*["'](\w+)["']/)?.[1] ?? "css";
|
|
69
|
+
let query = `?vue&type=style&index=${index}&lang.${lang}`;
|
|
70
|
+
if (/\bscoped\b/.test(attrs)) query += "&scoped=true";
|
|
71
|
+
imports.push(`import ${JSON.stringify(file + query)};`);
|
|
72
|
+
index++;
|
|
73
|
+
}
|
|
74
|
+
return imports;
|
|
75
|
+
}
|
|
54
76
|
function islandsPlugin(pagesDir, options) {
|
|
55
77
|
let pageScan = null;
|
|
56
78
|
const root = options?.root ?? dirname(pagesDir);
|
|
57
|
-
const islandManifest =
|
|
58
|
-
|
|
79
|
+
const islandManifest = createIslandManifest(root, COMPONENT_EXTENSIONS);
|
|
80
|
+
let cssFiles = /* @__PURE__ */ new Set();
|
|
59
81
|
let isDev = false;
|
|
60
82
|
let resolvedConfig = null;
|
|
61
|
-
function registerIsland(spec, importerDir, hydrate) {
|
|
62
|
-
const absPath = resolveSpecifier(spec, importerDir, COMPONENT_EXTENSIONS);
|
|
63
|
-
if (!absPath) return {
|
|
64
|
-
absPath,
|
|
65
|
-
islandId: spec
|
|
66
|
-
};
|
|
67
|
-
const islandId = normalizePath(relative(root, absPath)).replace(/\.[^.]+$/, "");
|
|
68
|
-
islandManifest.set(islandId, {
|
|
69
|
-
componentPath: absPath,
|
|
70
|
-
islandId,
|
|
71
|
-
hydrate
|
|
72
|
-
});
|
|
73
|
-
return {
|
|
74
|
-
absPath,
|
|
75
|
-
islandId
|
|
76
|
-
};
|
|
77
|
-
}
|
|
78
83
|
return [{
|
|
79
84
|
name: "void-vue:islands",
|
|
80
85
|
enforce: "pre",
|
|
@@ -93,33 +98,8 @@ function islandsPlugin(pagesDir, options) {
|
|
|
93
98
|
pagesDir,
|
|
94
99
|
environment: isDev ? "dev" : "prod"
|
|
95
100
|
});
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
filePath: page.componentPath,
|
|
99
|
-
island: page.island
|
|
100
|
-
})),
|
|
101
|
-
...pageScan.layouts.map((layout) => ({
|
|
102
|
-
filePath: layout.filePath,
|
|
103
|
-
island: layout.island
|
|
104
|
-
})),
|
|
105
|
-
...pageScan.namedLayouts.map((namedLayout) => ({
|
|
106
|
-
filePath: namedLayout.filePath,
|
|
107
|
-
island: namedLayout.island
|
|
108
|
-
}))
|
|
109
|
-
];
|
|
110
|
-
for (const entry of filesToScan) {
|
|
111
|
-
if (!entry.island) continue;
|
|
112
|
-
const filePath = resolve(pagesDir, entry.filePath);
|
|
113
|
-
const code = readFileSync(filePath, "utf-8");
|
|
114
|
-
const importerDir = dirname(filePath);
|
|
115
|
-
for (const islandImport of findIslandImports(code, filePath)) registerIsland(islandImport.specifier, importerDir, islandImport.hydrate);
|
|
116
|
-
}
|
|
117
|
-
for (const page of pageScan.pages) {
|
|
118
|
-
if (!page.island) continue;
|
|
119
|
-
if (page.componentPath.endsWith(".vue")) islandCssFiles.add(normalizePath(resolve(pagesDir, page.componentPath)));
|
|
120
|
-
const layoutChain = resolveEffectiveLayoutChain(page, pageScan.layouts, pageScan.namedLayouts);
|
|
121
|
-
for (const layout of layoutChain) islandCssFiles.add(normalizePath(resolve(pagesDir, layout.filePath)));
|
|
122
|
-
}
|
|
101
|
+
islandManifest.registerPages(pageScan, pagesDir);
|
|
102
|
+
cssFiles = islandCssFiles(pageScan, pagesDir, /\.vue$/);
|
|
123
103
|
}
|
|
124
104
|
},
|
|
125
105
|
buildEnd() {
|
|
@@ -141,14 +121,21 @@ function islandsPlugin(pagesDir, options) {
|
|
|
141
121
|
handler(id) {
|
|
142
122
|
if (id === "\0" + ISLANDS_RENDERER_ID) {
|
|
143
123
|
if (!pageScan) return "export function renderIslandPage() { throw new Error('No pages found'); }";
|
|
144
|
-
const
|
|
145
|
-
const mdScriptIds = new Set(mdPlugin?.api?.getClientScripts?.()?.keys() ?? []);
|
|
124
|
+
const mdScriptIds = mdClientScriptIds(resolvedConfig);
|
|
146
125
|
const needsJS = computePagesNeedingClientJS(pageScan, pagesDir, resolvedConfig);
|
|
147
126
|
return generateIslandRenderer(pageScan, pagesDir, mdScriptIds, needsJS);
|
|
148
127
|
}
|
|
149
128
|
if (id === "\0" + ISLANDS_CLIENT_ENTRY_ID) {
|
|
150
129
|
if (!pageScan) return "";
|
|
151
|
-
return generateIslandClientEntry(
|
|
130
|
+
return generateIslandClientEntry({
|
|
131
|
+
manifest: islandManifest,
|
|
132
|
+
cssFiles,
|
|
133
|
+
styles: recordedIslandStyles(pagesDir),
|
|
134
|
+
needsRuntime: islandManifest.entries.size > 0 || computePagesNeedingClientJS(pageScan, pagesDir, resolvedConfig).size > 0,
|
|
135
|
+
isDev,
|
|
136
|
+
styleBlockImports,
|
|
137
|
+
hydrateIsland: HYDRATE_ISLAND
|
|
138
|
+
});
|
|
152
139
|
}
|
|
153
140
|
}
|
|
154
141
|
},
|
|
@@ -166,7 +153,7 @@ function islandsPlugin(pagesDir, options) {
|
|
|
166
153
|
const strategy = islandImport.hydrate;
|
|
167
154
|
const spec = islandImport.specifier;
|
|
168
155
|
const rawName = `_raw_island_${idx++}`;
|
|
169
|
-
const { islandId } =
|
|
156
|
+
const { islandId } = islandManifest.register(spec, importerDir, strategy);
|
|
170
157
|
const replacement = `import ${rawName} from ${specRaw}`;
|
|
171
158
|
edits.push({
|
|
172
159
|
start: islandImport.start,
|
|
@@ -194,10 +181,7 @@ function islandsPlugin(pagesDir, options) {
|
|
|
194
181
|
async config(_, env) {
|
|
195
182
|
if (!existsSync(pagesDir)) return;
|
|
196
183
|
if (env.command === "serve") return { optimizeDeps: { include: ["vue"] } };
|
|
197
|
-
|
|
198
|
-
manifest: true,
|
|
199
|
-
rollupOptions: { input: { "islands-client": ISLANDS_CLIENT_ENTRY_ID } }
|
|
200
|
-
} } } };
|
|
184
|
+
return islandsBuildConfig(root, pagesDir);
|
|
201
185
|
}
|
|
202
186
|
}, islandStylesPlugin(pagesDir)];
|
|
203
187
|
}
|
|
@@ -266,53 +250,7 @@ export async function renderIslandPage(pageObj, assetTags) {
|
|
|
266
250
|
}
|
|
267
251
|
`;
|
|
268
252
|
}
|
|
269
|
-
|
|
270
|
-
const entries = [...manifest.values()];
|
|
271
|
-
const uniqueIslands = /* @__PURE__ */ new Map();
|
|
272
|
-
for (const entry of entries) uniqueIslands.set(entry.islandId, entry.componentPath);
|
|
273
|
-
const lines = [];
|
|
274
|
-
if (!isDev && styles) for (const id of styles) lines.push(`import ${JSON.stringify(id)};`);
|
|
275
|
-
else if (!isDev) {
|
|
276
|
-
const styleRe = /<style\b([^>]*)>/g;
|
|
277
|
-
for (const file of cssFiles) {
|
|
278
|
-
const content = readFileSync(file, "utf-8");
|
|
279
|
-
styleRe.lastIndex = 0;
|
|
280
|
-
let index = 0;
|
|
281
|
-
let match;
|
|
282
|
-
while ((match = styleRe.exec(content)) !== null) {
|
|
283
|
-
const attrs = match[1];
|
|
284
|
-
const scoped = /\bscoped\b/.test(attrs);
|
|
285
|
-
const langMatch = attrs.match(/\blang\s*=\s*["'](\w+)["']/);
|
|
286
|
-
const lang = langMatch ? langMatch[1] : "css";
|
|
287
|
-
let query = `?vue&type=style&index=${index}&lang.${lang}`;
|
|
288
|
-
if (scoped) query += "&scoped=true";
|
|
289
|
-
lines.push(`import ${JSON.stringify(file + query)};`);
|
|
290
|
-
index++;
|
|
291
|
-
}
|
|
292
|
-
for (const item of findStaticCssImports(content, file)) lines.push(rewriteCssImportForVirtualModule(item.statement, item.specifier, file));
|
|
293
|
-
}
|
|
294
|
-
}
|
|
295
|
-
if (!needsRuntime && !isDev) return lines.join("\n");
|
|
296
|
-
lines.push("import { initIslands } from \"void/pages-client\";");
|
|
297
|
-
lines.push("const islands = {");
|
|
298
|
-
for (const [id, path] of uniqueIslands) lines.push(` ${JSON.stringify(id)}: () => import(${JSON.stringify(path)}),`);
|
|
299
|
-
lines.push("};");
|
|
300
|
-
lines.push(`
|
|
301
|
-
async function hydrateIsland(el, mod, props, frontmatter) {
|
|
302
|
-
const { createSSRApp, h } = await import("vue");
|
|
303
|
-
const Component = mod.default || mod;
|
|
304
|
-
const app = createSSRApp({ render: () => h(Component, props) });
|
|
305
|
-
app.provide("__void_frontmatter", frontmatter);
|
|
306
|
-
app.mount(el);
|
|
307
|
-
el.setAttribute("data-hydrated", "true");
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
initIslands(islands, hydrateIsland);
|
|
311
|
-
`);
|
|
312
|
-
return lines.join("\n");
|
|
313
|
-
}
|
|
314
|
-
//#endregion
|
|
315
|
-
//#region src/plugin-pages.ts
|
|
253
|
+
// src/plugin-pages.ts
|
|
316
254
|
const CLIENT_ENTRY_ID = "virtual:void-pages-client";
|
|
317
255
|
/** Runtime helper injected into generated SSR and client page bundles. */
|
|
318
256
|
const PATCH_PROPS_RUNTIME = `
|
|
@@ -446,7 +384,7 @@ ${generateComponentManifest(scan, pagesDir)}
|
|
|
446
384
|
|
|
447
385
|
${PATCH_PROPS_RUNTIME}
|
|
448
386
|
|
|
449
|
-
export async function renderPage(pageObj, assetTags) {
|
|
387
|
+
export async function renderPage(pageObj, assetTags, requestUrl) {
|
|
450
388
|
const PageComponent = (await components[pageObj.component]()).default;
|
|
451
389
|
const layoutIds = layoutTree[pageObj.component] || [];
|
|
452
390
|
const layoutComponents = await Promise.all(
|
|
@@ -457,7 +395,7 @@ export async function renderPage(pageObj, assetTags) {
|
|
|
457
395
|
setup() {
|
|
458
396
|
provide("__void_shared", reactive(pageObj.shared || {}));
|
|
459
397
|
provide("__void_params", reactive(pageObj.params || {}));
|
|
460
|
-
provide("__void_router", createSsrRouter(pageObj.url || "/", pageObj.params || {}));
|
|
398
|
+
provide("__void_router", createSsrRouter(pageObj.url || "/", pageObj.params || {}, requestUrl));
|
|
461
399
|
provide("__void_navigation", idleNavigationState());
|
|
462
400
|
return () => {
|
|
463
401
|
patchProps(PageComponent, pageObj.props);
|
|
@@ -490,6 +428,7 @@ export async function renderPage(pageObj, assetTags) {
|
|
|
490
428
|
`;
|
|
491
429
|
}
|
|
492
430
|
function generateClientEntry(scan, pagesDir, viewTransitions, prefetch, staticPageData) {
|
|
431
|
+
const { defaultStrategy, hoverDelay, cacheFor } = resolvePrefetchConfig(prefetch);
|
|
493
432
|
return `
|
|
494
433
|
import { createApp, createSSRApp, h, reactive, provide, shallowRef, nextTick, ref } from "vue";
|
|
495
434
|
import { createDeferred, resolveDeferred, rejectDeferred } from "@void/vue/deferred";
|
|
@@ -536,8 +475,9 @@ const { router, initialPageData, initialComponent, initialLayouts, start, commit
|
|
|
536
475
|
staticPageData: ${!!staticPageData?.enabled},
|
|
537
476
|
staticPageDataFastPath: ${!!staticPageData?.fastPath},
|
|
538
477
|
viewTransitions: ${!!viewTransitions},
|
|
539
|
-
prefetchHoverDelay: ${
|
|
540
|
-
prefetchDefaultCacheFor: parseCacheFor(${JSON.stringify(
|
|
478
|
+
prefetchHoverDelay: ${hoverDelay},
|
|
479
|
+
prefetchDefaultCacheFor: parseCacheFor(${JSON.stringify(cacheFor)}),
|
|
480
|
+
prefetchDefaultStrategy: ${JSON.stringify(defaultStrategy)},
|
|
541
481
|
onRouteChange(url) {
|
|
542
482
|
routeUrl.value = url;
|
|
543
483
|
},
|
|
@@ -588,8 +528,7 @@ start();
|
|
|
588
528
|
commitInitialRender();
|
|
589
529
|
`;
|
|
590
530
|
}
|
|
591
|
-
|
|
592
|
-
//#region src/plugin.ts
|
|
531
|
+
// src/plugin.ts
|
|
593
532
|
function voidVue(options) {
|
|
594
533
|
const root = process.cwd();
|
|
595
534
|
const pagesDir = resolvePagesDir(root);
|
|
@@ -603,5 +542,4 @@ function voidVue(options) {
|
|
|
603
542
|
...islandsPlugin(pagesDir, { root })
|
|
604
543
|
];
|
|
605
544
|
}
|
|
606
|
-
//#endregion
|
|
607
545
|
export { voidVue };
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
//#region src/runtime/use-deferred.d.ts
|
|
2
1
|
export type DeferredRef<T = unknown> = {
|
|
3
2
|
value: null;
|
|
4
3
|
loading: true;
|
|
@@ -14,5 +13,4 @@ export type DeferredRef<T = unknown> = {
|
|
|
14
13
|
};
|
|
15
14
|
export declare function createDeferred<T = unknown>(): DeferredRef<T>;
|
|
16
15
|
export declare function resolveDeferred(ref: DeferredRef, data: unknown): void;
|
|
17
|
-
export declare function rejectDeferred(ref: DeferredRef, error: string): void;
|
|
18
|
-
//#endregion
|
|
16
|
+
export declare function rejectDeferred(ref: DeferredRef, error: string): void;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { reactive } from "vue";
|
|
2
|
-
|
|
2
|
+
// src/runtime/use-deferred.ts
|
|
3
3
|
function createDeferred() {
|
|
4
4
|
return reactive({
|
|
5
5
|
value: null,
|
|
@@ -17,5 +17,4 @@ function rejectDeferred(ref, error) {
|
|
|
17
17
|
r.error = new Error(error);
|
|
18
18
|
r.loading = false;
|
|
19
19
|
}
|
|
20
|
-
//#endregion
|
|
21
20
|
export { createDeferred, rejectDeferred, resolveDeferred };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@void/vue",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.6.0",
|
|
4
4
|
"description": "Vue pages, server rendering, forms, and navigation for Void.",
|
|
5
5
|
"homepage": "https://void.cloud",
|
|
6
6
|
"bugs": {
|
|
@@ -37,16 +37,16 @@
|
|
|
37
37
|
"@vitejs/plugin-vue": "^6.0.10"
|
|
38
38
|
},
|
|
39
39
|
"devDependencies": {
|
|
40
|
-
"@types/node": "^26.6.
|
|
40
|
+
"@types/node": "^26.6.6",
|
|
41
41
|
"pathslash": "^0.1.0",
|
|
42
|
-
"vite": "npm:@voidzero-dev/vite-plus-core@1.
|
|
43
|
-
"vite-plus": "1.
|
|
44
|
-
"void": "1.
|
|
42
|
+
"vite": "npm:@voidzero-dev/vite-plus-core@1.4.0",
|
|
43
|
+
"vite-plus": "1.4.0",
|
|
44
|
+
"void": "1.6.0",
|
|
45
45
|
"vue": "^3.5.44"
|
|
46
46
|
},
|
|
47
47
|
"peerDependencies": {
|
|
48
48
|
"vite": "^8.0.0",
|
|
49
|
-
"void": "1.
|
|
49
|
+
"void": "1.6.0",
|
|
50
50
|
"vue": "^3.5.0"
|
|
51
51
|
},
|
|
52
52
|
"scripts": {
|