vike-lite-react 1.0.17 → 1.1.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 +3 -0
- package/dist/__internal/client/onRenderClient.mjs +15 -13
- package/dist/__internal/server/onRenderHtml.d.mts +10 -2
- package/dist/__internal/server/onRenderHtml.mjs +42 -21
- package/dist/index.d.mts +1 -1
- package/dist/vite.d.mts +10 -1
- package/dist/vite.mjs +3 -2
- package/package.json +6 -6
package/README.md
CHANGED
|
@@ -35,6 +35,8 @@ export default {
|
|
|
35
35
|
// Default is `true` that enables React Hydration
|
|
36
36
|
// Set to `false` for Client Takeover (SPA mode)
|
|
37
37
|
hydration: true,
|
|
38
|
+
// Default is `true` that enables HTML Streaming
|
|
39
|
+
streaming: true
|
|
38
40
|
// Advanced: pass options directly to the underlying @vitejs/plugin-react
|
|
39
41
|
react: {
|
|
40
42
|
babel: {
|
|
@@ -51,6 +53,7 @@ export default {
|
|
|
51
53
|
| Option | Type | Default | Description
|
|
52
54
|
| - | - | - | -
|
|
53
55
|
| `hydration` | `boolean` | `true` | When `true`, the server renders the page to HTML and the client hydrates it (`hydrateRoot`). When `false`, the client discards the server-rendered HTML on load and mounts a fresh tree (`createRoot`) — useful for highly interactive pages where paying the hydration-mismatch tax isn't worth it.
|
|
56
|
+
| `streaming` | `boolean` | `true` | When `true`, streams the server-rendered app markup via the Web Streams API (`ReadableStream`, using `react-dom/server.edge`'s `renderToReadableStream`) instead of buffering it into a single string before sending the response. Works identically on Node.js, Deno, Bun and Edge runtimes. Ignored when `hydration: false`. See [HTTP Streaming](../vike-lite/doc/differences/STREAMING.md).
|
|
54
57
|
| `react` | `Options` (from `@vitejs/plugin-react`) | `{}` | Passed through to the underlying `@vitejs/plugin-react` instance. Use this for `jsxImportSource` (e.g. Emotion), custom Babel plugins, or `jsxRuntime: 'classic'`.
|
|
55
58
|
|
|
56
59
|
### 🪝 Hooks
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { t as PageContextProvider } from "../../PageContextProvider-CTiW2Vsa.mjs";
|
|
2
2
|
import { Component, useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
3
3
|
import { createRoot, hydrateRoot } from "react-dom/client";
|
|
4
|
+
import { buildInitialClientContext, buildNavigationPageContext, buildPageContextJsonUrl, consumeMatchingInitialContext, createLinkClickHandler, createLinkPrefetchHandler, createRoutePrefetcher, fetchPageContextJson, finalizeNavigation, loadViewModules, resolveHydrationView, tryRecoverFromStaleModuleGraph } from "vike-lite/__internal/client";
|
|
4
5
|
import { matchRoute, stripBase } from "vike-lite/__internal/shared";
|
|
5
|
-
import { buildInitialClientContext, buildPageContextJsonUrl, consumeMatchingInitialContext, createLinkClickHandler, createLinkPrefetchHandler, createRoutePrefetcher, fetchPageContextJson, finalizeNavigation, loadViewModules, resolveHydrationView, tryRecoverFromStaleModuleGraph } from "vike-lite/__internal/client";
|
|
6
6
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
7
|
-
//#region src/__internal/client/
|
|
7
|
+
//#region src/__internal/client/RouterApp.tsx
|
|
8
8
|
var RootErrorBoundary = class extends Component {
|
|
9
9
|
static getDerivedStateFromError(error) {
|
|
10
10
|
return { error };
|
|
@@ -127,19 +127,17 @@ function RouterApp(props) {
|
|
|
127
127
|
setCurrentPathname(stripBase(urlObjRedirect.pathname));
|
|
128
128
|
return;
|
|
129
129
|
}
|
|
130
|
-
if (ctx && (ctx.is404 || ctx.is500 || ctx.isError)) return renderErrorPage(ctx.is404, ctx.reason || "Server Error");
|
|
130
|
+
if (ctx && (ctx.is404 || ctx.is500 || ctx.isError)) return renderErrorPage(ctx.is404 ?? false, ctx.reason || "Server Error");
|
|
131
131
|
const newView = await loadViewModules(route);
|
|
132
132
|
if (signal.aborted) return;
|
|
133
|
-
setPageContext(() => ({
|
|
133
|
+
setPageContext(() => buildNavigationPageContext({
|
|
134
134
|
routeParams,
|
|
135
135
|
urlOriginal: urlObj.href,
|
|
136
136
|
urlPathname: pathname,
|
|
137
137
|
search: urlObj.search,
|
|
138
|
-
...ctx?.data
|
|
139
|
-
...ctx?.title
|
|
140
|
-
...contextOverride
|
|
141
|
-
isClientSide: true,
|
|
142
|
-
isHydration: false
|
|
138
|
+
...ctx?.data !== void 0 ? { data: ctx.data } : {},
|
|
139
|
+
...ctx?.title ? { title: ctx.title } : {},
|
|
140
|
+
...contextOverride
|
|
143
141
|
}));
|
|
144
142
|
setView(newView);
|
|
145
143
|
if (ctx?.title) document.title = ctx.title;
|
|
@@ -170,14 +168,18 @@ function RouterApp(props) {
|
|
|
170
168
|
reloadTick
|
|
171
169
|
]);
|
|
172
170
|
const { Page, Layout } = view;
|
|
171
|
+
const contextValue = useMemo(() => ({
|
|
172
|
+
pageContext,
|
|
173
|
+
setPageContext
|
|
174
|
+
}), [pageContext, setPageContext]);
|
|
175
|
+
if (!Page) return null;
|
|
173
176
|
return /* @__PURE__ */ jsx(RootErrorBoundary, { children: /* @__PURE__ */ jsx(PageContextProvider, {
|
|
174
|
-
value:
|
|
175
|
-
pageContext,
|
|
176
|
-
setPageContext
|
|
177
|
-
}), [pageContext, setPageContext]),
|
|
177
|
+
value: contextValue,
|
|
178
178
|
children: Layout ? /* @__PURE__ */ jsx(Layout, { children: /* @__PURE__ */ jsx(Page, {}) }) : /* @__PURE__ */ jsx(Page, {})
|
|
179
179
|
}) });
|
|
180
180
|
}
|
|
181
|
+
//#endregion
|
|
182
|
+
//#region src/__internal/client/onRenderClient.tsx
|
|
181
183
|
async function onRenderClient(clientOptions) {
|
|
182
184
|
const container = document.querySelector("#root");
|
|
183
185
|
const isHydration = clientOptions.hydration && !!globalThis.__PAGE_CONTEXT__;
|
|
@@ -8,7 +8,15 @@ interface ReactRenderContext extends RenderContext {
|
|
|
8
8
|
children: ReactNode;
|
|
9
9
|
}>;
|
|
10
10
|
hydration: boolean;
|
|
11
|
+
/**
|
|
12
|
+
* Stream the app markup via the Web Streams API (`ReadableStream`) instead of
|
|
13
|
+
* buffering it into a single string before sending it. Ignored in Client
|
|
14
|
+
* Takeover mode (`hydration: false`), since there's no server-rendered app
|
|
15
|
+
* markup to stream in the first place.
|
|
16
|
+
* @default false
|
|
17
|
+
*/
|
|
18
|
+
streaming?: boolean;
|
|
11
19
|
}
|
|
12
|
-
declare function onRenderHtml({ pageContext, Page, Head, Layout, pageTitleTag, serializedContext, assets, hydration, nonce }: ReactRenderContext):
|
|
20
|
+
declare function onRenderHtml({ pageContext, Page, Head, Layout, pageTitleTag, serializedContext, assets: { cssLinks, jsPreloads, entryClient }, hydration, streaming, nonce }: ReactRenderContext): Promise<any>;
|
|
13
21
|
//#endregion
|
|
14
|
-
export {
|
|
22
|
+
export { onRenderHtml };
|
|
@@ -1,36 +1,57 @@
|
|
|
1
1
|
import { t as PageContextProvider } from "../../PageContextProvider-CTiW2Vsa.mjs";
|
|
2
2
|
import { jsx } from "react/jsx-runtime";
|
|
3
3
|
import { renderToString } from "react-dom/server";
|
|
4
|
+
import { renderToReadableStream } from "react-dom/server.edge";
|
|
5
|
+
import { renderHtmlShell, renderHtmlShellStream } from "vike-lite/__internal/server";
|
|
4
6
|
//#region src/__internal/server/onRenderHtml.tsx
|
|
5
|
-
function onRenderHtml({ pageContext, Page, Head, Layout, pageTitleTag, serializedContext, assets, hydration, nonce }) {
|
|
6
|
-
const { cssLinks, jsPreloads, entryClient } = assets;
|
|
7
|
-
const nonceAttr = nonce ? ` nonce="${nonce}"` : "";
|
|
7
|
+
async function onRenderHtml({ pageContext, Page, Head, Layout, pageTitleTag, serializedContext, assets: { cssLinks, jsPreloads, entryClient }, hydration, streaming, nonce }) {
|
|
8
8
|
const providerValue = {
|
|
9
9
|
pageContext,
|
|
10
10
|
setPageContext: () => {}
|
|
11
11
|
};
|
|
12
|
-
|
|
13
|
-
<html lang="en">
|
|
14
|
-
<head>
|
|
15
|
-
<meta charset="utf-8">
|
|
16
|
-
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
17
|
-
${pageTitleTag}
|
|
18
|
-
${cssLinks}
|
|
19
|
-
${jsPreloads}
|
|
20
|
-
${Head ? renderToString(/* @__PURE__ */ jsx(PageContextProvider, {
|
|
12
|
+
const headHtml = Head ? renderToString(/* @__PURE__ */ jsx(PageContextProvider, {
|
|
21
13
|
value: providerValue,
|
|
22
14
|
children: /* @__PURE__ */ jsx(Head, {})
|
|
23
|
-
})) : ""
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
15
|
+
})) : "";
|
|
16
|
+
if (!hydration) return renderHtmlShell({
|
|
17
|
+
pageTitleTag,
|
|
18
|
+
cssLinks,
|
|
19
|
+
jsPreloads,
|
|
20
|
+
headHtml,
|
|
21
|
+
appHtml: "",
|
|
22
|
+
serializedContext,
|
|
23
|
+
entryClient,
|
|
24
|
+
nonce
|
|
25
|
+
});
|
|
26
|
+
const app = /* @__PURE__ */ jsx(PageContextProvider, {
|
|
28
27
|
value: providerValue,
|
|
29
28
|
children: Layout ? /* @__PURE__ */ jsx(Layout, { children: /* @__PURE__ */ jsx(Page, {}) }) : /* @__PURE__ */ jsx(Page, {})
|
|
30
|
-
})
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
29
|
+
});
|
|
30
|
+
if (streaming) return renderHtmlShellStream({
|
|
31
|
+
pageTitleTag,
|
|
32
|
+
cssLinks,
|
|
33
|
+
jsPreloads,
|
|
34
|
+
headHtml,
|
|
35
|
+
appStream: await renderToReadableStream(app, {
|
|
36
|
+
nonce,
|
|
37
|
+
onError(error) {
|
|
38
|
+
console.error("[vike-lite-react] Streaming render error:", error);
|
|
39
|
+
}
|
|
40
|
+
}),
|
|
41
|
+
serializedContext,
|
|
42
|
+
entryClient,
|
|
43
|
+
nonce
|
|
44
|
+
});
|
|
45
|
+
return renderHtmlShell({
|
|
46
|
+
pageTitleTag,
|
|
47
|
+
cssLinks,
|
|
48
|
+
jsPreloads,
|
|
49
|
+
headHtml,
|
|
50
|
+
appHtml: renderToString(app),
|
|
51
|
+
serializedContext,
|
|
52
|
+
entryClient,
|
|
53
|
+
nonce
|
|
54
|
+
});
|
|
34
55
|
}
|
|
35
56
|
//#endregion
|
|
36
57
|
export { onRenderHtml };
|
package/dist/index.d.mts
CHANGED
|
@@ -14,7 +14,7 @@ declare function useUrl(): URL;
|
|
|
14
14
|
//#endregion
|
|
15
15
|
//#region src/shared/globalContext.d.ts
|
|
16
16
|
interface PageContextValue {
|
|
17
|
-
pageContext:
|
|
17
|
+
pageContext: PageContext;
|
|
18
18
|
setPageContext: (updater: (prev: PageContextClient) => PageContextClient) => void;
|
|
19
19
|
}
|
|
20
20
|
//#endregion
|
package/dist/vite.d.mts
CHANGED
|
@@ -7,11 +7,20 @@ interface VikeLiteReactOptions {
|
|
|
7
7
|
* @default true
|
|
8
8
|
*/
|
|
9
9
|
hydration?: boolean;
|
|
10
|
+
/**
|
|
11
|
+
* Stream the server-rendered app markup via the Web Streams API (`ReadableStream`)
|
|
12
|
+
* instead of buffering it into a single string before sending the response.
|
|
13
|
+
* Uses `react-dom/server.edge`'s `renderToReadableStream`, so it works the same
|
|
14
|
+
* way on Node.js, Deno, Bun and Edge runtimes. Ignored when `hydration: false`
|
|
15
|
+
* (Client Takeover has no server-rendered app markup to stream).
|
|
16
|
+
* @default true
|
|
17
|
+
*/
|
|
18
|
+
streaming?: boolean;
|
|
10
19
|
/**
|
|
11
20
|
* Advanced: pass options directly to the underlying @vitejs/plugin-react.
|
|
12
21
|
*/
|
|
13
22
|
react?: Options;
|
|
14
23
|
}
|
|
15
|
-
declare function vikeLiteReact({ hydration, react: reactOptions }?: VikeLiteReactOptions): Plugin[];
|
|
24
|
+
declare function vikeLiteReact({ hydration, streaming, react: reactOptions }?: VikeLiteReactOptions): Plugin[];
|
|
16
25
|
//#endregion
|
|
17
26
|
export { VikeLiteReactOptions, vikeLiteReact as default };
|
package/dist/vite.mjs
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import react from "@vitejs/plugin-react";
|
|
2
2
|
import { createFrameworkAdapterPlugin } from "vike-lite/__internal/vite";
|
|
3
3
|
//#region src/vite.ts
|
|
4
|
-
function vikeLiteReact({ hydration = true, react: reactOptions } = {}) {
|
|
4
|
+
function vikeLiteReact({ hydration = true, streaming = true, react: reactOptions } = {}) {
|
|
5
5
|
const adapter = createFrameworkAdapterPlugin({
|
|
6
6
|
packageName: "vike-lite-react",
|
|
7
|
-
hydration
|
|
7
|
+
hydration,
|
|
8
|
+
streaming
|
|
8
9
|
});
|
|
9
10
|
return [...react(reactOptions), adapter];
|
|
10
11
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vike-lite-react",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -57,7 +57,7 @@
|
|
|
57
57
|
},
|
|
58
58
|
"devDependencies": {
|
|
59
59
|
"@testing-library/dom": "^10.4.1",
|
|
60
|
-
"@testing-library/jest-dom": "^
|
|
60
|
+
"@testing-library/jest-dom": "^7.0.0",
|
|
61
61
|
"@testing-library/react": "^16.3.2",
|
|
62
62
|
"@types/node": "^26.1.1",
|
|
63
63
|
"@types/react": "^19.2.17",
|
|
@@ -66,15 +66,15 @@
|
|
|
66
66
|
"jsdom": "^29.1.1",
|
|
67
67
|
"react": "^19.2.7",
|
|
68
68
|
"react-dom": "^19.2.7",
|
|
69
|
-
"tsdown": "^0.22.
|
|
70
|
-
"vike-lite": "1.
|
|
69
|
+
"tsdown": "^0.22.12",
|
|
70
|
+
"vike-lite": "1.16.0",
|
|
71
71
|
"vite": "^8.1.5",
|
|
72
72
|
"vitest": "^4.1.10"
|
|
73
73
|
},
|
|
74
74
|
"peerDependencies": {
|
|
75
75
|
"@vitejs/plugin-react": ">=6",
|
|
76
|
-
"react": "
|
|
77
|
-
"react-dom": "
|
|
76
|
+
"react": ">=19",
|
|
77
|
+
"react-dom": ">=19",
|
|
78
78
|
"vike-lite": "*",
|
|
79
79
|
"vite": ">=8"
|
|
80
80
|
},
|