@void/react 0.25.0 → 0.26.1
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 +9 -5
- package/dist/index.mjs +1 -1
- package/dist/plugin.d.mts +3 -1
- package/dist/plugin.mjs +20 -28
- package/dist/runtime/index.mjs +1 -1
- package/dist/{runtime-D_Wv52MZ.mjs → runtime--GesOVur.mjs} +4 -58
- package/package.json +12 -6
package/README.md
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
# @void/react
|
|
2
2
|
|
|
3
|
-
Build server-rendered
|
|
3
|
+
React adapter for [Void](https://void.cloud). Build server-rendered pages with typed server loaders, forms, layouts, and interactive islands.
|
|
4
|
+
|
|
5
|
+
Requires React 19. Navigation uses transitions and Suspense to keep the UI responsive while the next page loads.
|
|
4
6
|
|
|
5
7
|
## Setup
|
|
6
8
|
|
|
7
|
-
|
|
9
|
+
For a new app, run `npx void init` and choose React. To add the adapter to an existing Void app:
|
|
8
10
|
|
|
9
11
|
```sh
|
|
10
12
|
npm install @void/react
|
|
@@ -13,15 +15,17 @@ npm install @void/react
|
|
|
13
15
|
Add the adapter to your Vite config. It includes the framework's Vite plugin:
|
|
14
16
|
|
|
15
17
|
```ts
|
|
18
|
+
import { voidReact } from '@void/react/plugin';
|
|
16
19
|
import { defineConfig } from 'vite';
|
|
17
20
|
import { voidPlugin } from 'void';
|
|
18
|
-
import { voidReact } from '@void/react/plugin';
|
|
19
21
|
|
|
20
22
|
export default defineConfig({
|
|
21
23
|
plugins: [voidPlugin(), voidReact()],
|
|
22
24
|
});
|
|
23
25
|
```
|
|
24
26
|
|
|
25
|
-
|
|
27
|
+
## Documentation
|
|
28
|
+
|
|
29
|
+
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
30
|
|
|
27
|
-
|
|
31
|
+
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,4 +1,4 @@
|
|
|
1
|
-
import { a as useParams, c as Form, i as useShared, n as useNavigation, o as useRouter, r as useForm, s as Link, t as useIslandForm } from "./runtime
|
|
1
|
+
import { a as useParams, c as Form, i as useShared, n as useNavigation, o as useRouter, r as useForm, s as Link, t as useIslandForm } from "./runtime--GesOVur.mjs";
|
|
2
2
|
import { a as SharedContext, i as RouterContext, n as NavigationContext, r as ParamsContext, t as ErrorsContext } from "./context-B0GL7p5m.mjs";
|
|
3
3
|
import { n as setActionRouter, t as action } from "./action-4EvQppia.mjs";
|
|
4
4
|
export { ErrorsContext, Form, Link, NavigationContext, ParamsContext, RouterContext, SharedContext, action, setActionRouter, useForm, useIslandForm, useNavigation, useParams, useRouter, useShared };
|
package/dist/plugin.d.mts
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import react from "@vitejs/plugin-react";
|
|
2
2
|
import { Plugin } from "vite";
|
|
3
|
-
//#region src/plugin.d.ts
|
|
3
|
+
//#region src/plugin-pages.d.ts
|
|
4
4
|
type PrefetchConfig = {
|
|
5
5
|
hoverDelay?: number;
|
|
6
6
|
cacheFor?: number | string | [string, string];
|
|
7
7
|
};
|
|
8
|
+
//#endregion
|
|
9
|
+
//#region src/plugin.d.ts
|
|
8
10
|
export type VoidReactOptions = Readonly<{
|
|
9
11
|
react?: Parameters<typeof react>[0];
|
|
10
12
|
viewTransitions?: boolean;
|
package/dist/plugin.mjs
CHANGED
|
@@ -58,9 +58,23 @@ function islandsPlugin(pagesDir, options) {
|
|
|
58
58
|
const islandCssFiles = /* @__PURE__ */ new Set();
|
|
59
59
|
let viteRoot = "";
|
|
60
60
|
let resolvedConfig = null;
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
61
|
+
/** Resolve an island import and record it in the client manifest. */
|
|
62
|
+
function registerIsland(spec, importerDir, hydrate) {
|
|
63
|
+
const absPath = resolveSpecifier(spec, importerDir, COMPONENT_EXTENSIONS);
|
|
64
|
+
if (!absPath) return {
|
|
65
|
+
absPath,
|
|
66
|
+
islandId: spec
|
|
67
|
+
};
|
|
68
|
+
const islandId = normalizePath(relative(root, absPath)).replace(/\.[^.]+$/, "");
|
|
69
|
+
islandManifest.set(islandId, {
|
|
70
|
+
componentPath: absPath,
|
|
71
|
+
islandId,
|
|
72
|
+
hydrate
|
|
73
|
+
});
|
|
74
|
+
return {
|
|
75
|
+
absPath,
|
|
76
|
+
islandId
|
|
77
|
+
};
|
|
64
78
|
}
|
|
65
79
|
return [{
|
|
66
80
|
name: "void-react:islands",
|
|
@@ -96,19 +110,7 @@ function islandsPlugin(pagesDir, options) {
|
|
|
96
110
|
const filePath = resolve(pagesDir, entry.filePath);
|
|
97
111
|
const code = readFileSync(filePath, "utf-8");
|
|
98
112
|
const importerDir = dirname(filePath);
|
|
99
|
-
for (const islandImport of findIslandImports(code, filePath))
|
|
100
|
-
const spec = islandImport.specifier;
|
|
101
|
-
const strategy = islandImport.hydrate;
|
|
102
|
-
const absPath = resolveSpecifier(spec, importerDir, COMPONENT_EXTENSIONS);
|
|
103
|
-
if (absPath) {
|
|
104
|
-
const islandId = normalizePath(relative(root, absPath)).replace(/\.[^.]+$/, "");
|
|
105
|
-
islandManifest.set(islandId, {
|
|
106
|
-
componentPath: absPath,
|
|
107
|
-
islandId,
|
|
108
|
-
hydrate: strategy
|
|
109
|
-
});
|
|
110
|
-
}
|
|
111
|
-
}
|
|
113
|
+
for (const islandImport of findIslandImports(code, filePath)) registerIsland(islandImport.specifier, importerDir, islandImport.hydrate);
|
|
112
114
|
}
|
|
113
115
|
for (const page of pageScan.pages) {
|
|
114
116
|
if (!page.island) continue;
|
|
@@ -139,7 +141,7 @@ function islandsPlugin(pagesDir, options) {
|
|
|
139
141
|
if (!pageScan) return "export function renderIslandPage() { throw new Error('No pages found'); }";
|
|
140
142
|
const mdPlugin = resolvedConfig?.plugins?.find((plugin) => plugin.name === "void-md");
|
|
141
143
|
const mdScriptIds = new Set(mdPlugin?.api?.getClientScripts?.()?.keys() ?? []);
|
|
142
|
-
const needsJS =
|
|
144
|
+
const needsJS = computePagesNeedingClientJS(pageScan, pagesDir, resolvedConfig);
|
|
143
145
|
return generateIslandRenderer(pageScan, pagesDir, isDev, mdScriptIds, needsJS);
|
|
144
146
|
}
|
|
145
147
|
if (id === "\0" + ISLANDS_CLIENT_ENTRY_ID) {
|
|
@@ -166,16 +168,7 @@ function islandsPlugin(pagesDir, options) {
|
|
|
166
168
|
const strategy = islandImport.hydrate;
|
|
167
169
|
const spec = islandImport.specifier;
|
|
168
170
|
const rawName = `_raw_island_${idx++}`;
|
|
169
|
-
const
|
|
170
|
-
if (absPath) {
|
|
171
|
-
const islandId = normalizePath(relative(root, absPath)).replace(/\.[^.]+$/, "");
|
|
172
|
-
islandManifest.set(islandId, {
|
|
173
|
-
componentPath: absPath,
|
|
174
|
-
islandId,
|
|
175
|
-
hydrate: strategy
|
|
176
|
-
});
|
|
177
|
-
}
|
|
178
|
-
const islandId = absPath ? normalizePath(relative(root, absPath)).replace(/\.[^.]+$/, "") : spec;
|
|
171
|
+
const { islandId } = registerIsland(spec, importerDir, strategy);
|
|
179
172
|
const replacement = `import ${rawName} from ${specRaw}`;
|
|
180
173
|
edits.push({
|
|
181
174
|
start: islandImport.start,
|
|
@@ -439,7 +432,6 @@ import { renderReactPage } from "@void/react/pages-server";
|
|
|
439
432
|
${generateComponentManifest(scan, pagesDir)}
|
|
440
433
|
|
|
441
434
|
const PREAMBLE_SCRIPT = ${JSON.stringify(preambleScript)};
|
|
442
|
-
export const renderPreambleScript = PREAMBLE_SCRIPT;
|
|
443
435
|
|
|
444
436
|
export async function renderPage(pageObj, assetTags) {
|
|
445
437
|
return renderReactPage({
|
package/dist/runtime/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as useParams, c as Form, i as useShared, n as useNavigation, o as useRouter, r as useForm, s as Link, t as useIslandForm } from "../runtime
|
|
1
|
+
import { a as useParams, c as Form, i as useShared, n as useNavigation, o as useRouter, r as useForm, s as Link, t as useIslandForm } from "../runtime--GesOVur.mjs";
|
|
2
2
|
import { a as SharedContext, i as RouterContext, n as NavigationContext, r as ParamsContext, t as ErrorsContext } from "../context-B0GL7p5m.mjs";
|
|
3
3
|
import { n as setActionRouter, t as action } from "../action-4EvQppia.mjs";
|
|
4
4
|
export { ErrorsContext, Form, Link, NavigationContext, ParamsContext, RouterContext, SharedContext, action, setActionRouter, useForm, useIslandForm, useNavigation, useParams, useRouter, useShared };
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { a as SharedContext, i as RouterContext, n as NavigationContext, r as ParamsContext } from "./context-B0GL7p5m.mjs";
|
|
2
2
|
import "./action-4EvQppia.mjs";
|
|
3
3
|
import { useActionState, useCallback, useContext, useEffect, useMemo, useRef, useState } from "react";
|
|
4
|
-
import { VoidActionError, categorizeActionError, formErrorMessage, formRetryMessage, formSubmissionBlocked, isAbortError, isEqualFormValue, scheduleFormRetry, ssrProxy, submitAction } from "void/pages-client";
|
|
4
|
+
import { VoidActionError, categorizeActionError, formErrorMessage, formRetryMessage, formSubmissionBlocked, getValidationErrors, interpolateRouteParams, isAbortError, isEqualFormValue, isModifiedEvent, mergeDataIntoHref, scheduleFormRetry, ssrProxy, submitAction } from "void/pages-client";
|
|
5
5
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
6
6
|
//#region src/runtime/form.tsx
|
|
7
7
|
function Form({ form, method = "post", renderError, children, ...props }) {
|
|
@@ -26,45 +26,9 @@ function Form({ form, method = "post", renderError, children, ...props }) {
|
|
|
26
26
|
}
|
|
27
27
|
//#endregion
|
|
28
28
|
//#region src/runtime/link.tsx
|
|
29
|
-
function appendQueryValue(params, key, value) {
|
|
30
|
-
if (value === null || value === void 0) return false;
|
|
31
|
-
if (value instanceof Date) {
|
|
32
|
-
params.append(key, value.toISOString());
|
|
33
|
-
return true;
|
|
34
|
-
}
|
|
35
|
-
if (Array.isArray(value)) {
|
|
36
|
-
let appended = false;
|
|
37
|
-
for (const item of value) appended = appendQueryValue(params, key, item) || appended;
|
|
38
|
-
return appended;
|
|
39
|
-
}
|
|
40
|
-
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
|
|
41
|
-
params.append(key, String(value));
|
|
42
|
-
return true;
|
|
43
|
-
}
|
|
44
|
-
throw new Error(`Link: GET data only supports primitive values and arrays. Remove nested data for '${key}'.`);
|
|
45
|
-
}
|
|
46
|
-
function mergeDataIntoHref(href, data) {
|
|
47
|
-
if (!data) return href;
|
|
48
|
-
const hashIndex = href.indexOf("#");
|
|
49
|
-
const beforeHash = hashIndex === -1 ? href : href.slice(0, hashIndex);
|
|
50
|
-
const hash = hashIndex === -1 ? "" : href.slice(hashIndex);
|
|
51
|
-
const searchIndex = beforeHash.indexOf("?");
|
|
52
|
-
const path = searchIndex === -1 ? beforeHash : beforeHash.slice(0, searchIndex);
|
|
53
|
-
const search = searchIndex === -1 ? "" : beforeHash.slice(searchIndex + 1);
|
|
54
|
-
const params = new URLSearchParams(search);
|
|
55
|
-
let appended = false;
|
|
56
|
-
for (const [key, value] of Object.entries(data)) appended = appendQueryValue(params, key, value) || appended;
|
|
57
|
-
if (!appended) return href;
|
|
58
|
-
const query = params.toString();
|
|
59
|
-
return `${path}${query ? `?${query}` : ""}${hash}`;
|
|
60
|
-
}
|
|
61
29
|
function hasPrefetch(prefetch) {
|
|
62
30
|
return prefetch !== false;
|
|
63
31
|
}
|
|
64
|
-
function isModifiedEvent(e) {
|
|
65
|
-
const target = e.currentTarget.getAttribute("target");
|
|
66
|
-
return target !== null && target !== "_self" || e.ctrlKey || e.metaKey || e.shiftKey || e.altKey || e.button !== 0;
|
|
67
|
-
}
|
|
68
32
|
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 }) {
|
|
69
33
|
const router = useContext(RouterContext);
|
|
70
34
|
const elementRef = useRef(null);
|
|
@@ -270,21 +234,7 @@ function useShared() {
|
|
|
270
234
|
}
|
|
271
235
|
//#endregion
|
|
272
236
|
//#region src/runtime/use-form.ts
|
|
273
|
-
function getValidationErrors$1(error) {
|
|
274
|
-
const body = error.body;
|
|
275
|
-
if (body && typeof body === "object" && "errors" in body && body.errors && typeof body.errors === "object" && !Array.isArray(body.errors)) return body.errors;
|
|
276
|
-
return null;
|
|
277
|
-
}
|
|
278
237
|
function useForm(url, defaults, options) {
|
|
279
|
-
let resolvedUrl = url;
|
|
280
|
-
let actionQuery = "";
|
|
281
|
-
const qIdx = resolvedUrl.indexOf("?");
|
|
282
|
-
if (qIdx !== -1) {
|
|
283
|
-
actionQuery = resolvedUrl.slice(qIdx);
|
|
284
|
-
resolvedUrl = resolvedUrl.slice(0, qIdx);
|
|
285
|
-
}
|
|
286
|
-
if (options?.params) for (const [key, value] of Object.entries(options.params)) resolvedUrl = resolvedUrl.replace(`:${key}`, encodeURIComponent(value));
|
|
287
|
-
resolvedUrl = resolvedUrl + actionQuery;
|
|
288
238
|
const router = useContext(RouterContext);
|
|
289
239
|
const [data, setDataState] = useState(() => ({ ...defaults }));
|
|
290
240
|
const [defaultsState, setDefaultsState] = useState(() => ({ ...defaults }));
|
|
@@ -338,7 +288,7 @@ function useForm(url, defaults, options) {
|
|
|
338
288
|
setError(null);
|
|
339
289
|
let result;
|
|
340
290
|
try {
|
|
341
|
-
result = await submitAction(router,
|
|
291
|
+
result = await submitAction(router, interpolateRouteParams(url, options?.params), {
|
|
342
292
|
method,
|
|
343
293
|
data: { ...dataRef.current },
|
|
344
294
|
_resolveOnShell: true
|
|
@@ -348,7 +298,7 @@ function useForm(url, defaults, options) {
|
|
|
348
298
|
throw submitError;
|
|
349
299
|
}
|
|
350
300
|
if (!result.ok) {
|
|
351
|
-
const validationErrors = getValidationErrors
|
|
301
|
+
const validationErrors = getValidationErrors(result.error.body);
|
|
352
302
|
if (validationErrors) setErrors(validationErrors);
|
|
353
303
|
else setError(result.error);
|
|
354
304
|
return previousSuccessVersion;
|
|
@@ -400,10 +350,6 @@ function useNavigation() {
|
|
|
400
350
|
}
|
|
401
351
|
//#endregion
|
|
402
352
|
//#region src/runtime/use-island-form.ts
|
|
403
|
-
function getValidationErrors(body) {
|
|
404
|
-
if (body && typeof body === "object" && "errors" in body && body.errors && typeof body.errors === "object" && !Array.isArray(body.errors)) return body.errors;
|
|
405
|
-
return null;
|
|
406
|
-
}
|
|
407
353
|
async function readActionErrorBody(response) {
|
|
408
354
|
const contentType = response.headers.get("content-type") || "";
|
|
409
355
|
try {
|
|
@@ -480,7 +426,7 @@ function useIslandForm(defaults) {
|
|
|
480
426
|
setDefaultsState(nextDefaults);
|
|
481
427
|
successTimeoutRef.current = setTimeout(() => setRecentlySuccessful(false), 2e3);
|
|
482
428
|
window.location.reload();
|
|
483
|
-
} else
|
|
429
|
+
} else {
|
|
484
430
|
const body = await readActionErrorBody(response);
|
|
485
431
|
const actionError = new VoidActionError({
|
|
486
432
|
body,
|
package/package.json
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@void/react",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.26.1",
|
|
4
|
+
"description": "React 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",
|
|
@@ -32,7 +38,7 @@
|
|
|
32
38
|
"access": "public"
|
|
33
39
|
},
|
|
34
40
|
"dependencies": {
|
|
35
|
-
"@vitejs/plugin-react": "^6.1.
|
|
41
|
+
"@vitejs/plugin-react": "^6.1.2"
|
|
36
42
|
},
|
|
37
43
|
"devDependencies": {
|
|
38
44
|
"@types/node": "^26.6.4",
|
|
@@ -41,15 +47,15 @@
|
|
|
41
47
|
"pathslash": "^0.1.0",
|
|
42
48
|
"react": "^19.3.0",
|
|
43
49
|
"react-dom": "^19.3.0",
|
|
44
|
-
"vite": "npm:@voidzero-dev/vite-plus-core@1.
|
|
45
|
-
"vite-plus": "1.
|
|
46
|
-
"void": "0.
|
|
50
|
+
"vite": "npm:@voidzero-dev/vite-plus-core@1.1.0",
|
|
51
|
+
"vite-plus": "1.1.0",
|
|
52
|
+
"void": "0.26.1"
|
|
47
53
|
},
|
|
48
54
|
"peerDependencies": {
|
|
49
55
|
"react": "^19.0.0",
|
|
50
56
|
"react-dom": "^19.0.0",
|
|
51
57
|
"vite": "^8.0.0",
|
|
52
|
-
"void": "0.
|
|
58
|
+
"void": "0.26.1"
|
|
53
59
|
},
|
|
54
60
|
"scripts": {
|
|
55
61
|
"build": "vp pack",
|