@void/vue 0.26.0 → 0.27.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/README.md CHANGED
@@ -1,10 +1,10 @@
1
1
  # @void/vue
2
2
 
3
- Build server-rendered Void pages with Vue, server loaders, and typed forms.
3
+ Vue adapter for [Void](https://void.cloud). Build server-rendered pages with typed server loaders, forms, layouts, and interactive islands.
4
4
 
5
5
  ## Setup
6
6
 
7
- Install alongside `void`:
7
+ For a new app, run `npx void init` and choose Vue. To add the adapter to an existing Void app:
8
8
 
9
9
  ```sh
10
10
  npm install @void/vue
@@ -13,15 +13,17 @@ npm install @void/vue
13
13
  Add the adapter to your Vite config. It includes the framework's Vite plugin:
14
14
 
15
15
  ```ts
16
+ import { voidVue } from '@void/vue/plugin';
16
17
  import { defineConfig } from 'vite';
17
18
  import { voidPlugin } from 'void';
18
- import { voidVue } from '@void/vue/plugin';
19
19
 
20
20
  export default defineConfig({
21
21
  plugins: [voidPlugin(), voidVue()],
22
22
  });
23
23
  ```
24
24
 
25
- Follow [Pages Routing](https://void.cloud/guide/pages-routing/overview) to add pages, then use [loaders](https://void.cloud/guide/pages-routing/loaders), [forms](https://void.cloud/guide/pages-routing/actions-and-forms), and [layouts](https://void.cloud/guide/pages-routing/layouts).
25
+ ## Documentation
26
+
27
+ Follow [Pages Routing](https://void.cloud/guide/pages-routing/overview) to add pages, then use [loaders](https://void.cloud/guide/pages-routing/loaders), [forms](https://void.cloud/guide/pages-routing/actions-and-forms), and [layouts](https://void.cloud/guide/pages-routing/layouts). See the [adapter API reference](https://void.cloud/reference/api/pages) for plugin options and runtime helpers.
26
28
 
27
- See the [adapter API reference](https://void.cloud/reference/api/pages) for plugin options and runtime helpers, and [TESTING.md](../../TESTING.md) for repository verification.
29
+ Read the [Void documentation](https://void.cloud) online or use the Markdown guides and references in `node_modules/void/docs/`, included with the `void` package.
package/dist/index.mjs CHANGED
@@ -1,2 +1,2 @@
1
- import { a as useForm, c as useRouter, i as useNavigation, l as Link, n as setActionRouter, o as useShared, r as useIslandForm, s as useParams, t as action, u as Form } from "./runtime-DU7JTkNy.mjs";
1
+ import { a as useForm, c as useRouter, i as useNavigation, l as Link, n as setActionRouter, o as useShared, r as useIslandForm, s as useParams, t as action, u as Form } from "./runtime-lPB1mgy1.mjs";
2
2
  export { Form, Link, action, setActionRouter, useForm, useIslandForm, useNavigation, useParams, useRouter, useShared };
package/dist/plugin.d.mts CHANGED
@@ -1,9 +1,16 @@
1
1
  import vue from "@vitejs/plugin-vue";
2
2
  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
3
9
  //#region src/plugin.d.ts
4
10
  export type VoidVueOptions = Readonly<{
5
11
  vue?: Parameters<typeof vue>[0];
6
12
  viewTransitions?: boolean;
13
+ prefetch?: PrefetchConfig;
7
14
  }>;
8
15
  export declare function voidVue(options?: VoidVueOptions): Array<Plugin>;
9
16
  //#endregion
package/dist/plugin.mjs CHANGED
@@ -74,9 +74,23 @@ function islandsPlugin(pagesDir, options) {
74
74
  let isDev = false;
75
75
  let viteRoot = "";
76
76
  let resolvedConfig = null;
77
- function computePagesNeedingClientJS$1() {
78
- if (!pageScan) return /* @__PURE__ */ new Set();
79
- return computePagesNeedingClientJS(pageScan, pagesDir, resolvedConfig);
77
+ /** Resolve an island import and record it in the client manifest. */
78
+ function registerIsland(spec, importerDir, hydrate) {
79
+ const absPath = resolveSpecifier(spec, importerDir, COMPONENT_EXTENSIONS);
80
+ if (!absPath) return {
81
+ absPath,
82
+ islandId: spec
83
+ };
84
+ const islandId = normalizePath(relative(root, absPath)).replace(/\.[^.]+$/, "");
85
+ islandManifest.set(islandId, {
86
+ componentPath: absPath,
87
+ islandId,
88
+ hydrate
89
+ });
90
+ return {
91
+ absPath,
92
+ islandId
93
+ };
80
94
  }
81
95
  return [{
82
96
  name: "void-vue:islands",
@@ -113,19 +127,7 @@ function islandsPlugin(pagesDir, options) {
113
127
  const filePath = resolve(pagesDir, entry.filePath);
114
128
  const code = readFileSync(filePath, "utf-8");
115
129
  const importerDir = dirname(filePath);
116
- for (const islandImport of findIslandImports(code, filePath)) {
117
- const spec = islandImport.specifier;
118
- const strategy = islandImport.hydrate;
119
- const absPath = resolveSpecifier(spec, importerDir, COMPONENT_EXTENSIONS);
120
- if (absPath) {
121
- const islandId = normalizePath(relative(root, absPath)).replace(/\.[^.]+$/, "");
122
- islandManifest.set(islandId, {
123
- componentPath: absPath,
124
- islandId,
125
- hydrate: strategy
126
- });
127
- }
128
- }
130
+ for (const islandImport of findIslandImports(code, filePath)) registerIsland(islandImport.specifier, importerDir, islandImport.hydrate);
129
131
  }
130
132
  for (const page of pageScan.pages) {
131
133
  if (!page.island) continue;
@@ -156,7 +158,7 @@ function islandsPlugin(pagesDir, options) {
156
158
  if (!pageScan) return "export function renderIslandPage() { throw new Error('No pages found'); }";
157
159
  const mdPlugin = resolvedConfig?.plugins?.find((plugin) => plugin.name === "void-md");
158
160
  const mdScriptIds = new Set(mdPlugin?.api?.getClientScripts?.()?.keys() ?? []);
159
- const needsJS = computePagesNeedingClientJS$1();
161
+ const needsJS = computePagesNeedingClientJS(pageScan, pagesDir, resolvedConfig);
160
162
  return generateIslandRenderer(pageScan, pagesDir, mdScriptIds, needsJS);
161
163
  }
162
164
  if (id === "\0" + ISLANDS_CLIENT_ENTRY_ID) {
@@ -183,16 +185,7 @@ function islandsPlugin(pagesDir, options) {
183
185
  const strategy = islandImport.hydrate;
184
186
  const spec = islandImport.specifier;
185
187
  const rawName = `_raw_island_${idx++}`;
186
- const absPath = resolveSpecifier(spec, importerDir, COMPONENT_EXTENSIONS);
187
- if (absPath) {
188
- const islandId = normalizePath(relative(root, absPath)).replace(/\.[^.]+$/, "");
189
- islandManifest.set(islandId, {
190
- componentPath: absPath,
191
- islandId,
192
- hydrate: strategy
193
- });
194
- }
195
- const islandId = absPath ? normalizePath(relative(root, absPath)).replace(/\.[^.]+$/, "") : spec;
188
+ const { islandId } = registerIsland(spec, importerDir, strategy);
196
189
  const replacement = `import ${rawName} from ${specRaw}`;
197
190
  edits.push({
198
191
  start: islandImport.start,
@@ -618,7 +611,7 @@ currentLayouts.value = initialLayouts;
618
611
 
619
612
  const appEl = document.getElementById("app");
620
613
  if (!appEl) throw new Error("pages: Missing #app element for Void Vue pages hydration.");
621
- const createVoidApp = appEl?.getAttribute("data-void-ssr") === "false" ? createApp : createSSRApp;
614
+ const createVoidApp = appEl.getAttribute("data-void-ssr") === "false" ? createApp : createSSRApp;
622
615
  const app = createVoidApp({
623
616
  setup() {
624
617
  provide("__void_shared", shared);
@@ -656,7 +649,8 @@ function voidVue(options) {
656
649
  vue(options?.vue),
657
650
  ...pagesPlugin(pagesDir, {
658
651
  root,
659
- viewTransitions: options?.viewTransitions
652
+ viewTransitions: options?.viewTransitions,
653
+ prefetch: options?.prefetch
660
654
  }),
661
655
  ...islandsPlugin(pagesDir, { root })
662
656
  ];
@@ -1,2 +1,2 @@
1
- import { a as useForm, c as useRouter, i as useNavigation, l as Link, n as setActionRouter, o as useShared, r as useIslandForm, s as useParams, t as action, u as Form } from "../runtime-DU7JTkNy.mjs";
1
+ import { a as useForm, c as useRouter, i as useNavigation, l as Link, n as setActionRouter, o as useShared, r as useIslandForm, s as useParams, t as action, u as Form } from "../runtime-lPB1mgy1.mjs";
2
2
  export { Form, Link, action, setActionRouter, useForm, useIslandForm, useNavigation, useParams, useRouter, useShared };
@@ -1,4 +1,4 @@
1
- import { VoidActionError, categorizeActionError, createActionClient, formErrorMessage, formRetryMessage, formSubmissionBlocked, idleNavigationState, isAbortError, isEqualFormValue, scheduleFormRetry, ssrProxy, submitAction } from "void/pages-client";
1
+ import { VoidActionError, categorizeActionError, createActionClient, formErrorMessage, formRetryMessage, formSubmissionBlocked, getValidationErrors, idleNavigationState, interpolateRouteParams, isAbortError, isEqualFormValue, isModifiedEvent, mergeDataIntoHref, scheduleFormRetry, ssrProxy, submitAction } from "void/pages-client";
2
2
  import { computed, defineComponent, h, inject, onMounted, onUnmounted, reactive, ref, watch } from "vue";
3
3
  //#region src/runtime/form.ts
4
4
  const Form = defineComponent({
@@ -44,45 +44,9 @@ const Form = defineComponent({
44
44
  });
45
45
  //#endregion
46
46
  //#region src/runtime/link.ts
47
- function appendQueryValue(params, key, value) {
48
- if (value === null || value === void 0) return false;
49
- if (value instanceof Date) {
50
- params.append(key, value.toISOString());
51
- return true;
52
- }
53
- if (Array.isArray(value)) {
54
- let appended = false;
55
- for (const item of value) appended = appendQueryValue(params, key, item) || appended;
56
- return appended;
57
- }
58
- if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
59
- params.append(key, String(value));
60
- return true;
61
- }
62
- throw new Error(`Link: GET data only supports primitive values and arrays. Remove nested data for '${key}'.`);
63
- }
64
- function mergeDataIntoHref(href, data) {
65
- if (!data) return href;
66
- const hashIndex = href.indexOf("#");
67
- const beforeHash = hashIndex === -1 ? href : href.slice(0, hashIndex);
68
- const hash = hashIndex === -1 ? "" : href.slice(hashIndex);
69
- const searchIndex = beforeHash.indexOf("?");
70
- const path = searchIndex === -1 ? beforeHash : beforeHash.slice(0, searchIndex);
71
- const search = searchIndex === -1 ? "" : beforeHash.slice(searchIndex + 1);
72
- const params = new URLSearchParams(search);
73
- let appended = false;
74
- for (const [key, value] of Object.entries(data)) appended = appendQueryValue(params, key, value) || appended;
75
- if (!appended) return href;
76
- const query = params.toString();
77
- return `${path}${query ? `?${query}` : ""}${hash}`;
78
- }
79
47
  function hasPrefetch(prefetch) {
80
48
  return prefetch !== false;
81
49
  }
82
- function isModifiedEvent(e) {
83
- const target = e.currentTarget.getAttribute("target");
84
- return target !== null && target !== "_self" || e.ctrlKey || e.metaKey || e.shiftKey || e.altKey || e.button !== 0;
85
- }
86
50
  function callHandler(handler, event) {
87
51
  if (Array.isArray(handler)) {
88
52
  for (const fn of handler) if (typeof fn === "function") fn(event);
@@ -280,22 +244,8 @@ function useShared() {
280
244
  }
281
245
  //#endregion
282
246
  //#region src/runtime/use-form.ts
283
- function getValidationErrors$1(error) {
284
- const body = error.body;
285
- if (body && typeof body === "object" && "errors" in body && body.errors && typeof body.errors === "object" && !Array.isArray(body.errors)) return body.errors;
286
- return null;
287
- }
288
247
  function useForm(url, defaults, options) {
289
- let resolvedUrl = url;
290
248
  const formDefaults = defaults;
291
- let actionQuery = "";
292
- const qIdx = resolvedUrl.indexOf("?");
293
- if (qIdx !== -1) {
294
- actionQuery = resolvedUrl.slice(qIdx);
295
- resolvedUrl = resolvedUrl.slice(0, qIdx);
296
- }
297
- if (options?.params) for (const [key, value] of Object.entries(options.params)) resolvedUrl = resolvedUrl.replace(`:${key}`, encodeURIComponent(value));
298
- resolvedUrl = resolvedUrl + actionQuery;
299
249
  const router = inject("__void_router", null);
300
250
  const data = reactive({ ...formDefaults });
301
251
  const errors = reactive({});
@@ -329,13 +279,13 @@ function useForm(url, defaults, options) {
329
279
  clearError();
330
280
  try {
331
281
  if (!router) throw new Error("useForm(): requires the Void Router.");
332
- const result = await submitAction(router, resolvedUrl, {
282
+ const result = await submitAction(router, interpolateRouteParams(url, options?.params), {
333
283
  method,
334
284
  data: { ...data },
335
285
  ...opts
336
286
  });
337
287
  if (!result.ok) {
338
- const validationErrors = getValidationErrors$1(result.error);
288
+ const validationErrors = getValidationErrors(result.error.body);
339
289
  if (validationErrors) Object.assign(errors, validationErrors);
340
290
  else error.value = result.error;
341
291
  return;
@@ -377,10 +327,6 @@ function useNavigation() {
377
327
  }
378
328
  //#endregion
379
329
  //#region src/runtime/use-island-form.ts
380
- function getValidationErrors(body) {
381
- if (body && typeof body === "object" && "errors" in body && body.errors && typeof body.errors === "object" && !Array.isArray(body.errors)) return body.errors;
382
- return null;
383
- }
384
330
  async function readActionErrorBody(response) {
385
331
  const contentType = response.headers.get("content-type") || "";
386
332
  try {
@@ -443,7 +389,7 @@ function useIslandForm(defaults) {
443
389
  recentlySuccessful.value = false;
444
390
  }, 2e3);
445
391
  window.location.reload();
446
- } else if (!response.ok) {
392
+ } else {
447
393
  const body = await readActionErrorBody(response);
448
394
  const actionError = new VoidActionError({
449
395
  body,
package/package.json CHANGED
@@ -1,6 +1,12 @@
1
1
  {
2
2
  "name": "@void/vue",
3
- "version": "0.26.0",
3
+ "version": "0.27.0",
4
+ "description": "Vue pages, server rendering, forms, and navigation for Void.",
5
+ "homepage": "https://void.cloud",
6
+ "bugs": {
7
+ "url": "https://github.com/voidzero-dev/void/issues"
8
+ },
9
+ "license": "MIT",
4
10
  "repository": {
5
11
  "type": "git",
6
12
  "url": "git+https://github.com/voidzero-dev/void.git",
@@ -33,14 +39,14 @@
33
39
  "devDependencies": {
34
40
  "@types/node": "^26.6.4",
35
41
  "pathslash": "^0.1.0",
36
- "vite": "npm:@voidzero-dev/vite-plus-core@1.0.0",
37
- "vite-plus": "1.0.0",
38
- "void": "0.26.0",
42
+ "vite": "npm:@voidzero-dev/vite-plus-core@1.1.0",
43
+ "vite-plus": "1.1.0",
44
+ "void": "0.27.0",
39
45
  "vue": "^3.5.43"
40
46
  },
41
47
  "peerDependencies": {
42
48
  "vite": "^8.0.0",
43
- "void": "0.26.0",
49
+ "void": "0.27.0",
44
50
  "vue": "^3.5.0"
45
51
  },
46
52
  "scripts": {