@webtypen/webframez-react 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +227 -0
- package/dist/client.cjs +1 -0
- package/dist/client.d.ts +32 -0
- package/dist/client.js +1 -0
- package/dist/http.cjs +704 -0
- package/dist/http.d.ts +16 -0
- package/dist/http.js +678 -0
- package/dist/index.cjs +894 -0
- package/dist/index.d.ts +30 -0
- package/dist/index.js +857 -0
- package/dist/navigation.cjs +149 -0
- package/dist/navigation.d.ts +16 -0
- package/dist/navigation.js +125 -0
- package/dist/router.cjs +386 -0
- package/dist/router.d.ts +21 -0
- package/dist/router.js +355 -0
- package/dist/types.cjs +17 -0
- package/dist/types.d.ts +87 -0
- package/dist/types.js +0 -0
- package/dist/webframez-core.cjs +852 -0
- package/dist/webframez-core.d.ts +19 -0
- package/dist/webframez-core.js +823 -0
- package/package.json +84 -0
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/navigation.tsx
|
|
21
|
+
var navigation_exports = {};
|
|
22
|
+
__export(navigation_exports, {
|
|
23
|
+
Link: () => Link,
|
|
24
|
+
Redirect: () => Redirect
|
|
25
|
+
});
|
|
26
|
+
module.exports = __toCommonJS(navigation_exports);
|
|
27
|
+
var import_jsx_runtime = require("react/jsx-runtime");
|
|
28
|
+
function getDefaultBasename() {
|
|
29
|
+
const globalValue = globalThis.__RSC_BASENAME;
|
|
30
|
+
if (globalValue && globalValue !== "/") {
|
|
31
|
+
return globalValue;
|
|
32
|
+
}
|
|
33
|
+
if (typeof window === "undefined") {
|
|
34
|
+
return "";
|
|
35
|
+
}
|
|
36
|
+
const value = window.__RSC_BASENAME;
|
|
37
|
+
return value && value !== "/" ? value : "";
|
|
38
|
+
}
|
|
39
|
+
function getClientRouter() {
|
|
40
|
+
if (typeof window === "undefined") {
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
const value = window.__WEBFRAMEZ_ROUTER__;
|
|
44
|
+
return value ?? null;
|
|
45
|
+
}
|
|
46
|
+
function withLeadingSlash(value) {
|
|
47
|
+
return value.startsWith("/") ? value : `/${value}`;
|
|
48
|
+
}
|
|
49
|
+
function normalizeBase(base) {
|
|
50
|
+
if (!base || base.trim() === "" || base === "/") {
|
|
51
|
+
return "";
|
|
52
|
+
}
|
|
53
|
+
const withSlash = withLeadingSlash(base.trim());
|
|
54
|
+
return withSlash.endsWith("/") ? withSlash.slice(0, -1) : withSlash;
|
|
55
|
+
}
|
|
56
|
+
function isExternal(href) {
|
|
57
|
+
return /^[a-zA-Z][a-zA-Z\d+\-.]*:/.test(href) || href.startsWith("//");
|
|
58
|
+
}
|
|
59
|
+
function resolveHref(to, basename) {
|
|
60
|
+
if (!to || to.trim() === "") {
|
|
61
|
+
return "/";
|
|
62
|
+
}
|
|
63
|
+
const trimmed = to.trim();
|
|
64
|
+
if (isExternal(trimmed) || trimmed.startsWith("#")) {
|
|
65
|
+
return trimmed;
|
|
66
|
+
}
|
|
67
|
+
const [pathPart, hashPart] = trimmed.split("#", 2);
|
|
68
|
+
const [pathnamePart, queryPart] = pathPart.split("?", 2);
|
|
69
|
+
const pathname = withLeadingSlash(pathnamePart || "/");
|
|
70
|
+
const base = normalizeBase(basename);
|
|
71
|
+
let resolvedPath = pathname;
|
|
72
|
+
if (base && pathname !== "/" && !pathname.startsWith(`${base}/`) && pathname !== base) {
|
|
73
|
+
resolvedPath = `${base}${pathname}`;
|
|
74
|
+
} else if (base && pathname === "/") {
|
|
75
|
+
resolvedPath = base;
|
|
76
|
+
}
|
|
77
|
+
const query = queryPart ? `?${queryPart}` : "";
|
|
78
|
+
const hash = hashPart ? `#${hashPart}` : "";
|
|
79
|
+
return `${resolvedPath}${query}${hash}`;
|
|
80
|
+
}
|
|
81
|
+
function Link({ to, basename, onClick, ...rest }) {
|
|
82
|
+
const resolvedHref = resolveHref(to, basename ?? getDefaultBasename());
|
|
83
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
84
|
+
"a",
|
|
85
|
+
{
|
|
86
|
+
...rest,
|
|
87
|
+
href: resolvedHref,
|
|
88
|
+
onClick: (event) => {
|
|
89
|
+
onClick?.(event);
|
|
90
|
+
if (event.defaultPrevented) {
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
if (event.button !== 0 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) {
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
if (rest.target && rest.target !== "_self") {
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
if (isExternal(resolvedHref)) {
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
const router = getClientRouter();
|
|
103
|
+
if (!router) {
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
event.preventDefault();
|
|
107
|
+
router.push(resolvedHref);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
function Redirect({ to, basename, replace = true }) {
|
|
113
|
+
const resolvedHref = resolveHref(to, basename ?? getDefaultBasename());
|
|
114
|
+
if (typeof window !== "undefined") {
|
|
115
|
+
const currentHref = `${window.location.pathname}${window.location.search}${window.location.hash}`;
|
|
116
|
+
if (resolvedHref !== currentHref) {
|
|
117
|
+
setTimeout(() => {
|
|
118
|
+
if (isExternal(resolvedHref)) {
|
|
119
|
+
if (replace) {
|
|
120
|
+
window.location.replace(resolvedHref);
|
|
121
|
+
} else {
|
|
122
|
+
window.location.assign(resolvedHref);
|
|
123
|
+
}
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
const router = getClientRouter();
|
|
127
|
+
if (router) {
|
|
128
|
+
if (replace) {
|
|
129
|
+
router.replace(resolvedHref);
|
|
130
|
+
} else {
|
|
131
|
+
router.push(resolvedHref);
|
|
132
|
+
}
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
if (replace) {
|
|
136
|
+
window.location.replace(resolvedHref);
|
|
137
|
+
} else {
|
|
138
|
+
window.location.assign(resolvedHref);
|
|
139
|
+
}
|
|
140
|
+
}, 0);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
return null;
|
|
144
|
+
}
|
|
145
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
146
|
+
0 && (module.exports = {
|
|
147
|
+
Link,
|
|
148
|
+
Redirect
|
|
149
|
+
});
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type React from "react";
|
|
2
|
+
|
|
3
|
+
export type LinkProps = Omit<React.AnchorHTMLAttributes<HTMLAnchorElement>, "href"> & {
|
|
4
|
+
to: string;
|
|
5
|
+
basename?: string;
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
export function Link(props: LinkProps): React.ReactElement;
|
|
9
|
+
|
|
10
|
+
export type RedirectProps = {
|
|
11
|
+
to: string;
|
|
12
|
+
basename?: string;
|
|
13
|
+
replace?: boolean;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export function Redirect(props: RedirectProps): null;
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
// src/navigation.tsx
|
|
4
|
+
import { jsx } from "react/jsx-runtime";
|
|
5
|
+
function getDefaultBasename() {
|
|
6
|
+
const globalValue = globalThis.__RSC_BASENAME;
|
|
7
|
+
if (globalValue && globalValue !== "/") {
|
|
8
|
+
return globalValue;
|
|
9
|
+
}
|
|
10
|
+
if (typeof window === "undefined") {
|
|
11
|
+
return "";
|
|
12
|
+
}
|
|
13
|
+
const value = window.__RSC_BASENAME;
|
|
14
|
+
return value && value !== "/" ? value : "";
|
|
15
|
+
}
|
|
16
|
+
function getClientRouter() {
|
|
17
|
+
if (typeof window === "undefined") {
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
const value = window.__WEBFRAMEZ_ROUTER__;
|
|
21
|
+
return value ?? null;
|
|
22
|
+
}
|
|
23
|
+
function withLeadingSlash(value) {
|
|
24
|
+
return value.startsWith("/") ? value : `/${value}`;
|
|
25
|
+
}
|
|
26
|
+
function normalizeBase(base) {
|
|
27
|
+
if (!base || base.trim() === "" || base === "/") {
|
|
28
|
+
return "";
|
|
29
|
+
}
|
|
30
|
+
const withSlash = withLeadingSlash(base.trim());
|
|
31
|
+
return withSlash.endsWith("/") ? withSlash.slice(0, -1) : withSlash;
|
|
32
|
+
}
|
|
33
|
+
function isExternal(href) {
|
|
34
|
+
return /^[a-zA-Z][a-zA-Z\d+\-.]*:/.test(href) || href.startsWith("//");
|
|
35
|
+
}
|
|
36
|
+
function resolveHref(to, basename) {
|
|
37
|
+
if (!to || to.trim() === "") {
|
|
38
|
+
return "/";
|
|
39
|
+
}
|
|
40
|
+
const trimmed = to.trim();
|
|
41
|
+
if (isExternal(trimmed) || trimmed.startsWith("#")) {
|
|
42
|
+
return trimmed;
|
|
43
|
+
}
|
|
44
|
+
const [pathPart, hashPart] = trimmed.split("#", 2);
|
|
45
|
+
const [pathnamePart, queryPart] = pathPart.split("?", 2);
|
|
46
|
+
const pathname = withLeadingSlash(pathnamePart || "/");
|
|
47
|
+
const base = normalizeBase(basename);
|
|
48
|
+
let resolvedPath = pathname;
|
|
49
|
+
if (base && pathname !== "/" && !pathname.startsWith(`${base}/`) && pathname !== base) {
|
|
50
|
+
resolvedPath = `${base}${pathname}`;
|
|
51
|
+
} else if (base && pathname === "/") {
|
|
52
|
+
resolvedPath = base;
|
|
53
|
+
}
|
|
54
|
+
const query = queryPart ? `?${queryPart}` : "";
|
|
55
|
+
const hash = hashPart ? `#${hashPart}` : "";
|
|
56
|
+
return `${resolvedPath}${query}${hash}`;
|
|
57
|
+
}
|
|
58
|
+
function Link({ to, basename, onClick, ...rest }) {
|
|
59
|
+
const resolvedHref = resolveHref(to, basename ?? getDefaultBasename());
|
|
60
|
+
return /* @__PURE__ */ jsx(
|
|
61
|
+
"a",
|
|
62
|
+
{
|
|
63
|
+
...rest,
|
|
64
|
+
href: resolvedHref,
|
|
65
|
+
onClick: (event) => {
|
|
66
|
+
onClick?.(event);
|
|
67
|
+
if (event.defaultPrevented) {
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
if (event.button !== 0 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) {
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
if (rest.target && rest.target !== "_self") {
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
if (isExternal(resolvedHref)) {
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
const router = getClientRouter();
|
|
80
|
+
if (!router) {
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
event.preventDefault();
|
|
84
|
+
router.push(resolvedHref);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
function Redirect({ to, basename, replace = true }) {
|
|
90
|
+
const resolvedHref = resolveHref(to, basename ?? getDefaultBasename());
|
|
91
|
+
if (typeof window !== "undefined") {
|
|
92
|
+
const currentHref = `${window.location.pathname}${window.location.search}${window.location.hash}`;
|
|
93
|
+
if (resolvedHref !== currentHref) {
|
|
94
|
+
setTimeout(() => {
|
|
95
|
+
if (isExternal(resolvedHref)) {
|
|
96
|
+
if (replace) {
|
|
97
|
+
window.location.replace(resolvedHref);
|
|
98
|
+
} else {
|
|
99
|
+
window.location.assign(resolvedHref);
|
|
100
|
+
}
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
const router = getClientRouter();
|
|
104
|
+
if (router) {
|
|
105
|
+
if (replace) {
|
|
106
|
+
router.replace(resolvedHref);
|
|
107
|
+
} else {
|
|
108
|
+
router.push(resolvedHref);
|
|
109
|
+
}
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
if (replace) {
|
|
113
|
+
window.location.replace(resolvedHref);
|
|
114
|
+
} else {
|
|
115
|
+
window.location.assign(resolvedHref);
|
|
116
|
+
}
|
|
117
|
+
}, 0);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
return null;
|
|
121
|
+
}
|
|
122
|
+
export {
|
|
123
|
+
Link,
|
|
124
|
+
Redirect
|
|
125
|
+
};
|
package/dist/router.cjs
ADDED
|
@@ -0,0 +1,386 @@
|
|
|
1
|
+
var __create = Object.create;
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
6
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
+
var __export = (target, all) => {
|
|
8
|
+
for (var name in all)
|
|
9
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
10
|
+
};
|
|
11
|
+
var __copyProps = (to, from, except, desc) => {
|
|
12
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
13
|
+
for (let key of __getOwnPropNames(from))
|
|
14
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
15
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
16
|
+
}
|
|
17
|
+
return to;
|
|
18
|
+
};
|
|
19
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
20
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
21
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
22
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
23
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
24
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
25
|
+
mod
|
|
26
|
+
));
|
|
27
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
28
|
+
|
|
29
|
+
// src/router.ts
|
|
30
|
+
var router_exports = {};
|
|
31
|
+
__export(router_exports, {
|
|
32
|
+
RouteChildren: () => RouteChildren,
|
|
33
|
+
createFileRouter: () => createFileRouter,
|
|
34
|
+
parseSearchParams: () => parseSearchParams,
|
|
35
|
+
renderHeadToString: () => renderHeadToString
|
|
36
|
+
});
|
|
37
|
+
module.exports = __toCommonJS(router_exports);
|
|
38
|
+
|
|
39
|
+
// src/file-router.tsx
|
|
40
|
+
var import_node_fs = __toESM(require("node:fs"), 1);
|
|
41
|
+
var import_node_path = __toESM(require("node:path"), 1);
|
|
42
|
+
|
|
43
|
+
// src/router-runtime.tsx
|
|
44
|
+
var import_react = __toESM(require("react"), 1);
|
|
45
|
+
var ROUTE_CHILDREN_TAG = "webframez-route-children";
|
|
46
|
+
var ROUTE_CHILDREN_SENTINEL = "__webframezRouteChildren";
|
|
47
|
+
var ROUTE_CHILDREN_DISPLAY_NAME = "WebframezRouteChildren";
|
|
48
|
+
var RouteChildrenImpl = () => import_react.default.createElement(ROUTE_CHILDREN_TAG);
|
|
49
|
+
RouteChildrenImpl.displayName = ROUTE_CHILDREN_DISPLAY_NAME;
|
|
50
|
+
RouteChildrenImpl[ROUTE_CHILDREN_SENTINEL] = true;
|
|
51
|
+
var RouteChildren = RouteChildrenImpl;
|
|
52
|
+
function isRouteChildrenType(type) {
|
|
53
|
+
if (type === ROUTE_CHILDREN_TAG || type === RouteChildren) {
|
|
54
|
+
return true;
|
|
55
|
+
}
|
|
56
|
+
if (!type || typeof type !== "function" && typeof type !== "object") {
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
try {
|
|
60
|
+
const candidate = type;
|
|
61
|
+
return candidate[ROUTE_CHILDREN_SENTINEL] === true || candidate.displayName === ROUTE_CHILDREN_DISPLAY_NAME || candidate.name === "RouteChildren";
|
|
62
|
+
} catch {
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
function injectRouteChildren(node, routeChildren) {
|
|
67
|
+
if (node === null || node === void 0 || typeof node === "boolean") {
|
|
68
|
+
return node;
|
|
69
|
+
}
|
|
70
|
+
if (Array.isArray(node)) {
|
|
71
|
+
let changed = false;
|
|
72
|
+
const next = node.map((child) => {
|
|
73
|
+
const injected = injectRouteChildren(child, routeChildren);
|
|
74
|
+
if (injected !== child) {
|
|
75
|
+
changed = true;
|
|
76
|
+
}
|
|
77
|
+
return injected;
|
|
78
|
+
});
|
|
79
|
+
return changed ? next : node;
|
|
80
|
+
}
|
|
81
|
+
if (!import_react.default.isValidElement(node)) {
|
|
82
|
+
return node;
|
|
83
|
+
}
|
|
84
|
+
if (isRouteChildrenType(node.type)) {
|
|
85
|
+
return routeChildren;
|
|
86
|
+
}
|
|
87
|
+
const props = node.props;
|
|
88
|
+
if (!("children" in props)) {
|
|
89
|
+
return node;
|
|
90
|
+
}
|
|
91
|
+
const nextChildren = injectRouteChildren(props.children, routeChildren);
|
|
92
|
+
if (nextChildren === props.children) {
|
|
93
|
+
return node;
|
|
94
|
+
}
|
|
95
|
+
if (Array.isArray(nextChildren)) {
|
|
96
|
+
return import_react.default.cloneElement(node, void 0, ...nextChildren);
|
|
97
|
+
}
|
|
98
|
+
return import_react.default.cloneElement(node, void 0, nextChildren);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// src/file-router.tsx
|
|
102
|
+
var import_jsx_runtime = require("react/jsx-runtime");
|
|
103
|
+
function normalizePathname(pathname) {
|
|
104
|
+
const trimmed = pathname.replace(/\/+$/, "") || "/";
|
|
105
|
+
return trimmed.startsWith("/") ? trimmed : `/${trimmed}`;
|
|
106
|
+
}
|
|
107
|
+
function splitSegments(pathname) {
|
|
108
|
+
const normalized = normalizePathname(pathname);
|
|
109
|
+
if (normalized === "/") {
|
|
110
|
+
return [];
|
|
111
|
+
}
|
|
112
|
+
return normalized.slice(1).split("/").filter(Boolean);
|
|
113
|
+
}
|
|
114
|
+
function walkFiles(dir) {
|
|
115
|
+
if (!import_node_fs.default.existsSync(dir)) {
|
|
116
|
+
return [];
|
|
117
|
+
}
|
|
118
|
+
const result = [];
|
|
119
|
+
const stack = [dir];
|
|
120
|
+
while (stack.length > 0) {
|
|
121
|
+
const current = stack.pop();
|
|
122
|
+
if (!current) {
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
for (const entry of import_node_fs.default.readdirSync(current, { withFileTypes: true })) {
|
|
126
|
+
const fullPath = import_node_path.default.join(current, entry.name);
|
|
127
|
+
if (entry.isDirectory()) {
|
|
128
|
+
stack.push(fullPath);
|
|
129
|
+
} else if (entry.isFile()) {
|
|
130
|
+
result.push(fullPath);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
return result;
|
|
135
|
+
}
|
|
136
|
+
function readSearchParams(urlSearchParams) {
|
|
137
|
+
const output = {};
|
|
138
|
+
for (const key of urlSearchParams.keys()) {
|
|
139
|
+
const values = urlSearchParams.getAll(key);
|
|
140
|
+
output[key] = values.length <= 1 ? values[0] ?? "" : values;
|
|
141
|
+
}
|
|
142
|
+
return output;
|
|
143
|
+
}
|
|
144
|
+
function escapeHtml(value) {
|
|
145
|
+
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/\"/g, """).replace(/'/g, "'");
|
|
146
|
+
}
|
|
147
|
+
function toRouteEntry(pagesDir, filePath) {
|
|
148
|
+
const normalized = filePath.replace(/\\/g, "/");
|
|
149
|
+
if (!normalized.endsWith("/index.js")) {
|
|
150
|
+
return null;
|
|
151
|
+
}
|
|
152
|
+
const relativeDir = import_node_path.default.dirname(import_node_path.default.relative(pagesDir, filePath)).replace(/\\/g, "/");
|
|
153
|
+
const segments = relativeDir === "." ? [] : relativeDir.split("/").filter(Boolean);
|
|
154
|
+
const staticCount = segments.filter((segment) => !segment.startsWith("[")).length;
|
|
155
|
+
return {
|
|
156
|
+
filePath,
|
|
157
|
+
segments,
|
|
158
|
+
staticCount
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
function matchRoute(entry, pathname) {
|
|
162
|
+
const targetSegments = splitSegments(pathname);
|
|
163
|
+
if (entry.segments.length !== targetSegments.length) {
|
|
164
|
+
return null;
|
|
165
|
+
}
|
|
166
|
+
const params = {};
|
|
167
|
+
for (let index = 0; index < entry.segments.length; index += 1) {
|
|
168
|
+
const routeSegment = entry.segments[index];
|
|
169
|
+
const targetSegment = targetSegments[index];
|
|
170
|
+
if (routeSegment.startsWith("[") && routeSegment.endsWith("]")) {
|
|
171
|
+
const paramName = routeSegment.slice(1, -1);
|
|
172
|
+
if (!paramName) {
|
|
173
|
+
return null;
|
|
174
|
+
}
|
|
175
|
+
params[paramName] = decodeURIComponent(targetSegment);
|
|
176
|
+
continue;
|
|
177
|
+
}
|
|
178
|
+
if (routeSegment !== targetSegment) {
|
|
179
|
+
return null;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
return params;
|
|
183
|
+
}
|
|
184
|
+
function mergeHead(...configs) {
|
|
185
|
+
const merged = {
|
|
186
|
+
meta: [],
|
|
187
|
+
links: []
|
|
188
|
+
};
|
|
189
|
+
for (const config of configs) {
|
|
190
|
+
if (!config) {
|
|
191
|
+
continue;
|
|
192
|
+
}
|
|
193
|
+
if (config.title) {
|
|
194
|
+
merged.title = config.title;
|
|
195
|
+
}
|
|
196
|
+
if (config.description) {
|
|
197
|
+
merged.description = config.description;
|
|
198
|
+
}
|
|
199
|
+
if (config.favicon) {
|
|
200
|
+
merged.favicon = config.favicon;
|
|
201
|
+
}
|
|
202
|
+
if (config.meta) {
|
|
203
|
+
merged.meta?.push(...config.meta);
|
|
204
|
+
}
|
|
205
|
+
if (config.links) {
|
|
206
|
+
merged.links?.push(...config.links);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
return merged;
|
|
210
|
+
}
|
|
211
|
+
function renderHeadToString(head) {
|
|
212
|
+
const tags = [];
|
|
213
|
+
if (head.description) {
|
|
214
|
+
tags.push(
|
|
215
|
+
`<meta name="description" content="${escapeHtml(head.description)}" />`
|
|
216
|
+
);
|
|
217
|
+
}
|
|
218
|
+
if (head.favicon) {
|
|
219
|
+
tags.push(`<link rel="icon" href="${escapeHtml(head.favicon)}" />`);
|
|
220
|
+
}
|
|
221
|
+
for (const meta of head.meta ?? []) {
|
|
222
|
+
const attrs = Object.entries(meta).filter(([, value]) => Boolean(value)).map(([key, value]) => `${key}="${escapeHtml(String(value))}"`).join(" ");
|
|
223
|
+
tags.push(`<meta ${attrs} />`);
|
|
224
|
+
}
|
|
225
|
+
for (const link of head.links ?? []) {
|
|
226
|
+
const attrs = Object.entries(link).filter(([, value]) => Boolean(value)).map(([key, value]) => `${key}="${escapeHtml(String(value))}"`).join(" ");
|
|
227
|
+
tags.push(`<link ${attrs} />`);
|
|
228
|
+
}
|
|
229
|
+
return tags.join("\n");
|
|
230
|
+
}
|
|
231
|
+
function resolveModule(modulePath) {
|
|
232
|
+
delete require.cache[modulePath];
|
|
233
|
+
return require(modulePath);
|
|
234
|
+
}
|
|
235
|
+
async function resolveHead(candidate, context) {
|
|
236
|
+
if (!candidate.Head) {
|
|
237
|
+
return void 0;
|
|
238
|
+
}
|
|
239
|
+
return candidate.Head(context);
|
|
240
|
+
}
|
|
241
|
+
function findBestMatch(entries, pathname) {
|
|
242
|
+
const matches = [];
|
|
243
|
+
for (const entry of entries) {
|
|
244
|
+
const params = matchRoute(entry, pathname);
|
|
245
|
+
if (params) {
|
|
246
|
+
matches.push({ entry, params });
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
matches.sort((a, b) => {
|
|
250
|
+
if (b.entry.staticCount !== a.entry.staticCount) {
|
|
251
|
+
return b.entry.staticCount - a.entry.staticCount;
|
|
252
|
+
}
|
|
253
|
+
return b.entry.segments.length - a.entry.segments.length;
|
|
254
|
+
});
|
|
255
|
+
return matches[0] ?? null;
|
|
256
|
+
}
|
|
257
|
+
function normalizeAbortStatus(value) {
|
|
258
|
+
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
259
|
+
return 404;
|
|
260
|
+
}
|
|
261
|
+
const normalized = Math.trunc(value);
|
|
262
|
+
if (normalized < 100 || normalized > 599) {
|
|
263
|
+
return 404;
|
|
264
|
+
}
|
|
265
|
+
return normalized;
|
|
266
|
+
}
|
|
267
|
+
function createAbort(options) {
|
|
268
|
+
return {
|
|
269
|
+
__webframezRouteAbort: true,
|
|
270
|
+
statusCode: normalizeAbortStatus(options?.status),
|
|
271
|
+
message: options?.message?.trim() || "Page not found",
|
|
272
|
+
payload: options?.payload
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
function isRouteAbort(value) {
|
|
276
|
+
if (!value || typeof value !== "object") {
|
|
277
|
+
return false;
|
|
278
|
+
}
|
|
279
|
+
try {
|
|
280
|
+
return value.__webframezRouteAbort === true;
|
|
281
|
+
} catch {
|
|
282
|
+
return false;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
function createFileRouter(options) {
|
|
286
|
+
const pagesDir = options.pagesDir;
|
|
287
|
+
const layoutPath = import_node_path.default.join(pagesDir, "layout.js");
|
|
288
|
+
const errorPath = import_node_path.default.join(pagesDir, "errors.js");
|
|
289
|
+
function buildRouteEntries() {
|
|
290
|
+
const files = walkFiles(pagesDir);
|
|
291
|
+
return files.map((filePath) => toRouteEntry(pagesDir, filePath)).filter((entry) => entry !== null).sort((a, b) => {
|
|
292
|
+
if (b.staticCount !== a.staticCount) {
|
|
293
|
+
return b.staticCount - a.staticCount;
|
|
294
|
+
}
|
|
295
|
+
return b.segments.length - a.segments.length;
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
async function renderError(context, statusCode, message, payload) {
|
|
299
|
+
const errorProps = {
|
|
300
|
+
...context,
|
|
301
|
+
statusCode,
|
|
302
|
+
message,
|
|
303
|
+
payload
|
|
304
|
+
};
|
|
305
|
+
const layoutModule = import_node_fs.default.existsSync(layoutPath) ? resolveModule(layoutPath) : null;
|
|
306
|
+
if (!import_node_fs.default.existsSync(errorPath)) {
|
|
307
|
+
const fallback = /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("main", { style: { fontFamily: "system-ui, sans-serif", padding: 24 }, children: [
|
|
308
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("h1", { children: statusCode }),
|
|
309
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { children: message })
|
|
310
|
+
] });
|
|
311
|
+
return {
|
|
312
|
+
statusCode,
|
|
313
|
+
model: fallback,
|
|
314
|
+
head: {
|
|
315
|
+
title: `${statusCode} - ${message}`
|
|
316
|
+
}
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
const errorModule = resolveModule(errorPath);
|
|
320
|
+
const errorNode = errorModule.default(errorProps);
|
|
321
|
+
const layoutHead = layoutModule ? await resolveHead(layoutModule, context) : void 0;
|
|
322
|
+
const errorHead = await resolveHead(errorModule, errorProps);
|
|
323
|
+
const model = layoutModule ? injectRouteChildren(layoutModule.default(context), errorNode) : errorNode;
|
|
324
|
+
return {
|
|
325
|
+
statusCode,
|
|
326
|
+
model,
|
|
327
|
+
head: mergeHead(layoutHead, errorHead)
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
async function resolve(input) {
|
|
331
|
+
const pathname = normalizePathname(input.pathname);
|
|
332
|
+
const contextBase = {
|
|
333
|
+
pathname,
|
|
334
|
+
params: {},
|
|
335
|
+
searchParams: input.searchParams,
|
|
336
|
+
cookies: input.cookies ?? {},
|
|
337
|
+
abort: (options2) => {
|
|
338
|
+
throw createAbort(options2);
|
|
339
|
+
}
|
|
340
|
+
};
|
|
341
|
+
let activeContext = contextBase;
|
|
342
|
+
try {
|
|
343
|
+
const entries = buildRouteEntries();
|
|
344
|
+
const match = findBestMatch(entries, pathname);
|
|
345
|
+
if (!match) {
|
|
346
|
+
return renderError(contextBase, 404, "Page not found");
|
|
347
|
+
}
|
|
348
|
+
const context = {
|
|
349
|
+
...contextBase,
|
|
350
|
+
params: match.params
|
|
351
|
+
};
|
|
352
|
+
activeContext = context;
|
|
353
|
+
const pageModule = resolveModule(match.entry.filePath);
|
|
354
|
+
const layoutModule = import_node_fs.default.existsSync(layoutPath) ? resolveModule(layoutPath) : null;
|
|
355
|
+
const pageNode = pageModule.default(context);
|
|
356
|
+
const layoutHead = layoutModule ? await resolveHead(layoutModule, context) : void 0;
|
|
357
|
+
const pageHead = await resolveHead(pageModule, context);
|
|
358
|
+
const model = layoutModule ? injectRouteChildren(layoutModule.default(context), pageNode) : pageNode;
|
|
359
|
+
return {
|
|
360
|
+
statusCode: 200,
|
|
361
|
+
model,
|
|
362
|
+
head: mergeHead(layoutHead, pageHead)
|
|
363
|
+
};
|
|
364
|
+
} catch (error) {
|
|
365
|
+
if (isRouteAbort(error)) {
|
|
366
|
+
return renderError(activeContext, error.statusCode, error.message, error.payload);
|
|
367
|
+
}
|
|
368
|
+
console.error("[webframez-react] Failed to resolve route", error);
|
|
369
|
+
return renderError(contextBase, 500, "Internal server error");
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
return {
|
|
373
|
+
resolve,
|
|
374
|
+
readSearchParams
|
|
375
|
+
};
|
|
376
|
+
}
|
|
377
|
+
function parseSearchParams(query) {
|
|
378
|
+
return readSearchParams(query);
|
|
379
|
+
}
|
|
380
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
381
|
+
0 && (module.exports = {
|
|
382
|
+
RouteChildren,
|
|
383
|
+
createFileRouter,
|
|
384
|
+
parseSearchParams,
|
|
385
|
+
renderHeadToString
|
|
386
|
+
});
|
package/dist/router.d.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
import type { HeadConfig, RouteSearchParams } from "./types";
|
|
3
|
+
|
|
4
|
+
export type ResolvedRoute = {
|
|
5
|
+
statusCode: number;
|
|
6
|
+
model: ReactNode;
|
|
7
|
+
head: HeadConfig;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export function createFileRouter(options: { pagesDir: string }): {
|
|
11
|
+
resolve(input: {
|
|
12
|
+
pathname: string;
|
|
13
|
+
searchParams: RouteSearchParams;
|
|
14
|
+
cookies?: Record<string, string>;
|
|
15
|
+
}): Promise<ResolvedRoute>;
|
|
16
|
+
readSearchParams(urlSearchParams: URLSearchParams): RouteSearchParams;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export function parseSearchParams(query: URLSearchParams): RouteSearchParams;
|
|
20
|
+
export function renderHeadToString(head: HeadConfig): string;
|
|
21
|
+
export function RouteChildren(): ReactNode;
|