vike-lite-react 1.0.0 → 1.0.2

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.
@@ -0,0 +1,9 @@
1
+ import { t as PageContextReactContext } from "./globalContext-BIY5-Fdk.mjs";
2
+ //#region src/hooks/PageContextProvider.tsx
3
+ function PageContextProvider({ value, children }) {
4
+ return <PageContextReactContext.Provider value={value}>
5
+ {children}
6
+ </PageContextReactContext.Provider>;
7
+ }
8
+ //#endregion
9
+ export { PageContextProvider as t };
@@ -0,0 +1,8 @@
1
+ import { VikeState } from "vike-lite/__internal/server";
2
+ //#region src/__internal/client/onRenderClient.d.ts
3
+ declare function onRenderClient(clientOptions: {
4
+ routes: VikeState['routes'];
5
+ errorRoute: VikeState['errorRoute'];
6
+ }): Promise<void>;
7
+ //#endregion
8
+ export { onRenderClient as default };
@@ -0,0 +1,299 @@
1
+ import { t as PageContextProvider } from "../../PageContextProvider-Dec19bRR.mjs";
2
+ import { Component, useCallback, useEffect, useMemo, useRef, useState } from "react";
3
+ import { createRoot, hydrateRoot } from "react-dom/client";
4
+ import { matchRoute } from "vike-lite/__internal/shared";
5
+ import { hydration } from "virtual:vike-lite/config";
6
+ //#region src/__internal/shared/stripBase.ts
7
+ function stripBase(pathname) {
8
+ const { BASE_URL } = import.meta.env;
9
+ if (BASE_URL === "/" || !pathname.startsWith(BASE_URL)) return pathname;
10
+ const stripped = pathname.slice(BASE_URL.length);
11
+ return stripped.startsWith("/") ? stripped : "/" + stripped;
12
+ }
13
+ //#endregion
14
+ //#region src/__internal/client/onRenderClient.tsx
15
+ var RootErrorBoundary = class extends Component {
16
+ static getDerivedStateFromError(error) {
17
+ return { error };
18
+ }
19
+ state = { error: null };
20
+ render() {
21
+ if (this.state.error) return <div>Error: {this.state.error.message}</div>;
22
+ return this.props.children;
23
+ }
24
+ };
25
+ function RouterApp(props) {
26
+ const [pageContext, setPageContextState] = useState(props.initialContext);
27
+ const [view, setView] = useState(props.initialView);
28
+ const [currentUrl, setCurrentUrl] = useState(props.initialUrl);
29
+ const [currentPathname, setCurrentPathname] = useState(props.initialContext.urlPathname);
30
+ const [reloadTick, setReloadTick] = useState(0);
31
+ const shouldScrollToTop = useRef(false);
32
+ const pendingContextOverride = useRef(null);
33
+ const reloadResolvers = useRef([]);
34
+ const isFirstRun = useRef(true);
35
+ const setPageContext = useCallback((updater) => {
36
+ setPageContextState(updater);
37
+ }, []);
38
+ const matchedRoute = useMemo(() => matchRoute(currentPathname, props.routes), [currentPathname, props.routes]);
39
+ useEffect(() => {
40
+ const handleLinkClick = (e) => {
41
+ const target = e.target.closest("a");
42
+ if (!target?.href || target.target === "_blank" || e.ctrlKey || e.metaKey || e.altKey || e.shiftKey) return;
43
+ const url = new URL(target.href);
44
+ if (url.origin !== globalThis.location.origin) return;
45
+ if (url.pathname === globalThis.location.pathname && url.search === globalThis.location.search) return;
46
+ e.preventDefault();
47
+ globalThis.history.pushState({ triggeredBy: "vike-lite" }, "", url.href);
48
+ if (!url.hash) shouldScrollToTop.current = true;
49
+ setCurrentUrl(url.href);
50
+ setCurrentPathname(stripBase(url.pathname));
51
+ };
52
+ const prefetchedModules = /* @__PURE__ */ new Set();
53
+ function prefetchRoute(route) {
54
+ const modules = [
55
+ [route.page, route.Page],
56
+ [route.layout, route.Layout],
57
+ [route.head, route.Head]
58
+ ];
59
+ for (const [key, loader] of modules) {
60
+ if (!key || !loader || prefetchedModules.has(key)) continue;
61
+ prefetchedModules.add(key);
62
+ loader().catch(() => {
63
+ prefetchedModules.delete(key);
64
+ });
65
+ }
66
+ }
67
+ const handleLinkPrefetch = (e) => {
68
+ const target = e.target.closest("a");
69
+ if (!target?.href) return;
70
+ if (target.target && target.target !== "_self") return;
71
+ if (target.hasAttribute("download")) return;
72
+ const url = new URL(target.href);
73
+ if (url.origin !== globalThis.location.origin) return;
74
+ if (url.pathname === globalThis.location.pathname && url.search === globalThis.location.search) return;
75
+ const matched = matchRoute(stripBase(url.pathname), props.routes);
76
+ if (!matched) return;
77
+ prefetchRoute(matched.route);
78
+ };
79
+ const handlePopState = () => {
80
+ setCurrentUrl(globalThis.location.href);
81
+ setCurrentPathname(stripBase(globalThis.location.pathname));
82
+ };
83
+ const handleProgrammaticNavigate = (e) => {
84
+ const detail = e.detail || {};
85
+ if (!detail.keepScrollPosition) shouldScrollToTop.current = true;
86
+ if (detail.pageContext) pendingContextOverride.current = detail.pageContext;
87
+ setCurrentUrl(globalThis.location.href);
88
+ setCurrentPathname(stripBase(globalThis.location.pathname));
89
+ };
90
+ const handleProgrammaticReload = (e) => {
91
+ const resolve = e.detail?.resolve;
92
+ if (resolve) reloadResolvers.current.push(resolve);
93
+ setReloadTick((t) => t + 1);
94
+ };
95
+ document.addEventListener("click", handleLinkClick);
96
+ document.addEventListener("pointerenter", handleLinkPrefetch, { capture: true });
97
+ document.addEventListener("focusin", handleLinkPrefetch);
98
+ globalThis.addEventListener("popstate", handlePopState);
99
+ globalThis.addEventListener("vike-navigate", handleProgrammaticNavigate);
100
+ globalThis.addEventListener("vike-reload", handleProgrammaticReload);
101
+ return () => {
102
+ document.removeEventListener("click", handleLinkClick);
103
+ document.removeEventListener("pointerenter", handleLinkPrefetch, { capture: true });
104
+ document.removeEventListener("focusin", handleLinkPrefetch);
105
+ globalThis.removeEventListener("popstate", handlePopState);
106
+ globalThis.removeEventListener("vike-navigate", handleProgrammaticNavigate);
107
+ globalThis.removeEventListener("vike-reload", handleProgrammaticReload);
108
+ };
109
+ }, [props.routes]);
110
+ useEffect(() => {
111
+ const pathname = currentPathname;
112
+ const isReload = reloadTick > 0;
113
+ if (isFirstRun.current && !isReload && globalThis.__PAGE_CONTEXT__?.urlPathname === pathname) {
114
+ isFirstRun.current = false;
115
+ globalThis.__PAGE_CONTEXT__.urlPathname = void 0;
116
+ return;
117
+ }
118
+ isFirstRun.current = false;
119
+ const controller = new AbortController();
120
+ const signal = controller.signal;
121
+ const urlFull = currentUrl;
122
+ const matched = matchedRoute;
123
+ const loadRoute = async () => {
124
+ const contextOverride = pendingContextOverride.current;
125
+ pendingContextOverride.current = null;
126
+ function finalizeNavigation() {
127
+ if (shouldScrollToTop.current) {
128
+ globalThis.scrollTo(0, 0);
129
+ shouldScrollToTop.current = false;
130
+ } else if (globalThis.location.hash) requestAnimationFrame(() => {
131
+ try {
132
+ document.querySelector(decodeURIComponent(globalThis.location.hash))?.scrollIntoView();
133
+ } catch {}
134
+ });
135
+ }
136
+ const renderErrorPage = async (is404, message) => {
137
+ if (!props.errorRoute) return;
138
+ const [ErrorPageMod, ErrorLayoutMod, ErrorHeadMod] = await Promise.all([
139
+ props.errorRoute.Page(),
140
+ props.errorRoute.Layout?.() ?? null,
141
+ props.errorRoute.Head?.() ?? null
142
+ ]);
143
+ if (signal.aborted) return;
144
+ setPageContext(() => ({
145
+ urlOriginal: urlFull,
146
+ urlPathname: pathname,
147
+ routeParams: {},
148
+ is404,
149
+ is500: !is404,
150
+ errorMessage: message,
151
+ isClientSide: true,
152
+ isHydration: false
153
+ }));
154
+ setView({
155
+ Page: ErrorPageMod.Page ?? ErrorPageMod.default,
156
+ Layout: ErrorLayoutMod?.Layout ?? ErrorLayoutMod?.default ?? null,
157
+ Head: ErrorHeadMod?.Head ?? ErrorHeadMod?.default ?? null
158
+ });
159
+ document.title = is404 ? "Not Found" : "Server Error";
160
+ finalizeNavigation();
161
+ };
162
+ if (!matched) return renderErrorPage(true);
163
+ const { route, routeParams } = matched;
164
+ try {
165
+ const urlObj = new URL(urlFull);
166
+ const jsonTarget = pathname === "/" ? "/index" : pathname;
167
+ const { BASE_URL } = import.meta.env;
168
+ const jsonUrl = `${BASE_URL.endsWith("/") ? BASE_URL.slice(0, -1) : BASE_URL}${jsonTarget}.pageContext.json${urlObj.search}`;
169
+ let ctx = null;
170
+ if (route.data || route.title) {
171
+ const res = await fetch(jsonUrl, {
172
+ signal,
173
+ cache: isReload ? "no-cache" : "default"
174
+ });
175
+ const contentType = res.headers.get("content-type") ?? "";
176
+ if (!contentType.includes("application/json")) throw new Error(`Expected JSON but got "${contentType}" for ${jsonUrl}. Check your proxy/CDN configuration.`);
177
+ ctx = await res.json();
178
+ }
179
+ if (signal.aborted) return;
180
+ if (ctx?._redirect) {
181
+ const urlObjRedirect = new URL(ctx._redirect, globalThis.location.origin);
182
+ if (urlObjRedirect.origin !== globalThis.location.origin) {
183
+ globalThis.location.assign(ctx._redirect);
184
+ return;
185
+ }
186
+ globalThis.history.pushState({ triggeredBy: "vike-lite" }, "", ctx._redirect);
187
+ shouldScrollToTop.current = true;
188
+ setCurrentUrl(urlObjRedirect.href);
189
+ setCurrentPathname(stripBase(urlObjRedirect.pathname));
190
+ return;
191
+ }
192
+ if (ctx && (ctx.is404 || ctx.is500 || ctx.isError)) return renderErrorPage(ctx.is404, ctx.reason || "Server Error");
193
+ const [PageMod, LayoutMod, HeadMod] = await Promise.all([
194
+ route.Page(),
195
+ route.Layout?.() ?? null,
196
+ route.Head?.() ?? null
197
+ ]);
198
+ if (signal.aborted) return;
199
+ setPageContext(() => ({
200
+ routeParams,
201
+ urlOriginal: urlObj.href,
202
+ urlPathname: pathname,
203
+ search: urlObj.search,
204
+ ...ctx?.data && { data: ctx.data },
205
+ ...ctx?.title && { title: ctx.title },
206
+ ...contextOverride,
207
+ isClientSide: true,
208
+ isHydration: false
209
+ }));
210
+ setView({
211
+ Page: PageMod.Page ?? PageMod.default,
212
+ Layout: LayoutMod?.Layout ?? LayoutMod?.default ?? null,
213
+ Head: HeadMod?.Head ?? HeadMod?.default ?? null
214
+ });
215
+ if (ctx?.title) document.title = ctx.title;
216
+ requestAnimationFrame(() => {
217
+ if (globalThis.location.hash) return;
218
+ document.querySelector("#root")?.focus({ preventScroll: true });
219
+ });
220
+ finalizeNavigation();
221
+ } catch (error) {
222
+ if (error.name === "AbortError") return;
223
+ const message = error.message || "";
224
+ if (/dynamically imported module|importing a module script failed/i.test(message)) {
225
+ const GUARD_KEY = "vike-lite:reload-guard";
226
+ const last = Number(sessionStorage.getItem(GUARD_KEY) ?? 0);
227
+ if (Date.now() - last > 1e4) {
228
+ sessionStorage.setItem(GUARD_KEY, String(Date.now()));
229
+ console.warn("App update detected, forcing reload…");
230
+ globalThis.location.assign(urlFull);
231
+ return;
232
+ }
233
+ }
234
+ console.error("Router Error:", error);
235
+ renderErrorPage(false, message);
236
+ } finally {
237
+ if (!signal.aborted) {
238
+ for (const resolve of reloadResolvers.current) resolve();
239
+ reloadResolvers.current = [];
240
+ }
241
+ }
242
+ };
243
+ loadRoute();
244
+ return () => controller.abort();
245
+ }, [
246
+ currentPathname,
247
+ currentUrl,
248
+ matchedRoute,
249
+ reloadTick
250
+ ]);
251
+ const { Page, Layout } = view;
252
+ const contextValue = useMemo(() => ({
253
+ pageContext,
254
+ setPageContext
255
+ }), [pageContext, setPageContext]);
256
+ return <RootErrorBoundary>
257
+ <PageContextProvider value={contextValue}>
258
+ {Layout ? <Layout><Page /></Layout> : <Page />}
259
+ </PageContextProvider>
260
+ </RootErrorBoundary>;
261
+ }
262
+ async function onRenderClient(clientOptions) {
263
+ const container = document.querySelector("#root");
264
+ const rawContext = globalThis.__PAGE_CONTEXT__ ?? {};
265
+ const isHydration = hydration && !!globalThis.__PAGE_CONTEXT__;
266
+ const initialContext = {
267
+ ...rawContext,
268
+ isClientSide: true,
269
+ isHydration
270
+ };
271
+ let initialView = {
272
+ Page: null,
273
+ Layout: null,
274
+ Head: null
275
+ };
276
+ if (isHydration) {
277
+ const matched = matchRoute(initialContext.urlPathname ?? globalThis.location.pathname, clientOptions.routes);
278
+ if (matched) {
279
+ const [PageMod, LayoutMod, HeadMod] = await Promise.all([
280
+ matched.route.Page(),
281
+ matched.route.Layout?.() ?? null,
282
+ matched.route.Head?.() ?? null
283
+ ]);
284
+ initialView = {
285
+ Page: PageMod.Page ?? PageMod.default,
286
+ Layout: LayoutMod?.Layout ?? LayoutMod?.default ?? null,
287
+ Head: HeadMod?.Head ?? HeadMod?.default ?? null
288
+ };
289
+ }
290
+ }
291
+ const app = <RouterApp {...clientOptions} initialView={initialView} initialContext={initialContext} initialUrl={globalThis.location.href} />;
292
+ if (isHydration) hydrateRoot(container, app);
293
+ else {
294
+ container.replaceChildren();
295
+ createRoot(container).render(app);
296
+ }
297
+ }
298
+ //#endregion
299
+ export { onRenderClient as default };
@@ -0,0 +1,13 @@
1
+ import { ComponentType, ReactNode } from "react";
2
+ import { RenderContext } from "vike-lite/__internal/shared";
3
+ //#region src/__internal/server/onRenderHtml.d.ts
4
+ interface ReactRenderContext extends RenderContext {
5
+ Page: ComponentType;
6
+ Head?: ComponentType;
7
+ Layout?: ComponentType<{
8
+ children: ReactNode;
9
+ }>;
10
+ }
11
+ declare function onRenderHtml({ pageContext, Page, Head, Layout, pageTitleTag, serializedContext, assets }: ReactRenderContext): string;
12
+ //#endregion
13
+ export { ReactRenderContext, onRenderHtml };
@@ -0,0 +1,33 @@
1
+ import { t as PageContextProvider } from "../../PageContextProvider-Dec19bRR.mjs";
2
+ import { renderToString } from "react-dom/server";
3
+ import { hydration } from "virtual:vike-lite/renderer";
4
+ //#region src/__internal/server/onRenderHtml.tsx
5
+ function onRenderHtml({ pageContext, Page, Head, Layout, pageTitleTag, serializedContext, assets }) {
6
+ const { cssLinks, jsPreloads, entryClient } = assets;
7
+ const providerValue = {
8
+ pageContext,
9
+ setPageContext: () => {}
10
+ };
11
+ return `<!DOCTYPE html>
12
+ <html lang="en">
13
+ <head>
14
+ <meta charset="utf-8">
15
+ <meta name="viewport" content="width=device-width,initial-scale=1">
16
+ ${pageTitleTag}
17
+ ${cssLinks}
18
+ ${jsPreloads}
19
+ ${Head ? renderToString(<PageContextProvider value={providerValue}>
20
+ <Head />
21
+ </PageContextProvider>) : ""}
22
+ <script>window.__PAGE_CONTEXT__=${serializedContext}<\/script>
23
+ </head>
24
+ <body>
25
+ <div id="root" tabindex="-1">${hydration ? renderToString(<PageContextProvider value={providerValue}>
26
+ {Layout ? <Layout><Page /></Layout> : <Page />}
27
+ </PageContextProvider>) : ""}</div>
28
+ <script type="module" src="${entryClient}"><\/script>
29
+ </body>
30
+ </html>`;
31
+ }
32
+ //#endregion
33
+ export { onRenderHtml };
@@ -0,0 +1,8 @@
1
+ import { createContext } from "react";
2
+ //#region src/hooks/globalContext.tsx
3
+ const KEY = "__vike_lite_react_context__";
4
+ const g = globalThis;
5
+ if (!Object.hasOwn(g, KEY)) g[KEY] = createContext(null);
6
+ const PageContextReactContext = g[KEY];
7
+ //#endregion
8
+ export { PageContextReactContext as t };
package/dist/index.mjs CHANGED
@@ -1,10 +1,5 @@
1
- import { createContext, useCallback, useContext, useEffect, useMemo, useState } from "react";
2
- //#region src/hooks/globalContext.tsx
3
- const KEY = "__vike_lite_react_context__";
4
- const g = globalThis;
5
- if (!Object.hasOwn(g, KEY)) g[KEY] = createContext(null);
6
- const PageContextReactContext = g[KEY];
7
- //#endregion
1
+ import { t as PageContextReactContext } from "./globalContext-BIY5-Fdk.mjs";
2
+ import { useCallback, useContext, useEffect, useMemo, useState } from "react";
8
3
  //#region src/hooks/useData.tsx
