@webtypen/webframez-react 0.0.25 → 0.0.28
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 +20 -1
- package/dist/client.cjs +1 -1
- package/dist/client.js +1 -1
- package/dist/http.cjs +143 -11
- package/dist/http.js +143 -11
- package/dist/index.cjs +143 -11
- package/dist/index.js +143 -11
- package/dist/router.cjs +74 -7
- package/dist/router.js +74 -7
- package/dist/types.d.ts +16 -6
- package/dist/webframez-core.cjs +143 -11
- package/dist/webframez-core.js +143 -11
- package/package.json +7 -7
package/dist/router.js
CHANGED
|
@@ -11,6 +11,62 @@ import fs from "node:fs";
|
|
|
11
11
|
import Module from "node:module";
|
|
12
12
|
import path from "node:path";
|
|
13
13
|
|
|
14
|
+
// src/head.ts
|
|
15
|
+
var ABSOLUTE_ASSET_URL_PATTERN = /^(?:[a-zA-Z][a-zA-Z\d+\-.]*:|\/\/|#)/;
|
|
16
|
+
function normalizeHeadBasename(value) {
|
|
17
|
+
const trimmed = value?.trim();
|
|
18
|
+
if (!trimmed) {
|
|
19
|
+
return void 0;
|
|
20
|
+
}
|
|
21
|
+
if (trimmed === "/") {
|
|
22
|
+
return "/";
|
|
23
|
+
}
|
|
24
|
+
return trimmed.replace(/\/+$/, "");
|
|
25
|
+
}
|
|
26
|
+
function resolveHeadAssetUrl(assetUrl, basename) {
|
|
27
|
+
const normalizedAssetUrl = assetUrl.trim();
|
|
28
|
+
const normalizedBasename = normalizeHeadBasename(basename);
|
|
29
|
+
if (!normalizedAssetUrl || !normalizedBasename) {
|
|
30
|
+
return normalizedAssetUrl;
|
|
31
|
+
}
|
|
32
|
+
if (ABSOLUTE_ASSET_URL_PATTERN.test(normalizedAssetUrl)) {
|
|
33
|
+
return normalizedAssetUrl;
|
|
34
|
+
}
|
|
35
|
+
if (normalizedBasename !== "/" && (normalizedAssetUrl === normalizedBasename || normalizedAssetUrl.startsWith(`${normalizedBasename}/`))) {
|
|
36
|
+
return normalizedAssetUrl;
|
|
37
|
+
}
|
|
38
|
+
if (normalizedBasename === "/") {
|
|
39
|
+
return normalizedAssetUrl.startsWith("/") ? normalizedAssetUrl : `/${normalizedAssetUrl}`;
|
|
40
|
+
}
|
|
41
|
+
if (normalizedAssetUrl.startsWith("/")) {
|
|
42
|
+
return `${normalizedBasename}${normalizedAssetUrl}`;
|
|
43
|
+
}
|
|
44
|
+
return `${normalizedBasename}/${normalizedAssetUrl}`;
|
|
45
|
+
}
|
|
46
|
+
function normalizeHeadConfig(head, inheritedBasename) {
|
|
47
|
+
if (!head) {
|
|
48
|
+
return void 0;
|
|
49
|
+
}
|
|
50
|
+
const effectiveBasename = normalizeHeadBasename(head.basename) ?? normalizeHeadBasename(inheritedBasename);
|
|
51
|
+
const normalizedHead = {
|
|
52
|
+
...head,
|
|
53
|
+
...effectiveBasename ? { basename: effectiveBasename } : {}
|
|
54
|
+
};
|
|
55
|
+
if (normalizedHead.favicon) {
|
|
56
|
+
normalizedHead.favicon = resolveHeadAssetUrl(
|
|
57
|
+
normalizedHead.favicon,
|
|
58
|
+
effectiveBasename
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
if (normalizedHead.links) {
|
|
62
|
+
normalizedHead.links = normalizedHead.links.map((link) => ({
|
|
63
|
+
...link,
|
|
64
|
+
href: resolveHeadAssetUrl(link.href, effectiveBasename)
|
|
65
|
+
}));
|
|
66
|
+
}
|
|
67
|
+
return normalizedHead;
|
|
68
|
+
}
|
|
69
|
+
|
|
14
70
|
// src/router-runtime.tsx
|
|
15
71
|
import React from "react";
|
|
16
72
|
var ROUTE_CHILDREN_TAG = "webframez-route-children";
|
|
@@ -226,7 +282,8 @@ function mergeHead(...configs) {
|
|
|
226
282
|
meta: [],
|
|
227
283
|
links: []
|
|
228
284
|
};
|
|
229
|
-
for (const
|
|
285
|
+
for (const candidate of configs) {
|
|
286
|
+
const config = normalizeHeadConfig(candidate, merged.basename);
|
|
230
287
|
if (!config) {
|
|
231
288
|
continue;
|
|
232
289
|
}
|
|
@@ -236,6 +293,9 @@ function mergeHead(...configs) {
|
|
|
236
293
|
if (config.description) {
|
|
237
294
|
merged.description = config.description;
|
|
238
295
|
}
|
|
296
|
+
if (config.basename) {
|
|
297
|
+
merged.basename = config.basename;
|
|
298
|
+
}
|
|
239
299
|
if (config.favicon) {
|
|
240
300
|
merged.favicon = config.favicon;
|
|
241
301
|
}
|
|
@@ -249,22 +309,23 @@ function mergeHead(...configs) {
|
|
|
249
309
|
return merged;
|
|
250
310
|
}
|
|
251
311
|
function renderHeadToString(head) {
|
|
312
|
+
const normalizedHead = normalizeHeadConfig(head) ?? head;
|
|
252
313
|
const tags = [];
|
|
253
|
-
if (
|
|
314
|
+
if (normalizedHead.description) {
|
|
254
315
|
tags.push(
|
|
255
|
-
`<meta ${createManagedAttributes()} name="description" content="${escapeHtml(
|
|
316
|
+
`<meta ${createManagedAttributes()} name="description" content="${escapeHtml(normalizedHead.description)}" />`
|
|
256
317
|
);
|
|
257
318
|
}
|
|
258
|
-
if (
|
|
319
|
+
if (normalizedHead.favicon) {
|
|
259
320
|
tags.push(
|
|
260
|
-
`<link ${createManagedAttributes()} rel="icon" href="${escapeHtml(
|
|
321
|
+
`<link ${createManagedAttributes()} rel="icon" href="${escapeHtml(normalizedHead.favicon)}" />`
|
|
261
322
|
);
|
|
262
323
|
}
|
|
263
|
-
for (const meta of
|
|
324
|
+
for (const meta of normalizedHead.meta ?? []) {
|
|
264
325
|
const attrs = Object.entries(meta).filter(([, value]) => Boolean(value)).map(([key, value]) => `${key}="${escapeHtml(String(value))}"`).join(" ");
|
|
265
326
|
tags.push(`<meta ${createManagedAttributes()} ${attrs} />`);
|
|
266
327
|
}
|
|
267
|
-
for (const link of
|
|
328
|
+
for (const link of normalizedHead.links ?? []) {
|
|
268
329
|
const attrs = Object.entries(link).filter(([, value]) => Boolean(value)).map(([key, value]) => `${key}="${escapeHtml(String(value))}"`).join(" ");
|
|
269
330
|
tags.push(`<link ${createManagedAttributes()} ${attrs} />`);
|
|
270
331
|
}
|
|
@@ -480,6 +541,12 @@ function createFileRouter(options) {
|
|
|
480
541
|
params: {},
|
|
481
542
|
searchParams: input.searchParams,
|
|
482
543
|
cookies: input.cookies ?? {},
|
|
544
|
+
request: input.request ?? {
|
|
545
|
+
host: null,
|
|
546
|
+
pathname,
|
|
547
|
+
originalPathname: pathname,
|
|
548
|
+
headers: {}
|
|
549
|
+
},
|
|
483
550
|
abort: (options2) => {
|
|
484
551
|
throw createAbort(options2);
|
|
485
552
|
}
|
package/dist/types.d.ts
CHANGED
|
@@ -2,6 +2,14 @@ import type { ReactNode } from "react";
|
|
|
2
2
|
|
|
3
3
|
export type RouteParams = Record<string, string | string[]>;
|
|
4
4
|
export type RouteSearchParams = Record<string, string | string[]>;
|
|
5
|
+
export type RouteRequestHeaders = Record<string, string | string[] | undefined>;
|
|
6
|
+
|
|
7
|
+
export type RouteRequestContext = {
|
|
8
|
+
host: string | null;
|
|
9
|
+
pathname: string;
|
|
10
|
+
originalPathname: string;
|
|
11
|
+
headers: RouteRequestHeaders;
|
|
12
|
+
};
|
|
5
13
|
|
|
6
14
|
export type AbortRouteOptions = {
|
|
7
15
|
status?: number;
|
|
@@ -9,11 +17,12 @@ export type AbortRouteOptions = {
|
|
|
9
17
|
payload?: unknown;
|
|
10
18
|
};
|
|
11
19
|
|
|
12
|
-
export type RouteContext<TData =
|
|
20
|
+
export type RouteContext<TData = any> = {
|
|
13
21
|
pathname: string;
|
|
14
22
|
params: RouteParams;
|
|
15
23
|
searchParams: RouteSearchParams;
|
|
16
24
|
cookies: Record<string, string>;
|
|
25
|
+
request: RouteRequestContext;
|
|
17
26
|
data?: TData;
|
|
18
27
|
abort: (options?: AbortRouteOptions) => never;
|
|
19
28
|
};
|
|
@@ -22,11 +31,11 @@ export type InferPageData<TDataResolver> =
|
|
|
22
31
|
TDataResolver extends (...args: any[]) => any
|
|
23
32
|
? Awaited<ReturnType<TDataResolver>>
|
|
24
33
|
: TDataResolver;
|
|
25
|
-
export type PageProps<TData =
|
|
34
|
+
export type PageProps<TData = any> = RouteContext<InferPageData<TData>>;
|
|
26
35
|
export type PagePropsFromData<TDataResolver extends (...args: any[]) => any> =
|
|
27
36
|
PageProps<InferPageData<TDataResolver>>;
|
|
28
37
|
|
|
29
|
-
export type ErrorPageProps<TData =
|
|
38
|
+
export type ErrorPageProps<TData = any> = RouteContext<TData> & {
|
|
30
39
|
statusCode: number;
|
|
31
40
|
message: string;
|
|
32
41
|
payload?: unknown;
|
|
@@ -51,6 +60,7 @@ export type HeadLinkTag = {
|
|
|
51
60
|
export type HeadConfig = {
|
|
52
61
|
title?: string;
|
|
53
62
|
description?: string;
|
|
63
|
+
basename?: string;
|
|
54
64
|
favicon?: string;
|
|
55
65
|
meta?: HeadMetaTag[];
|
|
56
66
|
links?: HeadLinkTag[];
|
|
@@ -65,18 +75,18 @@ export type HeadResolver<TContext = RouteContext> = (
|
|
|
65
75
|
context: TContext
|
|
66
76
|
) => HeadConfig | Promise<HeadConfig>;
|
|
67
77
|
|
|
68
|
-
export type PageDataResolver<TData =
|
|
78
|
+
export type PageDataResolver<TData = any> = (
|
|
69
79
|
context: RouteContext
|
|
70
80
|
) => TData | Promise<TData>;
|
|
71
81
|
|
|
72
82
|
export type RouteMiddlewareResult = Record<string, unknown> | undefined | void;
|
|
73
|
-
export type RouteMiddlewareResolver<TData =
|
|
83
|
+
export type RouteMiddlewareResolver<TData = any> = (
|
|
74
84
|
context: RouteContext<TData>
|
|
75
85
|
) => RouteMiddlewareResult | Promise<RouteMiddlewareResult>;
|
|
76
86
|
export type RouteMiddlewareRegistry = Record<string, RouteMiddlewareResolver<any>>;
|
|
77
87
|
export type RouteMiddlewareConfig = string | string[];
|
|
78
88
|
|
|
79
|
-
export type PageModule<TData =
|
|
89
|
+
export type PageModule<TData = any> = {
|
|
80
90
|
default: (props: PageProps<TData>) => ReactNode | Promise<ReactNode>;
|
|
81
91
|
Head?: HeadResolver<PageProps<TData>>;
|
|
82
92
|
Data?: PageDataResolver<TData>;
|
package/dist/webframez-core.cjs
CHANGED
|
@@ -153,6 +153,62 @@ var import_node_fs = __toESM(require("node:fs"), 1);
|
|
|
153
153
|
var import_node_module2 = __toESM(require("node:module"), 1);
|
|
154
154
|
var import_node_path2 = __toESM(require("node:path"), 1);
|
|
155
155
|
|
|
156
|
+
// src/head.ts
|
|
157
|
+
var ABSOLUTE_ASSET_URL_PATTERN = /^(?:[a-zA-Z][a-zA-Z\d+\-.]*:|\/\/|#)/;
|
|
158
|
+
function normalizeHeadBasename(value) {
|
|
159
|
+
const trimmed = value?.trim();
|
|
160
|
+
if (!trimmed) {
|
|
161
|
+
return void 0;
|
|
162
|
+
}
|
|
163
|
+
if (trimmed === "/") {
|
|
164
|
+
return "/";
|
|
165
|
+
}
|
|
166
|
+
return trimmed.replace(/\/+$/, "");
|
|
167
|
+
}
|
|
168
|
+
function resolveHeadAssetUrl(assetUrl, basename) {
|
|
169
|
+
const normalizedAssetUrl = assetUrl.trim();
|
|
170
|
+
const normalizedBasename = normalizeHeadBasename(basename);
|
|
171
|
+
if (!normalizedAssetUrl || !normalizedBasename) {
|
|
172
|
+
return normalizedAssetUrl;
|
|
173
|
+
}
|
|
174
|
+
if (ABSOLUTE_ASSET_URL_PATTERN.test(normalizedAssetUrl)) {
|
|
175
|
+
return normalizedAssetUrl;
|
|
176
|
+
}
|
|
177
|
+
if (normalizedBasename !== "/" && (normalizedAssetUrl === normalizedBasename || normalizedAssetUrl.startsWith(`${normalizedBasename}/`))) {
|
|
178
|
+
return normalizedAssetUrl;
|
|
179
|
+
}
|
|
180
|
+
if (normalizedBasename === "/") {
|
|
181
|
+
return normalizedAssetUrl.startsWith("/") ? normalizedAssetUrl : `/${normalizedAssetUrl}`;
|
|
182
|
+
}
|
|
183
|
+
if (normalizedAssetUrl.startsWith("/")) {
|
|
184
|
+
return `${normalizedBasename}${normalizedAssetUrl}`;
|
|
185
|
+
}
|
|
186
|
+
return `${normalizedBasename}/${normalizedAssetUrl}`;
|
|
187
|
+
}
|
|
188
|
+
function normalizeHeadConfig(head, inheritedBasename) {
|
|
189
|
+
if (!head) {
|
|
190
|
+
return void 0;
|
|
191
|
+
}
|
|
192
|
+
const effectiveBasename = normalizeHeadBasename(head.basename) ?? normalizeHeadBasename(inheritedBasename);
|
|
193
|
+
const normalizedHead = {
|
|
194
|
+
...head,
|
|
195
|
+
...effectiveBasename ? { basename: effectiveBasename } : {}
|
|
196
|
+
};
|
|
197
|
+
if (normalizedHead.favicon) {
|
|
198
|
+
normalizedHead.favicon = resolveHeadAssetUrl(
|
|
199
|
+
normalizedHead.favicon,
|
|
200
|
+
effectiveBasename
|
|
201
|
+
);
|
|
202
|
+
}
|
|
203
|
+
if (normalizedHead.links) {
|
|
204
|
+
normalizedHead.links = normalizedHead.links.map((link) => ({
|
|
205
|
+
...link,
|
|
206
|
+
href: resolveHeadAssetUrl(link.href, effectiveBasename)
|
|
207
|
+
}));
|
|
208
|
+
}
|
|
209
|
+
return normalizedHead;
|
|
210
|
+
}
|
|
211
|
+
|
|
156
212
|
// src/router-runtime.tsx
|
|
157
213
|
var import_react = __toESM(require("react"), 1);
|
|
158
214
|
var ROUTE_CHILDREN_TAG = "webframez-route-children";
|
|
@@ -368,7 +424,8 @@ function mergeHead(...configs) {
|
|
|
368
424
|
meta: [],
|
|
369
425
|
links: []
|
|
370
426
|
};
|
|
371
|
-
for (const
|
|
427
|
+
for (const candidate of configs) {
|
|
428
|
+
const config = normalizeHeadConfig(candidate, merged.basename);
|
|
372
429
|
if (!config) {
|
|
373
430
|
continue;
|
|
374
431
|
}
|
|
@@ -378,6 +435,9 @@ function mergeHead(...configs) {
|
|
|
378
435
|
if (config.description) {
|
|
379
436
|
merged.description = config.description;
|
|
380
437
|
}
|
|
438
|
+
if (config.basename) {
|
|
439
|
+
merged.basename = config.basename;
|
|
440
|
+
}
|
|
381
441
|
if (config.favicon) {
|
|
382
442
|
merged.favicon = config.favicon;
|
|
383
443
|
}
|
|
@@ -391,22 +451,23 @@ function mergeHead(...configs) {
|
|
|
391
451
|
return merged;
|
|
392
452
|
}
|
|
393
453
|
function renderHeadToString(head) {
|
|
454
|
+
const normalizedHead = normalizeHeadConfig(head) ?? head;
|
|
394
455
|
const tags = [];
|
|
395
|
-
if (
|
|
456
|
+
if (normalizedHead.description) {
|
|
396
457
|
tags.push(
|
|
397
|
-
`<meta ${createManagedAttributes()} name="description" content="${escapeHtml(
|
|
458
|
+
`<meta ${createManagedAttributes()} name="description" content="${escapeHtml(normalizedHead.description)}" />`
|
|
398
459
|
);
|
|
399
460
|
}
|
|
400
|
-
if (
|
|
461
|
+
if (normalizedHead.favicon) {
|
|
401
462
|
tags.push(
|
|
402
|
-
`<link ${createManagedAttributes()} rel="icon" href="${escapeHtml(
|
|
463
|
+
`<link ${createManagedAttributes()} rel="icon" href="${escapeHtml(normalizedHead.favicon)}" />`
|
|
403
464
|
);
|
|
404
465
|
}
|
|
405
|
-
for (const meta of
|
|
466
|
+
for (const meta of normalizedHead.meta ?? []) {
|
|
406
467
|
const attrs = Object.entries(meta).filter(([, value]) => Boolean(value)).map(([key, value]) => `${key}="${escapeHtml(String(value))}"`).join(" ");
|
|
407
468
|
tags.push(`<meta ${createManagedAttributes()} ${attrs} />`);
|
|
408
469
|
}
|
|
409
|
-
for (const link of
|
|
470
|
+
for (const link of normalizedHead.links ?? []) {
|
|
410
471
|
const attrs = Object.entries(link).filter(([, value]) => Boolean(value)).map(([key, value]) => `${key}="${escapeHtml(String(value))}"`).join(" ");
|
|
411
472
|
tags.push(`<link ${createManagedAttributes()} ${attrs} />`);
|
|
412
473
|
}
|
|
@@ -622,6 +683,12 @@ function createFileRouter(options) {
|
|
|
622
683
|
params: {},
|
|
623
684
|
searchParams: input.searchParams,
|
|
624
685
|
cookies: input.cookies ?? {},
|
|
686
|
+
request: input.request ?? {
|
|
687
|
+
host: null,
|
|
688
|
+
pathname,
|
|
689
|
+
originalPathname: pathname,
|
|
690
|
+
headers: {}
|
|
691
|
+
},
|
|
625
692
|
abort: (options2) => {
|
|
626
693
|
throw createAbort(options2);
|
|
627
694
|
}
|
|
@@ -849,6 +916,60 @@ function normalizeBasePath(basePath) {
|
|
|
849
916
|
const withLeadingSlash = basePath.startsWith("/") ? basePath : `/${basePath}`;
|
|
850
917
|
return withLeadingSlash.endsWith("/") ? withLeadingSlash.slice(0, -1) : withLeadingSlash;
|
|
851
918
|
}
|
|
919
|
+
function normalizeRequestHostValue(value) {
|
|
920
|
+
const trimmed = value.trim();
|
|
921
|
+
if (!trimmed) {
|
|
922
|
+
return null;
|
|
923
|
+
}
|
|
924
|
+
const firstValue = trimmed.split(",")[0]?.trim() || "";
|
|
925
|
+
if (!firstValue) {
|
|
926
|
+
return null;
|
|
927
|
+
}
|
|
928
|
+
if (firstValue.startsWith("[")) {
|
|
929
|
+
const closingIndex = firstValue.indexOf("]");
|
|
930
|
+
if (closingIndex > 0) {
|
|
931
|
+
return firstValue.slice(1, closingIndex).toLowerCase();
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
return firstValue.replace(/:\d+$/, "").toLowerCase();
|
|
935
|
+
}
|
|
936
|
+
function getRequestHost(req) {
|
|
937
|
+
const candidates = [
|
|
938
|
+
req.headers["x-forwarded-host"],
|
|
939
|
+
req.headers["x-original-host"],
|
|
940
|
+
req.headers.host
|
|
941
|
+
];
|
|
942
|
+
for (const candidate of candidates) {
|
|
943
|
+
if (Array.isArray(candidate)) {
|
|
944
|
+
for (const singleValue of candidate) {
|
|
945
|
+
if (typeof singleValue !== "string") {
|
|
946
|
+
continue;
|
|
947
|
+
}
|
|
948
|
+
const normalized2 = normalizeRequestHostValue(singleValue);
|
|
949
|
+
if (normalized2) {
|
|
950
|
+
return normalized2;
|
|
951
|
+
}
|
|
952
|
+
}
|
|
953
|
+
continue;
|
|
954
|
+
}
|
|
955
|
+
if (typeof candidate !== "string") {
|
|
956
|
+
continue;
|
|
957
|
+
}
|
|
958
|
+
const normalized = normalizeRequestHostValue(candidate);
|
|
959
|
+
if (normalized) {
|
|
960
|
+
return normalized;
|
|
961
|
+
}
|
|
962
|
+
}
|
|
963
|
+
return null;
|
|
964
|
+
}
|
|
965
|
+
function createRouteRequestContext(req, pathname, originalPathname) {
|
|
966
|
+
return {
|
|
967
|
+
host: getRequestHost(req),
|
|
968
|
+
pathname,
|
|
969
|
+
originalPathname,
|
|
970
|
+
headers: req.headers
|
|
971
|
+
};
|
|
972
|
+
}
|
|
852
973
|
function stripBasePath(pathname, basePath) {
|
|
853
974
|
if (!basePath) {
|
|
854
975
|
return pathname;
|
|
@@ -1194,13 +1315,19 @@ function createNodeRequestHandler(options) {
|
|
|
1194
1315
|
}
|
|
1195
1316
|
if (url.pathname === rscPath) {
|
|
1196
1317
|
const pathname = stripBasePath(url.searchParams.get("path") || "/", basePath);
|
|
1318
|
+
const requestContext = createRouteRequestContext(
|
|
1319
|
+
req,
|
|
1320
|
+
pathname,
|
|
1321
|
+
url.searchParams.get("path") || "/"
|
|
1322
|
+
);
|
|
1197
1323
|
const search = new URLSearchParams(url.searchParams.get("search") || "");
|
|
1198
1324
|
const resolved2 = await withRequestBasename(
|
|
1199
1325
|
basePath,
|
|
1200
1326
|
() => router.resolve({
|
|
1201
1327
|
pathname,
|
|
1202
1328
|
searchParams: parseSearchParams(search),
|
|
1203
|
-
cookies: requestCookies
|
|
1329
|
+
cookies: requestCookies,
|
|
1330
|
+
request: requestContext
|
|
1204
1331
|
})
|
|
1205
1332
|
);
|
|
1206
1333
|
const payload = {
|
|
@@ -1240,7 +1367,12 @@ function createNodeRequestHandler(options) {
|
|
|
1240
1367
|
() => router.resolve({
|
|
1241
1368
|
pathname: stripBasePath(url.pathname, basePath),
|
|
1242
1369
|
searchParams: parseSearchParams(url.searchParams),
|
|
1243
|
-
cookies: requestCookies
|
|
1370
|
+
cookies: requestCookies,
|
|
1371
|
+
request: createRouteRequestContext(
|
|
1372
|
+
req,
|
|
1373
|
+
stripBasePath(url.pathname, basePath),
|
|
1374
|
+
url.pathname
|
|
1375
|
+
)
|
|
1244
1376
|
})
|
|
1245
1377
|
);
|
|
1246
1378
|
const initialPayload = {
|
|
@@ -1284,7 +1416,7 @@ function createNodeRequestHandler(options) {
|
|
|
1284
1416
|
rscEndpoint: rscPath,
|
|
1285
1417
|
rootHtml: createInitialHtmlErrorMarkup("Initial HTML render failed."),
|
|
1286
1418
|
initialFlightData,
|
|
1287
|
-
basename: basePath,
|
|
1419
|
+
basename: resolved?.head?.basename ?? basePath,
|
|
1288
1420
|
liveReloadPath: liveReloadPath || void 0,
|
|
1289
1421
|
liveReloadServerId: liveReloadPath ? devServerId : void 0
|
|
1290
1422
|
})
|
|
@@ -1303,7 +1435,7 @@ function createNodeRequestHandler(options) {
|
|
|
1303
1435
|
rscEndpoint: rscPath,
|
|
1304
1436
|
rootHtml,
|
|
1305
1437
|
initialFlightData,
|
|
1306
|
-
basename: basePath,
|
|
1438
|
+
basename: resolved.head.basename ?? basePath,
|
|
1307
1439
|
liveReloadPath: liveReloadPath || void 0,
|
|
1308
1440
|
liveReloadServerId: liveReloadPath ? devServerId : void 0
|
|
1309
1441
|
})
|
package/dist/webframez-core.js
CHANGED
|
@@ -125,6 +125,62 @@ import fs from "node:fs";
|
|
|
125
125
|
import Module from "node:module";
|
|
126
126
|
import path2 from "node:path";
|
|
127
127
|
|
|
128
|
+
// src/head.ts
|
|
129
|
+
var ABSOLUTE_ASSET_URL_PATTERN = /^(?:[a-zA-Z][a-zA-Z\d+\-.]*:|\/\/|#)/;
|
|
130
|
+
function normalizeHeadBasename(value) {
|
|
131
|
+
const trimmed = value?.trim();
|
|
132
|
+
if (!trimmed) {
|
|
133
|
+
return void 0;
|
|
134
|
+
}
|
|
135
|
+
if (trimmed === "/") {
|
|
136
|
+
return "/";
|
|
137
|
+
}
|
|
138
|
+
return trimmed.replace(/\/+$/, "");
|
|
139
|
+
}
|
|
140
|
+
function resolveHeadAssetUrl(assetUrl, basename) {
|
|
141
|
+
const normalizedAssetUrl = assetUrl.trim();
|
|
142
|
+
const normalizedBasename = normalizeHeadBasename(basename);
|
|
143
|
+
if (!normalizedAssetUrl || !normalizedBasename) {
|
|
144
|
+
return normalizedAssetUrl;
|
|
145
|
+
}
|
|
146
|
+
if (ABSOLUTE_ASSET_URL_PATTERN.test(normalizedAssetUrl)) {
|
|
147
|
+
return normalizedAssetUrl;
|
|
148
|
+
}
|
|
149
|
+
if (normalizedBasename !== "/" && (normalizedAssetUrl === normalizedBasename || normalizedAssetUrl.startsWith(`${normalizedBasename}/`))) {
|
|
150
|
+
return normalizedAssetUrl;
|
|
151
|
+
}
|
|
152
|
+
if (normalizedBasename === "/") {
|
|
153
|
+
return normalizedAssetUrl.startsWith("/") ? normalizedAssetUrl : `/${normalizedAssetUrl}`;
|
|
154
|
+
}
|
|
155
|
+
if (normalizedAssetUrl.startsWith("/")) {
|
|
156
|
+
return `${normalizedBasename}${normalizedAssetUrl}`;
|
|
157
|
+
}
|
|
158
|
+
return `${normalizedBasename}/${normalizedAssetUrl}`;
|
|
159
|
+
}
|
|
160
|
+
function normalizeHeadConfig(head, inheritedBasename) {
|
|
161
|
+
if (!head) {
|
|
162
|
+
return void 0;
|
|
163
|
+
}
|
|
164
|
+
const effectiveBasename = normalizeHeadBasename(head.basename) ?? normalizeHeadBasename(inheritedBasename);
|
|
165
|
+
const normalizedHead = {
|
|
166
|
+
...head,
|
|
167
|
+
...effectiveBasename ? { basename: effectiveBasename } : {}
|
|
168
|
+
};
|
|
169
|
+
if (normalizedHead.favicon) {
|
|
170
|
+
normalizedHead.favicon = resolveHeadAssetUrl(
|
|
171
|
+
normalizedHead.favicon,
|
|
172
|
+
effectiveBasename
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
if (normalizedHead.links) {
|
|
176
|
+
normalizedHead.links = normalizedHead.links.map((link) => ({
|
|
177
|
+
...link,
|
|
178
|
+
href: resolveHeadAssetUrl(link.href, effectiveBasename)
|
|
179
|
+
}));
|
|
180
|
+
}
|
|
181
|
+
return normalizedHead;
|
|
182
|
+
}
|
|
183
|
+
|
|
128
184
|
// src/router-runtime.tsx
|
|
129
185
|
import React from "react";
|
|
130
186
|
var ROUTE_CHILDREN_TAG = "webframez-route-children";
|
|
@@ -340,7 +396,8 @@ function mergeHead(...configs) {
|
|
|
340
396
|
meta: [],
|
|
341
397
|
links: []
|
|
342
398
|
};
|
|
343
|
-
for (const
|
|
399
|
+
for (const candidate of configs) {
|
|
400
|
+
const config = normalizeHeadConfig(candidate, merged.basename);
|
|
344
401
|
if (!config) {
|
|
345
402
|
continue;
|
|
346
403
|
}
|
|
@@ -350,6 +407,9 @@ function mergeHead(...configs) {
|
|
|
350
407
|
if (config.description) {
|
|
351
408
|
merged.description = config.description;
|
|
352
409
|
}
|
|
410
|
+
if (config.basename) {
|
|
411
|
+
merged.basename = config.basename;
|
|
412
|
+
}
|
|
353
413
|
if (config.favicon) {
|
|
354
414
|
merged.favicon = config.favicon;
|
|
355
415
|
}
|
|
@@ -363,22 +423,23 @@ function mergeHead(...configs) {
|
|
|
363
423
|
return merged;
|
|
364
424
|
}
|
|
365
425
|
function renderHeadToString(head) {
|
|
426
|
+
const normalizedHead = normalizeHeadConfig(head) ?? head;
|
|
366
427
|
const tags = [];
|
|
367
|
-
if (
|
|
428
|
+
if (normalizedHead.description) {
|
|
368
429
|
tags.push(
|
|
369
|
-
`<meta ${createManagedAttributes()} name="description" content="${escapeHtml(
|
|
430
|
+
`<meta ${createManagedAttributes()} name="description" content="${escapeHtml(normalizedHead.description)}" />`
|
|
370
431
|
);
|
|
371
432
|
}
|
|
372
|
-
if (
|
|
433
|
+
if (normalizedHead.favicon) {
|
|
373
434
|
tags.push(
|
|
374
|
-
`<link ${createManagedAttributes()} rel="icon" href="${escapeHtml(
|
|
435
|
+
`<link ${createManagedAttributes()} rel="icon" href="${escapeHtml(normalizedHead.favicon)}" />`
|
|
375
436
|
);
|
|
376
437
|
}
|
|
377
|
-
for (const meta of
|
|
438
|
+
for (const meta of normalizedHead.meta ?? []) {
|
|
378
439
|
const attrs = Object.entries(meta).filter(([, value]) => Boolean(value)).map(([key, value]) => `${key}="${escapeHtml(String(value))}"`).join(" ");
|
|
379
440
|
tags.push(`<meta ${createManagedAttributes()} ${attrs} />`);
|
|
380
441
|
}
|
|
381
|
-
for (const link of
|
|
442
|
+
for (const link of normalizedHead.links ?? []) {
|
|
382
443
|
const attrs = Object.entries(link).filter(([, value]) => Boolean(value)).map(([key, value]) => `${key}="${escapeHtml(String(value))}"`).join(" ");
|
|
383
444
|
tags.push(`<link ${createManagedAttributes()} ${attrs} />`);
|
|
384
445
|
}
|
|
@@ -594,6 +655,12 @@ function createFileRouter(options) {
|
|
|
594
655
|
params: {},
|
|
595
656
|
searchParams: input.searchParams,
|
|
596
657
|
cookies: input.cookies ?? {},
|
|
658
|
+
request: input.request ?? {
|
|
659
|
+
host: null,
|
|
660
|
+
pathname,
|
|
661
|
+
originalPathname: pathname,
|
|
662
|
+
headers: {}
|
|
663
|
+
},
|
|
597
664
|
abort: (options2) => {
|
|
598
665
|
throw createAbort(options2);
|
|
599
666
|
}
|
|
@@ -821,6 +888,60 @@ function normalizeBasePath(basePath) {
|
|
|
821
888
|
const withLeadingSlash = basePath.startsWith("/") ? basePath : `/${basePath}`;
|
|
822
889
|
return withLeadingSlash.endsWith("/") ? withLeadingSlash.slice(0, -1) : withLeadingSlash;
|
|
823
890
|
}
|
|
891
|
+
function normalizeRequestHostValue(value) {
|
|
892
|
+
const trimmed = value.trim();
|
|
893
|
+
if (!trimmed) {
|
|
894
|
+
return null;
|
|
895
|
+
}
|
|
896
|
+
const firstValue = trimmed.split(",")[0]?.trim() || "";
|
|
897
|
+
if (!firstValue) {
|
|
898
|
+
return null;
|
|
899
|
+
}
|
|
900
|
+
if (firstValue.startsWith("[")) {
|
|
901
|
+
const closingIndex = firstValue.indexOf("]");
|
|
902
|
+
if (closingIndex > 0) {
|
|
903
|
+
return firstValue.slice(1, closingIndex).toLowerCase();
|
|
904
|
+
}
|
|
905
|
+
}
|
|
906
|
+
return firstValue.replace(/:\d+$/, "").toLowerCase();
|
|
907
|
+
}
|
|
908
|
+
function getRequestHost(req) {
|
|
909
|
+
const candidates = [
|
|
910
|
+
req.headers["x-forwarded-host"],
|
|
911
|
+
req.headers["x-original-host"],
|
|
912
|
+
req.headers.host
|
|
913
|
+
];
|
|
914
|
+
for (const candidate of candidates) {
|
|
915
|
+
if (Array.isArray(candidate)) {
|
|
916
|
+
for (const singleValue of candidate) {
|
|
917
|
+
if (typeof singleValue !== "string") {
|
|
918
|
+
continue;
|
|
919
|
+
}
|
|
920
|
+
const normalized2 = normalizeRequestHostValue(singleValue);
|
|
921
|
+
if (normalized2) {
|
|
922
|
+
return normalized2;
|
|
923
|
+
}
|
|
924
|
+
}
|
|
925
|
+
continue;
|
|
926
|
+
}
|
|
927
|
+
if (typeof candidate !== "string") {
|
|
928
|
+
continue;
|
|
929
|
+
}
|
|
930
|
+
const normalized = normalizeRequestHostValue(candidate);
|
|
931
|
+
if (normalized) {
|
|
932
|
+
return normalized;
|
|
933
|
+
}
|
|
934
|
+
}
|
|
935
|
+
return null;
|
|
936
|
+
}
|
|
937
|
+
function createRouteRequestContext(req, pathname, originalPathname) {
|
|
938
|
+
return {
|
|
939
|
+
host: getRequestHost(req),
|
|
940
|
+
pathname,
|
|
941
|
+
originalPathname,
|
|
942
|
+
headers: req.headers
|
|
943
|
+
};
|
|
944
|
+
}
|
|
824
945
|
function stripBasePath(pathname, basePath) {
|
|
825
946
|
if (!basePath) {
|
|
826
947
|
return pathname;
|
|
@@ -1166,13 +1287,19 @@ function createNodeRequestHandler(options) {
|
|
|
1166
1287
|
}
|
|
1167
1288
|
if (url.pathname === rscPath) {
|
|
1168
1289
|
const pathname = stripBasePath(url.searchParams.get("path") || "/", basePath);
|
|
1290
|
+
const requestContext = createRouteRequestContext(
|
|
1291
|
+
req,
|
|
1292
|
+
pathname,
|
|
1293
|
+
url.searchParams.get("path") || "/"
|
|
1294
|
+
);
|
|
1169
1295
|
const search = new URLSearchParams(url.searchParams.get("search") || "");
|
|
1170
1296
|
const resolved2 = await withRequestBasename(
|
|
1171
1297
|
basePath,
|
|
1172
1298
|
() => router.resolve({
|
|
1173
1299
|
pathname,
|
|
1174
1300
|
searchParams: parseSearchParams(search),
|
|
1175
|
-
cookies: requestCookies
|
|
1301
|
+
cookies: requestCookies,
|
|
1302
|
+
request: requestContext
|
|
1176
1303
|
})
|
|
1177
1304
|
);
|
|
1178
1305
|
const payload = {
|
|
@@ -1212,7 +1339,12 @@ function createNodeRequestHandler(options) {
|
|
|
1212
1339
|
() => router.resolve({
|
|
1213
1340
|
pathname: stripBasePath(url.pathname, basePath),
|
|
1214
1341
|
searchParams: parseSearchParams(url.searchParams),
|
|
1215
|
-
cookies: requestCookies
|
|
1342
|
+
cookies: requestCookies,
|
|
1343
|
+
request: createRouteRequestContext(
|
|
1344
|
+
req,
|
|
1345
|
+
stripBasePath(url.pathname, basePath),
|
|
1346
|
+
url.pathname
|
|
1347
|
+
)
|
|
1216
1348
|
})
|
|
1217
1349
|
);
|
|
1218
1350
|
const initialPayload = {
|
|
@@ -1256,7 +1388,7 @@ function createNodeRequestHandler(options) {
|
|
|
1256
1388
|
rscEndpoint: rscPath,
|
|
1257
1389
|
rootHtml: createInitialHtmlErrorMarkup("Initial HTML render failed."),
|
|
1258
1390
|
initialFlightData,
|
|
1259
|
-
basename: basePath,
|
|
1391
|
+
basename: resolved?.head?.basename ?? basePath,
|
|
1260
1392
|
liveReloadPath: liveReloadPath || void 0,
|
|
1261
1393
|
liveReloadServerId: liveReloadPath ? devServerId : void 0
|
|
1262
1394
|
})
|
|
@@ -1275,7 +1407,7 @@ function createNodeRequestHandler(options) {
|
|
|
1275
1407
|
rscEndpoint: rscPath,
|
|
1276
1408
|
rootHtml,
|
|
1277
1409
|
initialFlightData,
|
|
1278
|
-
basename: basePath,
|
|
1410
|
+
basename: resolved.head.basename ?? basePath,
|
|
1279
1411
|
liveReloadPath: liveReloadPath || void 0,
|
|
1280
1412
|
liveReloadServerId: liveReloadPath ? devServerId : void 0
|
|
1281
1413
|
})
|