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 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/onRenderClient.tsx
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 && { data: ctx.data },
139
- ...ctx?.title && { title: 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: useMemo(() => ({
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): string;
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 { ReactRenderContext, onRenderHtml };
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
- return `<!DOCTYPE html>
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
- <script${nonceAttr}>window.__PAGE_CONTEXT__=${serializedContext}<\/script>
25
- </head>
26
- <body>
27
- <div id="root" tabindex="-1">${hydration ? renderToString(/* @__PURE__ */ jsx(PageContextProvider, {
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
- })) : ""}</div>
31
- <script type="module" src="${entryClient}"${nonceAttr}><\/script>
32
- </body>
33
- </html>`;
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: PageContextClient;
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.17",
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": "^6.9.1",
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.9",
70
- "vike-lite": "1.15.15",
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
  },