9
4
  function useData() {
10
5
  const ctx = useContext(PageContextReactContext);
package/dist/vite.d.mts CHANGED
@@ -12,6 +12,6 @@ interface VikeLiteReactOptions {
12
12
  */
13
13
  react?: Options;
14
14
  }
15
- declare function vikeLiteReact(options?: VikeLiteReactOptions): Plugin[];
15
+ declare function vikeLiteReact({ hydration, react: reactOptions }?: VikeLiteReactOptions): Plugin[];
16
16
  //#endregion
17
17
  export { VikeLiteReactOptions, vikeLiteReact as default };
package/dist/vite.mjs CHANGED
@@ -1,21 +1,24 @@
1
1
  import react from "@vitejs/plugin-react";
2
2
  //#region src/vite-plugin.ts
3
- function vikeLiteReact(options = {}) {
4
- const { hydration = true, react: reactOptions } = options;
3
+ function vikeLiteReact({ hydration = true, react: reactOptions } = {}) {
5
4
  const virtualConfigId = "virtual:vike-lite/config";
6
- const virtualRendererId = "virtual:vike-lite/renderer";
5
+ const virtualClientId = "virtual:vike-lite/client";
6
+ const virtualServerId = "virtual:vike-lite/server";
7
7
  const resolvedVirtualConfigId = "\0virtual:vike-lite/config";
8
- const resolvedVirtualRendererId = "\0virtual:vike-lite/renderer";
8
+ const resolvedVirtualClientId = "\0virtual:vike-lite/client";
9
+ const resolvedVirtualServerId = "\0virtual:vike-lite/server";
9
10
  const adapter = {
10
11
  name: "vike-lite-react",
11
12
  enforce: "pre",
12
13
  resolveId(id) {
13
14
  if (id === virtualConfigId) return resolvedVirtualConfigId;
14
- if (id === virtualRendererId) return resolvedVirtualRendererId;
15
+ if (id === virtualClientId) return resolvedVirtualClientId;
16
+ if (id === virtualServerId) return resolvedVirtualServerId;
15
17
  },
16
18
  load(id) {
17
- if (id === resolvedVirtualConfigId) return `export const hydration = ${JSON.stringify(hydration)};`;
18
- if (id === resolvedVirtualRendererId) return "export const onRenderHtml = () => import('vike-lite-react/__internal/server/onRenderHtml');export const onRenderClient = () => import('vike-lite-react/__internal/client/onRenderClient');";
19
+ if (id === resolvedVirtualConfigId) return `export const hydration=${JSON.stringify(hydration)};`;
20
+ if (id === resolvedVirtualClientId) return "export const onRenderClient=()=>import(\"vike-lite-react/__internal/client/onRenderClient\");";
21
+ if (id === resolvedVirtualServerId) return `export {onRenderHtml}from'vike-lite-react/__internal/server/onRenderHtml';`;
19
22
  }
20
23
  };
21
24
  return [...react(reactOptions), adapter];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vike-lite-react",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "module": "./dist/index.mjs",
@@ -15,12 +15,12 @@
15
15
  "import": "./dist/vite.mjs"
16
16
  },
17
17
  "./__internal/client/onRenderClient": {
18
- "types": "./src/__internal/client/onRenderClient.tsx",
19
- "import": "./src/__internal/client/onRenderClient.tsx"
18
+ "types": "./dist/__internal/client/onRenderClient.d.mts",
19
+ "import": "./dist/__internal/client/onRenderClient.mjs"
20
20
  },
21
21
  "./__internal/server/onRenderHtml": {
22
- "types": "./src/__internal/server/onRenderHtml.tsx",
23
- "import": "./src/__internal/server/onRenderHtml.tsx"
22
+ "types": "./dist/__internal/server/onRenderHtml.d.mts",
23
+ "import": "./dist/__internal/server/onRenderHtml.mjs"
24
24
  }
25
25
  },
26
26
  "repository": {
@@ -45,8 +45,8 @@
45
45
  "@vitejs/plugin-react": "^6.0.3",
46
46
  "react": "^19.2.7",
47
47
  "react-dom": "^19.2.7",
48
- "tsdown": "^0.22.5",
49
- "vike-lite": "1.13.0",
48
+ "tsdown": "^0.22.7",
49
+ "vike-lite": "1.13.3",
50
50
  "vite": "^8.1.4"
51
51
  },
52
52
  "peerDependencies": {