htmx-router 1.0.0-alpha.6 → 1.0.0-pre2
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/cli/index.js +38 -0
- package/cookies.d.ts +7 -3
- package/cookies.js +30 -10
- package/css.d.ts +10 -2
- package/css.js +21 -16
- package/defer.d.ts +14 -0
- package/defer.js +80 -0
- package/endpoint.d.ts +12 -5
- package/endpoint.js +11 -5
- package/event-source.js +6 -13
- package/index.d.ts +7 -7
- package/internal/compile/manifest.js +6 -5
- package/internal/component/defer.js +19 -0
- package/internal/component/index.d.ts +4 -0
- package/internal/component/index.js +4 -0
- package/internal/mount.d.ts +9 -1
- package/internal/mount.js +13 -6
- package/internal/request/http.d.ts +1 -1
- package/internal/request/index.d.ts +2 -1
- package/internal/request/native.d.ts +1 -1
- package/internal/request/native.js +1 -1
- package/internal/router.d.ts +15 -0
- package/internal/router.js +24 -0
- package/package.json +6 -8
- package/readme.md +20 -3
- package/router.d.ts +18 -36
- package/router.js +33 -58
- package/shell.d.ts +1 -1
- package/shell.js +15 -7
- package/util/parameters.d.ts +7 -4
- package/util/parameters.js +1 -14
- package/dynamic.d.ts +0 -5
- package/dynamic.js +0 -42
- package/example/eventdim-react/package.json +0 -67
- package/example/eventdim-react/server.js +0 -90
- package/example/island-react/global.d.ts +0 -8
- package/example/island-react/package.json +0 -38
- package/example/island-react/server.js +0 -58
- package/internal/cli/index.js +0 -15
- package/internal/compile/router.d.ts +0 -1
- package/internal/compile/router.js +0 -51
- package/internal/component/dynamic.js +0 -18
- package/internal/hash.d.ts +0 -4
- package/internal/hash.js +0 -10
- package/request/http.d.ts +0 -10
- package/request/http.js +0 -59
- package/request/index.d.ts +0 -13
- package/request/index.js +0 -3
- package/request/native.d.ts +0 -9
- package/request/native.js +0 -46
- package/vite/code-splitting.d.ts +0 -4
- package/vite/code-splitting.js +0 -14
- /package/{internal/cli → cli}/config.d.ts +0 -0
- /package/{internal/cli → cli}/config.js +0 -0
- /package/{internal/cli → cli}/index.d.ts +0 -0
- /package/internal/component/{dynamic.d.ts → defer.d.ts} +0 -0
package/package.json
CHANGED
|
@@ -1,19 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "htmx-router",
|
|
3
|
-
"version": "1.0.0-
|
|
4
|
-
"description": "A
|
|
3
|
+
"version": "1.0.0-pre2",
|
|
4
|
+
"description": "A lightweight SSR framework with server+client islands",
|
|
5
5
|
"keywords": [
|
|
6
|
-
"htmx",
|
|
7
|
-
"router",
|
|
8
|
-
""
|
|
6
|
+
"htmx", "router", "client islands", "ssr", "vite"
|
|
9
7
|
],
|
|
10
8
|
"main": "./index.js",
|
|
11
9
|
"type": "module",
|
|
12
10
|
"scripts": {
|
|
13
|
-
"build": "tsc
|
|
11
|
+
"build": "tsc"
|
|
14
12
|
},
|
|
15
13
|
"bin": {
|
|
16
|
-
"htmx-router": "
|
|
14
|
+
"htmx-router": "cli/index.js"
|
|
17
15
|
},
|
|
18
16
|
"repository": {
|
|
19
17
|
"type": "git",
|
|
@@ -31,8 +29,8 @@
|
|
|
31
29
|
},
|
|
32
30
|
"devDependencies": {
|
|
33
31
|
"@types/node": "^20.4.5",
|
|
32
|
+
"chalk": "^5.4.1",
|
|
34
33
|
"ts-node": "^10.9.1",
|
|
35
|
-
"tsc-alias": "^1.8.10",
|
|
36
34
|
"typescript": "^5.1.6"
|
|
37
35
|
}
|
|
38
36
|
}
|
package/readme.md
CHANGED
|
@@ -1,5 +1,22 @@
|
|
|
1
|
-
#
|
|
1
|
+
# HTMX Router
|
|
2
2
|
|
|
3
|
-
A
|
|
3
|
+
A lightweight file based router built on vite+htmx for SSR generation of full pages and html partials
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Features:
|
|
6
|
+
|
|
7
|
+
- BYO jsx templating
|
|
8
|
+
- File base routing
|
|
9
|
+
- Typesafe url path parameters
|
|
10
|
+
- Dynamic route fallthrough
|
|
11
|
+
- Server + Client Islands
|
|
12
|
+
- Route-less points
|
|
13
|
+
- CSS sheet generation
|
|
14
|
+
- [html](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta), [opengraph](https://ogp.me/), and [json+ld](https://json-ld.org/) metadata generation
|
|
15
|
+
- Bundle splitting for filtering out server code from the client
|
|
16
|
+
- Server-Side EventSource creation for SSE event dispatch
|
|
17
|
+
|
|
18
|
+
## Documentation
|
|
19
|
+
See https://htmx-router.ajanibilby.com/
|
|
20
|
+
|
|
21
|
+
## API
|
|
22
|
+
See https://htmx-router.ajanibilby.com/api
|
package/router.d.ts
CHANGED
|
@@ -1,36 +1,27 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { GenericContext } from "./internal/router.js";
|
|
2
|
+
import { Parameterized, ParameterPrelude, ParameterShaper } from './util/parameters.js';
|
|
2
3
|
import { RouteModule } from "./index.js";
|
|
3
4
|
import { Cookies } from './cookies.js';
|
|
4
|
-
export declare function GenerateRouteTree(
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
cookie: Cookies;
|
|
9
|
-
params: {
|
|
10
|
-
[key: string]: string;
|
|
11
|
-
};
|
|
12
|
-
url: URL;
|
|
13
|
-
render: (res: JSX.Element) => Response;
|
|
14
|
-
constructor(request: GenericContext["request"], url: GenericContext["url"], renderer: GenericContext["render"]);
|
|
15
|
-
shape<T extends ParameterShaper>(shape: T): RouteContext<T>;
|
|
16
|
-
}
|
|
5
|
+
export declare function GenerateRouteTree(props: {
|
|
6
|
+
modules: Record<string, unknown>;
|
|
7
|
+
scope: string;
|
|
8
|
+
}): RouteTree;
|
|
17
9
|
export declare class RouteContext<T extends ParameterShaper = {}> {
|
|
18
|
-
request: Request;
|
|
19
|
-
headers: Headers;
|
|
20
|
-
cookie: Cookies;
|
|
21
|
-
params: Parameterized<T>;
|
|
22
|
-
url: URL;
|
|
10
|
+
readonly request: Request;
|
|
11
|
+
readonly headers: Headers;
|
|
12
|
+
readonly cookie: Cookies;
|
|
13
|
+
readonly params: Parameterized<T>;
|
|
14
|
+
readonly url: URL;
|
|
23
15
|
render: (res: JSX.Element) => Response;
|
|
24
|
-
constructor(base: GenericContext, shape: T);
|
|
16
|
+
constructor(base: GenericContext | RouteContext, params: ParameterPrelude<T>, shape: T);
|
|
25
17
|
}
|
|
26
18
|
export declare class RouteTree {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
constructor(root?: boolean);
|
|
19
|
+
private nested;
|
|
20
|
+
private index;
|
|
21
|
+
private slug;
|
|
22
|
+
private wild;
|
|
23
|
+
private wildCard;
|
|
24
|
+
constructor();
|
|
34
25
|
ingest(path: string | string[], module: RouteModule<any>): void;
|
|
35
26
|
resolve(fragments: string[], ctx: GenericContext): Promise<Response | null>;
|
|
36
27
|
private _resolve;
|
|
@@ -38,14 +29,5 @@ export declare class RouteTree {
|
|
|
38
29
|
private resolveNext;
|
|
39
30
|
private resolveWild;
|
|
40
31
|
private resolveSlug;
|
|
41
|
-
private resolveNative;
|
|
42
32
|
private unwrap;
|
|
43
33
|
}
|
|
44
|
-
declare class RouteLeaf {
|
|
45
|
-
module: RouteModule<any>;
|
|
46
|
-
constructor(module: RouteModule<any>);
|
|
47
|
-
resolve(ctx: GenericContext): Promise<Response | null>;
|
|
48
|
-
error(ctx: GenericContext, e: unknown): Promise<Response>;
|
|
49
|
-
private renderWrapper;
|
|
50
|
-
}
|
|
51
|
-
export {};
|
package/router.js
CHANGED
|
@@ -1,43 +1,29 @@
|
|
|
1
1
|
import { ServerOnlyWarning } from "./internal/util.js";
|
|
2
2
|
ServerOnlyWarning("router");
|
|
3
|
+
// builtin routes
|
|
3
4
|
import * as endpoint from './endpoint.js';
|
|
4
|
-
import * as dynamic from './
|
|
5
|
+
import * as dynamic from './defer.js';
|
|
5
6
|
import * as mount from './internal/mount.js';
|
|
6
7
|
import * as css from './css.js';
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
export function GenerateRouteTree(props) {
|
|
9
|
+
if (!props.scope.endsWith("/"))
|
|
10
|
+
props.scope += "/";
|
|
10
11
|
const tree = new RouteTree();
|
|
11
|
-
for (const path in modules) {
|
|
12
|
-
const mod = modules[path];
|
|
12
|
+
for (const path in props.modules) {
|
|
13
|
+
const mod = props.modules[path];
|
|
13
14
|
const tail = path.lastIndexOf(".");
|
|
14
|
-
const url = path.slice(
|
|
15
|
+
const url = path.slice(props.scope.length, tail);
|
|
15
16
|
tree.ingest(url, mod);
|
|
16
17
|
if (mod.route)
|
|
17
18
|
mod.route(url);
|
|
18
19
|
}
|
|
20
|
+
// ingest router builtins
|
|
21
|
+
tree.ingest(endpoint.path, endpoint);
|
|
22
|
+
tree.ingest(dynamic.path, dynamic);
|
|
23
|
+
tree.ingest(mount.path, mount);
|
|
24
|
+
tree.ingest(css.path, css);
|
|
19
25
|
return tree;
|
|
20
26
|
}
|
|
21
|
-
export class GenericContext {
|
|
22
|
-
request;
|
|
23
|
-
headers; // response headers
|
|
24
|
-
cookie;
|
|
25
|
-
params;
|
|
26
|
-
url;
|
|
27
|
-
render;
|
|
28
|
-
constructor(request, url, renderer) {
|
|
29
|
-
this.cookie = new Cookies(request.headers);
|
|
30
|
-
this.headers = new Headers();
|
|
31
|
-
this.request = request;
|
|
32
|
-
this.params = {};
|
|
33
|
-
this.url = url;
|
|
34
|
-
this.render = renderer;
|
|
35
|
-
this.headers.set("x-powered-by", "htmx-router");
|
|
36
|
-
}
|
|
37
|
-
shape(shape) {
|
|
38
|
-
return new RouteContext(this, shape);
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
27
|
export class RouteContext {
|
|
42
28
|
request;
|
|
43
29
|
headers; // response headers
|
|
@@ -45,31 +31,39 @@ export class RouteContext {
|
|
|
45
31
|
params;
|
|
46
32
|
url;
|
|
47
33
|
render;
|
|
48
|
-
constructor(base, shape) {
|
|
49
|
-
this.params = Parameterize(base.params, shape);
|
|
34
|
+
constructor(base, params, shape) {
|
|
50
35
|
this.cookie = base.cookie;
|
|
51
36
|
this.headers = base.headers;
|
|
52
37
|
this.request = base.request;
|
|
53
38
|
this.render = base.render;
|
|
54
39
|
this.url = base.url;
|
|
40
|
+
this.params = {};
|
|
41
|
+
for (const key in shape) {
|
|
42
|
+
if (!(key in params))
|
|
43
|
+
console.warn(`Parameter ${key} not present in route, but defined in parameters`);
|
|
44
|
+
const func = shape[key];
|
|
45
|
+
const val = func(params[key] || "");
|
|
46
|
+
// NaN moment
|
|
47
|
+
if (func === Number && typeof val === "number" && isNaN(val))
|
|
48
|
+
throw new Error("Invalid Number");
|
|
49
|
+
this.params[key] = val;
|
|
50
|
+
}
|
|
55
51
|
}
|
|
56
52
|
}
|
|
57
53
|
export class RouteTree {
|
|
58
|
-
root;
|
|
59
54
|
nested;
|
|
60
55
|
// Leaf nodes
|
|
61
|
-
index; //
|
|
62
|
-
// Wild card
|
|
56
|
+
index; // _index.tsx
|
|
57
|
+
// Wild card routes
|
|
63
58
|
slug; // $
|
|
64
59
|
wild; // e.g. $userID
|
|
65
60
|
wildCard;
|
|
66
|
-
constructor(
|
|
67
|
-
this.root = root;
|
|
61
|
+
constructor() {
|
|
68
62
|
this.nested = new Map();
|
|
63
|
+
this.index = null;
|
|
69
64
|
this.wildCard = "";
|
|
70
|
-
this.slug = null;
|
|
71
65
|
this.wild = null;
|
|
72
|
-
this.
|
|
66
|
+
this.slug = null;
|
|
73
67
|
}
|
|
74
68
|
ingest(path, module) {
|
|
75
69
|
if (!Array.isArray(path))
|
|
@@ -87,7 +81,7 @@ export class RouteTree {
|
|
|
87
81
|
// Check wildcard isn't being changed
|
|
88
82
|
if (!this.wild) {
|
|
89
83
|
this.wildCard = wildCard;
|
|
90
|
-
this.wild = new RouteTree(
|
|
84
|
+
this.wild = new RouteTree();
|
|
91
85
|
}
|
|
92
86
|
else if (wildCard !== this.wildCard) {
|
|
93
87
|
throw new Error(`Redefinition of wild card ${this.wildCard} to ${wildCard}`);
|
|
@@ -98,7 +92,7 @@ export class RouteTree {
|
|
|
98
92
|
}
|
|
99
93
|
let next = this.nested.get(path[0]);
|
|
100
94
|
if (!next) {
|
|
101
|
-
next = new RouteTree(
|
|
95
|
+
next = new RouteTree();
|
|
102
96
|
this.nested.set(path[0], next);
|
|
103
97
|
}
|
|
104
98
|
path.splice(0, 1);
|
|
@@ -115,8 +109,7 @@ export class RouteTree {
|
|
|
115
109
|
}
|
|
116
110
|
}
|
|
117
111
|
async _resolve(fragments, ctx) {
|
|
118
|
-
let res = await this.
|
|
119
|
-
|| await this.resolveIndex(fragments, ctx)
|
|
112
|
+
let res = await this.resolveIndex(fragments, ctx)
|
|
120
113
|
|| await this.resolveNext(fragments, ctx)
|
|
121
114
|
|| await this.resolveWild(fragments, ctx)
|
|
122
115
|
|| await this.resolveSlug(fragments, ctx);
|
|
@@ -161,15 +154,6 @@ export class RouteTree {
|
|
|
161
154
|
: null;
|
|
162
155
|
return res;
|
|
163
156
|
}
|
|
164
|
-
async resolveNative(fragments, ctx) {
|
|
165
|
-
if (!this.root)
|
|
166
|
-
return null;
|
|
167
|
-
if (fragments.length < 2)
|
|
168
|
-
return null;
|
|
169
|
-
if (fragments[0] != "_")
|
|
170
|
-
return null;
|
|
171
|
-
return await ResolveNatively(fragments, ctx);
|
|
172
|
-
}
|
|
173
157
|
async unwrap(ctx, res) {
|
|
174
158
|
if (!this.slug)
|
|
175
159
|
throw res;
|
|
@@ -220,12 +204,3 @@ class RouteLeaf {
|
|
|
220
204
|
return null;
|
|
221
205
|
}
|
|
222
206
|
}
|
|
223
|
-
async function ResolveNatively(fragments, ctx) {
|
|
224
|
-
switch (fragments[1]) {
|
|
225
|
-
case "dynamic": return dynamic._resolve(fragments, ctx);
|
|
226
|
-
case "endpoint": return endpoint._resolve(fragments, ctx);
|
|
227
|
-
case "mount": return mount._resolve(fragments);
|
|
228
|
-
case "style": return css._resolve(fragments);
|
|
229
|
-
}
|
|
230
|
-
return null;
|
|
231
|
-
}
|
package/shell.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export type ShellOptions<D = {}> = D & MetaDescriptor;
|
|
2
|
-
export declare function
|
|
2
|
+
export declare function ApplyMetaDefaults(options: ShellOptions, defaults: Readonly<Partial<ShellOptions>>): void;
|
|
3
3
|
export type InferShellOptions<F> = F extends (jsx: any, options: infer U) => any ? U : never;
|
|
4
4
|
export type MetaDescriptor = {
|
|
5
5
|
title?: string;
|
package/shell.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ServerOnlyWarning } from "./internal/util.js";
|
|
2
2
|
ServerOnlyWarning("shell");
|
|
3
|
-
export function
|
|
3
|
+
export function ApplyMetaDefaults(options, defaults) {
|
|
4
4
|
if (defaults.title && !options.title)
|
|
5
5
|
options.title = defaults.title;
|
|
6
6
|
if (defaults.description && !options.description)
|
|
@@ -26,14 +26,22 @@ export function RenderMetaDescriptor(options) {
|
|
|
26
26
|
for (const json of options.jsonLD) {
|
|
27
27
|
out += `<script type="application/ld+json">${JSON.stringify(json)}</script>\n`;
|
|
28
28
|
}
|
|
29
|
-
// Auto apply og:title + og:description if not present
|
|
30
|
-
if (options.title && !options.og?.title)
|
|
31
|
-
out += `<meta property="og:title" content="${EscapeHTML(options.title)}">\n`;
|
|
32
|
-
if (options.description && !options.og?.description)
|
|
33
|
-
out += `<meta property="og:description" content="${EscapeHTML(options.description)}">\n`;
|
|
34
29
|
// Apply open graphs
|
|
35
|
-
if (options.og)
|
|
30
|
+
if (options.og) {
|
|
31
|
+
// Infer from meta if not present
|
|
32
|
+
if (!options.og.title)
|
|
33
|
+
options.og.title = options.title;
|
|
34
|
+
if (!options.og.description)
|
|
35
|
+
options.og.title = options.description;
|
|
36
36
|
out += RenderOpenGraph(options.og);
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
// Auto apply og:title + og:description if og not present
|
|
40
|
+
if (options.title)
|
|
41
|
+
out += `<meta property="og:title" content="${EscapeHTML(options.title)}">\n`;
|
|
42
|
+
if (options.description)
|
|
43
|
+
out += `<meta property="og:description" content="${EscapeHTML(options.description)}">\n`;
|
|
44
|
+
}
|
|
37
45
|
return out;
|
|
38
46
|
}
|
|
39
47
|
function RenderOpenGraph(og) {
|
package/util/parameters.d.ts
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
|
+
export type ParameterShaper = Record<string, (val: string) => any>;
|
|
2
|
+
export type ParameterPrelude<T extends ParameterShaper> = {
|
|
3
|
+
[K in keyof T]: string;
|
|
4
|
+
};
|
|
1
5
|
export type Parameterized<T extends ParameterShaper> = {
|
|
2
6
|
[K in keyof T]: ReturnType<T[K]>;
|
|
3
7
|
};
|
|
4
|
-
export type
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
}, shape: T): Parameterized<T>;
|
|
8
|
+
export type Parameterizer<T extends ParameterShaper> = {
|
|
9
|
+
[K in keyof T]: (val: ReturnType<T[K]>) => string;
|
|
10
|
+
};
|
package/util/parameters.js
CHANGED
|
@@ -1,14 +1 @@
|
|
|
1
|
-
export
|
|
2
|
-
const out = {};
|
|
3
|
-
for (const key in shape) {
|
|
4
|
-
if (!(key in params))
|
|
5
|
-
console.warn(`Parameter ${key} not present in route, but defined in parameters`);
|
|
6
|
-
const func = shape[key];
|
|
7
|
-
const val = func(params[key] || "");
|
|
8
|
-
// NaN moment
|
|
9
|
-
if (func === Number && typeof val === "number" && isNaN(val))
|
|
10
|
-
throw new Error("Invalid Number");
|
|
11
|
-
out[key] = val;
|
|
12
|
-
}
|
|
13
|
-
return out;
|
|
14
|
-
}
|
|
1
|
+
export {};
|
package/dynamic.d.ts
DELETED
|
@@ -1,5 +0,0 @@
|
|
|
1
|
-
import type { GenericContext } from "./router.js";
|
|
2
|
-
type Loader<T> = (ctx: GenericContext, params: T) => Promise<JSX.Element | Response>;
|
|
3
|
-
export declare function DynamicReference<T extends Record<string, string>>(loader: Loader<T>, params?: T): string;
|
|
4
|
-
export declare function _resolve(fragments: string[], ctx: GenericContext): Promise<Response | null>;
|
|
5
|
-
export {};
|
package/dynamic.js
DELETED
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
import { ServerOnlyWarning } from "./internal/util.js";
|
|
2
|
-
ServerOnlyWarning("dynamic-ref");
|
|
3
|
-
import { QuickHash } from "./internal/util.js";
|
|
4
|
-
const registry = new Map();
|
|
5
|
-
const index = new Map();
|
|
6
|
-
function Register(load) {
|
|
7
|
-
const existing = index.get(load);
|
|
8
|
-
if (existing)
|
|
9
|
-
return existing;
|
|
10
|
-
const hash = QuickHash(String(load));
|
|
11
|
-
const name = `${encodeURIComponent(load.name)}-${hash}`;
|
|
12
|
-
registry.set(name, load);
|
|
13
|
-
const url = `/_/dynamic/${name}`;
|
|
14
|
-
index.set(load, url);
|
|
15
|
-
return url;
|
|
16
|
-
}
|
|
17
|
-
export function DynamicReference(loader, params) {
|
|
18
|
-
let url = Register(loader);
|
|
19
|
-
if (params) {
|
|
20
|
-
const query = new URLSearchParams();
|
|
21
|
-
if (params)
|
|
22
|
-
for (const key in params)
|
|
23
|
-
query.set(key, params[key]);
|
|
24
|
-
url += "?" + query.toString();
|
|
25
|
-
}
|
|
26
|
-
return url;
|
|
27
|
-
}
|
|
28
|
-
export async function _resolve(fragments, ctx) {
|
|
29
|
-
if (!fragments[2])
|
|
30
|
-
return null;
|
|
31
|
-
const endpoint = registry.get(fragments[2]);
|
|
32
|
-
if (!endpoint)
|
|
33
|
-
return null;
|
|
34
|
-
const props = {};
|
|
35
|
-
for (const [key, value] of ctx.url.searchParams)
|
|
36
|
-
props[key] = value;
|
|
37
|
-
ctx.headers.set("X-Partial", "true");
|
|
38
|
-
const res = await endpoint(ctx, props);
|
|
39
|
-
if (res instanceof Response)
|
|
40
|
-
return res;
|
|
41
|
-
return ctx.render(res);
|
|
42
|
-
}
|
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "eventdim-react",
|
|
3
|
-
"private": "true",
|
|
4
|
-
"version": "1.0.0",
|
|
5
|
-
"main": "index.js",
|
|
6
|
-
"type": "module",
|
|
7
|
-
"scripts": {
|
|
8
|
-
"prepare": "npx htmx-router && prisma generate && prisma migrate deploy",
|
|
9
|
-
"docker": "docker compose up -d",
|
|
10
|
-
"dev": "node ./server.js",
|
|
11
|
-
"build": "run-s build:*",
|
|
12
|
-
"build:router": "npx htmx-router",
|
|
13
|
-
"build:prisma": "npx prisma generate",
|
|
14
|
-
"build:client": "vite build",
|
|
15
|
-
"build:server": "vite build --ssr app/entry.server.ts --outDir dist/server",
|
|
16
|
-
"validate": "run-s validate:*",
|
|
17
|
-
"validate:typecheck": "tsc --noEmit",
|
|
18
|
-
"validate:lint": "eslint --ignore-path .gitignore --cache --cache-location ./node_modules/.cache/eslint .",
|
|
19
|
-
"preview": "cross-env NODE_ENV=production node ./server.js"
|
|
20
|
-
},
|
|
21
|
-
"keywords": [],
|
|
22
|
-
"author": "",
|
|
23
|
-
"license": "ISC",
|
|
24
|
-
"description": "",
|
|
25
|
-
"dependencies": {
|
|
26
|
-
"@fortawesome/free-brands-svg-icons": "^6.7.2",
|
|
27
|
-
"@fortawesome/free-solid-svg-icons": "^6.7.2",
|
|
28
|
-
"@fortawesome/react-fontawesome": "^0.2.2",
|
|
29
|
-
"@prisma/client": "^6.1.0",
|
|
30
|
-
"bcryptjs": "^2.4.3",
|
|
31
|
-
"cbor2": "^1.8.0",
|
|
32
|
-
"cross-env": "^7.0.3",
|
|
33
|
-
"dotenv": "^16.4.7",
|
|
34
|
-
"express": "^4.21.2",
|
|
35
|
-
"htmx-router": "^1.0.0-alpha.5",
|
|
36
|
-
"morgan": "^1.10.0",
|
|
37
|
-
"react": "^19.0.0",
|
|
38
|
-
"react-dom": "^19.0.0",
|
|
39
|
-
"tiny-invariant": "^1.3.3",
|
|
40
|
-
"zxcvbn": "^4.4.2"
|
|
41
|
-
},
|
|
42
|
-
"devDependencies": {
|
|
43
|
-
"@types/bcryptjs": "^2.4.6",
|
|
44
|
-
"@types/express": "^4.17.21",
|
|
45
|
-
"@types/nodemailer": "^6.4.15",
|
|
46
|
-
"@types/react": "^18.2.20",
|
|
47
|
-
"@types/react-dom": "^18.2.7",
|
|
48
|
-
"@types/zxcvbn": "^4.4.4",
|
|
49
|
-
"@typescript-eslint/eslint-plugin": "^6.7.4",
|
|
50
|
-
"@typescript-eslint/parser": "^6.7.4",
|
|
51
|
-
"eslint": "^8.38.0",
|
|
52
|
-
"eslint-import-resolver-typescript": "^3.6.1",
|
|
53
|
-
"eslint-plugin-import": "^2.28.1",
|
|
54
|
-
"eslint-plugin-jsx-a11y": "^6.7.1",
|
|
55
|
-
"eslint-plugin-react": "^7.33.2",
|
|
56
|
-
"eslint-plugin-react-hooks": "^4.6.0",
|
|
57
|
-
"npm-run-all": "^4.1.5",
|
|
58
|
-
"prisma": "^6.1.0",
|
|
59
|
-
"typed-htmx": "^0.3.1",
|
|
60
|
-
"typescript": "^5.5.4",
|
|
61
|
-
"vite-tsconfig-paths": "^5.1.3",
|
|
62
|
-
"vite": "^6.0.1"
|
|
63
|
-
},
|
|
64
|
-
"engines": {
|
|
65
|
-
"node": ">=20.0.0"
|
|
66
|
-
}
|
|
67
|
-
}
|
|
@@ -1,90 +0,0 @@
|
|
|
1
|
-
/// <reference types="node" />
|
|
2
|
-
/* eslint-disable */
|
|
3
|
-
import 'dotenv/config'
|
|
4
|
-
import * as path from "path";
|
|
5
|
-
import { createRequestHandler } from 'htmx-router';
|
|
6
|
-
import { renderToString } from 'react-dom/server';
|
|
7
|
-
import express from 'express';
|
|
8
|
-
import morgan from "morgan";
|
|
9
|
-
|
|
10
|
-
const port = process.env.PORT || 3000;
|
|
11
|
-
const app = express();
|
|
12
|
-
|
|
13
|
-
const viteDevServer =
|
|
14
|
-
process.env.NODE_ENV === "production"
|
|
15
|
-
? null
|
|
16
|
-
: await import("vite").then((vite) =>
|
|
17
|
-
vite.createServer({
|
|
18
|
-
server: { middlewareMode: true },
|
|
19
|
-
appType: 'custom'
|
|
20
|
-
})
|
|
21
|
-
);
|
|
22
|
-
|
|
23
|
-
app.use(
|
|
24
|
-
viteDevServer
|
|
25
|
-
? viteDevServer.middlewares
|
|
26
|
-
: express.static("./dist/client")
|
|
27
|
-
);
|
|
28
|
-
|
|
29
|
-
// logging
|
|
30
|
-
app.use(morgan("tiny"));
|
|
31
|
-
|
|
32
|
-
const build = viteDevServer
|
|
33
|
-
? () => viteDevServer.ssrLoadModule('./app/entry.server.ts')
|
|
34
|
-
: await import('./dist/server/entry.server.js');
|
|
35
|
-
|
|
36
|
-
app.use('*', createRequestHandler.http({
|
|
37
|
-
build, viteDevServer,
|
|
38
|
-
render: (res) => {
|
|
39
|
-
const headers = new Headers();
|
|
40
|
-
headers.set("Content-Type", "text/html; charset=UTF-8");
|
|
41
|
-
headers.set("Cache-Control", "no-cache");
|
|
42
|
-
|
|
43
|
-
const stream = renderToString(res);
|
|
44
|
-
return new Response(stream, { headers });
|
|
45
|
-
}
|
|
46
|
-
}));
|
|
47
|
-
|
|
48
|
-
// Start http server
|
|
49
|
-
app.listen(port, () => {
|
|
50
|
-
console.log(`Server started at http://localhost:${port}`)
|
|
51
|
-
})
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
// Reload pages on file change
|
|
55
|
-
if (viteDevServer) {
|
|
56
|
-
const focus = path.resolve("./app");
|
|
57
|
-
viteDevServer.watcher.on('change', (file) => {
|
|
58
|
-
if (!file.startsWith(focus)) return;
|
|
59
|
-
console.log(`File changed: ${path.relative("./app", file)}`);
|
|
60
|
-
|
|
61
|
-
console.log('Triggering full page reload');
|
|
62
|
-
viteDevServer.ws.send({ type: 'full-reload' });
|
|
63
|
-
});
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
const shutdown = () => {
|
|
67
|
-
console.log("Shutting down server...");
|
|
68
|
-
|
|
69
|
-
// Close the server gracefully
|
|
70
|
-
server.close((err) => {
|
|
71
|
-
if (err) {
|
|
72
|
-
console.error("Error during server shutdown:", err);
|
|
73
|
-
process.exit(1);
|
|
74
|
-
}
|
|
75
|
-
console.log("Server shut down gracefully.");
|
|
76
|
-
process.exit(0);
|
|
77
|
-
});
|
|
78
|
-
};
|
|
79
|
-
|
|
80
|
-
process.on('SIGTERM', shutdown);
|
|
81
|
-
process.on('SIGHUP', shutdown);
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
process .on('unhandledRejection', (reason, p) => {
|
|
85
|
-
console.error(reason, 'Unhandled Rejection at Promise', p);
|
|
86
|
-
})
|
|
87
|
-
.on('uncaughtException', err => {
|
|
88
|
-
console.error(err, 'Uncaught Exception thrown');
|
|
89
|
-
process.exit(1);
|
|
90
|
-
});
|
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"type": "module",
|
|
3
|
-
"scripts": {
|
|
4
|
-
"prepare": "npx htmx-router",
|
|
5
|
-
"dev": "node ./server.js",
|
|
6
|
-
"build": "run-s build:*",
|
|
7
|
-
"build:router": "npx htmx-router",
|
|
8
|
-
"build:client": "vite build",
|
|
9
|
-
"build:server": "vite build --ssr app/entry.server.ts --outDir dist/server",
|
|
10
|
-
"preview": "cross-env NODE_ENV=production node ./server.js",
|
|
11
|
-
"validate": "npx tsc -noEmit"
|
|
12
|
-
},
|
|
13
|
-
"license": "MIT",
|
|
14
|
-
"dependencies": {
|
|
15
|
-
"cross-env": "^7.0.3",
|
|
16
|
-
"dotenv": "^16.3.1",
|
|
17
|
-
"express": "^4.21.1",
|
|
18
|
-
"morgan": "^1.10.0",
|
|
19
|
-
"npm-run-all": "^4.1.5",
|
|
20
|
-
"htmx-router": "^1.0.0-alpha.1",
|
|
21
|
-
"react": "^19.0.0",
|
|
22
|
-
"react-dom": "^19.0.0",
|
|
23
|
-
"serve-static": "^1.16.2",
|
|
24
|
-
"tsconfig-paths": "^4.2.0"
|
|
25
|
-
},
|
|
26
|
-
"devDependencies": {
|
|
27
|
-
"@types/express": "^5.0.0",
|
|
28
|
-
"@types/node": "^20.4.5",
|
|
29
|
-
"@types/react-dom": "^19.0.2",
|
|
30
|
-
"@types/react": "^19.0.1",
|
|
31
|
-
"@types/serve-static": "^1.15.7",
|
|
32
|
-
"ts-node": "^10.9.1",
|
|
33
|
-
"typed-htmx": "^0.3.1",
|
|
34
|
-
"typescript": "^5.1.6",
|
|
35
|
-
"vite-tsconfig-paths": "^5.1.3",
|
|
36
|
-
"vite": "^6.0.1"
|
|
37
|
-
}
|
|
38
|
-
}
|