@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.cjs
CHANGED
|
@@ -29,15 +29,40 @@ var import_client = require("react-dom/client");
|
|
|
29
29
|
|
|
30
30
|
// modules/runtime/client/constants.ts
|
|
31
31
|
var WINDOW_DATA_KEY = "__FW_DATA__";
|
|
32
|
+
var ROUTER_DATA_KEY = "__LOLY_ROUTER_DATA__";
|
|
32
33
|
var APP_CONTAINER_ID = "__app";
|
|
34
|
+
var ROUTER_NAVIGATE_KEY = "__LOLY_ROUTER_NAVIGATE__";
|
|
33
35
|
|
|
34
36
|
// modules/runtime/client/window-data.ts
|
|
37
|
+
var LAYOUT_PROPS_KEY = "__FW_LAYOUT_PROPS__";
|
|
35
38
|
function getWindowData() {
|
|
36
39
|
if (typeof window === "undefined") {
|
|
37
40
|
return null;
|
|
38
41
|
}
|
|
39
42
|
return window[WINDOW_DATA_KEY] ?? null;
|
|
40
43
|
}
|
|
44
|
+
function getPreservedLayoutProps() {
|
|
45
|
+
if (typeof window === "undefined") {
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
return window[LAYOUT_PROPS_KEY] ?? null;
|
|
49
|
+
}
|
|
50
|
+
function setPreservedLayoutProps(props) {
|
|
51
|
+
if (typeof window === "undefined") {
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
if (props === null) {
|
|
55
|
+
delete window[LAYOUT_PROPS_KEY];
|
|
56
|
+
} else {
|
|
57
|
+
window[LAYOUT_PROPS_KEY] = props;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
function getRouterData() {
|
|
61
|
+
if (typeof window === "undefined") {
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
return window[ROUTER_DATA_KEY] ?? null;
|
|
65
|
+
}
|
|
41
66
|
function setWindowData(data) {
|
|
42
67
|
window[WINDOW_DATA_KEY] = data;
|
|
43
68
|
if (typeof window !== "undefined") {
|
|
@@ -48,6 +73,16 @@ function setWindowData(data) {
|
|
|
48
73
|
);
|
|
49
74
|
}
|
|
50
75
|
}
|
|
76
|
+
function setRouterData(data) {
|
|
77
|
+
window[ROUTER_DATA_KEY] = data;
|
|
78
|
+
if (typeof window !== "undefined") {
|
|
79
|
+
window.dispatchEvent(
|
|
80
|
+
new CustomEvent("fw-router-data-refresh", {
|
|
81
|
+
detail: { data }
|
|
82
|
+
})
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
51
86
|
function getCurrentTheme() {
|
|
52
87
|
return getWindowData()?.theme ?? null;
|
|
53
88
|
}
|
|
@@ -93,22 +128,158 @@ function matchRouteClient(pathWithSearch, routes) {
|
|
|
93
128
|
}
|
|
94
129
|
|
|
95
130
|
// modules/runtime/client/metadata.ts
|
|
131
|
+
function getOrCreateMeta(selector, attributes) {
|
|
132
|
+
let meta = document.querySelector(selector);
|
|
133
|
+
if (!meta) {
|
|
134
|
+
meta = document.createElement("meta");
|
|
135
|
+
if (attributes.name) meta.name = attributes.name;
|
|
136
|
+
if (attributes.property) meta.setAttribute("property", attributes.property);
|
|
137
|
+
if (attributes.httpEquiv) meta.httpEquiv = attributes.httpEquiv;
|
|
138
|
+
document.head.appendChild(meta);
|
|
139
|
+
}
|
|
140
|
+
return meta;
|
|
141
|
+
}
|
|
142
|
+
function getOrCreateLink(rel, href) {
|
|
143
|
+
const selector = `link[rel="${rel}"]`;
|
|
144
|
+
let link = document.querySelector(selector);
|
|
145
|
+
if (!link) {
|
|
146
|
+
link = document.createElement("link");
|
|
147
|
+
link.rel = rel;
|
|
148
|
+
link.href = href;
|
|
149
|
+
document.head.appendChild(link);
|
|
150
|
+
} else {
|
|
151
|
+
link.href = href;
|
|
152
|
+
}
|
|
153
|
+
return link;
|
|
154
|
+
}
|
|
96
155
|
function applyMetadata(md) {
|
|
97
156
|
if (!md) return;
|
|
98
157
|
if (md.title) {
|
|
99
158
|
document.title = md.title;
|
|
100
159
|
}
|
|
101
160
|
if (md.description) {
|
|
102
|
-
|
|
103
|
-
'meta[name="description"]'
|
|
104
|
-
);
|
|
105
|
-
if (!meta) {
|
|
106
|
-
meta = document.createElement("meta");
|
|
107
|
-
meta.name = "description";
|
|
108
|
-
document.head.appendChild(meta);
|
|
109
|
-
}
|
|
161
|
+
const meta = getOrCreateMeta('meta[name="description"]', { name: "description" });
|
|
110
162
|
meta.content = md.description;
|
|
111
163
|
}
|
|
164
|
+
if (md.robots) {
|
|
165
|
+
const meta = getOrCreateMeta('meta[name="robots"]', { name: "robots" });
|
|
166
|
+
meta.content = md.robots;
|
|
167
|
+
}
|
|
168
|
+
if (md.themeColor) {
|
|
169
|
+
const meta = getOrCreateMeta('meta[name="theme-color"]', { name: "theme-color" });
|
|
170
|
+
meta.content = md.themeColor;
|
|
171
|
+
}
|
|
172
|
+
if (md.viewport) {
|
|
173
|
+
const meta = getOrCreateMeta('meta[name="viewport"]', { name: "viewport" });
|
|
174
|
+
meta.content = md.viewport;
|
|
175
|
+
}
|
|
176
|
+
if (md.canonical) {
|
|
177
|
+
getOrCreateLink("canonical", md.canonical);
|
|
178
|
+
}
|
|
179
|
+
if (md.openGraph) {
|
|
180
|
+
const og = md.openGraph;
|
|
181
|
+
if (og.title) {
|
|
182
|
+
const meta = getOrCreateMeta('meta[property="og:title"]', { property: "og:title" });
|
|
183
|
+
meta.content = og.title;
|
|
184
|
+
}
|
|
185
|
+
if (og.description) {
|
|
186
|
+
const meta = getOrCreateMeta('meta[property="og:description"]', { property: "og:description" });
|
|
187
|
+
meta.content = og.description;
|
|
188
|
+
}
|
|
189
|
+
if (og.type) {
|
|
190
|
+
const meta = getOrCreateMeta('meta[property="og:type"]', { property: "og:type" });
|
|
191
|
+
meta.content = og.type;
|
|
192
|
+
}
|
|
193
|
+
if (og.url) {
|
|
194
|
+
const meta = getOrCreateMeta('meta[property="og:url"]', { property: "og:url" });
|
|
195
|
+
meta.content = og.url;
|
|
196
|
+
}
|
|
197
|
+
if (og.image) {
|
|
198
|
+
if (typeof og.image === "string") {
|
|
199
|
+
const meta = getOrCreateMeta('meta[property="og:image"]', { property: "og:image" });
|
|
200
|
+
meta.content = og.image;
|
|
201
|
+
} else {
|
|
202
|
+
const meta = getOrCreateMeta('meta[property="og:image"]', { property: "og:image" });
|
|
203
|
+
meta.content = og.image.url;
|
|
204
|
+
if (og.image.width) {
|
|
205
|
+
const metaWidth = getOrCreateMeta('meta[property="og:image:width"]', { property: "og:image:width" });
|
|
206
|
+
metaWidth.content = String(og.image.width);
|
|
207
|
+
}
|
|
208
|
+
if (og.image.height) {
|
|
209
|
+
const metaHeight = getOrCreateMeta('meta[property="og:image:height"]', { property: "og:image:height" });
|
|
210
|
+
metaHeight.content = String(og.image.height);
|
|
211
|
+
}
|
|
212
|
+
if (og.image.alt) {
|
|
213
|
+
const metaAlt = getOrCreateMeta('meta[property="og:image:alt"]', { property: "og:image:alt" });
|
|
214
|
+
metaAlt.content = og.image.alt;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
if (og.siteName) {
|
|
219
|
+
const meta = getOrCreateMeta('meta[property="og:site_name"]', { property: "og:site_name" });
|
|
220
|
+
meta.content = og.siteName;
|
|
221
|
+
}
|
|
222
|
+
if (og.locale) {
|
|
223
|
+
const meta = getOrCreateMeta('meta[property="og:locale"]', { property: "og:locale" });
|
|
224
|
+
meta.content = og.locale;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
if (md.twitter) {
|
|
228
|
+
const twitter = md.twitter;
|
|
229
|
+
if (twitter.card) {
|
|
230
|
+
const meta = getOrCreateMeta('meta[name="twitter:card"]', { name: "twitter:card" });
|
|
231
|
+
meta.content = twitter.card;
|
|
232
|
+
}
|
|
233
|
+
if (twitter.title) {
|
|
234
|
+
const meta = getOrCreateMeta('meta[name="twitter:title"]', { name: "twitter:title" });
|
|
235
|
+
meta.content = twitter.title;
|
|
236
|
+
}
|
|
237
|
+
if (twitter.description) {
|
|
238
|
+
const meta = getOrCreateMeta('meta[name="twitter:description"]', { name: "twitter:description" });
|
|
239
|
+
meta.content = twitter.description;
|
|
240
|
+
}
|
|
241
|
+
if (twitter.image) {
|
|
242
|
+
const meta = getOrCreateMeta('meta[name="twitter:image"]', { name: "twitter:image" });
|
|
243
|
+
meta.content = twitter.image;
|
|
244
|
+
}
|
|
245
|
+
if (twitter.imageAlt) {
|
|
246
|
+
const meta = getOrCreateMeta('meta[name="twitter:image:alt"]', { name: "twitter:image:alt" });
|
|
247
|
+
meta.content = twitter.imageAlt;
|
|
248
|
+
}
|
|
249
|
+
if (twitter.site) {
|
|
250
|
+
const meta = getOrCreateMeta('meta[name="twitter:site"]', { name: "twitter:site" });
|
|
251
|
+
meta.content = twitter.site;
|
|
252
|
+
}
|
|
253
|
+
if (twitter.creator) {
|
|
254
|
+
const meta = getOrCreateMeta('meta[name="twitter:creator"]', { name: "twitter:creator" });
|
|
255
|
+
meta.content = twitter.creator;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
if (md.metaTags && Array.isArray(md.metaTags)) {
|
|
259
|
+
md.metaTags.forEach((tag) => {
|
|
260
|
+
let selector = "";
|
|
261
|
+
if (tag.name) {
|
|
262
|
+
selector = `meta[name="${tag.name}"]`;
|
|
263
|
+
} else if (tag.property) {
|
|
264
|
+
selector = `meta[property="${tag.property}"]`;
|
|
265
|
+
} else if (tag.httpEquiv) {
|
|
266
|
+
selector = `meta[http-equiv="${tag.httpEquiv}"]`;
|
|
267
|
+
}
|
|
268
|
+
if (selector) {
|
|
269
|
+
const meta = getOrCreateMeta(selector, {
|
|
270
|
+
name: tag.name,
|
|
271
|
+
property: tag.property,
|
|
272
|
+
httpEquiv: tag.httpEquiv
|
|
273
|
+
});
|
|
274
|
+
meta.content = tag.content;
|
|
275
|
+
}
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
if (md.links && Array.isArray(md.links)) {
|
|
279
|
+
md.links.forEach((link) => {
|
|
280
|
+
getOrCreateLink(link.rel, link.href);
|
|
281
|
+
});
|
|
282
|
+
}
|
|
112
283
|
}
|
|
113
284
|
|
|
114
285
|
// modules/runtime/client/AppShell.tsx
|
|
@@ -221,14 +392,16 @@ function deleteCacheEntry(key) {
|
|
|
221
392
|
function buildDataUrl(url) {
|
|
222
393
|
return url + (url.includes("?") ? "&" : "?") + "__fw_data=1";
|
|
223
394
|
}
|
|
224
|
-
async function fetchRouteDataOnce(url) {
|
|
395
|
+
async function fetchRouteDataOnce(url, skipLayoutHooks = true) {
|
|
225
396
|
const dataUrl = buildDataUrl(url);
|
|
226
|
-
const
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
397
|
+
const headers = {
|
|
398
|
+
"x-fw-data": "1",
|
|
399
|
+
Accept: "application/json"
|
|
400
|
+
};
|
|
401
|
+
if (skipLayoutHooks) {
|
|
402
|
+
headers["x-skip-layout-hooks"] = "true";
|
|
403
|
+
}
|
|
404
|
+
const res = await fetch(dataUrl, { headers });
|
|
232
405
|
let json = {};
|
|
233
406
|
try {
|
|
234
407
|
const text = await res.text();
|
|
@@ -254,7 +427,7 @@ async function getRouteData(url, options) {
|
|
|
254
427
|
deleteCacheEntry(key);
|
|
255
428
|
}
|
|
256
429
|
const entry = dataCache.get(key);
|
|
257
|
-
if (entry) {
|
|
430
|
+
if (entry && !options?.revalidate) {
|
|
258
431
|
if (entry.status === "fulfilled") {
|
|
259
432
|
updateLRU(key);
|
|
260
433
|
return entry.value;
|
|
@@ -263,12 +436,29 @@ async function getRouteData(url, options) {
|
|
|
263
436
|
return entry.promise;
|
|
264
437
|
}
|
|
265
438
|
}
|
|
266
|
-
const
|
|
267
|
-
|
|
439
|
+
const skipLayoutHooks = !options?.revalidate;
|
|
440
|
+
const currentEntry = dataCache.get(key);
|
|
441
|
+
if (currentEntry && !options?.revalidate) {
|
|
442
|
+
if (currentEntry.status === "fulfilled") {
|
|
443
|
+
updateLRU(key);
|
|
444
|
+
return currentEntry.value;
|
|
445
|
+
}
|
|
446
|
+
if (currentEntry.status === "pending") {
|
|
447
|
+
return currentEntry.promise;
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
const promise = fetchRouteDataOnce(url, skipLayoutHooks).then((value) => {
|
|
451
|
+
const entryAfterFetch = dataCache.get(key);
|
|
452
|
+
if (!entryAfterFetch || entryAfterFetch.status === "pending") {
|
|
453
|
+
setCacheEntry(key, { status: "fulfilled", value });
|
|
454
|
+
}
|
|
268
455
|
return value;
|
|
269
456
|
}).catch((error) => {
|
|
270
457
|
console.error("[client][cache] Error fetching route data:", error);
|
|
271
|
-
dataCache.
|
|
458
|
+
const entryAfterFetch = dataCache.get(key);
|
|
459
|
+
if (!entryAfterFetch || entryAfterFetch.status === "pending") {
|
|
460
|
+
dataCache.set(key, { status: "rejected", error });
|
|
461
|
+
}
|
|
272
462
|
throw error;
|
|
273
463
|
});
|
|
274
464
|
dataCache.set(key, { status: "pending", promise });
|
|
@@ -293,10 +483,22 @@ async function handleErrorRoute(nextUrl, json, errorRoute, setState) {
|
|
|
293
483
|
} else if (json.theme) {
|
|
294
484
|
theme = json.theme;
|
|
295
485
|
}
|
|
486
|
+
let layoutProps = {};
|
|
487
|
+
if (json.layoutProps !== void 0 && json.layoutProps !== null) {
|
|
488
|
+
layoutProps = json.layoutProps;
|
|
489
|
+
setPreservedLayoutProps(layoutProps);
|
|
490
|
+
} else {
|
|
491
|
+
const preserved = getPreservedLayoutProps();
|
|
492
|
+
if (preserved) {
|
|
493
|
+
layoutProps = preserved;
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
const pageProps = json.pageProps ?? json.props ?? {
|
|
497
|
+
error: json.message || "An error occurred"
|
|
498
|
+
};
|
|
296
499
|
const errorProps = {
|
|
297
|
-
...
|
|
298
|
-
|
|
299
|
-
},
|
|
500
|
+
...layoutProps,
|
|
501
|
+
...pageProps,
|
|
300
502
|
theme
|
|
301
503
|
};
|
|
302
504
|
const windowData = {
|
|
@@ -309,6 +511,13 @@ async function handleErrorRoute(nextUrl, json, errorRoute, setState) {
|
|
|
309
511
|
error: true
|
|
310
512
|
};
|
|
311
513
|
setWindowData(windowData);
|
|
514
|
+
const url = new URL(nextUrl, typeof window !== "undefined" ? window.location.origin : "http://localhost");
|
|
515
|
+
const routerData = {
|
|
516
|
+
pathname: url.pathname,
|
|
517
|
+
params: json.params || {},
|
|
518
|
+
searchParams: Object.fromEntries(url.searchParams.entries())
|
|
519
|
+
};
|
|
520
|
+
setRouterData(routerData);
|
|
312
521
|
setState({
|
|
313
522
|
url: nextUrl,
|
|
314
523
|
route: errorRoute,
|
|
@@ -318,10 +527,15 @@ async function handleErrorRoute(nextUrl, json, errorRoute, setState) {
|
|
|
318
527
|
});
|
|
319
528
|
return true;
|
|
320
529
|
} catch (loadError) {
|
|
321
|
-
console.error(
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
530
|
+
console.error("\n\u274C [client] Error loading error route components:");
|
|
531
|
+
console.error(loadError);
|
|
532
|
+
if (loadError instanceof Error) {
|
|
533
|
+
console.error(` Message: ${loadError.message}`);
|
|
534
|
+
if (loadError.stack) {
|
|
535
|
+
console.error(` Stack: ${loadError.stack.split("\n").slice(0, 3).join("\n ")}`);
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
console.error("\u{1F4A1} Falling back to full page reload\n");
|
|
325
539
|
window.location.href = nextUrl;
|
|
326
540
|
return false;
|
|
327
541
|
}
|
|
@@ -341,8 +555,20 @@ async function handleNotFoundRoute(nextUrl, json, notFoundRoute, setState) {
|
|
|
341
555
|
} else if (json.theme) {
|
|
342
556
|
theme = json.theme;
|
|
343
557
|
}
|
|
558
|
+
let layoutProps = {};
|
|
559
|
+
if (json.layoutProps !== void 0 && json.layoutProps !== null) {
|
|
560
|
+
layoutProps = json.layoutProps;
|
|
561
|
+
setPreservedLayoutProps(layoutProps);
|
|
562
|
+
} else {
|
|
563
|
+
const preserved = getPreservedLayoutProps();
|
|
564
|
+
if (preserved) {
|
|
565
|
+
layoutProps = preserved;
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
const pageProps = json.pageProps ?? json.props ?? {};
|
|
344
569
|
const notFoundProps = {
|
|
345
|
-
...
|
|
570
|
+
...layoutProps,
|
|
571
|
+
...pageProps,
|
|
346
572
|
theme
|
|
347
573
|
};
|
|
348
574
|
const windowData = {
|
|
@@ -355,6 +581,13 @@ async function handleNotFoundRoute(nextUrl, json, notFoundRoute, setState) {
|
|
|
355
581
|
error: false
|
|
356
582
|
};
|
|
357
583
|
setWindowData(windowData);
|
|
584
|
+
const url = new URL(nextUrl, typeof window !== "undefined" ? window.location.origin : "http://localhost");
|
|
585
|
+
const routerData = {
|
|
586
|
+
pathname: url.pathname,
|
|
587
|
+
params: {},
|
|
588
|
+
searchParams: Object.fromEntries(url.searchParams.entries())
|
|
589
|
+
};
|
|
590
|
+
setRouterData(routerData);
|
|
358
591
|
if (notFoundRoute) {
|
|
359
592
|
const components = await notFoundRoute.load();
|
|
360
593
|
setState({
|
|
@@ -392,27 +625,59 @@ async function handleNormalRoute(nextUrl, json, routes, setState) {
|
|
|
392
625
|
} else if (json.theme) {
|
|
393
626
|
theme = json.theme;
|
|
394
627
|
}
|
|
395
|
-
|
|
396
|
-
|
|
628
|
+
let layoutProps = {};
|
|
629
|
+
if (json.layoutProps !== void 0 && json.layoutProps !== null) {
|
|
630
|
+
layoutProps = json.layoutProps;
|
|
631
|
+
setPreservedLayoutProps(layoutProps);
|
|
632
|
+
} else {
|
|
633
|
+
const preserved = getPreservedLayoutProps();
|
|
634
|
+
if (preserved) {
|
|
635
|
+
layoutProps = preserved;
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
const pageProps = json.pageProps ?? json.props ?? {};
|
|
639
|
+
const combinedProps = {
|
|
640
|
+
...layoutProps,
|
|
641
|
+
...pageProps,
|
|
397
642
|
theme
|
|
398
643
|
// Always include theme
|
|
399
644
|
};
|
|
400
|
-
const
|
|
645
|
+
const pathnameForMatch = json.pathname || nextUrl;
|
|
646
|
+
let matched = matchRouteClient(pathnameForMatch, routes);
|
|
401
647
|
if (!matched) {
|
|
648
|
+
matched = matchRouteClient(nextUrl, routes);
|
|
649
|
+
}
|
|
650
|
+
if (!matched) {
|
|
651
|
+
console.warn(
|
|
652
|
+
`[client] Server returned data for ${nextUrl} but no route match found. Available routes:`,
|
|
653
|
+
routes.map((r) => r.pattern)
|
|
654
|
+
);
|
|
402
655
|
window.location.href = nextUrl;
|
|
403
656
|
return false;
|
|
404
657
|
}
|
|
658
|
+
const finalPathname = json.pathname || nextUrl;
|
|
405
659
|
const windowData = {
|
|
406
|
-
pathname:
|
|
660
|
+
pathname: finalPathname,
|
|
407
661
|
params: matched.params,
|
|
408
|
-
props:
|
|
662
|
+
props: combinedProps,
|
|
409
663
|
metadata: json.metadata ?? null,
|
|
410
664
|
theme,
|
|
411
665
|
notFound: false,
|
|
412
666
|
error: false
|
|
413
667
|
};
|
|
414
668
|
setWindowData(windowData);
|
|
415
|
-
const
|
|
669
|
+
const url = new URL(nextUrl, typeof window !== "undefined" ? window.location.origin : "http://localhost");
|
|
670
|
+
const routerData = {
|
|
671
|
+
pathname: url.pathname,
|
|
672
|
+
params: matched.params,
|
|
673
|
+
searchParams: Object.fromEntries(url.searchParams.entries())
|
|
674
|
+
};
|
|
675
|
+
setRouterData(routerData);
|
|
676
|
+
const prefetched = prefetchedRoutes.get(matched.route);
|
|
677
|
+
const components = prefetched ? await prefetched : await matched.route.load();
|
|
678
|
+
if (!prefetched) {
|
|
679
|
+
prefetchedRoutes.set(matched.route, Promise.resolve(components));
|
|
680
|
+
}
|
|
416
681
|
window.scrollTo({
|
|
417
682
|
top: 0,
|
|
418
683
|
behavior: "smooth"
|
|
@@ -422,7 +687,7 @@ async function handleNormalRoute(nextUrl, json, routes, setState) {
|
|
|
422
687
|
route: matched.route,
|
|
423
688
|
params: matched.params,
|
|
424
689
|
components,
|
|
425
|
-
props:
|
|
690
|
+
props: combinedProps
|
|
426
691
|
});
|
|
427
692
|
return true;
|
|
428
693
|
}
|
|
@@ -451,7 +716,7 @@ async function navigate(nextUrl, handlers, options) {
|
|
|
451
716
|
}
|
|
452
717
|
}
|
|
453
718
|
if (!ok) {
|
|
454
|
-
if (json
|
|
719
|
+
if (json?.redirect) {
|
|
455
720
|
window.location.href = json.redirect.destination;
|
|
456
721
|
return;
|
|
457
722
|
}
|
|
@@ -472,6 +737,47 @@ async function navigate(nextUrl, handlers, options) {
|
|
|
472
737
|
window.location.href = nextUrl;
|
|
473
738
|
}
|
|
474
739
|
}
|
|
740
|
+
var prefetchedRoutes = /* @__PURE__ */ new WeakMap();
|
|
741
|
+
function prefetchRoute(url, routes, notFoundRoute) {
|
|
742
|
+
const [pathname] = url.split("?");
|
|
743
|
+
const matched = matchRouteClient(pathname, routes);
|
|
744
|
+
if (!matched) {
|
|
745
|
+
if (notFoundRoute) {
|
|
746
|
+
const existing2 = prefetchedRoutes.get(notFoundRoute);
|
|
747
|
+
if (!existing2) {
|
|
748
|
+
const promise = notFoundRoute.load();
|
|
749
|
+
prefetchedRoutes.set(notFoundRoute, promise);
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
return;
|
|
753
|
+
}
|
|
754
|
+
const existing = prefetchedRoutes.get(matched.route);
|
|
755
|
+
if (!existing) {
|
|
756
|
+
const promise = matched.route.load();
|
|
757
|
+
prefetchedRoutes.set(matched.route, promise);
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
function createHoverHandler(routes, notFoundRoute) {
|
|
761
|
+
return function handleHover(ev) {
|
|
762
|
+
try {
|
|
763
|
+
const target = ev.target;
|
|
764
|
+
if (!target) return;
|
|
765
|
+
const anchor = target.closest("a[href]");
|
|
766
|
+
if (!anchor) return;
|
|
767
|
+
const href = anchor.getAttribute("href");
|
|
768
|
+
if (!href) return;
|
|
769
|
+
if (href.startsWith("#")) return;
|
|
770
|
+
const url = new URL(href, window.location.href);
|
|
771
|
+
if (url.origin !== window.location.origin) return;
|
|
772
|
+
if (anchor.target && anchor.target !== "_self") return;
|
|
773
|
+
const nextUrl = url.pathname + url.search;
|
|
774
|
+
const currentUrl = window.location.pathname + window.location.search;
|
|
775
|
+
if (nextUrl === currentUrl) return;
|
|
776
|
+
prefetchRoute(nextUrl, routes, notFoundRoute);
|
|
777
|
+
} catch (error) {
|
|
778
|
+
}
|
|
779
|
+
};
|
|
780
|
+
}
|
|
475
781
|
function createClickHandler(navigate2) {
|
|
476
782
|
return function handleClick(ev) {
|
|
477
783
|
try {
|
|
@@ -566,6 +872,14 @@ function AppShell({
|
|
|
566
872
|
},
|
|
567
873
|
[]
|
|
568
874
|
);
|
|
875
|
+
(0, import_react2.useEffect)(() => {
|
|
876
|
+
if (typeof window !== "undefined") {
|
|
877
|
+
window[ROUTER_NAVIGATE_KEY] = handleNavigate;
|
|
878
|
+
return () => {
|
|
879
|
+
delete window[ROUTER_NAVIGATE_KEY];
|
|
880
|
+
};
|
|
881
|
+
}
|
|
882
|
+
}, [handleNavigate]);
|
|
569
883
|
(0, import_react2.useEffect)(() => {
|
|
570
884
|
let isMounted = true;
|
|
571
885
|
async function handleNavigateInternal(nextUrl, options) {
|
|
@@ -574,12 +888,37 @@ function AppShell({
|
|
|
574
888
|
}
|
|
575
889
|
const handleClick = createClickHandler(handleNavigateInternal);
|
|
576
890
|
const handlePopState = createPopStateHandler(handleNavigateInternal);
|
|
891
|
+
const handleHover = createHoverHandler(routes, notFoundRoute);
|
|
577
892
|
window.addEventListener("click", handleClick, false);
|
|
578
893
|
window.addEventListener("popstate", handlePopState, false);
|
|
894
|
+
window.addEventListener("mouseover", handleHover, false);
|
|
579
895
|
return () => {
|
|
580
896
|
isMounted = false;
|
|
581
897
|
window.removeEventListener("click", handleClick, false);
|
|
582
898
|
window.removeEventListener("popstate", handlePopState, false);
|
|
899
|
+
window.removeEventListener("mouseover", handleHover, false);
|
|
900
|
+
};
|
|
901
|
+
}, [routes, notFoundRoute]);
|
|
902
|
+
(0, import_react2.useEffect)(() => {
|
|
903
|
+
const handleDataRefresh = () => {
|
|
904
|
+
const freshData = window[WINDOW_DATA_KEY];
|
|
905
|
+
if (!freshData) return;
|
|
906
|
+
const currentPathname = window.location.pathname;
|
|
907
|
+
const freshPathname = freshData.pathname;
|
|
908
|
+
if (freshPathname === currentPathname) {
|
|
909
|
+
if (freshData.metadata !== void 0) {
|
|
910
|
+
applyMetadata(freshData.metadata);
|
|
911
|
+
}
|
|
912
|
+
setState((prevState) => ({
|
|
913
|
+
...prevState,
|
|
914
|
+
props: freshData.props ?? prevState.props,
|
|
915
|
+
params: freshData.params ?? prevState.params
|
|
916
|
+
}));
|
|
917
|
+
}
|
|
918
|
+
};
|
|
919
|
+
window.addEventListener("fw-data-refresh", handleDataRefresh);
|
|
920
|
+
return () => {
|
|
921
|
+
window.removeEventListener("fw-data-refresh", handleDataRefresh);
|
|
583
922
|
};
|
|
584
923
|
}, []);
|
|
585
924
|
const isError = state.route === errorRoute;
|
|
@@ -589,6 +928,89 @@ function AppShell({
|
|
|
589
928
|
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(RouterContext.Provider, { value: { navigate: handleNavigate }, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(RouterView, { state }, routeKey) });
|
|
590
929
|
}
|
|
591
930
|
|
|
931
|
+
// modules/runtime/client/hot-reload.ts
|
|
932
|
+
function setupHotReload() {
|
|
933
|
+
const nodeEnv = process.env.NODE_ENV || "production";
|
|
934
|
+
const isDev = nodeEnv === "development";
|
|
935
|
+
if (!isDev) {
|
|
936
|
+
return;
|
|
937
|
+
}
|
|
938
|
+
console.log("[hot-reload] Setting up hot reload client...");
|
|
939
|
+
let eventSource = null;
|
|
940
|
+
let reloadTimeout = null;
|
|
941
|
+
let reconnectTimeout = null;
|
|
942
|
+
let reconnectAttempts = 0;
|
|
943
|
+
const MAX_RECONNECT_ATTEMPTS = 10;
|
|
944
|
+
const RECONNECT_DELAY = 1e3;
|
|
945
|
+
const RELOAD_DELAY = 100;
|
|
946
|
+
function connect() {
|
|
947
|
+
try {
|
|
948
|
+
if (eventSource) {
|
|
949
|
+
console.log("[hot-reload] Closing existing EventSource connection");
|
|
950
|
+
eventSource.close();
|
|
951
|
+
}
|
|
952
|
+
const endpoint = "/__fw/hot";
|
|
953
|
+
eventSource = new EventSource(endpoint);
|
|
954
|
+
eventSource.addEventListener("ping", (event) => {
|
|
955
|
+
if ("data" in event) {
|
|
956
|
+
console.log("[hot-reload] \u2705 Connected to hot reload server");
|
|
957
|
+
}
|
|
958
|
+
reconnectAttempts = 0;
|
|
959
|
+
});
|
|
960
|
+
eventSource.addEventListener("message", (event) => {
|
|
961
|
+
const data = event.data;
|
|
962
|
+
if (data && typeof data === "string" && data.startsWith("reload:")) {
|
|
963
|
+
const filePath = data.slice(7);
|
|
964
|
+
console.log(`[hot-reload] \u{1F4DD} File changed: ${filePath}, reloading...`);
|
|
965
|
+
if (reloadTimeout) {
|
|
966
|
+
clearTimeout(reloadTimeout);
|
|
967
|
+
}
|
|
968
|
+
reloadTimeout = setTimeout(() => {
|
|
969
|
+
try {
|
|
970
|
+
window.location.reload();
|
|
971
|
+
} catch (error) {
|
|
972
|
+
console.error("[hot-reload] \u274C Error reloading page:", error);
|
|
973
|
+
setTimeout(() => window.location.reload(), 100);
|
|
974
|
+
}
|
|
975
|
+
}, RELOAD_DELAY);
|
|
976
|
+
}
|
|
977
|
+
});
|
|
978
|
+
eventSource.onopen = () => {
|
|
979
|
+
reconnectAttempts = 0;
|
|
980
|
+
};
|
|
981
|
+
eventSource.onerror = (error) => {
|
|
982
|
+
const states = ["CONNECTING", "OPEN", "CLOSED"];
|
|
983
|
+
const state = states[eventSource?.readyState ?? 0] || "UNKNOWN";
|
|
984
|
+
if (eventSource?.readyState === EventSource.CONNECTING) {
|
|
985
|
+
console.log("[hot-reload] \u23F3 Still connecting...");
|
|
986
|
+
return;
|
|
987
|
+
} else if (eventSource?.readyState === EventSource.OPEN) {
|
|
988
|
+
console.warn("[hot-reload] \u26A0\uFE0F Connection error (but connection is open):", error);
|
|
989
|
+
} else {
|
|
990
|
+
console.warn(`[hot-reload] \u274C Connection closed (readyState: ${state})`);
|
|
991
|
+
if (reconnectAttempts < MAX_RECONNECT_ATTEMPTS) {
|
|
992
|
+
reconnectAttempts++;
|
|
993
|
+
const delay = RECONNECT_DELAY * reconnectAttempts;
|
|
994
|
+
if (reconnectTimeout) {
|
|
995
|
+
clearTimeout(reconnectTimeout);
|
|
996
|
+
}
|
|
997
|
+
reconnectTimeout = setTimeout(() => {
|
|
998
|
+
console.log(`[hot-reload] \u{1F504} Reconnecting... (attempt ${reconnectAttempts}/${MAX_RECONNECT_ATTEMPTS})`);
|
|
999
|
+
connect();
|
|
1000
|
+
}, delay);
|
|
1001
|
+
} else {
|
|
1002
|
+
console.error("[hot-reload] \u274C Max reconnect attempts reached. Please refresh the page manually.");
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
1005
|
+
};
|
|
1006
|
+
} catch (error) {
|
|
1007
|
+
console.error("[hot-reload] \u274C Failed to create EventSource:", error);
|
|
1008
|
+
console.error("[hot-reload] EventSource may not be supported in this browser.");
|
|
1009
|
+
}
|
|
1010
|
+
}
|
|
1011
|
+
connect();
|
|
1012
|
+
}
|
|
1013
|
+
|
|
592
1014
|
// modules/runtime/client/bootstrap.tsx
|
|
593
1015
|
var import_jsx_runtime3 = require("react/jsx-runtime");
|
|
594
1016
|
async function loadInitialRoute(initialUrl, initialData, routes, notFoundRoute, errorRoute) {
|
|
@@ -630,91 +1052,96 @@ async function loadInitialRoute(initialUrl, initialData, routes, notFoundRoute,
|
|
|
630
1052
|
props: initialData?.props ?? {}
|
|
631
1053
|
};
|
|
632
1054
|
}
|
|
633
|
-
function
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
1055
|
+
function initializeRouterData(initialUrl, initialData) {
|
|
1056
|
+
let routerData = getRouterData();
|
|
1057
|
+
if (!routerData) {
|
|
1058
|
+
const url = new URL(initialUrl, window.location.origin);
|
|
1059
|
+
const pathname = initialData?.pathname || url.pathname;
|
|
1060
|
+
routerData = {
|
|
1061
|
+
pathname,
|
|
1062
|
+
params: initialData?.params || {},
|
|
1063
|
+
searchParams: Object.fromEntries(url.searchParams.entries())
|
|
1064
|
+
};
|
|
1065
|
+
setRouterData(routerData);
|
|
638
1066
|
}
|
|
1067
|
+
}
|
|
1068
|
+
async function hydrateInitialRoute(container, initialUrl, initialData, routes, notFoundRoute, errorRoute) {
|
|
639
1069
|
try {
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
console.log("[hot-reload] Reloading page...");
|
|
653
|
-
window.location.reload();
|
|
654
|
-
}, 500);
|
|
655
|
-
}
|
|
656
|
-
});
|
|
657
|
-
eventSource.addEventListener("ping", () => {
|
|
658
|
-
console.log("[hot-reload] \u2713 Connected to hot reload server");
|
|
659
|
-
});
|
|
660
|
-
eventSource.onopen = () => {
|
|
661
|
-
console.log("[hot-reload] \u2713 SSE connection opened");
|
|
662
|
-
};
|
|
663
|
-
eventSource.onerror = (error) => {
|
|
664
|
-
const states = ["CONNECTING", "OPEN", "CLOSED"];
|
|
665
|
-
const state = states[eventSource.readyState] || "UNKNOWN";
|
|
666
|
-
if (eventSource.readyState === EventSource.CONNECTING) {
|
|
667
|
-
console.log("[hot-reload] Connecting...");
|
|
668
|
-
} else if (eventSource.readyState === EventSource.OPEN) {
|
|
669
|
-
console.warn("[hot-reload] Connection error (but connection is open):", error);
|
|
670
|
-
} else {
|
|
671
|
-
console.log("[hot-reload] Connection closed (readyState:", state, ")");
|
|
1070
|
+
const initialState = await loadInitialRoute(
|
|
1071
|
+
initialUrl,
|
|
1072
|
+
initialData,
|
|
1073
|
+
routes,
|
|
1074
|
+
notFoundRoute,
|
|
1075
|
+
errorRoute
|
|
1076
|
+
);
|
|
1077
|
+
if (initialData?.metadata) {
|
|
1078
|
+
try {
|
|
1079
|
+
applyMetadata(initialData.metadata);
|
|
1080
|
+
} catch (metadataError) {
|
|
1081
|
+
console.warn("[client] Error applying metadata:", metadataError);
|
|
672
1082
|
}
|
|
673
|
-
}
|
|
1083
|
+
}
|
|
1084
|
+
(0, import_client.hydrateRoot)(
|
|
1085
|
+
container,
|
|
1086
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
1087
|
+
AppShell,
|
|
1088
|
+
{
|
|
1089
|
+
initialState,
|
|
1090
|
+
routes,
|
|
1091
|
+
notFoundRoute,
|
|
1092
|
+
errorRoute
|
|
1093
|
+
}
|
|
1094
|
+
)
|
|
1095
|
+
);
|
|
674
1096
|
} catch (error) {
|
|
675
|
-
console.
|
|
1097
|
+
console.error(
|
|
1098
|
+
"[client] Error loading initial route components for",
|
|
1099
|
+
initialUrl,
|
|
1100
|
+
error
|
|
1101
|
+
);
|
|
1102
|
+
throw error;
|
|
676
1103
|
}
|
|
677
1104
|
}
|
|
678
1105
|
function bootstrapClient(routes, notFoundRoute, errorRoute = null) {
|
|
679
|
-
console.log("[client] Bootstrap starting, setting up hot reload...");
|
|
680
1106
|
setupHotReload();
|
|
681
|
-
(async
|
|
682
|
-
const container = document.getElementById(APP_CONTAINER_ID);
|
|
683
|
-
const initialData = getWindowData();
|
|
684
|
-
if (!container) {
|
|
685
|
-
console.error(`Container #${APP_CONTAINER_ID} not found for hydration`);
|
|
686
|
-
return;
|
|
687
|
-
}
|
|
688
|
-
const initialUrl = window.location.pathname + window.location.search;
|
|
1107
|
+
(async () => {
|
|
689
1108
|
try {
|
|
690
|
-
const
|
|
1109
|
+
const container = document.getElementById(APP_CONTAINER_ID);
|
|
1110
|
+
if (!container) {
|
|
1111
|
+
console.error(`
|
|
1112
|
+
\u274C [client] Hydration failed: Container #${APP_CONTAINER_ID} not found`);
|
|
1113
|
+
console.error("\u{1F4A1} This usually means:");
|
|
1114
|
+
console.error(" \u2022 The HTML structure doesn't match what React expects");
|
|
1115
|
+
console.error(" \u2022 The container was removed before hydration");
|
|
1116
|
+
console.error(" \u2022 There's a mismatch between SSR and client HTML\n");
|
|
1117
|
+
return;
|
|
1118
|
+
}
|
|
1119
|
+
const initialData = getWindowData();
|
|
1120
|
+
const initialUrl = (initialData?.pathname || window.location.pathname) + window.location.search;
|
|
1121
|
+
if (initialData?.props) {
|
|
1122
|
+
setPreservedLayoutProps(initialData.props);
|
|
1123
|
+
}
|
|
1124
|
+
const routerPathname = initialData?.pathname || window.location.pathname;
|
|
1125
|
+
initializeRouterData(routerPathname + window.location.search, initialData);
|
|
1126
|
+
await hydrateInitialRoute(
|
|
1127
|
+
container,
|
|
691
1128
|
initialUrl,
|
|
692
1129
|
initialData,
|
|
693
1130
|
routes,
|
|
694
1131
|
notFoundRoute,
|
|
695
1132
|
errorRoute
|
|
696
1133
|
);
|
|
697
|
-
if (initialData?.metadata) {
|
|
698
|
-
applyMetadata(initialData.metadata);
|
|
699
|
-
}
|
|
700
|
-
(0, import_client.hydrateRoot)(
|
|
701
|
-
container,
|
|
702
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
703
|
-
AppShell,
|
|
704
|
-
{
|
|
705
|
-
initialState,
|
|
706
|
-
routes,
|
|
707
|
-
notFoundRoute,
|
|
708
|
-
errorRoute
|
|
709
|
-
}
|
|
710
|
-
)
|
|
711
|
-
);
|
|
712
1134
|
} catch (error) {
|
|
713
|
-
console.error(
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
error
|
|
717
|
-
|
|
1135
|
+
console.error("\n\u274C [client] Fatal error during bootstrap:");
|
|
1136
|
+
console.error(error);
|
|
1137
|
+
if (error instanceof Error) {
|
|
1138
|
+
console.error("\nError details:");
|
|
1139
|
+
console.error(` Message: ${error.message}`);
|
|
1140
|
+
if (error.stack) {
|
|
1141
|
+
console.error(` Stack: ${error.stack}`);
|
|
1142
|
+
}
|
|
1143
|
+
}
|
|
1144
|
+
console.error("\n\u{1F4A1} Attempting page reload to recover...\n");
|
|
718
1145
|
window.location.reload();
|
|
719
1146
|
}
|
|
720
1147
|
})();
|