@lolyjs/core 0.2.0-alpha.3 → 0.2.0-alpha.31
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 +1462 -761
- package/dist/{bootstrap-BiCQmSkx.d.mts → bootstrap-BfGTMUkj.d.mts} +19 -0
- package/dist/{bootstrap-BiCQmSkx.d.ts → bootstrap-BfGTMUkj.d.ts} +19 -0
- package/dist/cli.cjs +15909 -2468
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +15932 -2481
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +18098 -4160
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.mts +323 -55
- package/dist/index.d.ts +323 -55
- package/dist/index.js +18216 -4271
- package/dist/index.js.map +1 -1
- package/dist/index.types-JJ0KjvFU.d.mts +266 -0
- package/dist/index.types-JJ0KjvFU.d.ts +266 -0
- package/dist/react/cache.cjs +107 -32
- package/dist/react/cache.cjs.map +1 -1
- package/dist/react/cache.d.mts +29 -21
- package/dist/react/cache.d.ts +29 -21
- package/dist/react/cache.js +107 -32
- package/dist/react/cache.js.map +1 -1
- package/dist/react/components.cjs +11 -12
- package/dist/react/components.cjs.map +1 -1
- package/dist/react/components.js +11 -12
- package/dist/react/components.js.map +1 -1
- package/dist/react/hooks.cjs +124 -74
- package/dist/react/hooks.cjs.map +1 -1
- package/dist/react/hooks.d.mts +6 -24
- package/dist/react/hooks.d.ts +6 -24
- package/dist/react/hooks.js +122 -71
- package/dist/react/hooks.js.map +1 -1
- package/dist/react/sockets.cjs +5 -6
- package/dist/react/sockets.cjs.map +1 -1
- package/dist/react/sockets.js +5 -6
- package/dist/react/sockets.js.map +1 -1
- package/dist/react/themes.cjs +61 -18
- package/dist/react/themes.cjs.map +1 -1
- package/dist/react/themes.js +63 -20
- package/dist/react/themes.js.map +1 -1
- package/dist/runtime.cjs +531 -104
- package/dist/runtime.cjs.map +1 -1
- package/dist/runtime.d.mts +2 -2
- package/dist/runtime.d.ts +2 -2
- package/dist/runtime.js +531 -104
- package/dist/runtime.js.map +1 -1
- package/package.json +56 -14
package/dist/runtime.js
CHANGED
|
@@ -3,15 +3,40 @@ import { hydrateRoot } from "react-dom/client";
|
|
|
3
3
|
|
|
4
4
|
// modules/runtime/client/constants.ts
|
|
5
5
|
var WINDOW_DATA_KEY = "__FW_DATA__";
|
|
6
|
+
var ROUTER_DATA_KEY = "__LOLY_ROUTER_DATA__";
|
|
6
7
|
var APP_CONTAINER_ID = "__app";
|
|
8
|
+
var ROUTER_NAVIGATE_KEY = "__LOLY_ROUTER_NAVIGATE__";
|
|
7
9
|
|
|
8
10
|
// modules/runtime/client/window-data.ts
|
|
11
|
+
var LAYOUT_PROPS_KEY = "__FW_LAYOUT_PROPS__";
|
|
9
12
|
function getWindowData() {
|
|
10
13
|
if (typeof window === "undefined") {
|
|
11
14
|
return null;
|
|
12
15
|
}
|
|
13
16
|
return window[WINDOW_DATA_KEY] ?? null;
|
|
14
17
|
}
|
|
18
|
+
function getPreservedLayoutProps() {
|
|
19
|
+
if (typeof window === "undefined") {
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
return window[LAYOUT_PROPS_KEY] ?? null;
|
|
23
|
+
}
|
|
24
|
+
function setPreservedLayoutProps(props) {
|
|
25
|
+
if (typeof window === "undefined") {
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
if (props === null) {
|
|
29
|
+
delete window[LAYOUT_PROPS_KEY];
|
|
30
|
+
} else {
|
|
31
|
+
window[LAYOUT_PROPS_KEY] = props;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
function getRouterData() {
|
|
35
|
+
if (typeof window === "undefined") {
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
return window[ROUTER_DATA_KEY] ?? null;
|
|
39
|
+
}
|
|
15
40
|
function setWindowData(data) {
|
|
16
41
|
window[WINDOW_DATA_KEY] = data;
|
|
17
42
|
if (typeof window !== "undefined") {
|
|
@@ -22,6 +47,16 @@ function setWindowData(data) {
|
|
|
22
47
|
);
|
|
23
48
|
}
|
|
24
49
|
}
|
|
50
|
+
function setRouterData(data) {
|
|
51
|
+
window[ROUTER_DATA_KEY] = data;
|
|
52
|
+
if (typeof window !== "undefined") {
|
|
53
|
+
window.dispatchEvent(
|
|
54
|
+
new CustomEvent("fw-router-data-refresh", {
|
|
55
|
+
detail: { data }
|
|
56
|
+
})
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
25
60
|
function getCurrentTheme() {
|
|
26
61
|
return getWindowData()?.theme ?? null;
|
|
27
62
|
}
|
|
@@ -67,22 +102,158 @@ function matchRouteClient(pathWithSearch, routes) {
|
|
|
67
102
|
}
|
|
68
103
|
|
|
69
104
|
// modules/runtime/client/metadata.ts
|
|
105
|
+
function getOrCreateMeta(selector, attributes) {
|
|
106
|
+
let meta = document.querySelector(selector);
|
|
107
|
+
if (!meta) {
|
|
108
|
+
meta = document.createElement("meta");
|
|
109
|
+
if (attributes.name) meta.name = attributes.name;
|
|
110
|
+
if (attributes.property) meta.setAttribute("property", attributes.property);
|
|
111
|
+
if (attributes.httpEquiv) meta.httpEquiv = attributes.httpEquiv;
|
|
112
|
+
document.head.appendChild(meta);
|
|
113
|
+
}
|
|
114
|
+
return meta;
|
|
115
|
+
}
|
|
116
|
+
function getOrCreateLink(rel, href) {
|
|
117
|
+
const selector = `link[rel="${rel}"]`;
|
|
118
|
+
let link = document.querySelector(selector);
|
|
119
|
+
if (!link) {
|
|
120
|
+
link = document.createElement("link");
|
|
121
|
+
link.rel = rel;
|
|
122
|
+
link.href = href;
|
|
123
|
+
document.head.appendChild(link);
|
|
124
|
+
} else {
|
|
125
|
+
link.href = href;
|
|
126
|
+
}
|
|
127
|
+
return link;
|
|
128
|
+
}
|
|
70
129
|
function applyMetadata(md) {
|
|
71
130
|
if (!md) return;
|
|
72
131
|
if (md.title) {
|
|
73
132
|
document.title = md.title;
|
|
74
133
|
}
|
|
75
134
|
if (md.description) {
|
|
76
|
-
|
|
77
|
-
'meta[name="description"]'
|
|
78
|
-
);
|
|
79
|
-
if (!meta) {
|
|
80
|
-
meta = document.createElement("meta");
|
|
81
|
-
meta.name = "description";
|
|
82
|
-
document.head.appendChild(meta);
|
|
83
|
-
}
|
|
135
|
+
const meta = getOrCreateMeta('meta[name="description"]', { name: "description" });
|
|
84
136
|
meta.content = md.description;
|
|
85
137
|
}
|
|
138
|
+
if (md.robots) {
|
|
139
|
+
const meta = getOrCreateMeta('meta[name="robots"]', { name: "robots" });
|
|
140
|
+
meta.content = md.robots;
|
|
141
|
+
}
|
|
142
|
+
if (md.themeColor) {
|
|
143
|
+
const meta = getOrCreateMeta('meta[name="theme-color"]', { name: "theme-color" });
|
|
144
|
+
meta.content = md.themeColor;
|
|
145
|
+
}
|
|
146
|
+
if (md.viewport) {
|
|
147
|
+
const meta = getOrCreateMeta('meta[name="viewport"]', { name: "viewport" });
|
|
148
|
+
meta.content = md.viewport;
|
|
149
|
+
}
|
|
150
|
+
if (md.canonical) {
|
|
151
|
+
getOrCreateLink("canonical", md.canonical);
|
|
152
|
+
}
|
|
153
|
+
if (md.openGraph) {
|
|
154
|
+
const og = md.openGraph;
|
|
155
|
+
if (og.title) {
|
|
156
|
+
const meta = getOrCreateMeta('meta[property="og:title"]', { property: "og:title" });
|
|
157
|
+
meta.content = og.title;
|
|
158
|
+
}
|
|
159
|
+
if (og.description) {
|
|
160
|
+
const meta = getOrCreateMeta('meta[property="og:description"]', { property: "og:description" });
|
|
161
|
+
meta.content = og.description;
|
|
162
|
+
}
|
|
163
|
+
if (og.type) {
|
|
164
|
+
const meta = getOrCreateMeta('meta[property="og:type"]', { property: "og:type" });
|
|
165
|
+
meta.content = og.type;
|
|
166
|
+
}
|
|
167
|
+
if (og.url) {
|
|
168
|
+
const meta = getOrCreateMeta('meta[property="og:url"]', { property: "og:url" });
|
|
169
|
+
meta.content = og.url;
|
|
170
|
+
}
|
|
171
|
+
if (og.image) {
|
|
172
|
+
if (typeof og.image === "string") {
|
|
173
|
+
const meta = getOrCreateMeta('meta[property="og:image"]', { property: "og:image" });
|
|
174
|
+
meta.content = og.image;
|
|
175
|
+
} else {
|
|
176
|
+
const meta = getOrCreateMeta('meta[property="og:image"]', { property: "og:image" });
|
|
177
|
+
meta.content = og.image.url;
|
|
178
|
+
if (og.image.width) {
|
|
179
|
+
const metaWidth = getOrCreateMeta('meta[property="og:image:width"]', { property: "og:image:width" });
|
|
180
|
+
metaWidth.content = String(og.image.width);
|
|
181
|
+
}
|
|
182
|
+
if (og.image.height) {
|
|
183
|
+
const metaHeight = getOrCreateMeta('meta[property="og:image:height"]', { property: "og:image:height" });
|
|
184
|
+
metaHeight.content = String(og.image.height);
|
|
185
|
+
}
|
|
186
|
+
if (og.image.alt) {
|
|
187
|
+
const metaAlt = getOrCreateMeta('meta[property="og:image:alt"]', { property: "og:image:alt" });
|
|
188
|
+
metaAlt.content = og.image.alt;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
if (og.siteName) {
|
|
193
|
+
const meta = getOrCreateMeta('meta[property="og:site_name"]', { property: "og:site_name" });
|
|
194
|
+
meta.content = og.siteName;
|
|
195
|
+
}
|
|
196
|
+
if (og.locale) {
|
|
197
|
+
const meta = getOrCreateMeta('meta[property="og:locale"]', { property: "og:locale" });
|
|
198
|
+
meta.content = og.locale;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
if (md.twitter) {
|
|
202
|
+
const twitter = md.twitter;
|
|
203
|
+
if (twitter.card) {
|
|
204
|
+
const meta = getOrCreateMeta('meta[name="twitter:card"]', { name: "twitter:card" });
|
|
205
|
+
meta.content = twitter.card;
|
|
206
|
+
}
|
|
207
|
+
if (twitter.title) {
|
|
208
|
+
const meta = getOrCreateMeta('meta[name="twitter:title"]', { name: "twitter:title" });
|
|
209
|
+
meta.content = twitter.title;
|
|
210
|
+
}
|
|
211
|
+
if (twitter.description) {
|
|
212
|
+
const meta = getOrCreateMeta('meta[name="twitter:description"]', { name: "twitter:description" });
|
|
213
|
+
meta.content = twitter.description;
|
|
214
|
+
}
|
|
215
|
+
if (twitter.image) {
|
|
216
|
+
const meta = getOrCreateMeta('meta[name="twitter:image"]', { name: "twitter:image" });
|
|
217
|
+
meta.content = twitter.image;
|
|
218
|
+
}
|
|
219
|
+
if (twitter.imageAlt) {
|
|
220
|
+
const meta = getOrCreateMeta('meta[name="twitter:image:alt"]', { name: "twitter:image:alt" });
|
|
221
|
+
meta.content = twitter.imageAlt;
|
|
222
|
+
}
|
|
223
|
+
if (twitter.site) {
|
|
224
|
+
const meta = getOrCreateMeta('meta[name="twitter:site"]', { name: "twitter:site" });
|
|
225
|
+
meta.content = twitter.site;
|
|
226
|
+
}
|
|
227
|
+
if (twitter.creator) {
|
|
228
|
+
const meta = getOrCreateMeta('meta[name="twitter:creator"]', { name: "twitter:creator" });
|
|
229
|
+
meta.content = twitter.creator;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
if (md.metaTags && Array.isArray(md.metaTags)) {
|
|
233
|
+
md.metaTags.forEach((tag) => {
|
|
234
|
+
let selector = "";
|
|
235
|
+
if (tag.name) {
|
|
236
|
+
selector = `meta[name="${tag.name}"]`;
|
|
237
|
+
} else if (tag.property) {
|
|
238
|
+
selector = `meta[property="${tag.property}"]`;
|
|
239
|
+
} else if (tag.httpEquiv) {
|
|
240
|
+
selector = `meta[http-equiv="${tag.httpEquiv}"]`;
|
|
241
|
+
}
|
|
242
|
+
if (selector) {
|
|
243
|
+
const meta = getOrCreateMeta(selector, {
|
|
244
|
+
name: tag.name,
|
|
245
|
+
property: tag.property,
|
|
246
|
+
httpEquiv: tag.httpEquiv
|
|
247
|
+
});
|
|
248
|
+
meta.content = tag.content;
|
|
249
|
+
}
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
if (md.links && Array.isArray(md.links)) {
|
|
253
|
+
md.links.forEach((link) => {
|
|
254
|
+
getOrCreateLink(link.rel, link.href);
|
|
255
|
+
});
|
|
256
|
+
}
|
|
86
257
|
}
|
|
87
258
|
|
|
88
259
|
// modules/runtime/client/AppShell.tsx
|
|
@@ -195,14 +366,16 @@ function deleteCacheEntry(key) {
|
|
|
195
366
|
function buildDataUrl(url) {
|
|
196
367
|
return url + (url.includes("?") ? "&" : "?") + "__fw_data=1";
|
|
197
368
|
}
|
|
198
|
-
async function fetchRouteDataOnce(url) {
|
|
369
|
+
async function fetchRouteDataOnce(url, skipLayoutHooks = true) {
|
|
199
370
|
const dataUrl = buildDataUrl(url);
|
|
200
|
-
const
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
371
|
+
const headers = {
|
|
372
|
+
"x-fw-data": "1",
|
|
373
|
+
Accept: "application/json"
|
|
374
|
+
};
|
|
375
|
+
if (skipLayoutHooks) {
|
|
376
|
+
headers["x-skip-layout-hooks"] = "true";
|
|
377
|
+
}
|
|
378
|
+
const res = await fetch(dataUrl, { headers });
|
|
206
379
|
let json = {};
|
|
207
380
|
try {
|
|
208
381
|
const text = await res.text();
|
|
@@ -228,7 +401,7 @@ async function getRouteData(url, options) {
|
|
|
228
401
|
deleteCacheEntry(key);
|
|
229
402
|
}
|
|
230
403
|
const entry = dataCache.get(key);
|
|
231
|
-
if (entry) {
|
|
404
|
+
if (entry && !options?.revalidate) {
|
|
232
405
|
if (entry.status === "fulfilled") {
|
|
233
406
|
updateLRU(key);
|
|
234
407
|
return entry.value;
|
|
@@ -237,12 +410,29 @@ async function getRouteData(url, options) {
|
|
|
237
410
|
return entry.promise;
|
|
238
411
|
}
|
|
239
412
|
}
|
|
240
|
-
const
|
|
241
|
-
|
|
413
|
+
const skipLayoutHooks = !options?.revalidate;
|
|
414
|
+
const currentEntry = dataCache.get(key);
|
|
415
|
+
if (currentEntry && !options?.revalidate) {
|
|
416
|
+
if (currentEntry.status === "fulfilled") {
|
|
417
|
+
updateLRU(key);
|
|
418
|
+
return currentEntry.value;
|
|
419
|
+
}
|
|
420
|
+
if (currentEntry.status === "pending") {
|
|
421
|
+
return currentEntry.promise;
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
const promise = fetchRouteDataOnce(url, skipLayoutHooks).then((value) => {
|
|
425
|
+
const entryAfterFetch = dataCache.get(key);
|
|
426
|
+
if (!entryAfterFetch || entryAfterFetch.status === "pending") {
|
|
427
|
+
setCacheEntry(key, { status: "fulfilled", value });
|
|
428
|
+
}
|
|
242
429
|
return value;
|
|
243
430
|
}).catch((error) => {
|
|
244
431
|
console.error("[client][cache] Error fetching route data:", error);
|
|
245
|
-
dataCache.
|
|
432
|
+
const entryAfterFetch = dataCache.get(key);
|
|
433
|
+
if (!entryAfterFetch || entryAfterFetch.status === "pending") {
|
|
434
|
+
dataCache.set(key, { status: "rejected", error });
|
|
435
|
+
}
|
|
246
436
|
throw error;
|
|
247
437
|
});
|
|
248
438
|
dataCache.set(key, { status: "pending", promise });
|
|
@@ -267,10 +457,22 @@ async function handleErrorRoute(nextUrl, json, errorRoute, setState) {
|
|
|
267
457
|
} else if (json.theme) {
|
|
268
458
|
theme = json.theme;
|
|
269
459
|
}
|
|
460
|
+
let layoutProps = {};
|
|
461
|
+
if (json.layoutProps !== void 0 && json.layoutProps !== null) {
|
|
462
|
+
layoutProps = json.layoutProps;
|
|
463
|
+
setPreservedLayoutProps(layoutProps);
|
|
464
|
+
} else {
|
|
465
|
+
const preserved = getPreservedLayoutProps();
|
|
466
|
+
if (preserved) {
|
|
467
|
+
layoutProps = preserved;
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
const pageProps = json.pageProps ?? json.props ?? {
|
|
471
|
+
error: json.message || "An error occurred"
|
|
472
|
+
};
|
|
270
473
|
const errorProps = {
|
|
271
|
-
...
|
|
272
|
-
|
|
273
|
-
},
|
|
474
|
+
...layoutProps,
|
|
475
|
+
...pageProps,
|
|
274
476
|
theme
|
|
275
477
|
};
|
|
276
478
|
const windowData = {
|
|
@@ -283,6 +485,13 @@ async function handleErrorRoute(nextUrl, json, errorRoute, setState) {
|
|
|
283
485
|
error: true
|
|
284
486
|
};
|
|
285
487
|
setWindowData(windowData);
|
|
488
|
+
const url = new URL(nextUrl, typeof window !== "undefined" ? window.location.origin : "http://localhost");
|
|
489
|
+
const routerData = {
|
|
490
|
+
pathname: url.pathname,
|
|
491
|
+
params: json.params || {},
|
|
492
|
+
searchParams: Object.fromEntries(url.searchParams.entries())
|
|
493
|
+
};
|
|
494
|
+
setRouterData(routerData);
|
|
286
495
|
setState({
|
|
287
496
|
url: nextUrl,
|
|
288
497
|
route: errorRoute,
|
|
@@ -292,10 +501,15 @@ async function handleErrorRoute(nextUrl, json, errorRoute, setState) {
|
|
|
292
501
|
});
|
|
293
502
|
return true;
|
|
294
503
|
} catch (loadError) {
|
|
295
|
-
console.error(
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
504
|
+
console.error("\n\u274C [client] Error loading error route components:");
|
|
505
|
+
console.error(loadError);
|
|
506
|
+
if (loadError instanceof Error) {
|
|
507
|
+
console.error(` Message: ${loadError.message}`);
|
|
508
|
+
if (loadError.stack) {
|
|
509
|
+
console.error(` Stack: ${loadError.stack.split("\n").slice(0, 3).join("\n ")}`);
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
console.error("\u{1F4A1} Falling back to full page reload\n");
|
|
299
513
|
window.location.href = nextUrl;
|
|
300
514
|
return false;
|
|
301
515
|
}
|
|
@@ -315,8 +529,20 @@ async function handleNotFoundRoute(nextUrl, json, notFoundRoute, setState) {
|
|
|
315
529
|
} else if (json.theme) {
|
|
316
530
|
theme = json.theme;
|
|
317
531
|
}
|
|
532
|
+
let layoutProps = {};
|
|
533
|
+
if (json.layoutProps !== void 0 && json.layoutProps !== null) {
|
|
534
|
+
layoutProps = json.layoutProps;
|
|
535
|
+
setPreservedLayoutProps(layoutProps);
|
|
536
|
+
} else {
|
|
537
|
+
const preserved = getPreservedLayoutProps();
|
|
538
|
+
if (preserved) {
|
|
539
|
+
layoutProps = preserved;
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
const pageProps = json.pageProps ?? json.props ?? {};
|
|
318
543
|
const notFoundProps = {
|
|
319
|
-
...
|
|
544
|
+
...layoutProps,
|
|
545
|
+
...pageProps,
|
|
320
546
|
theme
|
|
321
547
|
};
|
|
322
548
|
const windowData = {
|
|
@@ -329,6 +555,13 @@ async function handleNotFoundRoute(nextUrl, json, notFoundRoute, setState) {
|
|
|
329
555
|
error: false
|
|
330
556
|
};
|
|
331
557
|
setWindowData(windowData);
|
|
558
|
+
const url = new URL(nextUrl, typeof window !== "undefined" ? window.location.origin : "http://localhost");
|
|
559
|
+
const routerData = {
|
|
560
|
+
pathname: url.pathname,
|
|
561
|
+
params: {},
|
|
562
|
+
searchParams: Object.fromEntries(url.searchParams.entries())
|
|
563
|
+
};
|
|
564
|
+
setRouterData(routerData);
|
|
332
565
|
if (notFoundRoute) {
|
|
333
566
|
const components = await notFoundRoute.load();
|
|
334
567
|
setState({
|
|
@@ -366,27 +599,59 @@ async function handleNormalRoute(nextUrl, json, routes, setState) {
|
|
|
366
599
|
} else if (json.theme) {
|
|
367
600
|
theme = json.theme;
|
|
368
601
|
}
|
|
369
|
-
|
|
370
|
-
|
|
602
|
+
let layoutProps = {};
|
|
603
|
+
if (json.layoutProps !== void 0 && json.layoutProps !== null) {
|
|
604
|
+
layoutProps = json.layoutProps;
|
|
605
|
+
setPreservedLayoutProps(layoutProps);
|
|
606
|
+
} else {
|
|
607
|
+
const preserved = getPreservedLayoutProps();
|
|
608
|
+
if (preserved) {
|
|
609
|
+
layoutProps = preserved;
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
const pageProps = json.pageProps ?? json.props ?? {};
|
|
613
|
+
const combinedProps = {
|
|
614
|
+
...layoutProps,
|
|
615
|
+
...pageProps,
|
|
371
616
|
theme
|
|
372
617
|
// Always include theme
|
|
373
618
|
};
|
|
374
|
-
const
|
|
619
|
+
const pathnameForMatch = json.pathname || nextUrl;
|
|
620
|
+
let matched = matchRouteClient(pathnameForMatch, routes);
|
|
375
621
|
if (!matched) {
|
|
622
|
+
matched = matchRouteClient(nextUrl, routes);
|
|
623
|
+
}
|
|
624
|
+
if (!matched) {
|
|
625
|
+
console.warn(
|
|
626
|
+
`[client] Server returned data for ${nextUrl} but no route match found. Available routes:`,
|
|
627
|
+
routes.map((r) => r.pattern)
|
|
628
|
+
);
|
|
376
629
|
window.location.href = nextUrl;
|
|
377
630
|
return false;
|
|
378
631
|
}
|
|
632
|
+
const finalPathname = json.pathname || nextUrl;
|
|
379
633
|
const windowData = {
|
|
380
|
-
pathname:
|
|
634
|
+
pathname: finalPathname,
|
|
381
635
|
params: matched.params,
|
|
382
|
-
props:
|
|
636
|
+
props: combinedProps,
|
|
383
637
|
metadata: json.metadata ?? null,
|
|
384
638
|
theme,
|
|
385
639
|
notFound: false,
|
|
386
640
|
error: false
|
|
387
641
|
};
|
|
388
642
|
setWindowData(windowData);
|
|
389
|
-
const
|
|
643
|
+
const url = new URL(nextUrl, typeof window !== "undefined" ? window.location.origin : "http://localhost");
|
|
644
|
+
const routerData = {
|
|
645
|
+
pathname: url.pathname,
|
|
646
|
+
params: matched.params,
|
|
647
|
+
searchParams: Object.fromEntries(url.searchParams.entries())
|
|
648
|
+
};
|
|
649
|
+
setRouterData(routerData);
|
|
650
|
+
const prefetched = prefetchedRoutes.get(matched.route);
|
|
651
|
+
const components = prefetched ? await prefetched : await matched.route.load();
|
|
652
|
+
if (!prefetched) {
|
|
653
|
+
prefetchedRoutes.set(matched.route, Promise.resolve(components));
|
|
654
|
+
}
|
|
390
655
|
window.scrollTo({
|
|
391
656
|
top: 0,
|
|
392
657
|
behavior: "smooth"
|
|
@@ -396,7 +661,7 @@ async function handleNormalRoute(nextUrl, json, routes, setState) {
|
|
|
396
661
|
route: matched.route,
|
|
397
662
|
params: matched.params,
|
|
398
663
|
components,
|
|
399
|
-
props:
|
|
664
|
+
props: combinedProps
|
|
400
665
|
});
|
|
401
666
|
return true;
|
|
402
667
|
}
|
|
@@ -425,7 +690,7 @@ async function navigate(nextUrl, handlers, options) {
|
|
|
425
690
|
}
|
|
426
691
|
}
|
|
427
692
|
if (!ok) {
|
|
428
|
-
if (json
|
|
693
|
+
if (json?.redirect) {
|
|
429
694
|
window.location.href = json.redirect.destination;
|
|
430
695
|
return;
|
|
431
696
|
}
|
|
@@ -446,6 +711,47 @@ async function navigate(nextUrl, handlers, options) {
|
|
|
446
711
|
window.location.href = nextUrl;
|
|
447
712
|
}
|
|
448
713
|
}
|
|
714
|
+
var prefetchedRoutes = /* @__PURE__ */ new WeakMap();
|
|
715
|
+
function prefetchRoute(url, routes, notFoundRoute) {
|
|
716
|
+
const [pathname] = url.split("?");
|
|
717
|
+
const matched = matchRouteClient(pathname, routes);
|
|
718
|
+
if (!matched) {
|
|
719
|
+
if (notFoundRoute) {
|
|
720
|
+
const existing2 = prefetchedRoutes.get(notFoundRoute);
|
|
721
|
+
if (!existing2) {
|
|
722
|
+
const promise = notFoundRoute.load();
|
|
723
|
+
prefetchedRoutes.set(notFoundRoute, promise);
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
return;
|
|
727
|
+
}
|
|
728
|
+
const existing = prefetchedRoutes.get(matched.route);
|
|
729
|
+
if (!existing) {
|
|
730
|
+
const promise = matched.route.load();
|
|
731
|
+
prefetchedRoutes.set(matched.route, promise);
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
function createHoverHandler(routes, notFoundRoute) {
|
|
735
|
+
return function handleHover(ev) {
|
|
736
|
+
try {
|
|
737
|
+
const target = ev.target;
|
|
738
|
+
if (!target) return;
|
|
739
|
+
const anchor = target.closest("a[href]");
|
|
740
|
+
if (!anchor) return;
|
|
741
|
+
const href = anchor.getAttribute("href");
|
|
742
|
+
if (!href) return;
|
|
743
|
+
if (href.startsWith("#")) return;
|
|
744
|
+
const url = new URL(href, window.location.href);
|
|
745
|
+
if (url.origin !== window.location.origin) return;
|
|
746
|
+
if (anchor.target && anchor.target !== "_self") return;
|
|
747
|
+
const nextUrl = url.pathname + url.search;
|
|
748
|
+
const currentUrl = window.location.pathname + window.location.search;
|
|
749
|
+
if (nextUrl === currentUrl) return;
|
|
750
|
+
prefetchRoute(nextUrl, routes, notFoundRoute);
|
|
751
|
+
} catch (error) {
|
|
752
|
+
}
|
|
753
|
+
};
|
|
754
|
+
}
|
|
449
755
|
function createClickHandler(navigate2) {
|
|
450
756
|
return function handleClick(ev) {
|
|
451
757
|
try {
|
|
@@ -540,6 +846,14 @@ function AppShell({
|
|
|
540
846
|
},
|
|
541
847
|
[]
|
|
542
848
|
);
|
|
849
|
+
useEffect(() => {
|
|
850
|
+
if (typeof window !== "undefined") {
|
|
851
|
+
window[ROUTER_NAVIGATE_KEY] = handleNavigate;
|
|
852
|
+
return () => {
|
|
853
|
+
delete window[ROUTER_NAVIGATE_KEY];
|
|
854
|
+
};
|
|
855
|
+
}
|
|
856
|
+
}, [handleNavigate]);
|
|
543
857
|
useEffect(() => {
|
|
544
858
|
let isMounted = true;
|
|
545
859
|
async function handleNavigateInternal(nextUrl, options) {
|
|
@@ -548,12 +862,37 @@ function AppShell({
|
|
|
548
862
|
}
|
|
549
863
|
const handleClick = createClickHandler(handleNavigateInternal);
|
|
550
864
|
const handlePopState = createPopStateHandler(handleNavigateInternal);
|
|
865
|
+
const handleHover = createHoverHandler(routes, notFoundRoute);
|
|
551
866
|
window.addEventListener("click", handleClick, false);
|
|
552
867
|
window.addEventListener("popstate", handlePopState, false);
|
|
868
|
+
window.addEventListener("mouseover", handleHover, false);
|
|
553
869
|
return () => {
|
|
554
870
|
isMounted = false;
|
|
555
871
|
window.removeEventListener("click", handleClick, false);
|
|
556
872
|
window.removeEventListener("popstate", handlePopState, false);
|
|
873
|
+
window.removeEventListener("mouseover", handleHover, false);
|
|
874
|
+
};
|
|
875
|
+
}, [routes, notFoundRoute]);
|
|
876
|
+
useEffect(() => {
|
|
877
|
+
const handleDataRefresh = () => {
|
|
878
|
+
const freshData = window[WINDOW_DATA_KEY];
|
|
879
|
+
if (!freshData) return;
|
|
880
|
+
const currentPathname = window.location.pathname;
|
|
881
|
+
const freshPathname = freshData.pathname;
|
|
882
|
+
if (freshPathname === currentPathname) {
|
|
883
|
+
if (freshData.metadata !== void 0) {
|
|
884
|
+
applyMetadata(freshData.metadata);
|
|
885
|
+
}
|
|
886
|
+
setState((prevState) => ({
|
|
887
|
+
...prevState,
|
|
888
|
+
props: freshData.props ?? prevState.props,
|
|
889
|
+
params: freshData.params ?? prevState.params
|
|
890
|
+
}));
|
|
891
|
+
}
|
|
892
|
+
};
|
|
893
|
+
window.addEventListener("fw-data-refresh", handleDataRefresh);
|
|
894
|
+
return () => {
|
|
895
|
+
window.removeEventListener("fw-data-refresh", handleDataRefresh);
|
|
557
896
|
};
|
|
558
897
|
}, []);
|
|
559
898
|
const isError = state.route === errorRoute;
|
|
@@ -563,6 +902,89 @@ function AppShell({
|
|
|
563
902
|
return /* @__PURE__ */ jsx2(RouterContext.Provider, { value: { navigate: handleNavigate }, children: /* @__PURE__ */ jsx2(RouterView, { state }, routeKey) });
|
|
564
903
|
}
|
|
565
904
|
|
|
905
|
+
// modules/runtime/client/hot-reload.ts
|
|
906
|
+
function setupHotReload() {
|
|
907
|
+
const nodeEnv = process.env.NODE_ENV || "production";
|
|
908
|
+
const isDev = nodeEnv === "development";
|
|
909
|
+
if (!isDev) {
|
|
910
|
+
return;
|
|
911
|
+
}
|
|
912
|
+
console.log("[hot-reload] Setting up hot reload client...");
|
|
913
|
+
let eventSource = null;
|
|
914
|
+
let reloadTimeout = null;
|
|
915
|
+
let reconnectTimeout = null;
|
|
916
|
+
let reconnectAttempts = 0;
|
|
917
|
+
const MAX_RECONNECT_ATTEMPTS = 10;
|
|
918
|
+
const RECONNECT_DELAY = 1e3;
|
|
919
|
+
const RELOAD_DELAY = 100;
|
|
920
|
+
function connect() {
|
|
921
|
+
try {
|
|
922
|
+
if (eventSource) {
|
|
923
|
+
console.log("[hot-reload] Closing existing EventSource connection");
|
|
924
|
+
eventSource.close();
|
|
925
|
+
}
|
|
926
|
+
const endpoint = "/__fw/hot";
|
|
927
|
+
eventSource = new EventSource(endpoint);
|
|
928
|
+
eventSource.addEventListener("ping", (event) => {
|
|
929
|
+
if ("data" in event) {
|
|
930
|
+
console.log("[hot-reload] \u2705 Connected to hot reload server");
|
|
931
|
+
}
|
|
932
|
+
reconnectAttempts = 0;
|
|
933
|
+
});
|
|
934
|
+
eventSource.addEventListener("message", (event) => {
|
|
935
|
+
const data = event.data;
|
|
936
|
+
if (data && typeof data === "string" && data.startsWith("reload:")) {
|
|
937
|
+
const filePath = data.slice(7);
|
|
938
|
+
console.log(`[hot-reload] \u{1F4DD} File changed: ${filePath}, reloading...`);
|
|
939
|
+
if (reloadTimeout) {
|
|
940
|
+
clearTimeout(reloadTimeout);
|
|
941
|
+
}
|
|
942
|
+
reloadTimeout = setTimeout(() => {
|
|
943
|
+
try {
|
|
944
|
+
window.location.reload();
|
|
945
|
+
} catch (error) {
|
|
946
|
+
console.error("[hot-reload] \u274C Error reloading page:", error);
|
|
947
|
+
setTimeout(() => window.location.reload(), 100);
|
|
948
|
+
}
|
|
949
|
+
}, RELOAD_DELAY);
|
|
950
|
+
}
|
|
951
|
+
});
|
|
952
|
+
eventSource.onopen = () => {
|
|
953
|
+
reconnectAttempts = 0;
|
|
954
|
+
};
|
|
955
|
+
eventSource.onerror = (error) => {
|
|
956
|
+
const states = ["CONNECTING", "OPEN", "CLOSED"];
|
|
957
|
+
const state = states[eventSource?.readyState ?? 0] || "UNKNOWN";
|
|
958
|
+
if (eventSource?.readyState === EventSource.CONNECTING) {
|
|
959
|
+
console.log("[hot-reload] \u23F3 Still connecting...");
|
|
960
|
+
return;
|
|
961
|
+
} else if (eventSource?.readyState === EventSource.OPEN) {
|
|
962
|
+
console.warn("[hot-reload] \u26A0\uFE0F Connection error (but connection is open):", error);
|
|
963
|
+
} else {
|
|
964
|
+
console.warn(`[hot-reload] \u274C Connection closed (readyState: ${state})`);
|
|
965
|
+
if (reconnectAttempts < MAX_RECONNECT_ATTEMPTS) {
|
|
966
|
+
reconnectAttempts++;
|
|
967
|
+
const delay = RECONNECT_DELAY * reconnectAttempts;
|
|
968
|
+
if (reconnectTimeout) {
|
|
969
|
+
clearTimeout(reconnectTimeout);
|
|
970
|
+
}
|
|
971
|
+
reconnectTimeout = setTimeout(() => {
|
|
972
|
+
console.log(`[hot-reload] \u{1F504} Reconnecting... (attempt ${reconnectAttempts}/${MAX_RECONNECT_ATTEMPTS})`);
|
|
973
|
+
connect();
|
|
974
|
+
}, delay);
|
|
975
|
+
} else {
|
|
976
|
+
console.error("[hot-reload] \u274C Max reconnect attempts reached. Please refresh the page manually.");
|
|
977
|
+
}
|
|
978
|
+
}
|
|
979
|
+
};
|
|
980
|
+
} catch (error) {
|
|
981
|
+
console.error("[hot-reload] \u274C Failed to create EventSource:", error);
|
|
982
|
+
console.error("[hot-reload] EventSource may not be supported in this browser.");
|
|
983
|
+
}
|
|
984
|
+
}
|
|
985
|
+
connect();
|
|
986
|
+
}
|
|
987
|
+
|
|
566
988
|
// modules/runtime/client/bootstrap.tsx
|
|
567
989
|
import { jsx as jsx3 } from "react/jsx-runtime";
|
|
568
990
|
async function loadInitialRoute(initialUrl, initialData, routes, notFoundRoute, errorRoute) {
|
|
@@ -604,91 +1026,96 @@ async function loadInitialRoute(initialUrl, initialData, routes, notFoundRoute,
|
|
|
604
1026
|
props: initialData?.props ?? {}
|
|
605
1027
|
};
|
|
606
1028
|
}
|
|
607
|
-
function
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
1029
|
+
function initializeRouterData(initialUrl, initialData) {
|
|
1030
|
+
let routerData = getRouterData();
|
|
1031
|
+
if (!routerData) {
|
|
1032
|
+
const url = new URL(initialUrl, window.location.origin);
|
|
1033
|
+
const pathname = initialData?.pathname || url.pathname;
|
|
1034
|
+
routerData = {
|
|
1035
|
+
pathname,
|
|
1036
|
+
params: initialData?.params || {},
|
|
1037
|
+
searchParams: Object.fromEntries(url.searchParams.entries())
|
|
1038
|
+
};
|
|
1039
|
+
setRouterData(routerData);
|
|
612
1040
|
}
|
|
1041
|
+
}
|
|
1042
|
+
async function hydrateInitialRoute(container, initialUrl, initialData, routes, notFoundRoute, errorRoute) {
|
|
613
1043
|
try {
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
console.log("[hot-reload] Reloading page...");
|
|
627
|
-
window.location.reload();
|
|
628
|
-
}, 500);
|
|
629
|
-
}
|
|
630
|
-
});
|
|
631
|
-
eventSource.addEventListener("ping", () => {
|
|
632
|
-
console.log("[hot-reload] \u2713 Connected to hot reload server");
|
|
633
|
-
});
|
|
634
|
-
eventSource.onopen = () => {
|
|
635
|
-
console.log("[hot-reload] \u2713 SSE connection opened");
|
|
636
|
-
};
|
|
637
|
-
eventSource.onerror = (error) => {
|
|
638
|
-
const states = ["CONNECTING", "OPEN", "CLOSED"];
|
|
639
|
-
const state = states[eventSource.readyState] || "UNKNOWN";
|
|
640
|
-
if (eventSource.readyState === EventSource.CONNECTING) {
|
|
641
|
-
console.log("[hot-reload] Connecting...");
|
|
642
|
-
} else if (eventSource.readyState === EventSource.OPEN) {
|
|
643
|
-
console.warn("[hot-reload] Connection error (but connection is open):", error);
|
|
644
|
-
} else {
|
|
645
|
-
console.log("[hot-reload] Connection closed (readyState:", state, ")");
|
|
1044
|
+
const initialState = await loadInitialRoute(
|
|
1045
|
+
initialUrl,
|
|
1046
|
+
initialData,
|
|
1047
|
+
routes,
|
|
1048
|
+
notFoundRoute,
|
|
1049
|
+
errorRoute
|
|
1050
|
+
);
|
|
1051
|
+
if (initialData?.metadata) {
|
|
1052
|
+
try {
|
|
1053
|
+
applyMetadata(initialData.metadata);
|
|
1054
|
+
} catch (metadataError) {
|
|
1055
|
+
console.warn("[client] Error applying metadata:", metadataError);
|
|
646
1056
|
}
|
|
647
|
-
}
|
|
1057
|
+
}
|
|
1058
|
+
hydrateRoot(
|
|
1059
|
+
container,
|
|
1060
|
+
/* @__PURE__ */ jsx3(
|
|
1061
|
+
AppShell,
|
|
1062
|
+
{
|
|
1063
|
+
initialState,
|
|
1064
|
+
routes,
|
|
1065
|
+
notFoundRoute,
|
|
1066
|
+
errorRoute
|
|
1067
|
+
}
|
|
1068
|
+
)
|
|
1069
|
+
);
|
|
648
1070
|
} catch (error) {
|
|
649
|
-
console.
|
|
1071
|
+
console.error(
|
|
1072
|
+
"[client] Error loading initial route components for",
|
|
1073
|
+
initialUrl,
|
|
1074
|
+
error
|
|
1075
|
+
);
|
|
1076
|
+
throw error;
|
|
650
1077
|
}
|
|
651
1078
|
}
|
|
652
1079
|
function bootstrapClient(routes, notFoundRoute, errorRoute = null) {
|
|
653
|
-
console.log("[client] Bootstrap starting, setting up hot reload...");
|
|
654
1080
|
setupHotReload();
|
|
655
|
-
(async
|
|
656
|
-
const container = document.getElementById(APP_CONTAINER_ID);
|
|
657
|
-
const initialData = getWindowData();
|
|
658
|
-
if (!container) {
|
|
659
|
-
console.error(`Container #${APP_CONTAINER_ID} not found for hydration`);
|
|
660
|
-
return;
|
|
661
|
-
}
|
|
662
|
-
const initialUrl = window.location.pathname + window.location.search;
|
|
1081
|
+
(async () => {
|
|
663
1082
|
try {
|
|
664
|
-
const
|
|
1083
|
+
const container = document.getElementById(APP_CONTAINER_ID);
|
|
1084
|
+
if (!container) {
|
|
1085
|
+
console.error(`
|
|
1086
|
+
\u274C [client] Hydration failed: Container #${APP_CONTAINER_ID} not found`);
|
|
1087
|
+
console.error("\u{1F4A1} This usually means:");
|
|
1088
|
+
console.error(" \u2022 The HTML structure doesn't match what React expects");
|
|
1089
|
+
console.error(" \u2022 The container was removed before hydration");
|
|
1090
|
+
console.error(" \u2022 There's a mismatch between SSR and client HTML\n");
|
|
1091
|
+
return;
|
|
1092
|
+
}
|
|
1093
|
+
const initialData = getWindowData();
|
|
1094
|
+
const initialUrl = (initialData?.pathname || window.location.pathname) + window.location.search;
|
|
1095
|
+
if (initialData?.props) {
|
|
1096
|
+
setPreservedLayoutProps(initialData.props);
|
|
1097
|
+
}
|
|
1098
|
+
const routerPathname = initialData?.pathname || window.location.pathname;
|
|
1099
|
+
initializeRouterData(routerPathname + window.location.search, initialData);
|
|
1100
|
+
await hydrateInitialRoute(
|
|
1101
|
+
container,
|
|
665
1102
|
initialUrl,
|
|
666
1103
|
initialData,
|
|
667
1104
|
routes,
|
|
668
1105
|
notFoundRoute,
|
|
669
1106
|
errorRoute
|
|
670
1107
|
);
|
|
671
|
-
if (initialData?.metadata) {
|
|
672
|
-
applyMetadata(initialData.metadata);
|
|
673
|
-
}
|
|
674
|
-
hydrateRoot(
|
|
675
|
-
container,
|
|
676
|
-
/* @__PURE__ */ jsx3(
|
|
677
|
-
AppShell,
|
|
678
|
-
{
|
|
679
|
-
initialState,
|
|
680
|
-
routes,
|
|
681
|
-
notFoundRoute,
|
|
682
|
-
errorRoute
|
|
683
|
-
}
|
|
684
|
-
)
|
|
685
|
-
);
|
|
686
1108
|
} catch (error) {
|
|
687
|
-
console.error(
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
error
|
|
691
|
-
|
|
1109
|
+
console.error("\n\u274C [client] Fatal error during bootstrap:");
|
|
1110
|
+
console.error(error);
|
|
1111
|
+
if (error instanceof Error) {
|
|
1112
|
+
console.error("\nError details:");
|
|
1113
|
+
console.error(` Message: ${error.message}`);
|
|
1114
|
+
if (error.stack) {
|
|
1115
|
+
console.error(` Stack: ${error.stack}`);
|
|
1116
|
+
}
|
|
1117
|
+
}
|
|
1118
|
+
console.error("\n\u{1F4A1} Attempting page reload to recover...\n");
|
|
692
1119
|
window.location.reload();
|
|
693
1120
|
}
|
|
694
1121
|
})();
|