@lolyjs/core 0.2.0-alpha.9 → 0.3.0-alpha.1
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 +1593 -762
- package/dist/{bootstrap-DgvWWDim.d.mts → bootstrap-BfGTMUkj.d.mts} +12 -0
- package/dist/{bootstrap-DgvWWDim.d.ts → bootstrap-BfGTMUkj.d.ts} +12 -0
- package/dist/cli.cjs +16399 -2602
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.mjs +19097 -0
- package/dist/cli.mjs.map +1 -0
- package/dist/index.cjs +17421 -3205
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.mts +323 -56
- package/dist/index.d.ts +323 -56
- package/dist/index.mjs +20237 -0
- package/dist/index.mjs.map +1 -0
- package/dist/index.types-Duhjyfit.d.mts +280 -0
- package/dist/index.types-Duhjyfit.d.ts +280 -0
- package/dist/react/cache.cjs +82 -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 → cache.mjs} +84 -34
- package/dist/react/cache.mjs.map +1 -0
- package/dist/react/components.cjs +11 -12
- package/dist/react/components.cjs.map +1 -1
- package/dist/react/{components.js → components.mjs} +12 -13
- package/dist/react/components.mjs.map +1 -0
- package/dist/react/hooks.cjs +16 -6
- package/dist/react/hooks.cjs.map +1 -1
- package/dist/react/{hooks.js → hooks.mjs} +24 -14
- package/dist/react/hooks.mjs.map +1 -0
- package/dist/react/sockets.cjs +5 -6
- package/dist/react/sockets.cjs.map +1 -1
- package/dist/react/sockets.mjs +21 -0
- package/dist/react/sockets.mjs.map +1 -0
- package/dist/react/themes.cjs +61 -18
- package/dist/react/themes.cjs.map +1 -1
- package/dist/react/{themes.js → themes.mjs} +64 -21
- package/dist/react/themes.mjs.map +1 -0
- package/dist/runtime.cjs +465 -117
- 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 → runtime.mjs} +466 -118
- package/dist/runtime.mjs.map +1 -0
- package/package.json +26 -26
- package/dist/cli.js +0 -5291
- package/dist/cli.js.map +0 -1
- package/dist/index.js +0 -6015
- package/dist/index.js.map +0 -1
- package/dist/react/cache.js.map +0 -1
- package/dist/react/components.js.map +0 -1
- package/dist/react/hooks.js.map +0 -1
- package/dist/react/sockets.js +0 -22
- package/dist/react/sockets.js.map +0 -1
- package/dist/react/themes.js.map +0 -1
- package/dist/runtime.js.map +0 -1
package/dist/runtime.cjs
CHANGED
|
@@ -34,12 +34,29 @@ var APP_CONTAINER_ID = "__app";
|
|
|
34
34
|
var ROUTER_NAVIGATE_KEY = "__LOLY_ROUTER_NAVIGATE__";
|
|
35
35
|
|
|
36
36
|
// modules/runtime/client/window-data.ts
|
|
37
|
+
var LAYOUT_PROPS_KEY = "__FW_LAYOUT_PROPS__";
|
|
37
38
|
function getWindowData() {
|
|
38
39
|
if (typeof window === "undefined") {
|
|
39
40
|
return null;
|
|
40
41
|
}
|
|
41
42
|
return window[WINDOW_DATA_KEY] ?? null;
|
|
42
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
|
+
}
|
|
43
60
|
function getRouterData() {
|
|
44
61
|
if (typeof window === "undefined") {
|
|
45
62
|
return null;
|
|
@@ -111,22 +128,158 @@ function matchRouteClient(pathWithSearch, routes) {
|
|
|
111
128
|
}
|
|
112
129
|
|
|
113
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
|
+
}
|
|
114
155
|
function applyMetadata(md) {
|
|
115
156
|
if (!md) return;
|
|
116
157
|
if (md.title) {
|
|
117
158
|
document.title = md.title;
|
|
118
159
|
}
|
|
119
160
|
if (md.description) {
|
|
120
|
-
|
|
121
|
-
'meta[name="description"]'
|
|
122
|
-
);
|
|
123
|
-
if (!meta) {
|
|
124
|
-
meta = document.createElement("meta");
|
|
125
|
-
meta.name = "description";
|
|
126
|
-
document.head.appendChild(meta);
|
|
127
|
-
}
|
|
161
|
+
const meta = getOrCreateMeta('meta[name="description"]', { name: "description" });
|
|
128
162
|
meta.content = md.description;
|
|
129
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
|
+
}
|
|
130
283
|
}
|
|
131
284
|
|
|
132
285
|
// modules/runtime/client/AppShell.tsx
|
|
@@ -239,14 +392,16 @@ function deleteCacheEntry(key) {
|
|
|
239
392
|
function buildDataUrl(url) {
|
|
240
393
|
return url + (url.includes("?") ? "&" : "?") + "__fw_data=1";
|
|
241
394
|
}
|
|
242
|
-
async function fetchRouteDataOnce(url) {
|
|
395
|
+
async function fetchRouteDataOnce(url, skipLayoutHooks = true) {
|
|
243
396
|
const dataUrl = buildDataUrl(url);
|
|
244
|
-
const
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
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 });
|
|
250
405
|
let json = {};
|
|
251
406
|
try {
|
|
252
407
|
const text = await res.text();
|
|
@@ -272,7 +427,7 @@ async function getRouteData(url, options) {
|
|
|
272
427
|
deleteCacheEntry(key);
|
|
273
428
|
}
|
|
274
429
|
const entry = dataCache.get(key);
|
|
275
|
-
if (entry) {
|
|
430
|
+
if (entry && !options?.revalidate) {
|
|
276
431
|
if (entry.status === "fulfilled") {
|
|
277
432
|
updateLRU(key);
|
|
278
433
|
return entry.value;
|
|
@@ -281,12 +436,29 @@ async function getRouteData(url, options) {
|
|
|
281
436
|
return entry.promise;
|
|
282
437
|
}
|
|
283
438
|
}
|
|
284
|
-
const
|
|
285
|
-
|
|
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
|
+
}
|
|
286
455
|
return value;
|
|
287
456
|
}).catch((error) => {
|
|
288
457
|
console.error("[client][cache] Error fetching route data:", error);
|
|
289
|
-
dataCache.
|
|
458
|
+
const entryAfterFetch = dataCache.get(key);
|
|
459
|
+
if (!entryAfterFetch || entryAfterFetch.status === "pending") {
|
|
460
|
+
dataCache.set(key, { status: "rejected", error });
|
|
461
|
+
}
|
|
290
462
|
throw error;
|
|
291
463
|
});
|
|
292
464
|
dataCache.set(key, { status: "pending", promise });
|
|
@@ -311,10 +483,22 @@ async function handleErrorRoute(nextUrl, json, errorRoute, setState) {
|
|
|
311
483
|
} else if (json.theme) {
|
|
312
484
|
theme = json.theme;
|
|
313
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
|
+
};
|
|
314
499
|
const errorProps = {
|
|
315
|
-
...
|
|
316
|
-
|
|
317
|
-
},
|
|
500
|
+
...layoutProps,
|
|
501
|
+
...pageProps,
|
|
318
502
|
theme
|
|
319
503
|
};
|
|
320
504
|
const windowData = {
|
|
@@ -343,10 +527,15 @@ async function handleErrorRoute(nextUrl, json, errorRoute, setState) {
|
|
|
343
527
|
});
|
|
344
528
|
return true;
|
|
345
529
|
} catch (loadError) {
|
|
346
|
-
console.error(
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
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");
|
|
350
539
|
window.location.href = nextUrl;
|
|
351
540
|
return false;
|
|
352
541
|
}
|
|
@@ -366,8 +555,20 @@ async function handleNotFoundRoute(nextUrl, json, notFoundRoute, setState) {
|
|
|
366
555
|
} else if (json.theme) {
|
|
367
556
|
theme = json.theme;
|
|
368
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 ?? {};
|
|
369
569
|
const notFoundProps = {
|
|
370
|
-
...
|
|
570
|
+
...layoutProps,
|
|
571
|
+
...pageProps,
|
|
371
572
|
theme
|
|
372
573
|
};
|
|
373
574
|
const windowData = {
|
|
@@ -424,20 +625,41 @@ async function handleNormalRoute(nextUrl, json, routes, setState) {
|
|
|
424
625
|
} else if (json.theme) {
|
|
425
626
|
theme = json.theme;
|
|
426
627
|
}
|
|
427
|
-
|
|
428
|
-
|
|
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,
|
|
429
642
|
theme
|
|
430
643
|
// Always include theme
|
|
431
644
|
};
|
|
432
|
-
const
|
|
645
|
+
const pathnameForMatch = json.pathname || nextUrl;
|
|
646
|
+
let matched = matchRouteClient(pathnameForMatch, routes);
|
|
433
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
|
+
);
|
|
434
655
|
window.location.href = nextUrl;
|
|
435
656
|
return false;
|
|
436
657
|
}
|
|
658
|
+
const finalPathname = json.pathname || nextUrl;
|
|
437
659
|
const windowData = {
|
|
438
|
-
pathname:
|
|
660
|
+
pathname: finalPathname,
|
|
439
661
|
params: matched.params,
|
|
440
|
-
props:
|
|
662
|
+
props: combinedProps,
|
|
441
663
|
metadata: json.metadata ?? null,
|
|
442
664
|
theme,
|
|
443
665
|
notFound: false,
|
|
@@ -451,7 +673,11 @@ async function handleNormalRoute(nextUrl, json, routes, setState) {
|
|
|
451
673
|
searchParams: Object.fromEntries(url.searchParams.entries())
|
|
452
674
|
};
|
|
453
675
|
setRouterData(routerData);
|
|
454
|
-
const
|
|
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
|
+
}
|
|
455
681
|
window.scrollTo({
|
|
456
682
|
top: 0,
|
|
457
683
|
behavior: "smooth"
|
|
@@ -461,7 +687,7 @@ async function handleNormalRoute(nextUrl, json, routes, setState) {
|
|
|
461
687
|
route: matched.route,
|
|
462
688
|
params: matched.params,
|
|
463
689
|
components,
|
|
464
|
-
props:
|
|
690
|
+
props: combinedProps
|
|
465
691
|
});
|
|
466
692
|
return true;
|
|
467
693
|
}
|
|
@@ -490,7 +716,7 @@ async function navigate(nextUrl, handlers, options) {
|
|
|
490
716
|
}
|
|
491
717
|
}
|
|
492
718
|
if (!ok) {
|
|
493
|
-
if (json
|
|
719
|
+
if (json?.redirect) {
|
|
494
720
|
window.location.href = json.redirect.destination;
|
|
495
721
|
return;
|
|
496
722
|
}
|
|
@@ -511,6 +737,47 @@ async function navigate(nextUrl, handlers, options) {
|
|
|
511
737
|
window.location.href = nextUrl;
|
|
512
738
|
}
|
|
513
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
|
+
}
|
|
514
781
|
function createClickHandler(navigate2) {
|
|
515
782
|
return function handleClick(ev) {
|
|
516
783
|
try {
|
|
@@ -621,17 +888,20 @@ function AppShell({
|
|
|
621
888
|
}
|
|
622
889
|
const handleClick = createClickHandler(handleNavigateInternal);
|
|
623
890
|
const handlePopState = createPopStateHandler(handleNavigateInternal);
|
|
891
|
+
const handleHover = createHoverHandler(routes, notFoundRoute);
|
|
624
892
|
window.addEventListener("click", handleClick, false);
|
|
625
893
|
window.addEventListener("popstate", handlePopState, false);
|
|
894
|
+
window.addEventListener("mouseover", handleHover, false);
|
|
626
895
|
return () => {
|
|
627
896
|
isMounted = false;
|
|
628
897
|
window.removeEventListener("click", handleClick, false);
|
|
629
898
|
window.removeEventListener("popstate", handlePopState, false);
|
|
899
|
+
window.removeEventListener("mouseover", handleHover, false);
|
|
630
900
|
};
|
|
631
|
-
}, []);
|
|
901
|
+
}, [routes, notFoundRoute]);
|
|
632
902
|
(0, import_react2.useEffect)(() => {
|
|
633
903
|
const handleDataRefresh = () => {
|
|
634
|
-
const freshData = window
|
|
904
|
+
const freshData = window[WINDOW_DATA_KEY];
|
|
635
905
|
if (!freshData) return;
|
|
636
906
|
const currentPathname = window.location.pathname;
|
|
637
907
|
const freshPathname = freshData.pathname;
|
|
@@ -650,7 +920,7 @@ function AppShell({
|
|
|
650
920
|
return () => {
|
|
651
921
|
window.removeEventListener("fw-data-refresh", handleDataRefresh);
|
|
652
922
|
};
|
|
653
|
-
}, [
|
|
923
|
+
}, []);
|
|
654
924
|
const isError = state.route === errorRoute;
|
|
655
925
|
const isNotFound = state.route === notFoundRoute;
|
|
656
926
|
const routeType = isError ? "error" : isNotFound ? "notfound" : "normal";
|
|
@@ -658,6 +928,89 @@ function AppShell({
|
|
|
658
928
|
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(RouterContext.Provider, { value: { navigate: handleNavigate }, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(RouterView, { state }, routeKey) });
|
|
659
929
|
}
|
|
660
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
|
+
|
|
661
1014
|
// modules/runtime/client/bootstrap.tsx
|
|
662
1015
|
var import_jsx_runtime3 = require("react/jsx-runtime");
|
|
663
1016
|
async function loadInitialRoute(initialUrl, initialData, routes, notFoundRoute, errorRoute) {
|
|
@@ -699,101 +1052,96 @@ async function loadInitialRoute(initialUrl, initialData, routes, notFoundRoute,
|
|
|
699
1052
|
props: initialData?.props ?? {}
|
|
700
1053
|
};
|
|
701
1054
|
}
|
|
702
|
-
function
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
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);
|
|
707
1066
|
}
|
|
1067
|
+
}
|
|
1068
|
+
async function hydrateInitialRoute(container, initialUrl, initialData, routes, notFoundRoute, errorRoute) {
|
|
708
1069
|
try {
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
console.log("[hot-reload] Reloading page...");
|
|
722
|
-
window.location.reload();
|
|
723
|
-
}, 500);
|
|
724
|
-
}
|
|
725
|
-
});
|
|
726
|
-
eventSource.addEventListener("ping", () => {
|
|
727
|
-
console.log("[hot-reload] \u2713 Connected to hot reload server");
|
|
728
|
-
});
|
|
729
|
-
eventSource.onopen = () => {
|
|
730
|
-
console.log("[hot-reload] \u2713 SSE connection opened");
|
|
731
|
-
};
|
|
732
|
-
eventSource.onerror = (error) => {
|
|
733
|
-
const states = ["CONNECTING", "OPEN", "CLOSED"];
|
|
734
|
-
const state = states[eventSource.readyState] || "UNKNOWN";
|
|
735
|
-
if (eventSource.readyState === EventSource.CONNECTING) {
|
|
736
|
-
console.log("[hot-reload] Connecting...");
|
|
737
|
-
} else if (eventSource.readyState === EventSource.OPEN) {
|
|
738
|
-
console.warn("[hot-reload] Connection error (but connection is open):", error);
|
|
739
|
-
} else {
|
|
740
|
-
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);
|
|
741
1082
|
}
|
|
742
|
-
}
|
|
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
|
+
);
|
|
743
1096
|
} catch (error) {
|
|
744
|
-
console.
|
|
1097
|
+
console.error(
|
|
1098
|
+
"[client] Error loading initial route components for",
|
|
1099
|
+
initialUrl,
|
|
1100
|
+
error
|
|
1101
|
+
);
|
|
1102
|
+
throw error;
|
|
745
1103
|
}
|
|
746
1104
|
}
|
|
747
1105
|
function bootstrapClient(routes, notFoundRoute, errorRoute = null) {
|
|
748
|
-
console.log("[client] Bootstrap starting, setting up hot reload...");
|
|
749
1106
|
setupHotReload();
|
|
750
|
-
(async
|
|
751
|
-
const container = document.getElementById(APP_CONTAINER_ID);
|
|
752
|
-
const initialData = getWindowData();
|
|
753
|
-
if (!container) {
|
|
754
|
-
console.error(`Container #${APP_CONTAINER_ID} not found for hydration`);
|
|
755
|
-
return;
|
|
756
|
-
}
|
|
757
|
-
const initialUrl = window.location.pathname + window.location.search;
|
|
758
|
-
let routerData = getRouterData();
|
|
759
|
-
if (!routerData) {
|
|
760
|
-
const url = new URL(initialUrl, window.location.origin);
|
|
761
|
-
routerData = {
|
|
762
|
-
pathname: url.pathname,
|
|
763
|
-
params: initialData?.params || {},
|
|
764
|
-
searchParams: Object.fromEntries(url.searchParams.entries())
|
|
765
|
-
};
|
|
766
|
-
setRouterData(routerData);
|
|
767
|
-
}
|
|
1107
|
+
(async () => {
|
|
768
1108
|
try {
|
|
769
|
-
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,
|
|
770
1128
|
initialUrl,
|
|
771
1129
|
initialData,
|
|
772
1130
|
routes,
|
|
773
1131
|
notFoundRoute,
|
|
774
1132
|
errorRoute
|
|
775
1133
|
);
|
|
776
|
-
if (initialData?.metadata) {
|
|
777
|
-
applyMetadata(initialData.metadata);
|
|
778
|
-
}
|
|
779
|
-
(0, import_client.hydrateRoot)(
|
|
780
|
-
container,
|
|
781
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
782
|
-
AppShell,
|
|
783
|
-
{
|
|
784
|
-
initialState,
|
|
785
|
-
routes,
|
|
786
|
-
notFoundRoute,
|
|
787
|
-
errorRoute
|
|
788
|
-
}
|
|
789
|
-
)
|
|
790
|
-
);
|
|
791
1134
|
} catch (error) {
|
|
792
|
-
console.error(
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
error
|
|
796
|
-
|
|
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");
|
|
797
1145
|
window.location.reload();
|
|
798
1146
|
}
|
|
799
1147
|
})();
|