@lolyjs/core 0.2.0-alpha.9 → 0.3.0-alpha.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +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 +16397 -2601
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.mjs +19096 -0
- package/dist/cli.mjs.map +1 -0
- package/dist/index.cjs +17419 -3204
- 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 +20236 -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
|
@@ -8,12 +8,29 @@ var APP_CONTAINER_ID = "__app";
|
|
|
8
8
|
var ROUTER_NAVIGATE_KEY = "__LOLY_ROUTER_NAVIGATE__";
|
|
9
9
|
|
|
10
10
|
// modules/runtime/client/window-data.ts
|
|
11
|
+
var LAYOUT_PROPS_KEY = "__FW_LAYOUT_PROPS__";
|
|
11
12
|
function getWindowData() {
|
|
12
13
|
if (typeof window === "undefined") {
|
|
13
14
|
return null;
|
|
14
15
|
}
|
|
15
16
|
return window[WINDOW_DATA_KEY] ?? null;
|
|
16
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
|
+
}
|
|
17
34
|
function getRouterData() {
|
|
18
35
|
if (typeof window === "undefined") {
|
|
19
36
|
return null;
|
|
@@ -85,22 +102,158 @@ function matchRouteClient(pathWithSearch, routes) {
|
|
|
85
102
|
}
|
|
86
103
|
|
|
87
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
|
+
}
|
|
88
129
|
function applyMetadata(md) {
|
|
89
130
|
if (!md) return;
|
|
90
131
|
if (md.title) {
|
|
91
132
|
document.title = md.title;
|
|
92
133
|
}
|
|
93
134
|
if (md.description) {
|
|
94
|
-
|
|
95
|
-
'meta[name="description"]'
|
|
96
|
-
);
|
|
97
|
-
if (!meta) {
|
|
98
|
-
meta = document.createElement("meta");
|
|
99
|
-
meta.name = "description";
|
|
100
|
-
document.head.appendChild(meta);
|
|
101
|
-
}
|
|
135
|
+
const meta = getOrCreateMeta('meta[name="description"]', { name: "description" });
|
|
102
136
|
meta.content = md.description;
|
|
103
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
|
+
}
|
|
104
257
|
}
|
|
105
258
|
|
|
106
259
|
// modules/runtime/client/AppShell.tsx
|
|
@@ -213,14 +366,16 @@ function deleteCacheEntry(key) {
|
|
|
213
366
|
function buildDataUrl(url) {
|
|
214
367
|
return url + (url.includes("?") ? "&" : "?") + "__fw_data=1";
|
|
215
368
|
}
|
|
216
|
-
async function fetchRouteDataOnce(url) {
|
|
369
|
+
async function fetchRouteDataOnce(url, skipLayoutHooks = true) {
|
|
217
370
|
const dataUrl = buildDataUrl(url);
|
|
218
|
-
const
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
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 });
|
|
224
379
|
let json = {};
|
|
225
380
|
try {
|
|
226
381
|
const text = await res.text();
|
|
@@ -246,7 +401,7 @@ async function getRouteData(url, options) {
|
|
|
246
401
|
deleteCacheEntry(key);
|
|
247
402
|
}
|
|
248
403
|
const entry = dataCache.get(key);
|
|
249
|
-
if (entry) {
|
|
404
|
+
if (entry && !options?.revalidate) {
|
|
250
405
|
if (entry.status === "fulfilled") {
|
|
251
406
|
updateLRU(key);
|
|
252
407
|
return entry.value;
|
|
@@ -255,12 +410,29 @@ async function getRouteData(url, options) {
|
|
|
255
410
|
return entry.promise;
|
|
256
411
|
}
|
|
257
412
|
}
|
|
258
|
-
const
|
|
259
|
-
|
|
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
|
+
}
|
|
260
429
|
return value;
|
|
261
430
|
}).catch((error) => {
|
|
262
431
|
console.error("[client][cache] Error fetching route data:", error);
|
|
263
|
-
dataCache.
|
|
432
|
+
const entryAfterFetch = dataCache.get(key);
|
|
433
|
+
if (!entryAfterFetch || entryAfterFetch.status === "pending") {
|
|
434
|
+
dataCache.set(key, { status: "rejected", error });
|
|
435
|
+
}
|
|
264
436
|
throw error;
|
|
265
437
|
});
|
|
266
438
|
dataCache.set(key, { status: "pending", promise });
|
|
@@ -285,10 +457,22 @@ async function handleErrorRoute(nextUrl, json, errorRoute, setState) {
|
|
|
285
457
|
} else if (json.theme) {
|
|
286
458
|
theme = json.theme;
|
|
287
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
|
+
};
|
|
288
473
|
const errorProps = {
|
|
289
|
-
...
|
|
290
|
-
|
|
291
|
-
},
|
|
474
|
+
...layoutProps,
|
|
475
|
+
...pageProps,
|
|
292
476
|
theme
|
|
293
477
|
};
|
|
294
478
|
const windowData = {
|
|
@@ -317,10 +501,15 @@ async function handleErrorRoute(nextUrl, json, errorRoute, setState) {
|
|
|
317
501
|
});
|
|
318
502
|
return true;
|
|
319
503
|
} catch (loadError) {
|
|
320
|
-
console.error(
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
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");
|
|
324
513
|
window.location.href = nextUrl;
|
|
325
514
|
return false;
|
|
326
515
|
}
|
|
@@ -340,8 +529,20 @@ async function handleNotFoundRoute(nextUrl, json, notFoundRoute, setState) {
|
|
|
340
529
|
} else if (json.theme) {
|
|
341
530
|
theme = json.theme;
|
|
342
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 ?? {};
|
|
343
543
|
const notFoundProps = {
|
|
344
|
-
...
|
|
544
|
+
...layoutProps,
|
|
545
|
+
...pageProps,
|
|
345
546
|
theme
|
|
346
547
|
};
|
|
347
548
|
const windowData = {
|
|
@@ -398,20 +599,41 @@ async function handleNormalRoute(nextUrl, json, routes, setState) {
|
|
|
398
599
|
} else if (json.theme) {
|
|
399
600
|
theme = json.theme;
|
|
400
601
|
}
|
|
401
|
-
|
|
402
|
-
|
|
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,
|
|
403
616
|
theme
|
|
404
617
|
// Always include theme
|
|
405
618
|
};
|
|
406
|
-
const
|
|
619
|
+
const pathnameForMatch = json.pathname || nextUrl;
|
|
620
|
+
let matched = matchRouteClient(pathnameForMatch, routes);
|
|
407
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
|
+
);
|
|
408
629
|
window.location.href = nextUrl;
|
|
409
630
|
return false;
|
|
410
631
|
}
|
|
632
|
+
const finalPathname = json.pathname || nextUrl;
|
|
411
633
|
const windowData = {
|
|
412
|
-
pathname:
|
|
634
|
+
pathname: finalPathname,
|
|
413
635
|
params: matched.params,
|
|
414
|
-
props:
|
|
636
|
+
props: combinedProps,
|
|
415
637
|
metadata: json.metadata ?? null,
|
|
416
638
|
theme,
|
|
417
639
|
notFound: false,
|
|
@@ -425,7 +647,11 @@ async function handleNormalRoute(nextUrl, json, routes, setState) {
|
|
|
425
647
|
searchParams: Object.fromEntries(url.searchParams.entries())
|
|
426
648
|
};
|
|
427
649
|
setRouterData(routerData);
|
|
428
|
-
const
|
|
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
|
+
}
|
|
429
655
|
window.scrollTo({
|
|
430
656
|
top: 0,
|
|
431
657
|
behavior: "smooth"
|
|
@@ -435,7 +661,7 @@ async function handleNormalRoute(nextUrl, json, routes, setState) {
|
|
|
435
661
|
route: matched.route,
|
|
436
662
|
params: matched.params,
|
|
437
663
|
components,
|
|
438
|
-
props:
|
|
664
|
+
props: combinedProps
|
|
439
665
|
});
|
|
440
666
|
return true;
|
|
441
667
|
}
|
|
@@ -464,7 +690,7 @@ async function navigate(nextUrl, handlers, options) {
|
|
|
464
690
|
}
|
|
465
691
|
}
|
|
466
692
|
if (!ok) {
|
|
467
|
-
if (json
|
|
693
|
+
if (json?.redirect) {
|
|
468
694
|
window.location.href = json.redirect.destination;
|
|
469
695
|
return;
|
|
470
696
|
}
|
|
@@ -485,6 +711,47 @@ async function navigate(nextUrl, handlers, options) {
|
|
|
485
711
|
window.location.href = nextUrl;
|
|
486
712
|
}
|
|
487
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
|
+
}
|
|
488
755
|
function createClickHandler(navigate2) {
|
|
489
756
|
return function handleClick(ev) {
|
|
490
757
|
try {
|
|
@@ -595,17 +862,20 @@ function AppShell({
|
|
|
595
862
|
}
|
|
596
863
|
const handleClick = createClickHandler(handleNavigateInternal);
|
|
597
864
|
const handlePopState = createPopStateHandler(handleNavigateInternal);
|
|
865
|
+
const handleHover = createHoverHandler(routes, notFoundRoute);
|
|
598
866
|
window.addEventListener("click", handleClick, false);
|
|
599
867
|
window.addEventListener("popstate", handlePopState, false);
|
|
868
|
+
window.addEventListener("mouseover", handleHover, false);
|
|
600
869
|
return () => {
|
|
601
870
|
isMounted = false;
|
|
602
871
|
window.removeEventListener("click", handleClick, false);
|
|
603
872
|
window.removeEventListener("popstate", handlePopState, false);
|
|
873
|
+
window.removeEventListener("mouseover", handleHover, false);
|
|
604
874
|
};
|
|
605
|
-
}, []);
|
|
875
|
+
}, [routes, notFoundRoute]);
|
|
606
876
|
useEffect(() => {
|
|
607
877
|
const handleDataRefresh = () => {
|
|
608
|
-
const freshData = window
|
|
878
|
+
const freshData = window[WINDOW_DATA_KEY];
|
|
609
879
|
if (!freshData) return;
|
|
610
880
|
const currentPathname = window.location.pathname;
|
|
611
881
|
const freshPathname = freshData.pathname;
|
|
@@ -624,7 +894,7 @@ function AppShell({
|
|
|
624
894
|
return () => {
|
|
625
895
|
window.removeEventListener("fw-data-refresh", handleDataRefresh);
|
|
626
896
|
};
|
|
627
|
-
}, [
|
|
897
|
+
}, []);
|
|
628
898
|
const isError = state.route === errorRoute;
|
|
629
899
|
const isNotFound = state.route === notFoundRoute;
|
|
630
900
|
const routeType = isError ? "error" : isNotFound ? "notfound" : "normal";
|
|
@@ -632,6 +902,89 @@ function AppShell({
|
|
|
632
902
|
return /* @__PURE__ */ jsx2(RouterContext.Provider, { value: { navigate: handleNavigate }, children: /* @__PURE__ */ jsx2(RouterView, { state }, routeKey) });
|
|
633
903
|
}
|
|
634
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
|
+
|
|
635
988
|
// modules/runtime/client/bootstrap.tsx
|
|
636
989
|
import { jsx as jsx3 } from "react/jsx-runtime";
|
|
637
990
|
async function loadInitialRoute(initialUrl, initialData, routes, notFoundRoute, errorRoute) {
|
|
@@ -673,101 +1026,96 @@ async function loadInitialRoute(initialUrl, initialData, routes, notFoundRoute,
|
|
|
673
1026
|
props: initialData?.props ?? {}
|
|
674
1027
|
};
|
|
675
1028
|
}
|
|
676
|
-
function
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
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);
|
|
681
1040
|
}
|
|
1041
|
+
}
|
|
1042
|
+
async function hydrateInitialRoute(container, initialUrl, initialData, routes, notFoundRoute, errorRoute) {
|
|
682
1043
|
try {
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
console.log("[hot-reload] Reloading page...");
|
|
696
|
-
window.location.reload();
|
|
697
|
-
}, 500);
|
|
698
|
-
}
|
|
699
|
-
});
|
|
700
|
-
eventSource.addEventListener("ping", () => {
|
|
701
|
-
console.log("[hot-reload] \u2713 Connected to hot reload server");
|
|
702
|
-
});
|
|
703
|
-
eventSource.onopen = () => {
|
|
704
|
-
console.log("[hot-reload] \u2713 SSE connection opened");
|
|
705
|
-
};
|
|
706
|
-
eventSource.onerror = (error) => {
|
|
707
|
-
const states = ["CONNECTING", "OPEN", "CLOSED"];
|
|
708
|
-
const state = states[eventSource.readyState] || "UNKNOWN";
|
|
709
|
-
if (eventSource.readyState === EventSource.CONNECTING) {
|
|
710
|
-
console.log("[hot-reload] Connecting...");
|
|
711
|
-
} else if (eventSource.readyState === EventSource.OPEN) {
|
|
712
|
-
console.warn("[hot-reload] Connection error (but connection is open):", error);
|
|
713
|
-
} else {
|
|
714
|
-
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);
|
|
715
1056
|
}
|
|
716
|
-
}
|
|
1057
|
+
}
|
|
1058
|
+
hydrateRoot(
|
|
1059
|
+
container,
|
|
1060
|
+
/* @__PURE__ */ jsx3(
|
|
1061
|
+
AppShell,
|
|
1062
|
+
{
|
|
1063
|
+
initialState,
|
|
1064
|
+
routes,
|
|
1065
|
+
notFoundRoute,
|
|
1066
|
+
errorRoute
|
|
1067
|
+
}
|
|
1068
|
+
)
|
|
1069
|
+
);
|
|
717
1070
|
} catch (error) {
|
|
718
|
-
console.
|
|
1071
|
+
console.error(
|
|
1072
|
+
"[client] Error loading initial route components for",
|
|
1073
|
+
initialUrl,
|
|
1074
|
+
error
|
|
1075
|
+
);
|
|
1076
|
+
throw error;
|
|
719
1077
|
}
|
|
720
1078
|
}
|
|
721
1079
|
function bootstrapClient(routes, notFoundRoute, errorRoute = null) {
|
|
722
|
-
console.log("[client] Bootstrap starting, setting up hot reload...");
|
|
723
1080
|
setupHotReload();
|
|
724
|
-
(async
|
|
725
|
-
const container = document.getElementById(APP_CONTAINER_ID);
|
|
726
|
-
const initialData = getWindowData();
|
|
727
|
-
if (!container) {
|
|
728
|
-
console.error(`Container #${APP_CONTAINER_ID} not found for hydration`);
|
|
729
|
-
return;
|
|
730
|
-
}
|
|
731
|
-
const initialUrl = window.location.pathname + window.location.search;
|
|
732
|
-
let routerData = getRouterData();
|
|
733
|
-
if (!routerData) {
|
|
734
|
-
const url = new URL(initialUrl, window.location.origin);
|
|
735
|
-
routerData = {
|
|
736
|
-
pathname: url.pathname,
|
|
737
|
-
params: initialData?.params || {},
|
|
738
|
-
searchParams: Object.fromEntries(url.searchParams.entries())
|
|
739
|
-
};
|
|
740
|
-
setRouterData(routerData);
|
|
741
|
-
}
|
|
1081
|
+
(async () => {
|
|
742
1082
|
try {
|
|
743
|
-
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,
|
|
744
1102
|
initialUrl,
|
|
745
1103
|
initialData,
|
|
746
1104
|
routes,
|
|
747
1105
|
notFoundRoute,
|
|
748
1106
|
errorRoute
|
|
749
1107
|
);
|
|
750
|
-
if (initialData?.metadata) {
|
|
751
|
-
applyMetadata(initialData.metadata);
|
|
752
|
-
}
|
|
753
|
-
hydrateRoot(
|
|
754
|
-
container,
|
|
755
|
-
/* @__PURE__ */ jsx3(
|
|
756
|
-
AppShell,
|
|
757
|
-
{
|
|
758
|
-
initialState,
|
|
759
|
-
routes,
|
|
760
|
-
notFoundRoute,
|
|
761
|
-
errorRoute
|
|
762
|
-
}
|
|
763
|
-
)
|
|
764
|
-
);
|
|
765
1108
|
} catch (error) {
|
|
766
|
-
console.error(
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
error
|
|
770
|
-
|
|
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");
|
|
771
1119
|
window.location.reload();
|
|
772
1120
|
}
|
|
773
1121
|
})();
|
|
@@ -775,4 +1123,4 @@ function bootstrapClient(routes, notFoundRoute, errorRoute = null) {
|
|
|
775
1123
|
export {
|
|
776
1124
|
bootstrapClient
|
|
777
1125
|
};
|
|
778
|
-
//# sourceMappingURL=runtime.
|
|
1126
|
+
//# sourceMappingURL=runtime.mjs.map
|