@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/dist/router.js ADDED
@@ -0,0 +1,355 @@
1
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
2
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
3
+ }) : x)(function(x) {
4
+ if (typeof require !== "undefined")
5
+ return require.apply(this, arguments);
6
+ throw Error('Dynamic require of "' + x + '" is not supported');
7
+ });
8
+
9
+ // src/file-router.tsx
10
+ import fs from "node:fs";
11
+ import path from "node:path";
12
+
13
+ // src/router-runtime.tsx
14
+ import React from "react";
15
+ var ROUTE_CHILDREN_TAG = "webframez-route-children";
16
+ var ROUTE_CHILDREN_SENTINEL = "__webframezRouteChildren";
17
+ var ROUTE_CHILDREN_DISPLAY_NAME = "WebframezRouteChildren";
18
+ var RouteChildrenImpl = () => React.createElement(ROUTE_CHILDREN_TAG);
19
+ RouteChildrenImpl.displayName = ROUTE_CHILDREN_DISPLAY_NAME;
20
+ RouteChildrenImpl[ROUTE_CHILDREN_SENTINEL] = true;
21
+ var RouteChildren = RouteChildrenImpl;
22
+ function isRouteChildrenType(type) {
23
+ if (type === ROUTE_CHILDREN_TAG || type === RouteChildren) {
24
+ return true;
25
+ }
26
+ if (!type || typeof type !== "function" && typeof type !== "object") {
27
+ return false;
28
+ }
29
+ try {
30
+ const candidate = type;
31
+ return candidate[ROUTE_CHILDREN_SENTINEL] === true || candidate.displayName === ROUTE_CHILDREN_DISPLAY_NAME || candidate.name === "RouteChildren";
32
+ } catch {
33
+ return false;
34
+ }
35
+ }
36
+ function injectRouteChildren(node, routeChildren) {
37
+ if (node === null || node === void 0 || typeof node === "boolean") {
38
+ return node;
39
+ }
40
+ if (Array.isArray(node)) {
41
+ let changed = false;
42
+ const next = node.map((child) => {
43
+ const injected = injectRouteChildren(child, routeChildren);
44
+ if (injected !== child) {
45
+ changed = true;
46
+ }
47
+ return injected;
48
+ });
49
+ return changed ? next : node;
50
+ }
51
+ if (!React.isValidElement(node)) {
52
+ return node;
53
+ }
54
+ if (isRouteChildrenType(node.type)) {
55
+ return routeChildren;
56
+ }
57
+ const props = node.props;
58
+ if (!("children" in props)) {
59
+ return node;
60
+ }
61
+ const nextChildren = injectRouteChildren(props.children, routeChildren);
62
+ if (nextChildren === props.children) {
63
+ return node;
64
+ }
65
+ if (Array.isArray(nextChildren)) {
66
+ return React.cloneElement(node, void 0, ...nextChildren);
67
+ }
68
+ return React.cloneElement(node, void 0, nextChildren);
69
+ }
70
+
71
+ // src/file-router.tsx
72
+ import { jsx, jsxs } from "react/jsx-runtime";
73
+ function normalizePathname(pathname) {
74
+ const trimmed = pathname.replace(/\/+$/, "") || "/";
75
+ return trimmed.startsWith("/") ? trimmed : `/${trimmed}`;
76
+ }
77
+ function splitSegments(pathname) {
78
+ const normalized = normalizePathname(pathname);
79
+ if (normalized === "/") {
80
+ return [];
81
+ }
82
+ return normalized.slice(1).split("/").filter(Boolean);
83
+ }
84
+ function walkFiles(dir) {
85
+ if (!fs.existsSync(dir)) {
86
+ return [];
87
+ }
88
+ const result = [];
89
+ const stack = [dir];
90
+ while (stack.length > 0) {
91
+ const current = stack.pop();
92
+ if (!current) {
93
+ continue;
94
+ }
95
+ for (const entry of fs.readdirSync(current, { withFileTypes: true })) {
96
+ const fullPath = path.join(current, entry.name);
97
+ if (entry.isDirectory()) {
98
+ stack.push(fullPath);
99
+ } else if (entry.isFile()) {
100
+ result.push(fullPath);
101
+ }
102
+ }
103
+ }
104
+ return result;
105
+ }
106
+ function readSearchParams(urlSearchParams) {
107
+ const output = {};
108
+ for (const key of urlSearchParams.keys()) {
109
+ const values = urlSearchParams.getAll(key);
110
+ output[key] = values.length <= 1 ? values[0] ?? "" : values;
111
+ }
112
+ return output;
113
+ }
114
+ function escapeHtml(value) {
115
+ return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/\"/g, "&quot;").replace(/'/g, "&#39;");
116
+ }
117
+ function toRouteEntry(pagesDir, filePath) {
118
+ const normalized = filePath.replace(/\\/g, "/");
119
+ if (!normalized.endsWith("/index.js")) {
120
+ return null;
121
+ }
122
+ const relativeDir = path.dirname(path.relative(pagesDir, filePath)).replace(/\\/g, "/");
123
+ const segments = relativeDir === "." ? [] : relativeDir.split("/").filter(Boolean);
124
+ const staticCount = segments.filter((segment) => !segment.startsWith("[")).length;
125
+ return {
126
+ filePath,
127
+ segments,
128
+ staticCount
129
+ };
130
+ }
131
+ function matchRoute(entry, pathname) {
132
+ const targetSegments = splitSegments(pathname);
133
+ if (entry.segments.length !== targetSegments.length) {
134
+ return null;
135
+ }
136
+ const params = {};
137
+ for (let index = 0; index < entry.segments.length; index += 1) {
138
+ const routeSegment = entry.segments[index];
139
+ const targetSegment = targetSegments[index];
140
+ if (routeSegment.startsWith("[") && routeSegment.endsWith("]")) {
141
+ const paramName = routeSegment.slice(1, -1);
142
+ if (!paramName) {
143
+ return null;
144
+ }
145
+ params[paramName] = decodeURIComponent(targetSegment);
146
+ continue;
147
+ }
148
+ if (routeSegment !== targetSegment) {
149
+ return null;
150
+ }
151
+ }
152
+ return params;
153
+ }
154
+ function mergeHead(...configs) {
155
+ const merged = {
156
+ meta: [],
157
+ links: []
158
+ };
159
+ for (const config of configs) {
160
+ if (!config) {
161
+ continue;
162
+ }
163
+ if (config.title) {
164
+ merged.title = config.title;
165
+ }
166
+ if (config.description) {
167
+ merged.description = config.description;
168
+ }
169
+ if (config.favicon) {
170
+ merged.favicon = config.favicon;
171
+ }
172
+ if (config.meta) {
173
+ merged.meta?.push(...config.meta);
174
+ }
175
+ if (config.links) {
176
+ merged.links?.push(...config.links);
177
+ }
178
+ }
179
+ return merged;
180
+ }
181
+ function renderHeadToString(head) {
182
+ const tags = [];
183
+ if (head.description) {
184
+ tags.push(
185
+ `<meta name="description" content="${escapeHtml(head.description)}" />`
186
+ );
187
+ }
188
+ if (head.favicon) {
189
+ tags.push(`<link rel="icon" href="${escapeHtml(head.favicon)}" />`);
190
+ }
191
+ for (const meta of head.meta ?? []) {
192
+ const attrs = Object.entries(meta).filter(([, value]) => Boolean(value)).map(([key, value]) => `${key}="${escapeHtml(String(value))}"`).join(" ");
193
+ tags.push(`<meta ${attrs} />`);
194
+ }
195
+ for (const link of head.links ?? []) {
196
+ const attrs = Object.entries(link).filter(([, value]) => Boolean(value)).map(([key, value]) => `${key}="${escapeHtml(String(value))}"`).join(" ");
197
+ tags.push(`<link ${attrs} />`);
198
+ }
199
+ return tags.join("\n");
200
+ }
201
+ function resolveModule(modulePath) {
202
+ delete __require.cache[modulePath];
203
+ return __require(modulePath);
204
+ }
205
+ async function resolveHead(candidate, context) {
206
+ if (!candidate.Head) {
207
+ return void 0;
208
+ }
209
+ return candidate.Head(context);
210
+ }
211
+ function findBestMatch(entries, pathname) {
212
+ const matches = [];
213
+ for (const entry of entries) {
214
+ const params = matchRoute(entry, pathname);
215
+ if (params) {
216
+ matches.push({ entry, params });
217
+ }
218
+ }
219
+ matches.sort((a, b) => {
220
+ if (b.entry.staticCount !== a.entry.staticCount) {
221
+ return b.entry.staticCount - a.entry.staticCount;
222
+ }
223
+ return b.entry.segments.length - a.entry.segments.length;
224
+ });
225
+ return matches[0] ?? null;
226
+ }
227
+ function normalizeAbortStatus(value) {
228
+ if (typeof value !== "number" || !Number.isFinite(value)) {
229
+ return 404;
230
+ }
231
+ const normalized = Math.trunc(value);
232
+ if (normalized < 100 || normalized > 599) {
233
+ return 404;
234
+ }
235
+ return normalized;
236
+ }
237
+ function createAbort(options) {
238
+ return {
239
+ __webframezRouteAbort: true,
240
+ statusCode: normalizeAbortStatus(options?.status),
241
+ message: options?.message?.trim() || "Page not found",
242
+ payload: options?.payload
243
+ };
244
+ }
245
+ function isRouteAbort(value) {
246
+ if (!value || typeof value !== "object") {
247
+ return false;
248
+ }
249
+ try {
250
+ return value.__webframezRouteAbort === true;
251
+ } catch {
252
+ return false;
253
+ }
254
+ }
255
+ function createFileRouter(options) {
256
+ const pagesDir = options.pagesDir;
257
+ const layoutPath = path.join(pagesDir, "layout.js");
258
+ const errorPath = path.join(pagesDir, "errors.js");
259
+ function buildRouteEntries() {
260
+ const files = walkFiles(pagesDir);
261
+ return files.map((filePath) => toRouteEntry(pagesDir, filePath)).filter((entry) => entry !== null).sort((a, b) => {
262
+ if (b.staticCount !== a.staticCount) {
263
+ return b.staticCount - a.staticCount;
264
+ }
265
+ return b.segments.length - a.segments.length;
266
+ });
267
+ }
268
+ async function renderError(context, statusCode, message, payload) {
269
+ const errorProps = {
270
+ ...context,
271
+ statusCode,
272
+ message,
273
+ payload
274
+ };
275
+ const layoutModule = fs.existsSync(layoutPath) ? resolveModule(layoutPath) : null;
276
+ if (!fs.existsSync(errorPath)) {
277
+ const fallback = /* @__PURE__ */ jsxs("main", { style: { fontFamily: "system-ui, sans-serif", padding: 24 }, children: [
278
+ /* @__PURE__ */ jsx("h1", { children: statusCode }),
279
+ /* @__PURE__ */ jsx("p", { children: message })
280
+ ] });
281
+ return {
282
+ statusCode,
283
+ model: fallback,
284
+ head: {
285
+ title: `${statusCode} - ${message}`
286
+ }
287
+ };
288
+ }
289
+ const errorModule = resolveModule(errorPath);
290
+ const errorNode = errorModule.default(errorProps);
291
+ const layoutHead = layoutModule ? await resolveHead(layoutModule, context) : void 0;
292
+ const errorHead = await resolveHead(errorModule, errorProps);
293
+ const model = layoutModule ? injectRouteChildren(layoutModule.default(context), errorNode) : errorNode;
294
+ return {
295
+ statusCode,
296
+ model,
297
+ head: mergeHead(layoutHead, errorHead)
298
+ };
299
+ }
300
+ async function resolve(input) {
301
+ const pathname = normalizePathname(input.pathname);
302
+ const contextBase = {
303
+ pathname,
304
+ params: {},
305
+ searchParams: input.searchParams,
306
+ cookies: input.cookies ?? {},
307
+ abort: (options2) => {
308
+ throw createAbort(options2);
309
+ }
310
+ };
311
+ let activeContext = contextBase;
312
+ try {
313
+ const entries = buildRouteEntries();
314
+ const match = findBestMatch(entries, pathname);
315
+ if (!match) {
316
+ return renderError(contextBase, 404, "Page not found");
317
+ }
318
+ const context = {
319
+ ...contextBase,
320
+ params: match.params
321
+ };
322
+ activeContext = context;
323
+ const pageModule = resolveModule(match.entry.filePath);
324
+ const layoutModule = fs.existsSync(layoutPath) ? resolveModule(layoutPath) : null;
325
+ const pageNode = pageModule.default(context);
326
+ const layoutHead = layoutModule ? await resolveHead(layoutModule, context) : void 0;
327
+ const pageHead = await resolveHead(pageModule, context);
328
+ const model = layoutModule ? injectRouteChildren(layoutModule.default(context), pageNode) : pageNode;
329
+ return {
330
+ statusCode: 200,
331
+ model,
332
+ head: mergeHead(layoutHead, pageHead)
333
+ };
334
+ } catch (error) {
335
+ if (isRouteAbort(error)) {
336
+ return renderError(activeContext, error.statusCode, error.message, error.payload);
337
+ }
338
+ console.error("[webframez-react] Failed to resolve route", error);
339
+ return renderError(contextBase, 500, "Internal server error");
340
+ }
341
+ }
342
+ return {
343
+ resolve,
344
+ readSearchParams
345
+ };
346
+ }
347
+ function parseSearchParams(query) {
348
+ return readSearchParams(query);
349
+ }
350
+ export {
351
+ RouteChildren,
352
+ createFileRouter,
353
+ parseSearchParams,
354
+ renderHeadToString
355
+ };
package/dist/types.cjs ADDED
@@ -0,0 +1,17 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
3
+ var __getOwnPropNames = Object.getOwnPropertyNames;
4
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
5
+ var __copyProps = (to, from, except, desc) => {
6
+ if (from && typeof from === "object" || typeof from === "function") {
7
+ for (let key of __getOwnPropNames(from))
8
+ if (!__hasOwnProp.call(to, key) && key !== except)
9
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
10
+ }
11
+ return to;
12
+ };
13
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
14
+
15
+ // src/types.ts
16
+ var types_exports = {};
17
+ module.exports = __toCommonJS(types_exports);
@@ -0,0 +1,87 @@
1
+ import type { ReactNode } from "react";
2
+
3
+ export type RouteParams = Record<string, string>;
4
+ export type RouteSearchParams = Record<string, string | string[]>;
5
+
6
+ export type AbortRouteOptions = {
7
+ status?: number;
8
+ message?: string;
9
+ payload?: unknown;
10
+ };
11
+
12
+ export type RouteContext = {
13
+ pathname: string;
14
+ params: RouteParams;
15
+ searchParams: RouteSearchParams;
16
+ cookies: Record<string, string>;
17
+ abort: (options?: AbortRouteOptions) => never;
18
+ };
19
+
20
+ export type PageProps = RouteContext;
21
+
22
+ export type ErrorPageProps = RouteContext & {
23
+ statusCode: number;
24
+ message: string;
25
+ payload?: unknown;
26
+ };
27
+
28
+ export type HeadMetaTag = {
29
+ name?: string;
30
+ property?: string;
31
+ content: string;
32
+ httpEquiv?: string;
33
+ charset?: string;
34
+ };
35
+
36
+ export type HeadLinkTag = {
37
+ rel: string;
38
+ href: string;
39
+ type?: string;
40
+ sizes?: string;
41
+ media?: string;
42
+ };
43
+
44
+ export type HeadConfig = {
45
+ title?: string;
46
+ description?: string;
47
+ favicon?: string;
48
+ meta?: HeadMetaTag[];
49
+ links?: HeadLinkTag[];
50
+ };
51
+
52
+ export type HeadResolver<TContext = RouteContext> = (
53
+ context: TContext
54
+ ) => HeadConfig | Promise<HeadConfig>;
55
+
56
+ export type PageModule = {
57
+ default: (props: PageProps) => ReactNode;
58
+ Head?: HeadResolver<RouteContext>;
59
+ };
60
+
61
+ export type LayoutModule = {
62
+ default: (props: RouteContext) => ReactNode;
63
+ Head?: HeadResolver<RouteContext>;
64
+ };
65
+
66
+ export type ErrorModule = {
67
+ default: (props: ErrorPageProps) => ReactNode;
68
+ Head?: HeadResolver<ErrorPageProps>;
69
+ };
70
+
71
+ export type CreateHtmlShellOptions = {
72
+ title?: string;
73
+ rscEndpoint?: string;
74
+ clientScriptUrl?: string;
75
+ headTags?: string;
76
+ rootHtml?: string;
77
+ basename?: string;
78
+ liveReloadPath?: string;
79
+ liveReloadServerId?: string;
80
+ };
81
+
82
+ export type SendRSCOptions = {
83
+ moduleMap?: Record<string, unknown>;
84
+ onError?: (error: unknown) => void;
85
+ statusCode?: number;
86
+ contentType?: string;
87
+ };
package/dist/types.js ADDED
File without changes