@void/vue 1.5.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 -35
- package/dist/index.mjs +76 -113
- package/dist/plugin.d.mts +2 -6
- package/dist/plugin.mjs +48 -106
- package/package.json +3 -3
package/dist/index.d.mts
CHANGED
|
@@ -1,50 +1,23 @@
|
|
|
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
5
|
export declare const Form: Component;
|
|
6
|
-
|
|
6
|
+
type LinkProps = BaseLinkProps & Omit<AnchorHTMLAttributes, keyof BaseLinkProps> & Omit<ButtonHTMLAttributes, keyof BaseLinkProps>;
|
|
7
|
+
export declare const Link: DefineSetupFnComponent<LinkProps>;
|
|
7
8
|
export declare function useRouter(): VoidRouter;
|
|
8
9
|
export declare function useParams<T extends Record<string, string> = Record<string, string>>(): T;
|
|
9
10
|
export declare function useShared<T = CloudContextVariables["shared"]>(): T;
|
|
10
|
-
type
|
|
11
|
-
data?: Record<string, unknown>;
|
|
12
|
-
};
|
|
13
|
-
export declare function useForm<U extends ActionUrl & string>(url: U, defaults: ResolveActionBody<U>, options?: ActionFormOptions<U>): {
|
|
14
|
-
data: ResolveActionBody<U>;
|
|
15
|
-
errors: Partial<Record<keyof ResolveActionBody<U> & string, string>>;
|
|
16
|
-
error: VoidActionError | null;
|
|
17
|
-
pending: boolean;
|
|
18
|
-
hasChanges: boolean;
|
|
19
|
-
wasSuccessful: boolean;
|
|
20
|
-
recentlySuccessful: boolean;
|
|
11
|
+
type FormReturn<TData> = FormState<TData, "in-place"> & Readonly<{
|
|
21
12
|
post(opts?: FormSubmitOptions): Promise<void>;
|
|
22
13
|
put(opts?: FormSubmitOptions): Promise<void>;
|
|
23
14
|
patch(opts?: FormSubmitOptions): Promise<void>;
|
|
24
15
|
delete(opts?: FormSubmitOptions): Promise<void>;
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
setError(field: keyof ResolveActionBody<U> & string, message: string): void;
|
|
28
|
-
clearErrors(...fields: ReadonlyArray<keyof ResolveActionBody<U> & string>): void;
|
|
29
|
-
clearError(): void;
|
|
30
|
-
};
|
|
16
|
+
}>;
|
|
17
|
+
export declare function useForm<U extends ActionUrl & string>(url: U, defaults: ResolveActionBody<U>, options?: ActionFormOptions<U>): FormReturn<ResolveActionBody<U>>;
|
|
31
18
|
export declare function useNavigation(): NavigationState;
|
|
32
|
-
type IslandFormReturn<T extends Record<string, unknown>> = Readonly<{
|
|
33
|
-
data: T;
|
|
34
|
-
errors: Record<string, string>;
|
|
35
|
-
error: VoidActionError | null;
|
|
36
|
-
pending: boolean;
|
|
37
|
-
hasChanges: boolean;
|
|
38
|
-
wasSuccessful: boolean;
|
|
39
|
-
recentlySuccessful: boolean;
|
|
19
|
+
type IslandFormReturn<T extends Record<string, unknown>> = IslandFormState<T, "in-place"> & Readonly<{
|
|
40
20
|
submit(url: string, method: string): Promise<void>;
|
|
41
|
-
post(url: string): Promise<void>;
|
|
42
|
-
put(url: string): Promise<void>;
|
|
43
|
-
patch(url: string): Promise<void>;
|
|
44
|
-
delete(url: string): Promise<void>;
|
|
45
|
-
reset(...fields: ReadonlyArray<keyof T & string>): void;
|
|
46
|
-
clearErrors(...fields: ReadonlyArray<string>): void;
|
|
47
|
-
clearError(): void;
|
|
48
21
|
}>;
|
|
49
22
|
/**
|
|
50
23
|
* Form composable for island pages. Uses fetch + page reload instead of the
|
package/dist/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
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({
|
|
@@ -43,6 +43,64 @@ const Form = defineComponent({
|
|
|
43
43
|
}
|
|
44
44
|
});
|
|
45
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
|
+
};
|
|
46
104
|
function callHandler(handler, event) {
|
|
47
105
|
if (Array.isArray(handler)) {
|
|
48
106
|
for (const fn of handler) if (typeof fn === "function") fn(event);
|
|
@@ -50,108 +108,28 @@ function callHandler(handler, event) {
|
|
|
50
108
|
}
|
|
51
109
|
const Link = defineComponent({
|
|
52
110
|
name: "Link",
|
|
53
|
-
props:
|
|
54
|
-
href: {
|
|
55
|
-
type: String,
|
|
56
|
-
required: true
|
|
57
|
-
},
|
|
58
|
-
method: {
|
|
59
|
-
type: String,
|
|
60
|
-
default: "GET"
|
|
61
|
-
},
|
|
62
|
-
data: {
|
|
63
|
-
type: Object,
|
|
64
|
-
default: void 0
|
|
65
|
-
},
|
|
66
|
-
preserveScroll: {
|
|
67
|
-
type: Boolean,
|
|
68
|
-
default: false
|
|
69
|
-
},
|
|
70
|
-
preserveState: {
|
|
71
|
-
type: Boolean,
|
|
72
|
-
default: false
|
|
73
|
-
},
|
|
74
|
-
replace: {
|
|
75
|
-
type: Boolean,
|
|
76
|
-
default: false
|
|
77
|
-
},
|
|
78
|
-
reloadDocument: {
|
|
79
|
-
type: Boolean,
|
|
80
|
-
default: false
|
|
81
|
-
},
|
|
82
|
-
viewTransition: {
|
|
83
|
-
type: Boolean,
|
|
84
|
-
default: void 0
|
|
85
|
-
},
|
|
86
|
-
onNavigate: {
|
|
87
|
-
type: Function,
|
|
88
|
-
default: void 0
|
|
89
|
-
},
|
|
90
|
-
prefetch: {
|
|
91
|
-
type: [
|
|
92
|
-
Boolean,
|
|
93
|
-
String,
|
|
94
|
-
Array
|
|
95
|
-
],
|
|
96
|
-
default: false
|
|
97
|
-
},
|
|
98
|
-
cacheFor: {
|
|
99
|
-
type: [
|
|
100
|
-
Number,
|
|
101
|
-
String,
|
|
102
|
-
Array
|
|
103
|
-
],
|
|
104
|
-
default: void 0
|
|
105
|
-
}
|
|
106
|
-
},
|
|
111
|
+
props: linkPropOptions,
|
|
107
112
|
setup(props, { slots, attrs }) {
|
|
108
113
|
const router = inject("__void_router", null);
|
|
114
|
+
const navigation = inject("__void_navigation", null);
|
|
109
115
|
const elRef = ref(null);
|
|
110
|
-
const
|
|
111
|
-
const
|
|
112
|
-
const
|
|
113
|
-
const
|
|
114
|
-
if (props.prefetch === false || props.reloadDocument) return [];
|
|
115
|
-
if (props.prefetch === true) return ["hover"];
|
|
116
|
-
if (typeof props.prefetch === "string") return [props.prefetch];
|
|
117
|
-
return props.prefetch;
|
|
118
|
-
});
|
|
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));
|
|
119
120
|
function doPrefetch() {
|
|
120
|
-
|
|
121
|
-
const current = window.location.pathname + window.location.search;
|
|
122
|
-
if (hrefWithData.value === current) return;
|
|
123
|
-
const options = { method: normalizedMethod.value };
|
|
124
|
-
if (props.cacheFor !== void 0) options.cacheFor = props.cacheFor;
|
|
125
|
-
router.prefetch(hrefWithData.value, options);
|
|
121
|
+
prefetchLink(router, link.value.href, props.cacheFor);
|
|
126
122
|
}
|
|
127
123
|
function onClick(e) {
|
|
128
124
|
callHandler(attrs.onClick, e);
|
|
129
|
-
|
|
130
|
-
if (e.currentTarget.hasAttribute("download")) return;
|
|
131
|
-
if (new URL(hrefWithData.value, window.location.origin).origin !== window.location.origin) return;
|
|
132
|
-
let navigatePrevented = false;
|
|
133
|
-
props.onNavigate?.({ preventDefault() {
|
|
134
|
-
navigatePrevented = true;
|
|
135
|
-
} });
|
|
136
|
-
if (navigatePrevented) {
|
|
137
|
-
e.preventDefault();
|
|
138
|
-
return;
|
|
139
|
-
}
|
|
140
|
-
e.preventDefault();
|
|
141
|
-
router.visit(hrefWithData.value, {
|
|
142
|
-
method: normalizedMethod.value,
|
|
143
|
-
data: isGet.value ? void 0 : props.data,
|
|
144
|
-
preserveScroll: props.preserveScroll,
|
|
145
|
-
preserveState: props.preserveState,
|
|
146
|
-
replace: props.replace,
|
|
147
|
-
viewTransition: props.viewTransition
|
|
148
|
-
});
|
|
125
|
+
handleLinkClick(e, router, link.value, props);
|
|
149
126
|
}
|
|
150
127
|
let hoverTimer = null;
|
|
151
|
-
let
|
|
128
|
+
let stopObserving = null;
|
|
152
129
|
function onMouseEnter(e) {
|
|
153
130
|
callHandler(attrs.onMouseenter ?? attrs.onMouseEnter, e);
|
|
154
131
|
if (e.defaultPrevented) return;
|
|
132
|
+
if (hoverTimer) clearTimeout(hoverTimer);
|
|
155
133
|
hoverTimer = setTimeout(doPrefetch, router?._hoverDelay ?? 75);
|
|
156
134
|
}
|
|
157
135
|
function onMouseLeave(e) {
|
|
@@ -175,28 +153,15 @@ const Link = defineComponent({
|
|
|
175
153
|
}
|
|
176
154
|
onMounted(() => {
|
|
177
155
|
if (strategies.value.includes("mount")) doPrefetch();
|
|
178
|
-
if (strategies.value.includes("visible") && elRef.value)
|
|
179
|
-
observer = new IntersectionObserver((entries) => {
|
|
180
|
-
if (entries.some((e) => e.isIntersecting)) {
|
|
181
|
-
doPrefetch();
|
|
182
|
-
observer?.disconnect();
|
|
183
|
-
observer = null;
|
|
184
|
-
}
|
|
185
|
-
}, { rootMargin: "0px" });
|
|
186
|
-
observer.observe(elRef.value);
|
|
187
|
-
}
|
|
156
|
+
if (strategies.value.includes("visible") && elRef.value) stopObserving = observeLinkVisible(elRef.value, doPrefetch);
|
|
188
157
|
});
|
|
189
158
|
onUnmounted(() => {
|
|
190
159
|
if (hoverTimer) clearTimeout(hoverTimer);
|
|
191
|
-
|
|
192
|
-
observer.disconnect();
|
|
193
|
-
observer = null;
|
|
194
|
-
}
|
|
160
|
+
stopObserving?.();
|
|
195
161
|
});
|
|
196
162
|
return () => {
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
const tag = isGet.value ? "a" : "button";
|
|
163
|
+
const { href, isGet } = link.value;
|
|
164
|
+
const tag = isGet ? "a" : "button";
|
|
200
165
|
const eventHandlers = { onClick };
|
|
201
166
|
if (strategies.value.includes("hover")) {
|
|
202
167
|
eventHandlers.onMouseenter = onMouseEnter;
|
|
@@ -209,9 +174,11 @@ const Link = defineComponent({
|
|
|
209
174
|
eventHandlers.onTouchstart = onTouchStart;
|
|
210
175
|
}
|
|
211
176
|
return h(tag, {
|
|
177
|
+
...activeAttributes.value,
|
|
178
|
+
...pendingAttributes.value,
|
|
212
179
|
...attrs,
|
|
213
180
|
ref: elRef,
|
|
214
|
-
...isGet
|
|
181
|
+
...isGet ? { href } : { type: "button" },
|
|
215
182
|
style: [{ touchAction: "manipulation" }, attrs.style],
|
|
216
183
|
...eventHandlers
|
|
217
184
|
}, slots.default?.());
|
|
@@ -328,10 +295,6 @@ function useNavigation() {
|
|
|
328
295
|
return inject("__void_navigation", null) ?? idleNavigationState();
|
|
329
296
|
}
|
|
330
297
|
// src/runtime/use-island-form.ts
|
|
331
|
-
/**
|
|
332
|
-
* Form composable for island pages. Uses fetch + page reload instead of the
|
|
333
|
-
* Void Router (which is not available in island mode).
|
|
334
|
-
*/
|
|
335
298
|
function useIslandForm(defaults) {
|
|
336
299
|
const data = reactive({ ...defaults });
|
|
337
300
|
const errors = reactive({});
|
package/dist/plugin.d.mts
CHANGED
|
@@ -1,13 +1,9 @@
|
|
|
1
1
|
import vue from "@vitejs/plugin-vue";
|
|
2
|
+
import { PrefetchConfig } from "void/pages";
|
|
2
3
|
import { Plugin } from "vite";
|
|
3
|
-
type PrefetchConfig = {
|
|
4
|
-
hoverDelay?: number;
|
|
5
|
-
cacheFor?: number | string | [string, string];
|
|
6
|
-
};
|
|
7
4
|
export type VoidVueOptions = Readonly<{
|
|
8
5
|
vue?: Parameters<typeof vue>[0];
|
|
9
6
|
viewTransitions?: boolean;
|
|
10
7
|
prefetch?: PrefetchConfig;
|
|
11
8
|
}>;
|
|
12
|
-
export declare function voidVue(options?: VoidVueOptions): Array<Plugin>;
|
|
13
|
-
export {};
|
|
9
|
+
export declare function voidVue(options?: VoidVueOptions): Array<Plugin>;
|
package/dist/plugin.mjs
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
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
|
-
import { ISLANDS_CLIENT_ENTRY_ID, ISLANDS_RENDERER_ID, computePagesNeedingClientJS, editIslandSource, findIslandImports, findStaticCssImports, hasIslandPages, islandStylesPlugin, recordIslandStyles, recordedIslandStyles, resolveSpecifier, rewriteCssImportForVirtualModule, watchPagesForRescan } from "void/pages-islands-plugin";
|
|
7
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";
|
|
@@ -50,30 +50,36 @@ const COMPONENT_EXTENSIONS = [
|
|
|
50
50
|
".ts",
|
|
51
51
|
".js"
|
|
52
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
|
+
}
|
|
53
76
|
function islandsPlugin(pagesDir, options) {
|
|
54
77
|
let pageScan = null;
|
|
55
78
|
const root = options?.root ?? dirname(pagesDir);
|
|
56
|
-
const islandManifest =
|
|
57
|
-
|
|
79
|
+
const islandManifest = createIslandManifest(root, COMPONENT_EXTENSIONS);
|
|
80
|
+
let cssFiles = /* @__PURE__ */ new Set();
|
|
58
81
|
let isDev = false;
|
|
59
82
|
let resolvedConfig = null;
|
|
60
|
-
function registerIsland(spec, importerDir, hydrate) {
|
|
61
|
-
const absPath = resolveSpecifier(spec, importerDir, COMPONENT_EXTENSIONS);
|
|
62
|
-
if (!absPath) return {
|
|
63
|
-
absPath,
|
|
64
|
-
islandId: spec
|
|
65
|
-
};
|
|
66
|
-
const islandId = normalizePath(relative(root, absPath)).replace(/\.[^.]+$/, "");
|
|
67
|
-
islandManifest.set(islandId, {
|
|
68
|
-
componentPath: absPath,
|
|
69
|
-
islandId,
|
|
70
|
-
hydrate
|
|
71
|
-
});
|
|
72
|
-
return {
|
|
73
|
-
absPath,
|
|
74
|
-
islandId
|
|
75
|
-
};
|
|
76
|
-
}
|
|
77
83
|
return [{
|
|
78
84
|
name: "void-vue:islands",
|
|
79
85
|
enforce: "pre",
|
|
@@ -92,33 +98,8 @@ function islandsPlugin(pagesDir, options) {
|
|
|
92
98
|
pagesDir,
|
|
93
99
|
environment: isDev ? "dev" : "prod"
|
|
94
100
|
});
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
filePath: page.componentPath,
|
|
98
|
-
island: page.island
|
|
99
|
-
})),
|
|
100
|
-
...pageScan.layouts.map((layout) => ({
|
|
101
|
-
filePath: layout.filePath,
|
|
102
|
-
island: layout.island
|
|
103
|
-
})),
|
|
104
|
-
...pageScan.namedLayouts.map((namedLayout) => ({
|
|
105
|
-
filePath: namedLayout.filePath,
|
|
106
|
-
island: namedLayout.island
|
|
107
|
-
}))
|
|
108
|
-
];
|
|
109
|
-
for (const entry of filesToScan) {
|
|
110
|
-
if (!entry.island) continue;
|
|
111
|
-
const filePath = resolve(pagesDir, entry.filePath);
|
|
112
|
-
const code = readFileSync(filePath, "utf-8");
|
|
113
|
-
const importerDir = dirname(filePath);
|
|
114
|
-
for (const islandImport of findIslandImports(code, filePath)) registerIsland(islandImport.specifier, importerDir, islandImport.hydrate);
|
|
115
|
-
}
|
|
116
|
-
for (const page of pageScan.pages) {
|
|
117
|
-
if (!page.island) continue;
|
|
118
|
-
if (page.componentPath.endsWith(".vue")) islandCssFiles.add(normalizePath(resolve(pagesDir, page.componentPath)));
|
|
119
|
-
const layoutChain = resolveEffectiveLayoutChain(page, pageScan.layouts, pageScan.namedLayouts);
|
|
120
|
-
for (const layout of layoutChain) islandCssFiles.add(normalizePath(resolve(pagesDir, layout.filePath)));
|
|
121
|
-
}
|
|
101
|
+
islandManifest.registerPages(pageScan, pagesDir);
|
|
102
|
+
cssFiles = islandCssFiles(pageScan, pagesDir, /\.vue$/);
|
|
122
103
|
}
|
|
123
104
|
},
|
|
124
105
|
buildEnd() {
|
|
@@ -140,14 +121,21 @@ function islandsPlugin(pagesDir, options) {
|
|
|
140
121
|
handler(id) {
|
|
141
122
|
if (id === "\0" + ISLANDS_RENDERER_ID) {
|
|
142
123
|
if (!pageScan) return "export function renderIslandPage() { throw new Error('No pages found'); }";
|
|
143
|
-
const
|
|
144
|
-
const mdScriptIds = new Set(mdPlugin?.api?.getClientScripts?.()?.keys() ?? []);
|
|
124
|
+
const mdScriptIds = mdClientScriptIds(resolvedConfig);
|
|
145
125
|
const needsJS = computePagesNeedingClientJS(pageScan, pagesDir, resolvedConfig);
|
|
146
126
|
return generateIslandRenderer(pageScan, pagesDir, mdScriptIds, needsJS);
|
|
147
127
|
}
|
|
148
128
|
if (id === "\0" + ISLANDS_CLIENT_ENTRY_ID) {
|
|
149
129
|
if (!pageScan) return "";
|
|
150
|
-
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
|
+
});
|
|
151
139
|
}
|
|
152
140
|
}
|
|
153
141
|
},
|
|
@@ -165,7 +153,7 @@ function islandsPlugin(pagesDir, options) {
|
|
|
165
153
|
const strategy = islandImport.hydrate;
|
|
166
154
|
const spec = islandImport.specifier;
|
|
167
155
|
const rawName = `_raw_island_${idx++}`;
|
|
168
|
-
const { islandId } =
|
|
156
|
+
const { islandId } = islandManifest.register(spec, importerDir, strategy);
|
|
169
157
|
const replacement = `import ${rawName} from ${specRaw}`;
|
|
170
158
|
edits.push({
|
|
171
159
|
start: islandImport.start,
|
|
@@ -193,10 +181,7 @@ function islandsPlugin(pagesDir, options) {
|
|
|
193
181
|
async config(_, env) {
|
|
194
182
|
if (!existsSync(pagesDir)) return;
|
|
195
183
|
if (env.command === "serve") return { optimizeDeps: { include: ["vue"] } };
|
|
196
|
-
|
|
197
|
-
manifest: true,
|
|
198
|
-
rollupOptions: { input: { "islands-client": ISLANDS_CLIENT_ENTRY_ID } }
|
|
199
|
-
} } } };
|
|
184
|
+
return islandsBuildConfig(root, pagesDir);
|
|
200
185
|
}
|
|
201
186
|
}, islandStylesPlugin(pagesDir)];
|
|
202
187
|
}
|
|
@@ -265,51 +250,6 @@ export async function renderIslandPage(pageObj, assetTags) {
|
|
|
265
250
|
}
|
|
266
251
|
`;
|
|
267
252
|
}
|
|
268
|
-
function generateIslandClientEntry(manifest, cssFiles, styles, needsRuntime, isDev) {
|
|
269
|
-
const entries = [...manifest.values()];
|
|
270
|
-
const uniqueIslands = /* @__PURE__ */ new Map();
|
|
271
|
-
for (const entry of entries) uniqueIslands.set(entry.islandId, entry.componentPath);
|
|
272
|
-
const lines = [];
|
|
273
|
-
if (!isDev && styles) for (const id of styles) lines.push(`import ${JSON.stringify(id)};`);
|
|
274
|
-
else if (!isDev) {
|
|
275
|
-
const styleRe = /<style\b([^>]*)>/g;
|
|
276
|
-
for (const file of cssFiles) {
|
|
277
|
-
const content = readFileSync(file, "utf-8");
|
|
278
|
-
styleRe.lastIndex = 0;
|
|
279
|
-
let index = 0;
|
|
280
|
-
let match;
|
|
281
|
-
while ((match = styleRe.exec(content)) !== null) {
|
|
282
|
-
const attrs = match[1];
|
|
283
|
-
const scoped = /\bscoped\b/.test(attrs);
|
|
284
|
-
const langMatch = attrs.match(/\blang\s*=\s*["'](\w+)["']/);
|
|
285
|
-
const lang = langMatch ? langMatch[1] : "css";
|
|
286
|
-
let query = `?vue&type=style&index=${index}&lang.${lang}`;
|
|
287
|
-
if (scoped) query += "&scoped=true";
|
|
288
|
-
lines.push(`import ${JSON.stringify(file + query)};`);
|
|
289
|
-
index++;
|
|
290
|
-
}
|
|
291
|
-
for (const item of findStaticCssImports(content, file)) lines.push(rewriteCssImportForVirtualModule(item.statement, item.specifier, file));
|
|
292
|
-
}
|
|
293
|
-
}
|
|
294
|
-
if (!needsRuntime && !isDev) return lines.join("\n");
|
|
295
|
-
lines.push("import { initIslands } from \"void/pages-client\";");
|
|
296
|
-
lines.push("const islands = {");
|
|
297
|
-
for (const [id, path] of uniqueIslands) lines.push(` ${JSON.stringify(id)}: () => import(${JSON.stringify(path)}),`);
|
|
298
|
-
lines.push("};");
|
|
299
|
-
lines.push(`
|
|
300
|
-
async function hydrateIsland(el, mod, props, frontmatter) {
|
|
301
|
-
const { createSSRApp, h } = await import("vue");
|
|
302
|
-
const Component = mod.default || mod;
|
|
303
|
-
const app = createSSRApp({ render: () => h(Component, props) });
|
|
304
|
-
app.provide("__void_frontmatter", frontmatter);
|
|
305
|
-
app.mount(el);
|
|
306
|
-
el.setAttribute("data-hydrated", "true");
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
initIslands(islands, hydrateIsland);
|
|
310
|
-
`);
|
|
311
|
-
return lines.join("\n");
|
|
312
|
-
}
|
|
313
253
|
// src/plugin-pages.ts
|
|
314
254
|
const CLIENT_ENTRY_ID = "virtual:void-pages-client";
|
|
315
255
|
/** Runtime helper injected into generated SSR and client page bundles. */
|
|
@@ -444,7 +384,7 @@ ${generateComponentManifest(scan, pagesDir)}
|
|
|
444
384
|
|
|
445
385
|
${PATCH_PROPS_RUNTIME}
|
|
446
386
|
|
|
447
|
-
export async function renderPage(pageObj, assetTags) {
|
|
387
|
+
export async function renderPage(pageObj, assetTags, requestUrl) {
|
|
448
388
|
const PageComponent = (await components[pageObj.component]()).default;
|
|
449
389
|
const layoutIds = layoutTree[pageObj.component] || [];
|
|
450
390
|
const layoutComponents = await Promise.all(
|
|
@@ -455,7 +395,7 @@ export async function renderPage(pageObj, assetTags) {
|
|
|
455
395
|
setup() {
|
|
456
396
|
provide("__void_shared", reactive(pageObj.shared || {}));
|
|
457
397
|
provide("__void_params", reactive(pageObj.params || {}));
|
|
458
|
-
provide("__void_router", createSsrRouter(pageObj.url || "/", pageObj.params || {}));
|
|
398
|
+
provide("__void_router", createSsrRouter(pageObj.url || "/", pageObj.params || {}, requestUrl));
|
|
459
399
|
provide("__void_navigation", idleNavigationState());
|
|
460
400
|
return () => {
|
|
461
401
|
patchProps(PageComponent, pageObj.props);
|
|
@@ -488,6 +428,7 @@ export async function renderPage(pageObj, assetTags) {
|
|
|
488
428
|
`;
|
|
489
429
|
}
|
|
490
430
|
function generateClientEntry(scan, pagesDir, viewTransitions, prefetch, staticPageData) {
|
|
431
|
+
const { defaultStrategy, hoverDelay, cacheFor } = resolvePrefetchConfig(prefetch);
|
|
491
432
|
return `
|
|
492
433
|
import { createApp, createSSRApp, h, reactive, provide, shallowRef, nextTick, ref } from "vue";
|
|
493
434
|
import { createDeferred, resolveDeferred, rejectDeferred } from "@void/vue/deferred";
|
|
@@ -534,8 +475,9 @@ const { router, initialPageData, initialComponent, initialLayouts, start, commit
|
|
|
534
475
|
staticPageData: ${!!staticPageData?.enabled},
|
|
535
476
|
staticPageDataFastPath: ${!!staticPageData?.fastPath},
|
|
536
477
|
viewTransitions: ${!!viewTransitions},
|
|
537
|
-
prefetchHoverDelay: ${
|
|
538
|
-
prefetchDefaultCacheFor: parseCacheFor(${JSON.stringify(
|
|
478
|
+
prefetchHoverDelay: ${hoverDelay},
|
|
479
|
+
prefetchDefaultCacheFor: parseCacheFor(${JSON.stringify(cacheFor)}),
|
|
480
|
+
prefetchDefaultStrategy: ${JSON.stringify(defaultStrategy)},
|
|
539
481
|
onRouteChange(url) {
|
|
540
482
|
routeUrl.value = url;
|
|
541
483
|
},
|
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": {
|
|
@@ -41,12 +41,12 @@
|
|
|
41
41
|
"pathslash": "^0.1.0",
|
|
42
42
|
"vite": "npm:@voidzero-dev/vite-plus-core@1.4.0",
|
|
43
43
|
"vite-plus": "1.4.0",
|
|
44
|
-
"void": "1.
|
|
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": {
|