@lolyjs/core 0.2.0-alpha.2 → 0.2.0-alpha.20
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/LICENCE.md +9 -0
- package/README.md +1074 -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 +16933 -4416
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +16943 -4416
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +14387 -1372
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.mts +295 -57
- package/dist/index.d.ts +295 -57
- package/dist/index.js +15621 -2597
- package/dist/index.js.map +1 -1
- package/dist/index.types-DMOO-uvF.d.mts +221 -0
- package/dist/index.types-DMOO-uvF.d.ts +221 -0
- package/dist/react/cache.cjs +107 -32
- package/dist/react/cache.cjs.map +1 -1
- package/dist/react/cache.d.mts +27 -21
- package/dist/react/cache.d.ts +27 -21
- package/dist/react/cache.js +107 -32
- package/dist/react/cache.js.map +1 -1
- package/dist/react/components.cjs +10 -8
- package/dist/react/components.cjs.map +1 -1
- package/dist/react/components.js +10 -8
- package/dist/react/components.js.map +1 -1
- package/dist/react/hooks.cjs +208 -26
- package/dist/react/hooks.cjs.map +1 -1
- package/dist/react/hooks.d.mts +75 -15
- package/dist/react/hooks.d.ts +75 -15
- package/dist/react/hooks.js +208 -26
- package/dist/react/hooks.js.map +1 -1
- package/dist/react/sockets.cjs +13 -6
- package/dist/react/sockets.cjs.map +1 -1
- package/dist/react/sockets.js +13 -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 +544 -111
- 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 +540 -107
- package/dist/runtime.js.map +1 -1
- package/package.json +49 -4
package/dist/runtime.cjs
CHANGED
|
@@ -29,12 +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() {
|
|
39
|
+
if (typeof window === "undefined") {
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
36
42
|
return window[WINDOW_DATA_KEY] ?? null;
|
|
37
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
|
+
}
|
|
38
66
|
function setWindowData(data) {
|
|
39
67
|
window[WINDOW_DATA_KEY] = data;
|
|
40
68
|
if (typeof window !== "undefined") {
|
|
@@ -45,6 +73,16 @@ function setWindowData(data) {
|
|
|
45
73
|
);
|
|
46
74
|
}
|
|
47
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
|
+
}
|
|
48
86
|
function getCurrentTheme() {
|
|
49
87
|
return getWindowData()?.theme ?? null;
|
|
50
88
|
}
|
|
@@ -90,26 +128,162 @@ function matchRouteClient(pathWithSearch, routes) {
|
|
|
90
128
|
}
|
|
91
129
|
|
|
92
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
|
+
}
|
|
93
155
|
function applyMetadata(md) {
|
|
94
156
|
if (!md) return;
|
|
95
157
|
if (md.title) {
|
|
96
158
|
document.title = md.title;
|
|
97
159
|
}
|
|
98
160
|
if (md.description) {
|
|
99
|
-
|
|
100
|
-
'meta[name="description"]'
|
|
101
|
-
);
|
|
102
|
-
if (!meta) {
|
|
103
|
-
meta = document.createElement("meta");
|
|
104
|
-
meta.name = "description";
|
|
105
|
-
document.head.appendChild(meta);
|
|
106
|
-
}
|
|
161
|
+
const meta = getOrCreateMeta('meta[name="description"]', { name: "description" });
|
|
107
162
|
meta.content = md.description;
|
|
108
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
|
+
}
|
|
109
283
|
}
|
|
110
284
|
|
|
111
285
|
// modules/runtime/client/AppShell.tsx
|
|
112
|
-
var
|
|
286
|
+
var import_react2 = require("react");
|
|
113
287
|
|
|
114
288
|
// modules/runtime/client/RouterView.tsx
|
|
115
289
|
var import_jsx_runtime = require("react/jsx-runtime");
|
|
@@ -218,14 +392,16 @@ function deleteCacheEntry(key) {
|
|
|
218
392
|
function buildDataUrl(url) {
|
|
219
393
|
return url + (url.includes("?") ? "&" : "?") + "__fw_data=1";
|
|
220
394
|
}
|
|
221
|
-
async function fetchRouteDataOnce(url) {
|
|
395
|
+
async function fetchRouteDataOnce(url, skipLayoutHooks = true) {
|
|
222
396
|
const dataUrl = buildDataUrl(url);
|
|
223
|
-
const
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
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 });
|
|
229
405
|
let json = {};
|
|
230
406
|
try {
|
|
231
407
|
const text = await res.text();
|
|
@@ -251,7 +427,7 @@ async function getRouteData(url, options) {
|
|
|
251
427
|
deleteCacheEntry(key);
|
|
252
428
|
}
|
|
253
429
|
const entry = dataCache.get(key);
|
|
254
|
-
if (entry) {
|
|
430
|
+
if (entry && !options?.revalidate) {
|
|
255
431
|
if (entry.status === "fulfilled") {
|
|
256
432
|
updateLRU(key);
|
|
257
433
|
return entry.value;
|
|
@@ -260,12 +436,29 @@ async function getRouteData(url, options) {
|
|
|
260
436
|
return entry.promise;
|
|
261
437
|
}
|
|
262
438
|
}
|
|
263
|
-
const
|
|
264
|
-
|
|
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
|
+
}
|
|
265
455
|
return value;
|
|
266
456
|
}).catch((error) => {
|
|
267
457
|
console.error("[client][cache] Error fetching route data:", error);
|
|
268
|
-
dataCache.
|
|
458
|
+
const entryAfterFetch = dataCache.get(key);
|
|
459
|
+
if (!entryAfterFetch || entryAfterFetch.status === "pending") {
|
|
460
|
+
dataCache.set(key, { status: "rejected", error });
|
|
461
|
+
}
|
|
269
462
|
throw error;
|
|
270
463
|
});
|
|
271
464
|
dataCache.set(key, { status: "pending", promise });
|
|
@@ -290,10 +483,22 @@ async function handleErrorRoute(nextUrl, json, errorRoute, setState) {
|
|
|
290
483
|
} else if (json.theme) {
|
|
291
484
|
theme = json.theme;
|
|
292
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
|
+
};
|
|
293
499
|
const errorProps = {
|
|
294
|
-
...
|
|
295
|
-
|
|
296
|
-
},
|
|
500
|
+
...layoutProps,
|
|
501
|
+
...pageProps,
|
|
297
502
|
theme
|
|
298
503
|
};
|
|
299
504
|
const windowData = {
|
|
@@ -306,6 +511,13 @@ async function handleErrorRoute(nextUrl, json, errorRoute, setState) {
|
|
|
306
511
|
error: true
|
|
307
512
|
};
|
|
308
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);
|
|
309
521
|
setState({
|
|
310
522
|
url: nextUrl,
|
|
311
523
|
route: errorRoute,
|
|
@@ -315,10 +527,15 @@ async function handleErrorRoute(nextUrl, json, errorRoute, setState) {
|
|
|
315
527
|
});
|
|
316
528
|
return true;
|
|
317
529
|
} catch (loadError) {
|
|
318
|
-
console.error(
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
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");
|
|
322
539
|
window.location.href = nextUrl;
|
|
323
540
|
return false;
|
|
324
541
|
}
|
|
@@ -338,8 +555,20 @@ async function handleNotFoundRoute(nextUrl, json, notFoundRoute, setState) {
|
|
|
338
555
|
} else if (json.theme) {
|
|
339
556
|
theme = json.theme;
|
|
340
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 ?? {};
|
|
341
569
|
const notFoundProps = {
|
|
342
|
-
...
|
|
570
|
+
...layoutProps,
|
|
571
|
+
...pageProps,
|
|
343
572
|
theme
|
|
344
573
|
};
|
|
345
574
|
const windowData = {
|
|
@@ -352,6 +581,13 @@ async function handleNotFoundRoute(nextUrl, json, notFoundRoute, setState) {
|
|
|
352
581
|
error: false
|
|
353
582
|
};
|
|
354
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);
|
|
355
591
|
if (notFoundRoute) {
|
|
356
592
|
const components = await notFoundRoute.load();
|
|
357
593
|
setState({
|
|
@@ -389,8 +625,20 @@ async function handleNormalRoute(nextUrl, json, routes, setState) {
|
|
|
389
625
|
} else if (json.theme) {
|
|
390
626
|
theme = json.theme;
|
|
391
627
|
}
|
|
392
|
-
|
|
393
|
-
|
|
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,
|
|
394
642
|
theme
|
|
395
643
|
// Always include theme
|
|
396
644
|
};
|
|
@@ -402,14 +650,25 @@ async function handleNormalRoute(nextUrl, json, routes, setState) {
|
|
|
402
650
|
const windowData = {
|
|
403
651
|
pathname: nextUrl,
|
|
404
652
|
params: matched.params,
|
|
405
|
-
props:
|
|
653
|
+
props: combinedProps,
|
|
406
654
|
metadata: json.metadata ?? null,
|
|
407
655
|
theme,
|
|
408
656
|
notFound: false,
|
|
409
657
|
error: false
|
|
410
658
|
};
|
|
411
659
|
setWindowData(windowData);
|
|
412
|
-
const
|
|
660
|
+
const url = new URL(nextUrl, typeof window !== "undefined" ? window.location.origin : "http://localhost");
|
|
661
|
+
const routerData = {
|
|
662
|
+
pathname: url.pathname,
|
|
663
|
+
params: matched.params,
|
|
664
|
+
searchParams: Object.fromEntries(url.searchParams.entries())
|
|
665
|
+
};
|
|
666
|
+
setRouterData(routerData);
|
|
667
|
+
const prefetched = prefetchedRoutes.get(matched.route);
|
|
668
|
+
const components = prefetched ? await prefetched : await matched.route.load();
|
|
669
|
+
if (!prefetched) {
|
|
670
|
+
prefetchedRoutes.set(matched.route, Promise.resolve(components));
|
|
671
|
+
}
|
|
413
672
|
window.scrollTo({
|
|
414
673
|
top: 0,
|
|
415
674
|
behavior: "smooth"
|
|
@@ -419,7 +678,7 @@ async function handleNormalRoute(nextUrl, json, routes, setState) {
|
|
|
419
678
|
route: matched.route,
|
|
420
679
|
params: matched.params,
|
|
421
680
|
components,
|
|
422
|
-
props:
|
|
681
|
+
props: combinedProps
|
|
423
682
|
});
|
|
424
683
|
return true;
|
|
425
684
|
}
|
|
@@ -448,7 +707,7 @@ async function navigate(nextUrl, handlers, options) {
|
|
|
448
707
|
}
|
|
449
708
|
}
|
|
450
709
|
if (!ok) {
|
|
451
|
-
if (json
|
|
710
|
+
if (json?.redirect) {
|
|
452
711
|
window.location.href = json.redirect.destination;
|
|
453
712
|
return;
|
|
454
713
|
}
|
|
@@ -469,6 +728,47 @@ async function navigate(nextUrl, handlers, options) {
|
|
|
469
728
|
window.location.href = nextUrl;
|
|
470
729
|
}
|
|
471
730
|
}
|
|
731
|
+
var prefetchedRoutes = /* @__PURE__ */ new WeakMap();
|
|
732
|
+
function prefetchRoute(url, routes, notFoundRoute) {
|
|
733
|
+
const [pathname] = url.split("?");
|
|
734
|
+
const matched = matchRouteClient(pathname, routes);
|
|
735
|
+
if (!matched) {
|
|
736
|
+
if (notFoundRoute) {
|
|
737
|
+
const existing2 = prefetchedRoutes.get(notFoundRoute);
|
|
738
|
+
if (!existing2) {
|
|
739
|
+
const promise = notFoundRoute.load();
|
|
740
|
+
prefetchedRoutes.set(notFoundRoute, promise);
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
return;
|
|
744
|
+
}
|
|
745
|
+
const existing = prefetchedRoutes.get(matched.route);
|
|
746
|
+
if (!existing) {
|
|
747
|
+
const promise = matched.route.load();
|
|
748
|
+
prefetchedRoutes.set(matched.route, promise);
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
function createHoverHandler(routes, notFoundRoute) {
|
|
752
|
+
return function handleHover(ev) {
|
|
753
|
+
try {
|
|
754
|
+
const target = ev.target;
|
|
755
|
+
if (!target) return;
|
|
756
|
+
const anchor = target.closest("a[href]");
|
|
757
|
+
if (!anchor) return;
|
|
758
|
+
const href = anchor.getAttribute("href");
|
|
759
|
+
if (!href) return;
|
|
760
|
+
if (href.startsWith("#")) return;
|
|
761
|
+
const url = new URL(href, window.location.href);
|
|
762
|
+
if (url.origin !== window.location.origin) return;
|
|
763
|
+
if (anchor.target && anchor.target !== "_self") return;
|
|
764
|
+
const nextUrl = url.pathname + url.search;
|
|
765
|
+
const currentUrl = window.location.pathname + window.location.search;
|
|
766
|
+
if (nextUrl === currentUrl) return;
|
|
767
|
+
prefetchRoute(nextUrl, routes, notFoundRoute);
|
|
768
|
+
} catch (error) {
|
|
769
|
+
}
|
|
770
|
+
};
|
|
771
|
+
}
|
|
472
772
|
function createClickHandler(navigate2) {
|
|
473
773
|
return function handleClick(ev) {
|
|
474
774
|
try {
|
|
@@ -528,6 +828,10 @@ function createPopStateHandler(navigate2) {
|
|
|
528
828
|
};
|
|
529
829
|
}
|
|
530
830
|
|
|
831
|
+
// modules/runtime/client/RouterContext.tsx
|
|
832
|
+
var import_react = require("react");
|
|
833
|
+
var RouterContext = (0, import_react.createContext)(null);
|
|
834
|
+
|
|
531
835
|
// modules/runtime/client/AppShell.tsx
|
|
532
836
|
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
533
837
|
function AppShell({
|
|
@@ -536,14 +840,14 @@ function AppShell({
|
|
|
536
840
|
notFoundRoute,
|
|
537
841
|
errorRoute
|
|
538
842
|
}) {
|
|
539
|
-
const [state, setState] = (0,
|
|
540
|
-
const handlersRef = (0,
|
|
843
|
+
const [state, setState] = (0, import_react2.useState)(initialState);
|
|
844
|
+
const handlersRef = (0, import_react2.useRef)({
|
|
541
845
|
setState,
|
|
542
846
|
routes,
|
|
543
847
|
notFoundRoute,
|
|
544
848
|
errorRoute
|
|
545
849
|
});
|
|
546
|
-
(0,
|
|
850
|
+
(0, import_react2.useEffect)(() => {
|
|
547
851
|
handlersRef.current = {
|
|
548
852
|
setState,
|
|
549
853
|
routes,
|
|
@@ -551,27 +855,153 @@ function AppShell({
|
|
|
551
855
|
errorRoute
|
|
552
856
|
};
|
|
553
857
|
}, [routes, notFoundRoute, errorRoute]);
|
|
554
|
-
(0,
|
|
858
|
+
const handleNavigate = (0, import_react2.useCallback)(
|
|
859
|
+
async (nextUrl, options) => {
|
|
860
|
+
await navigate(nextUrl, handlersRef.current, {
|
|
861
|
+
revalidate: options?.revalidate
|
|
862
|
+
});
|
|
863
|
+
},
|
|
864
|
+
[]
|
|
865
|
+
);
|
|
866
|
+
(0, import_react2.useEffect)(() => {
|
|
867
|
+
if (typeof window !== "undefined") {
|
|
868
|
+
window[ROUTER_NAVIGATE_KEY] = handleNavigate;
|
|
869
|
+
return () => {
|
|
870
|
+
delete window[ROUTER_NAVIGATE_KEY];
|
|
871
|
+
};
|
|
872
|
+
}
|
|
873
|
+
}, [handleNavigate]);
|
|
874
|
+
(0, import_react2.useEffect)(() => {
|
|
555
875
|
let isMounted = true;
|
|
556
|
-
async function
|
|
876
|
+
async function handleNavigateInternal(nextUrl, options) {
|
|
557
877
|
if (!isMounted) return;
|
|
558
878
|
await navigate(nextUrl, handlersRef.current, options);
|
|
559
879
|
}
|
|
560
|
-
const handleClick = createClickHandler(
|
|
561
|
-
const handlePopState = createPopStateHandler(
|
|
880
|
+
const handleClick = createClickHandler(handleNavigateInternal);
|
|
881
|
+
const handlePopState = createPopStateHandler(handleNavigateInternal);
|
|
882
|
+
const handleHover = createHoverHandler(routes, notFoundRoute);
|
|
562
883
|
window.addEventListener("click", handleClick, false);
|
|
563
884
|
window.addEventListener("popstate", handlePopState, false);
|
|
885
|
+
window.addEventListener("mouseover", handleHover, false);
|
|
564
886
|
return () => {
|
|
565
887
|
isMounted = false;
|
|
566
888
|
window.removeEventListener("click", handleClick, false);
|
|
567
889
|
window.removeEventListener("popstate", handlePopState, false);
|
|
890
|
+
window.removeEventListener("mouseover", handleHover, false);
|
|
891
|
+
};
|
|
892
|
+
}, [routes, notFoundRoute]);
|
|
893
|
+
(0, import_react2.useEffect)(() => {
|
|
894
|
+
const handleDataRefresh = () => {
|
|
895
|
+
const freshData = window[WINDOW_DATA_KEY];
|
|
896
|
+
if (!freshData) return;
|
|
897
|
+
const currentPathname = window.location.pathname;
|
|
898
|
+
const freshPathname = freshData.pathname;
|
|
899
|
+
if (freshPathname === currentPathname) {
|
|
900
|
+
if (freshData.metadata !== void 0) {
|
|
901
|
+
applyMetadata(freshData.metadata);
|
|
902
|
+
}
|
|
903
|
+
setState((prevState) => ({
|
|
904
|
+
...prevState,
|
|
905
|
+
props: freshData.props ?? prevState.props,
|
|
906
|
+
params: freshData.params ?? prevState.params
|
|
907
|
+
}));
|
|
908
|
+
}
|
|
909
|
+
};
|
|
910
|
+
window.addEventListener("fw-data-refresh", handleDataRefresh);
|
|
911
|
+
return () => {
|
|
912
|
+
window.removeEventListener("fw-data-refresh", handleDataRefresh);
|
|
568
913
|
};
|
|
569
914
|
}, []);
|
|
570
915
|
const isError = state.route === errorRoute;
|
|
571
916
|
const isNotFound = state.route === notFoundRoute;
|
|
572
917
|
const routeType = isError ? "error" : isNotFound ? "notfound" : "normal";
|
|
573
918
|
const routeKey = `${state.url}:${routeType}`;
|
|
574
|
-
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(RouterView, { state }, routeKey);
|
|
919
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(RouterContext.Provider, { value: { navigate: handleNavigate }, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(RouterView, { state }, routeKey) });
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
// modules/runtime/client/hot-reload.ts
|
|
923
|
+
function setupHotReload() {
|
|
924
|
+
const nodeEnv = process.env.NODE_ENV || "production";
|
|
925
|
+
const isDev = nodeEnv === "development";
|
|
926
|
+
console.log(`[hot-reload] NODE_ENV: ${nodeEnv}, isDev: ${isDev}`);
|
|
927
|
+
if (!isDev) {
|
|
928
|
+
console.log("[hot-reload] Skipping hot reload setup (not in development mode)");
|
|
929
|
+
return;
|
|
930
|
+
}
|
|
931
|
+
console.log("[hot-reload] Setting up hot reload client...");
|
|
932
|
+
let eventSource = null;
|
|
933
|
+
let reloadTimeout = null;
|
|
934
|
+
let reconnectTimeout = null;
|
|
935
|
+
let reconnectAttempts = 0;
|
|
936
|
+
const MAX_RECONNECT_ATTEMPTS = 10;
|
|
937
|
+
const RECONNECT_DELAY = 1e3;
|
|
938
|
+
const RELOAD_DELAY = 100;
|
|
939
|
+
function connect() {
|
|
940
|
+
try {
|
|
941
|
+
if (eventSource) {
|
|
942
|
+
console.log("[hot-reload] Closing existing EventSource connection");
|
|
943
|
+
eventSource.close();
|
|
944
|
+
}
|
|
945
|
+
const endpoint = "/__fw/hot";
|
|
946
|
+
eventSource = new EventSource(endpoint);
|
|
947
|
+
eventSource.addEventListener("ping", (event) => {
|
|
948
|
+
if ("data" in event) {
|
|
949
|
+
console.log("[hot-reload] \u2705 Connected to hot reload server");
|
|
950
|
+
}
|
|
951
|
+
reconnectAttempts = 0;
|
|
952
|
+
});
|
|
953
|
+
eventSource.addEventListener("message", (event) => {
|
|
954
|
+
const data = event.data;
|
|
955
|
+
if (data && typeof data === "string" && data.startsWith("reload:")) {
|
|
956
|
+
const filePath = data.slice(7);
|
|
957
|
+
console.log(`[hot-reload] \u{1F4DD} File changed: ${filePath}, reloading...`);
|
|
958
|
+
if (reloadTimeout) {
|
|
959
|
+
clearTimeout(reloadTimeout);
|
|
960
|
+
}
|
|
961
|
+
reloadTimeout = setTimeout(() => {
|
|
962
|
+
try {
|
|
963
|
+
window.location.reload();
|
|
964
|
+
} catch (error) {
|
|
965
|
+
console.error("[hot-reload] \u274C Error reloading page:", error);
|
|
966
|
+
setTimeout(() => window.location.reload(), 100);
|
|
967
|
+
}
|
|
968
|
+
}, RELOAD_DELAY);
|
|
969
|
+
}
|
|
970
|
+
});
|
|
971
|
+
eventSource.onopen = () => {
|
|
972
|
+
reconnectAttempts = 0;
|
|
973
|
+
};
|
|
974
|
+
eventSource.onerror = (error) => {
|
|
975
|
+
const states = ["CONNECTING", "OPEN", "CLOSED"];
|
|
976
|
+
const state = states[eventSource?.readyState ?? 0] || "UNKNOWN";
|
|
977
|
+
if (eventSource?.readyState === EventSource.CONNECTING) {
|
|
978
|
+
console.log("[hot-reload] \u23F3 Still connecting...");
|
|
979
|
+
return;
|
|
980
|
+
} else if (eventSource?.readyState === EventSource.OPEN) {
|
|
981
|
+
console.warn("[hot-reload] \u26A0\uFE0F Connection error (but connection is open):", error);
|
|
982
|
+
} else {
|
|
983
|
+
console.warn(`[hot-reload] \u274C Connection closed (readyState: ${state})`);
|
|
984
|
+
if (reconnectAttempts < MAX_RECONNECT_ATTEMPTS) {
|
|
985
|
+
reconnectAttempts++;
|
|
986
|
+
const delay = RECONNECT_DELAY * reconnectAttempts;
|
|
987
|
+
if (reconnectTimeout) {
|
|
988
|
+
clearTimeout(reconnectTimeout);
|
|
989
|
+
}
|
|
990
|
+
reconnectTimeout = setTimeout(() => {
|
|
991
|
+
console.log(`[hot-reload] \u{1F504} Reconnecting... (attempt ${reconnectAttempts}/${MAX_RECONNECT_ATTEMPTS})`);
|
|
992
|
+
connect();
|
|
993
|
+
}, delay);
|
|
994
|
+
} else {
|
|
995
|
+
console.error("[hot-reload] \u274C Max reconnect attempts reached. Please refresh the page manually.");
|
|
996
|
+
}
|
|
997
|
+
}
|
|
998
|
+
};
|
|
999
|
+
} catch (error) {
|
|
1000
|
+
console.error("[hot-reload] \u274C Failed to create EventSource:", error);
|
|
1001
|
+
console.error("[hot-reload] EventSource may not be supported in this browser.");
|
|
1002
|
+
}
|
|
1003
|
+
}
|
|
1004
|
+
connect();
|
|
575
1005
|
}
|
|
576
1006
|
|
|
577
1007
|
// modules/runtime/client/bootstrap.tsx
|
|
@@ -615,91 +1045,94 @@ async function loadInitialRoute(initialUrl, initialData, routes, notFoundRoute,
|
|
|
615
1045
|
props: initialData?.props ?? {}
|
|
616
1046
|
};
|
|
617
1047
|
}
|
|
618
|
-
function
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
1048
|
+
function initializeRouterData(initialUrl, initialData) {
|
|
1049
|
+
let routerData = getRouterData();
|
|
1050
|
+
if (!routerData) {
|
|
1051
|
+
const url = new URL(initialUrl, window.location.origin);
|
|
1052
|
+
routerData = {
|
|
1053
|
+
pathname: url.pathname,
|
|
1054
|
+
params: initialData?.params || {},
|
|
1055
|
+
searchParams: Object.fromEntries(url.searchParams.entries())
|
|
1056
|
+
};
|
|
1057
|
+
setRouterData(routerData);
|
|
623
1058
|
}
|
|
1059
|
+
}
|
|
1060
|
+
async function hydrateInitialRoute(container, initialUrl, initialData, routes, notFoundRoute, errorRoute) {
|
|
624
1061
|
try {
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
console.log("[hot-reload] Reloading page...");
|
|
638
|
-
window.location.reload();
|
|
639
|
-
}, 500);
|
|
640
|
-
}
|
|
641
|
-
});
|
|
642
|
-
eventSource.addEventListener("ping", () => {
|
|
643
|
-
console.log("[hot-reload] \u2713 Connected to hot reload server");
|
|
644
|
-
});
|
|
645
|
-
eventSource.onopen = () => {
|
|
646
|
-
console.log("[hot-reload] \u2713 SSE connection opened");
|
|
647
|
-
};
|
|
648
|
-
eventSource.onerror = (error) => {
|
|
649
|
-
const states = ["CONNECTING", "OPEN", "CLOSED"];
|
|
650
|
-
const state = states[eventSource.readyState] || "UNKNOWN";
|
|
651
|
-
if (eventSource.readyState === EventSource.CONNECTING) {
|
|
652
|
-
console.log("[hot-reload] Connecting...");
|
|
653
|
-
} else if (eventSource.readyState === EventSource.OPEN) {
|
|
654
|
-
console.warn("[hot-reload] Connection error (but connection is open):", error);
|
|
655
|
-
} else {
|
|
656
|
-
console.log("[hot-reload] Connection closed (readyState:", state, ")");
|
|
1062
|
+
const initialState = await loadInitialRoute(
|
|
1063
|
+
initialUrl,
|
|
1064
|
+
initialData,
|
|
1065
|
+
routes,
|
|
1066
|
+
notFoundRoute,
|
|
1067
|
+
errorRoute
|
|
1068
|
+
);
|
|
1069
|
+
if (initialData?.metadata) {
|
|
1070
|
+
try {
|
|
1071
|
+
applyMetadata(initialData.metadata);
|
|
1072
|
+
} catch (metadataError) {
|
|
1073
|
+
console.warn("[client] Error applying metadata:", metadataError);
|
|
657
1074
|
}
|
|
658
|
-
}
|
|
1075
|
+
}
|
|
1076
|
+
(0, import_client.hydrateRoot)(
|
|
1077
|
+
container,
|
|
1078
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
1079
|
+
AppShell,
|
|
1080
|
+
{
|
|
1081
|
+
initialState,
|
|
1082
|
+
routes,
|
|
1083
|
+
notFoundRoute,
|
|
1084
|
+
errorRoute
|
|
1085
|
+
}
|
|
1086
|
+
)
|
|
1087
|
+
);
|
|
659
1088
|
} catch (error) {
|
|
660
|
-
console.
|
|
1089
|
+
console.error(
|
|
1090
|
+
"[client] Error loading initial route components for",
|
|
1091
|
+
initialUrl,
|
|
1092
|
+
error
|
|
1093
|
+
);
|
|
1094
|
+
throw error;
|
|
661
1095
|
}
|
|
662
1096
|
}
|
|
663
1097
|
function bootstrapClient(routes, notFoundRoute, errorRoute = null) {
|
|
664
|
-
console.log("[client] Bootstrap starting, setting up hot reload...");
|
|
665
1098
|
setupHotReload();
|
|
666
|
-
(async
|
|
667
|
-
const container = document.getElementById(APP_CONTAINER_ID);
|
|
668
|
-
const initialData = getWindowData();
|
|
669
|
-
if (!container) {
|
|
670
|
-
console.error(`Container #${APP_CONTAINER_ID} not found for hydration`);
|
|
671
|
-
return;
|
|
672
|
-
}
|
|
673
|
-
const initialUrl = window.location.pathname + window.location.search;
|
|
1099
|
+
(async () => {
|
|
674
1100
|
try {
|
|
675
|
-
const
|
|
1101
|
+
const container = document.getElementById(APP_CONTAINER_ID);
|
|
1102
|
+
if (!container) {
|
|
1103
|
+
console.error(`
|
|
1104
|
+
\u274C [client] Hydration failed: Container #${APP_CONTAINER_ID} not found`);
|
|
1105
|
+
console.error("\u{1F4A1} This usually means:");
|
|
1106
|
+
console.error(" \u2022 The HTML structure doesn't match what React expects");
|
|
1107
|
+
console.error(" \u2022 The container was removed before hydration");
|
|
1108
|
+
console.error(" \u2022 There's a mismatch between SSR and client HTML\n");
|
|
1109
|
+
return;
|
|
1110
|
+
}
|
|
1111
|
+
const initialData = getWindowData();
|
|
1112
|
+
const initialUrl = window.location.pathname + window.location.search;
|
|
1113
|
+
if (initialData?.props) {
|
|
1114
|
+
setPreservedLayoutProps(initialData.props);
|
|
1115
|
+
}
|
|
1116
|
+
initializeRouterData(initialUrl, initialData);
|
|
1117
|
+
await hydrateInitialRoute(
|
|
1118
|
+
container,
|
|
676
1119
|
initialUrl,
|
|
677
1120
|
initialData,
|
|
678
1121
|
routes,
|
|
679
1122
|
notFoundRoute,
|
|
680
1123
|
errorRoute
|
|
681
1124
|
);
|
|
682
|
-
if (initialData?.metadata) {
|
|
683
|
-
applyMetadata(initialData.metadata);
|
|
684
|
-
}
|
|
685
|
-
(0, import_client.hydrateRoot)(
|
|
686
|
-
container,
|
|
687
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
688
|
-
AppShell,
|
|
689
|
-
{
|
|
690
|
-
initialState,
|
|
691
|
-
routes,
|
|
692
|
-
notFoundRoute,
|
|
693
|
-
errorRoute
|
|
694
|
-
}
|
|
695
|
-
)
|
|
696
|
-
);
|
|
697
1125
|
} catch (error) {
|
|
698
|
-
console.error(
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
error
|
|
702
|
-
|
|
1126
|
+
console.error("\n\u274C [client] Fatal error during bootstrap:");
|
|
1127
|
+
console.error(error);
|
|
1128
|
+
if (error instanceof Error) {
|
|
1129
|
+
console.error("\nError details:");
|
|
1130
|
+
console.error(` Message: ${error.message}`);
|
|
1131
|
+
if (error.stack) {
|
|
1132
|
+
console.error(` Stack: ${error.stack}`);
|
|
1133
|
+
}
|
|
1134
|
+
}
|
|
1135
|
+
console.error("\n\u{1F4A1} Attempting page reload to recover...\n");
|
|
703
1136
|
window.location.reload();
|
|
704
1137
|
}
|
|
705
1138
|
})();
|