htmx-router 1.0.0-alpha.4 → 1.0.0-alpha.6

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.
Files changed (97) hide show
  1. package/{bin/util/css.js → css.js} +1 -1
  2. package/dynamic.d.ts +5 -0
  3. package/{bin/util/dynamic.js → dynamic.js} +20 -7
  4. package/{bin/util/endpoint.d.ts → endpoint.d.ts} +2 -2
  5. package/{bin/util/endpoint.js → endpoint.js} +3 -1
  6. package/event-source.d.ts +26 -0
  7. package/event-source.js +123 -0
  8. package/example/eventdim-react/package.json +67 -0
  9. package/example/eventdim-react/server.js +90 -0
  10. package/example/island-react/global.d.ts +8 -0
  11. package/example/island-react/package.json +38 -0
  12. package/example/island-react/server.js +58 -0
  13. package/global.d.ts +7 -0
  14. package/index.d.ts +19 -0
  15. package/index.js +2 -0
  16. package/internal/cli/config.d.ts +13 -0
  17. package/internal/cli/config.js +11 -0
  18. package/internal/cli/index.js +15 -0
  19. package/internal/client.d.ts +1 -0
  20. package/{bin/client/entry.js → internal/client.js} +3 -1
  21. package/internal/compile/manifest.d.ts +1 -0
  22. package/internal/compile/manifest.js +178 -0
  23. package/internal/compile/router.d.ts +1 -0
  24. package/internal/compile/router.js +51 -0
  25. package/internal/component/dynamic.d.ts +4 -0
  26. package/internal/component/dynamic.js +18 -0
  27. package/internal/component/head.d.ts +5 -0
  28. package/internal/component/head.js +22 -0
  29. package/internal/component/scripts.d.ts +4 -0
  30. package/internal/component/scripts.js +23 -0
  31. package/{bin/client → internal}/mount.js +15 -9
  32. package/internal/request/http.d.ts +10 -0
  33. package/internal/request/http.js +61 -0
  34. package/{bin → internal}/request/index.d.ts +3 -3
  35. package/internal/request/index.js +8 -0
  36. package/{bin → internal}/request/native.d.ts +2 -2
  37. package/{bin → internal}/request/native.js +12 -14
  38. package/{bin/helper.d.ts → internal/util.d.ts} +2 -0
  39. package/{bin/helper.js → internal/util.js} +15 -0
  40. package/package.json +9 -5
  41. package/readme.md +2 -214
  42. package/{bin/request → request}/http.d.ts +1 -1
  43. package/{bin/request → request}/http.js +22 -4
  44. package/request/index.d.ts +13 -0
  45. package/request/index.js +3 -0
  46. package/request/native.d.ts +9 -0
  47. package/request/native.js +46 -0
  48. package/response.d.ts +13 -0
  49. package/{bin/response.js → response.js} +25 -12
  50. package/{bin/router.d.ts → router.d.ts} +12 -10
  51. package/{bin/router.js → router.js} +62 -48
  52. package/shell.d.ts +120 -0
  53. package/shell.js +253 -0
  54. package/{bin/util → util}/parameters.d.ts +0 -3
  55. package/{bin/util → util}/parameters.js +0 -3
  56. package/{bin/util → util}/path-builder.js +2 -0
  57. package/util/route.d.ts +2 -0
  58. package/util/route.js +58 -0
  59. package/vite/bundle-splitter.d.ts +4 -0
  60. package/vite/bundle-splitter.js +26 -0
  61. package/vite/client-island.d.ts +4 -0
  62. package/vite/client-island.js +14 -0
  63. package/vite/code-splitting.d.ts +4 -0
  64. package/vite/code-splitting.js +14 -0
  65. package/vite/index.d.ts +3 -0
  66. package/vite/index.js +3 -0
  67. package/vite/router.d.ts +2 -0
  68. package/vite/router.js +29 -0
  69. package/bin/cli/config.d.ts +0 -10
  70. package/bin/cli/config.js +0 -4
  71. package/bin/cli/index.js +0 -72
  72. package/bin/client/entry.d.ts +0 -1
  73. package/bin/client/index.d.ts +0 -7
  74. package/bin/client/index.js +0 -126
  75. package/bin/client/watch.d.ts +0 -1
  76. package/bin/client/watch.js +0 -11
  77. package/bin/index.d.ts +0 -9
  78. package/bin/index.js +0 -8
  79. package/bin/request/index.js +0 -6
  80. package/bin/response.d.ts +0 -4
  81. package/bin/types.d.ts +0 -10
  82. package/bin/types.js +0 -1
  83. package/bin/util/dynamic.d.ts +0 -8
  84. package/bin/util/event-source.d.ts +0 -16
  85. package/bin/util/event-source.js +0 -85
  86. package/bin/util/index.d.ts +0 -1
  87. package/bin/util/index.js +0 -7
  88. package/bin/util/shell.d.ts +0 -32
  89. package/bin/util/shell.js +0 -8
  90. /package/{bin/util/cookies.d.ts → cookies.d.ts} +0 -0
  91. /package/{bin/util/cookies.js → cookies.js} +0 -0
  92. /package/{bin/util/css.d.ts → css.d.ts} +0 -0
  93. /package/{bin → internal}/cli/index.d.ts +0 -0
  94. /package/{bin/util → internal}/hash.d.ts +0 -0
  95. /package/{bin/util → internal}/hash.js +0 -0
  96. /package/{bin/client → internal}/mount.d.ts +0 -0
  97. /package/{bin/util → util}/path-builder.d.ts +0 -0
@@ -1,4 +1,4 @@
1
- import { QuickHash } from "../util/hash.js";
1
+ import { QuickHash } from "./internal/util.js";
2
2
  const classNamePattern = /^[a-zA-Z_][a-zA-Z0-9_-]*$/;
3
3
  const registry = new Map();
4
4
  let cache = null;
package/dynamic.d.ts ADDED
@@ -0,0 +1,5 @@
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 {};
@@ -1,20 +1,30 @@
1
- /**
2
- * This whole file is only for internal use but the generated router for the <Dynamic> component
3
- */
4
- import { QuickHash } from "../util/hash.js";
1
+ import { ServerOnlyWarning } from "./internal/util.js";
2
+ ServerOnlyWarning("dynamic-ref");
3
+ import { QuickHash } from "./internal/util.js";
5
4
  const registry = new Map();
6
5
  const index = new Map();
7
- export function RegisterDynamic(load) {
6
+ function Register(load) {
8
7
  const existing = index.get(load);
9
8
  if (existing)
10
9
  return existing;
11
10
  const hash = QuickHash(String(load));
12
11
  const name = `${encodeURIComponent(load.name)}-${hash}`;
13
12
  registry.set(name, load);
14
- const url = `/_/dynamic/${name}?`;
13
+ const url = `/_/dynamic/${name}`;
15
14
  index.set(load, url);
16
15
  return url;
17
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
+ }
18
28
  export async function _resolve(fragments, ctx) {
19
29
  if (!fragments[2])
20
30
  return null;
@@ -25,5 +35,8 @@ export async function _resolve(fragments, ctx) {
25
35
  for (const [key, value] of ctx.url.searchParams)
26
36
  props[key] = value;
27
37
  ctx.headers.set("X-Partial", "true");
28
- return ctx.render(await endpoint(props, ctx));
38
+ const res = await endpoint(ctx, props);
39
+ if (res instanceof Response)
40
+ return res;
41
+ return ctx.render(res);
29
42
  }
@@ -1,5 +1,5 @@
1
- import { RenderFunction } from "../types.js";
2
- import { GenericContext } from "../router.js";
1
+ import type { GenericContext } from "./router.js";
2
+ import type { RenderFunction } from "./index.js";
3
3
  /**
4
4
  * Create a route-less endpoint
5
5
  * The name is optional and will be inferred from the function if not given (helpful for network waterfalls)
@@ -1,4 +1,6 @@
1
- import { QuickHash } from "../util/hash.js";
1
+ import { ServerOnlyWarning } from "./internal/util.js";
2
+ ServerOnlyWarning("endpoint");
3
+ import { QuickHash } from "./internal/util.js";
2
4
  const registry = new Map();
3
5
  /**
4
6
  * Create a route-less endpoint
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Helper for Server-Sent-Events, with auto close on SIGTERM and SIGHUP messages
3
+ * Includes a keep alive empty packet sent every 30sec (because Chrome implodes at 120sec, and can be unreliable at 60sec)
4
+ */
5
+ export declare class EventSource {
6
+ private controller;
7
+ private timer;
8
+ private state;
9
+ readonly response: Response;
10
+ readonly url: string;
11
+ constructor(request: Request, keepAlive?: number);
12
+ get readyState(): number;
13
+ private sendBytes;
14
+ private sendText;
15
+ private keepAlive;
16
+ dispatch(type: string, data: string): boolean;
17
+ close(unlink?: boolean): boolean;
18
+ }
19
+ export declare class EventSourceSet extends Set<EventSource> {
20
+ /** Send update to all EventSources, auto closing failed dispatches */
21
+ dispatch(type: string, data: string): void;
22
+ /** Cull all closed connections */
23
+ cull(): void;
24
+ /** Close all connections */
25
+ closeAll(): void;
26
+ }
@@ -0,0 +1,123 @@
1
+ import { ServerOnlyWarning } from "./internal/util.js";
2
+ ServerOnlyWarning("event-source");
3
+ /**
4
+ * Helper for Server-Sent-Events, with auto close on SIGTERM and SIGHUP messages
5
+ * Includes a keep alive empty packet sent every 30sec (because Chrome implodes at 120sec, and can be unreliable at 60sec)
6
+ */
7
+ export class EventSource {
8
+ controller;
9
+ timer;
10
+ state;
11
+ response;
12
+ url; // just to make it polyfill
13
+ constructor(request, keepAlive = 30_000) {
14
+ this.controller = null;
15
+ this.state = 0;
16
+ this.url = request.url;
17
+ const stream = new ReadableStream({
18
+ start: (c) => { this.controller = c; this.state = 1; },
19
+ cancel: () => { this.close(); }
20
+ });
21
+ request.signal.addEventListener('abort', () => this.close());
22
+ this.response = new Response(stream, { headers });
23
+ this.timer = setInterval(() => this.keepAlive(), keepAlive);
24
+ register.push(this);
25
+ }
26
+ get readyState() {
27
+ return this.state;
28
+ }
29
+ sendBytes(chunk) {
30
+ if (!this.controller)
31
+ return false;
32
+ try {
33
+ this.controller.enqueue(chunk);
34
+ return true;
35
+ }
36
+ catch (e) {
37
+ console.error(e);
38
+ this.close(); // unbind on failure
39
+ return false;
40
+ }
41
+ }
42
+ sendText(chunk) {
43
+ return this.sendBytes(encoder.encode(chunk));
44
+ }
45
+ keepAlive() {
46
+ return this.sendText("\n\n");
47
+ }
48
+ dispatch(type, data) {
49
+ return this.sendText(`event: ${type}\ndata: ${data}\n\n`);
50
+ }
51
+ close(unlink = true) {
52
+ if (this.state === 2)
53
+ return false;
54
+ if (unlink) {
55
+ const i = register.indexOf(this);
56
+ if (i !== -1)
57
+ register.splice(i, 1);
58
+ }
59
+ try {
60
+ this.controller?.close();
61
+ }
62
+ catch (e) {
63
+ console.error(e);
64
+ this.controller = null;
65
+ return false;
66
+ }
67
+ // Cleanup
68
+ if (this.timer)
69
+ clearInterval(this.timer);
70
+ this.controller = null;
71
+ this.state = 2;
72
+ return true;
73
+ }
74
+ }
75
+ export class EventSourceSet extends Set {
76
+ /** Send update to all EventSources, auto closing failed dispatches */
77
+ dispatch(type, data) {
78
+ for (const stream of this) {
79
+ if (stream.readyState === 0)
80
+ continue; // skip initializing
81
+ const success = stream.dispatch(type, data);
82
+ if (!success)
83
+ this.delete(stream);
84
+ }
85
+ }
86
+ /** Cull all closed connections */
87
+ cull() {
88
+ for (const stream of this) {
89
+ if (stream.readyState !== 2)
90
+ continue;
91
+ this.delete(stream);
92
+ }
93
+ }
94
+ /** Close all connections */
95
+ closeAll() {
96
+ for (const stream of this)
97
+ stream.close();
98
+ this.clear();
99
+ }
100
+ }
101
+ // global for easy reuse
102
+ const encoder = new TextEncoder();
103
+ const headers = new Headers();
104
+ // Chunked encoding with immediate forwarding by proxies (i.e. nginx)
105
+ headers.set("X-Accel-Buffering", "no");
106
+ headers.set("Transfer-Encoding", "chunked");
107
+ headers.set("Content-Type", "text/event-stream");
108
+ headers.set("Keep-Alive", "timeout=120"); // the maximum keep alive chrome shouldn't ignore
109
+ headers.set("Connection", "keep-alive");
110
+ // Auto close all SSE streams when shutdown requested
111
+ // Without this graceful shutdowns will hang indefinitely
112
+ const register = new Array();
113
+ function CloseAll() {
114
+ for (const connection of register)
115
+ connection.close(false); // don't waste time unregistering
116
+ }
117
+ if (process) {
118
+ process.on('SIGTERM', CloseAll);
119
+ process.on('SIGHUP', CloseAll);
120
+ }
121
+ else {
122
+ console.warn("htmx-router's EventSource has been unsafely loaded on the client");
123
+ }
@@ -0,0 +1,67 @@
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
+ }
@@ -0,0 +1,90 @@
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
+ });
@@ -0,0 +1,8 @@
1
+ import { ReactNode } from 'react';
2
+
3
+ declare global {
4
+ namespace JSX {
5
+ type Element = ReactNode;
6
+ interface HTMLAttributes extends HtmxAttributes {}
7
+ }
8
+ }
@@ -0,0 +1,38 @@
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
+ }
@@ -0,0 +1,58 @@
1
+ import { createRequestHandler } from 'htmx-router';
2
+ import { renderToString } from 'react-dom/server';
3
+ import express from 'express';
4
+ import morgan from "morgan";
5
+
6
+ const port = process.env.PORT || 5173;
7
+ const app = express();
8
+
9
+
10
+ const viteDevServer =
11
+ process.env.NODE_ENV === "production"
12
+ ? null
13
+ : await import("vite").then((vite) =>
14
+ vite.createServer({
15
+ server: { middlewareMode: true },
16
+ appType: 'custom'
17
+ })
18
+ );
19
+
20
+ app.use(
21
+ viteDevServer
22
+ ? viteDevServer.middlewares
23
+ : express.static("./dist/client")
24
+ );
25
+
26
+ // logging
27
+ app.use(morgan("tiny"));
28
+
29
+ const build = viteDevServer
30
+ ? () => viteDevServer.ssrLoadModule('./app/entry.server.ts')
31
+ : await import('./dist/server/entry.server.js');
32
+
33
+ app.use('*', createRequestHandler.http({
34
+ build, viteDevServer,
35
+ render: (res) => {
36
+ const headers = new Headers();
37
+ headers.set("Content-Type", "text/html; charset=UTF-8");
38
+
39
+ const stream = renderToString(res);
40
+ return new Response(stream, { headers });
41
+ }
42
+ }));
43
+
44
+
45
+ // Start http server
46
+ app.listen(port, () => {
47
+ console.log(`Server started at http://localhost:${port}`)
48
+ })
49
+
50
+
51
+ // Reload pages on file change
52
+ if (viteDevServer)
53
+ viteDevServer.watcher.on('change', (file) => {
54
+ console.log(`File changed: ${file}`);
55
+
56
+ console.log('Triggering full page reload');
57
+ viteDevServer.ws.send({ type: 'full-reload' });
58
+ });
package/global.d.ts ADDED
@@ -0,0 +1,7 @@
1
+ declare namespace JSX {
2
+ interface Element {}
3
+ interface IntrinsicElements {
4
+ [elementName: string]: any;
5
+ }
6
+ interface Fragment {}
7
+ }
package/index.d.ts ADDED
@@ -0,0 +1,19 @@
1
+ import type { GenericContext, RouteContext } from "./router.js";
2
+ import type { ParameterShaper } from "./util/parameters.js";
3
+ import { createRequestHandler } from "./internal/request/index.js";
4
+ export type RenderFunction<T> = (args: T) => Promise<Response | JSX.Element | null>;
5
+ export type CatchFunction<T> = (args: T, err: unknown) => Promise<Response | JSX.Element>;
6
+ export type RouteModule<T extends ParameterShaper> = {
7
+ parameters?: T;
8
+ loader?: RenderFunction<RouteContext<T>>;
9
+ action?: RenderFunction<RouteContext<T>>;
10
+ error?: CatchFunction<RouteContext<T>>;
11
+ route?: (params: Record<string, string>) => string;
12
+ };
13
+ export type ClientIslandManifest<T> = {
14
+ [K in keyof T]: ClientIsland<T[K]>;
15
+ };
16
+ type ClientIsland<T> = T extends (props: infer P) => JSX.Element ? (props: P & {
17
+ children?: JSX.Element;
18
+ }) => JSX.Element : T;
19
+ export { createRequestHandler, GenericContext, RouteContext };
package/index.js ADDED
@@ -0,0 +1,2 @@
1
+ import { createRequestHandler } from "./internal/request/index.js";
2
+ export { createRequestHandler };
@@ -0,0 +1,13 @@
1
+ type Config = {
2
+ framework: string;
3
+ client?: {
4
+ source: string;
5
+ output: {
6
+ server: string;
7
+ client: string;
8
+ };
9
+ };
10
+ component?: Record<string, string>;
11
+ };
12
+ export declare function ReadConfig(): Promise<Config>;
13
+ export {};
@@ -0,0 +1,11 @@
1
+ import { existsSync } from "fs";
2
+ import { readFile } from "fs/promises";
3
+ const DEFAULT = {
4
+ framework: "generic"
5
+ };
6
+ export async function ReadConfig() {
7
+ const path = process.argv[2] || "./htmx.config.json";
8
+ if (!existsSync(path))
9
+ return DEFAULT;
10
+ return JSON.parse(await readFile(path, "utf-8"));
11
+ }
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ import { readFile, writeFile } from "fs/promises";
4
+ import { CompileManifest } from "../compile/manifest.js";
5
+ import { ReadConfig } from "../cli/config.js";
6
+ const config = await ReadConfig();
7
+ if (config.client) {
8
+ console.info("Building client island manifest");
9
+ const source = await readFile(config.client.source, "utf8");
10
+ await writeFile(config.client.output.server, CompileManifest(config.framework, source, true));
11
+ await writeFile(config.client.output.client, CompileManifest(config.framework, source, false));
12
+ }
13
+ if (config.component) {
14
+ console.info("Building components");
15
+ }
@@ -0,0 +1 @@
1
+ export declare function GetClientEntryURL(): Promise<string | undefined>;
@@ -1,3 +1,5 @@
1
+ import { ServerOnlyWarning } from "./util.js";
2
+ ServerOnlyWarning("client-url");
1
3
  import { readFile } from "fs/promises";
2
4
  export async function GetClientEntryURL() {
3
5
  if (process.env.NODE_ENV !== "production")
@@ -7,6 +9,6 @@ export async function GetClientEntryURL() {
7
9
  const def = config[key];
8
10
  if (!def.isEntry)
9
11
  continue;
10
- return def.file;
12
+ return "/" + def.file;
11
13
  }
12
14
  }
@@ -0,0 +1 @@
1
+ export declare function CompileManifest(adapter: string, source: string, ssr: boolean): string;