redweb 0.7.2 → 0.7.4
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/LICENSE +21 -21
- package/README.md +90 -252
- package/index.d.ts +173 -173
- package/index.js +19 -19
- package/package.json +27 -27
- package/src/htmx/HtmxRenderer.js +65 -65
- package/src/htmx/RedWebHtmxComponent.js +11 -11
- package/src/http/BaseHttpServer.js +105 -105
- package/src/http/HttpServer.js +14 -14
- package/src/http/HttpsServer.js +17 -17
- package/src/http/index.js +1 -1
- package/src/sslConfig.js +20 -20
- package/src/ws/BaseHandler.js +39 -39
- package/src/ws/BaseSocketServer.js +76 -62
- package/src/ws/DefaultHandler.js +13 -13
- package/src/ws/DefaultRoute.js +13 -13
- package/src/ws/SecureSocketServer.js +20 -20
- package/src/ws/SocketRegistry.js +37 -37
- package/src/ws/SocketRoute.js +149 -149
- package/src/ws/SocketServer.js +18 -18
- package/src/ws/SocketService.js +32 -32
- package/src/ws/index.js +8 -8
- package/src/ws/util.js +9 -9
package/index.d.ts
CHANGED
|
@@ -1,173 +1,173 @@
|
|
|
1
|
-
declare module 'redweb' {
|
|
2
|
-
import { Application } from 'express';
|
|
3
|
-
import { CorsOptions } from 'cors';
|
|
4
|
-
import { Server as HttpServer } from 'http';
|
|
5
|
-
import { WebSocket } from 'ws';
|
|
6
|
-
|
|
7
|
-
/** ─────────────────── HTTP / CORE ─────────────────── */
|
|
8
|
-
|
|
9
|
-
export type RedWebEncoding = 'json' | 'urlencoded';
|
|
10
|
-
|
|
11
|
-
export interface RedWebOptions {
|
|
12
|
-
port?: number;
|
|
13
|
-
bind?: string;
|
|
14
|
-
publicPaths?: string[];
|
|
15
|
-
services?: Array<{ serviceName: string; method: string; function: Function }>;
|
|
16
|
-
listenCallback?: () => void;
|
|
17
|
-
encoding?: RedWebEncoding;
|
|
18
|
-
ssl?: { key: string; cert: string };
|
|
19
|
-
server?: Application;
|
|
20
|
-
corsOptions?: CorsOptions;
|
|
21
|
-
enableHtmxRendering?: boolean;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
/** ─────────────────── SOCKET SERVER ─────────────────── */
|
|
25
|
-
|
|
26
|
-
export interface SocketServerOptions {
|
|
27
|
-
server?: HttpServer;
|
|
28
|
-
port?: number;
|
|
29
|
-
routes?: Array<new () => SocketRoute>;
|
|
30
|
-
ssl?: { key: string; cert: string };
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
/** ─────────────────── ROUTES & HANDLERS ─────────────────── */
|
|
34
|
-
|
|
35
|
-
export interface SocketRouteConfig {
|
|
36
|
-
path: string;
|
|
37
|
-
handlers: Array<new () => BaseHandler>;
|
|
38
|
-
services?: Array<new () => SocketService>;
|
|
39
|
-
allowDuplicateConnections?: boolean;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
/** Socket‑side autonomous service (game loops, timers, etc.) */
|
|
43
|
-
export abstract class SocketService {
|
|
44
|
-
name: string;
|
|
45
|
-
tickRateMs?: number;
|
|
46
|
-
protected _tickHandle?: NodeJS.Timeout;
|
|
47
|
-
|
|
48
|
-
constructor(name: string, tickRateMs?: number);
|
|
49
|
-
|
|
50
|
-
/** Called once when the route is initialised */
|
|
51
|
-
onInit(route: SocketRoute): void;
|
|
52
|
-
|
|
53
|
-
/** Optional recurring tick (respecting tickRateMs) */
|
|
54
|
-
onTick?(): void;
|
|
55
|
-
|
|
56
|
-
/** Called on process shutdown / route removal */
|
|
57
|
-
onShutdown(): void;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
/** Message handler, triggered by client messages */
|
|
61
|
-
export class BaseHandler {
|
|
62
|
-
name: string;
|
|
63
|
-
constructor(name: string);
|
|
64
|
-
|
|
65
|
-
handleMessage(
|
|
66
|
-
socket: WebSocket & {
|
|
67
|
-
sendJson: (message: object) => void;
|
|
68
|
-
broadcast: (message: object) => void;
|
|
69
|
-
},
|
|
70
|
-
message: any
|
|
71
|
-
): void;
|
|
72
|
-
|
|
73
|
-
onMessage(socket: WebSocket, message: any): void;
|
|
74
|
-
onInitialContact(socket: WebSocket): void;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
export class SocketRoute {
|
|
78
|
-
path: string;
|
|
79
|
-
handlers: BaseHandler[];
|
|
80
|
-
clients: Map<string, WebSocket>;
|
|
81
|
-
allowDuplicateConnections?: boolean;
|
|
82
|
-
|
|
83
|
-
constructor(config: SocketRouteConfig);
|
|
84
|
-
|
|
85
|
-
addHandler(handler: new () => BaseHandler): void;
|
|
86
|
-
handleMessage(sock: WebSocket, data: any): void;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
/** ─────────────────── SERVER BASE ─────────────────── */
|
|
90
|
-
|
|
91
|
-
export class BaseSocketServer {
|
|
92
|
-
clients: Map<string, WebSocket>;
|
|
93
|
-
server: HttpServer;
|
|
94
|
-
routes: SocketRoute[];
|
|
95
|
-
|
|
96
|
-
constructor(server: HttpServer, options?: SocketServerOptions);
|
|
97
|
-
|
|
98
|
-
addRoute(route: new () => SocketRoute): void;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
/** ─────────────────── REGISTRY & UTIL TYPES ─────────────────── */
|
|
102
|
-
|
|
103
|
-
export interface SocketWrapper {
|
|
104
|
-
socket: WebSocket;
|
|
105
|
-
id: string;
|
|
106
|
-
send: (type: string, payload: Record<string, any>) => void;
|
|
107
|
-
getSanitized?(): Record<string, any>;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
export type SocketMessage = {
|
|
111
|
-
type: string;
|
|
112
|
-
[key: string]: any;
|
|
113
|
-
};
|
|
114
|
-
|
|
115
|
-
/** Generic event‑driven registry for socket objects */
|
|
116
|
-
/** Generic event-driven registry for socket objects */
|
|
117
|
-
export class SocketRegistry<T extends SocketWrapper = SocketWrapper> extends EventEmitter {
|
|
118
|
-
protected items: T[];
|
|
119
|
-
|
|
120
|
-
constructor();
|
|
121
|
-
|
|
122
|
-
/** Adds a socket-bound object to the registry */
|
|
123
|
-
add(item: T): void;
|
|
124
|
-
|
|
125
|
-
/**
|
|
126
|
-
* Removes a socket-bound object by reference or id (default key: 'id')
|
|
127
|
-
* @param itemOrId Object or ID string
|
|
128
|
-
* @param by Key name to match against (default is 'id')
|
|
129
|
-
*/
|
|
130
|
-
remove(itemOrId: T | string, by?: keyof T): boolean;
|
|
131
|
-
|
|
132
|
-
/** Returns a shallow copy of all registered items */
|
|
133
|
-
all(): T[];
|
|
134
|
-
|
|
135
|
-
/** Returns the number of registered items */
|
|
136
|
-
count(): number;
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
/** ─────────────────── CONCRETE SERVERS ─────────────────── */
|
|
140
|
-
|
|
141
|
-
export class SocketServer extends BaseSocketServer {
|
|
142
|
-
constructor(options?: SocketServerOptions);
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
export class SecureSocketServer extends BaseSocketServer {
|
|
146
|
-
constructor(options?: SocketServerOptions);
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
export class HttpServer {
|
|
150
|
-
constructor(options?: RedWebOptions);
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
export class HttpsServer {
|
|
154
|
-
constructor(options?: RedWebOptions);
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
/** ─────────────────── CONSTANTS ─────────────────── */
|
|
158
|
-
|
|
159
|
-
export const METHODS: {
|
|
160
|
-
GET: 'get';
|
|
161
|
-
POST: 'post';
|
|
162
|
-
PUT: 'put';
|
|
163
|
-
DELETE: 'delete';
|
|
164
|
-
};
|
|
165
|
-
|
|
166
|
-
export const ENCODINGS: {
|
|
167
|
-
json: 'json';
|
|
168
|
-
urlencoded: 'urlencoded';
|
|
169
|
-
};
|
|
170
|
-
|
|
171
|
-
export const HTTP_OPTIONS: RedWebOptions;
|
|
172
|
-
export const SOCKET_OPTIONS: SocketServerOptions;
|
|
173
|
-
}
|
|
1
|
+
declare module 'redweb' {
|
|
2
|
+
import { Application } from 'express';
|
|
3
|
+
import { CorsOptions } from 'cors';
|
|
4
|
+
import { Server as HttpServer } from 'http';
|
|
5
|
+
import { WebSocket } from 'ws';
|
|
6
|
+
|
|
7
|
+
/** ─────────────────── HTTP / CORE ─────────────────── */
|
|
8
|
+
|
|
9
|
+
export type RedWebEncoding = 'json' | 'urlencoded';
|
|
10
|
+
|
|
11
|
+
export interface RedWebOptions {
|
|
12
|
+
port?: number;
|
|
13
|
+
bind?: string;
|
|
14
|
+
publicPaths?: string[];
|
|
15
|
+
services?: Array<{ serviceName: string; method: string; function: Function }>;
|
|
16
|
+
listenCallback?: () => void;
|
|
17
|
+
encoding?: RedWebEncoding;
|
|
18
|
+
ssl?: { key: string; cert: string };
|
|
19
|
+
server?: Application;
|
|
20
|
+
corsOptions?: CorsOptions;
|
|
21
|
+
enableHtmxRendering?: boolean;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** ─────────────────── SOCKET SERVER ─────────────────── */
|
|
25
|
+
|
|
26
|
+
export interface SocketServerOptions {
|
|
27
|
+
server?: HttpServer;
|
|
28
|
+
port?: number;
|
|
29
|
+
routes?: Array<new () => SocketRoute>;
|
|
30
|
+
ssl?: { key: string; cert: string };
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** ─────────────────── ROUTES & HANDLERS ─────────────────── */
|
|
34
|
+
|
|
35
|
+
export interface SocketRouteConfig {
|
|
36
|
+
path: string;
|
|
37
|
+
handlers: Array<new () => BaseHandler>;
|
|
38
|
+
services?: Array<new () => SocketService>;
|
|
39
|
+
allowDuplicateConnections?: boolean;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** Socket‑side autonomous service (game loops, timers, etc.) */
|
|
43
|
+
export abstract class SocketService {
|
|
44
|
+
name: string;
|
|
45
|
+
tickRateMs?: number;
|
|
46
|
+
protected _tickHandle?: NodeJS.Timeout;
|
|
47
|
+
|
|
48
|
+
constructor(name: string, tickRateMs?: number);
|
|
49
|
+
|
|
50
|
+
/** Called once when the route is initialised */
|
|
51
|
+
onInit(route: SocketRoute): void;
|
|
52
|
+
|
|
53
|
+
/** Optional recurring tick (respecting tickRateMs) */
|
|
54
|
+
onTick?(): void;
|
|
55
|
+
|
|
56
|
+
/** Called on process shutdown / route removal */
|
|
57
|
+
onShutdown(): void;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** Message handler, triggered by client messages */
|
|
61
|
+
export class BaseHandler {
|
|
62
|
+
name: string;
|
|
63
|
+
constructor(name: string);
|
|
64
|
+
|
|
65
|
+
handleMessage(
|
|
66
|
+
socket: WebSocket & {
|
|
67
|
+
sendJson: (message: object) => void;
|
|
68
|
+
broadcast: (message: object) => void;
|
|
69
|
+
},
|
|
70
|
+
message: any
|
|
71
|
+
): void;
|
|
72
|
+
|
|
73
|
+
onMessage(socket: WebSocket, message: any): void;
|
|
74
|
+
onInitialContact(socket: WebSocket): void;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export class SocketRoute {
|
|
78
|
+
path: string;
|
|
79
|
+
handlers: BaseHandler[];
|
|
80
|
+
clients: Map<string, WebSocket>;
|
|
81
|
+
allowDuplicateConnections?: boolean;
|
|
82
|
+
|
|
83
|
+
constructor(config: SocketRouteConfig);
|
|
84
|
+
|
|
85
|
+
addHandler(handler: new () => BaseHandler): void;
|
|
86
|
+
handleMessage(sock: WebSocket, data: any): void;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** ─────────────────── SERVER BASE ─────────────────── */
|
|
90
|
+
|
|
91
|
+
export class BaseSocketServer {
|
|
92
|
+
clients: Map<string, WebSocket>;
|
|
93
|
+
server: HttpServer;
|
|
94
|
+
routes: SocketRoute[];
|
|
95
|
+
|
|
96
|
+
constructor(server: HttpServer, options?: SocketServerOptions);
|
|
97
|
+
|
|
98
|
+
addRoute(route: new () => SocketRoute): void;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/** ─────────────────── REGISTRY & UTIL TYPES ─────────────────── */
|
|
102
|
+
|
|
103
|
+
export interface SocketWrapper {
|
|
104
|
+
socket: WebSocket;
|
|
105
|
+
id: string;
|
|
106
|
+
send: (type: string, payload: Record<string, any>) => void;
|
|
107
|
+
getSanitized?(): Record<string, any>;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export type SocketMessage = {
|
|
111
|
+
type: string;
|
|
112
|
+
[key: string]: any;
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
/** Generic event‑driven registry for socket objects */
|
|
116
|
+
/** Generic event-driven registry for socket objects */
|
|
117
|
+
export class SocketRegistry<T extends SocketWrapper = SocketWrapper> extends EventEmitter {
|
|
118
|
+
protected items: T[];
|
|
119
|
+
|
|
120
|
+
constructor();
|
|
121
|
+
|
|
122
|
+
/** Adds a socket-bound object to the registry */
|
|
123
|
+
add(item: T): void;
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Removes a socket-bound object by reference or id (default key: 'id')
|
|
127
|
+
* @param itemOrId Object or ID string
|
|
128
|
+
* @param by Key name to match against (default is 'id')
|
|
129
|
+
*/
|
|
130
|
+
remove(itemOrId: T | string, by?: keyof T): boolean;
|
|
131
|
+
|
|
132
|
+
/** Returns a shallow copy of all registered items */
|
|
133
|
+
all(): T[];
|
|
134
|
+
|
|
135
|
+
/** Returns the number of registered items */
|
|
136
|
+
count(): number;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/** ─────────────────── CONCRETE SERVERS ─────────────────── */
|
|
140
|
+
|
|
141
|
+
export class SocketServer extends BaseSocketServer {
|
|
142
|
+
constructor(options?: SocketServerOptions);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export class SecureSocketServer extends BaseSocketServer {
|
|
146
|
+
constructor(options?: SocketServerOptions);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export class HttpServer {
|
|
150
|
+
constructor(options?: RedWebOptions);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export class HttpsServer {
|
|
154
|
+
constructor(options?: RedWebOptions);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/** ─────────────────── CONSTANTS ─────────────────── */
|
|
158
|
+
|
|
159
|
+
export const METHODS: {
|
|
160
|
+
GET: 'get';
|
|
161
|
+
POST: 'post';
|
|
162
|
+
PUT: 'put';
|
|
163
|
+
DELETE: 'delete';
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
export const ENCODINGS: {
|
|
167
|
+
json: 'json';
|
|
168
|
+
urlencoded: 'urlencoded';
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
export const HTTP_OPTIONS: RedWebOptions;
|
|
172
|
+
export const SOCKET_OPTIONS: SocketServerOptions;
|
|
173
|
+
}
|
package/index.js
CHANGED
|
@@ -1,19 +1,19 @@
|
|
|
1
|
-
const { METHODS } = require('./src/http');
|
|
2
|
-
const { sendJson } = require('./src/ws/util');
|
|
3
|
-
const { SocketServer, SecureSocketServer, SOCKET_OPTIONS, SocketRoute, SocketService, SocketRegistry } = require('./src/ws');
|
|
4
|
-
const { BaseHandler } = require('./src/ws/BaseHandler');
|
|
5
|
-
const HttpServer = require('./src/http/HttpServer');
|
|
6
|
-
const HttpsServer = require('./src/http/HttpsServer');
|
|
7
|
-
module.exports = {
|
|
8
|
-
HttpServer,
|
|
9
|
-
HttpsServer,
|
|
10
|
-
SocketServer,
|
|
11
|
-
SecureSocketServer,
|
|
12
|
-
BaseHandler,
|
|
13
|
-
SocketRoute,
|
|
14
|
-
SocketService,
|
|
15
|
-
SocketRegistry,
|
|
16
|
-
sendJson,
|
|
17
|
-
SOCKET_OPTIONS,
|
|
18
|
-
METHODS
|
|
19
|
-
};
|
|
1
|
+
const { METHODS } = require('./src/http');
|
|
2
|
+
const { sendJson } = require('./src/ws/util');
|
|
3
|
+
const { SocketServer, SecureSocketServer, SOCKET_OPTIONS, SocketRoute, SocketService, SocketRegistry } = require('./src/ws');
|
|
4
|
+
const { BaseHandler } = require('./src/ws/BaseHandler');
|
|
5
|
+
const HttpServer = require('./src/http/HttpServer');
|
|
6
|
+
const HttpsServer = require('./src/http/HttpsServer');
|
|
7
|
+
module.exports = {
|
|
8
|
+
HttpServer,
|
|
9
|
+
HttpsServer,
|
|
10
|
+
SocketServer,
|
|
11
|
+
SecureSocketServer,
|
|
12
|
+
BaseHandler,
|
|
13
|
+
SocketRoute,
|
|
14
|
+
SocketService,
|
|
15
|
+
SocketRegistry,
|
|
16
|
+
sendJson,
|
|
17
|
+
SOCKET_OPTIONS,
|
|
18
|
+
METHODS
|
|
19
|
+
};
|
package/package.json
CHANGED
|
@@ -1,27 +1,27 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "redweb",
|
|
3
|
-
"version": "0.7.
|
|
4
|
-
"description": "A way to quickly set up an express server",
|
|
5
|
-
"main": "index.js",
|
|
6
|
-
"scripts": {
|
|
7
|
-
"test": "
|
|
8
|
-
},
|
|
9
|
-
"files": [
|
|
10
|
-
"src/*",
|
|
11
|
-
"index.d.ts"
|
|
12
|
-
],
|
|
13
|
-
"keywords": [],
|
|
14
|
-
"author": "",
|
|
15
|
-
"license": "ISC",
|
|
16
|
-
"dependencies": {
|
|
17
|
-
"cors": "^2.8.5",
|
|
18
|
-
"express": "^4.19.2",
|
|
19
|
-
"ws": "^8.17.0"
|
|
20
|
-
},
|
|
21
|
-
"devDependencies": {
|
|
22
|
-
"@types/express": "^4.17.21",
|
|
23
|
-
"@types/jest": "^29.5.12",
|
|
24
|
-
"jest": "^29.7.0",
|
|
25
|
-
"supertest": "^7.0.0"
|
|
26
|
-
}
|
|
27
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "redweb",
|
|
3
|
+
"version": "0.7.4",
|
|
4
|
+
"description": "A way to quickly set up an express server",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"test": "npx jest"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"src/*",
|
|
11
|
+
"index.d.ts"
|
|
12
|
+
],
|
|
13
|
+
"keywords": [],
|
|
14
|
+
"author": "",
|
|
15
|
+
"license": "ISC",
|
|
16
|
+
"dependencies": {
|
|
17
|
+
"cors": "^2.8.5",
|
|
18
|
+
"express": "^4.19.2",
|
|
19
|
+
"ws": "^8.17.0"
|
|
20
|
+
},
|
|
21
|
+
"devDependencies": {
|
|
22
|
+
"@types/express": "^4.17.21",
|
|
23
|
+
"@types/jest": "^29.5.12",
|
|
24
|
+
"jest": "^29.7.0",
|
|
25
|
+
"supertest": "^7.0.0"
|
|
26
|
+
}
|
|
27
|
+
}
|
package/src/htmx/HtmxRenderer.js
CHANGED
|
@@ -1,65 +1,65 @@
|
|
|
1
|
-
const fs = require('fs');
|
|
2
|
-
const vm = require('vm');
|
|
3
|
-
const path = require('path');
|
|
4
|
-
|
|
5
|
-
class HtmxRenderer {
|
|
6
|
-
/**
|
|
7
|
-
* Render an .htmx file as JavaScript with embedded print statements.
|
|
8
|
-
* @param {string} filePath - Path to the .htmx file.
|
|
9
|
-
* @returns {string} Rendered HTML string with normalized whitespace.
|
|
10
|
-
*/
|
|
11
|
-
static render(filePath) {
|
|
12
|
-
if (!fs.existsSync(filePath)) {
|
|
13
|
-
throw new Error(`Template file not found: ${filePath}`);
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
let output = '';
|
|
17
|
-
const templateContent = fs.readFileSync(filePath, 'utf-8');
|
|
18
|
-
|
|
19
|
-
// Transform <@ ... @/> blocks into print() calls
|
|
20
|
-
const transformedTemplate = templateContent.replace(
|
|
21
|
-
/<@>([\s\S]*?)<@\/>/g,
|
|
22
|
-
(_, content) => `print(\`${content.replace(/{{\s*(.*?)\s*}}/g, '${$1}')}\`);`
|
|
23
|
-
);
|
|
24
|
-
|
|
25
|
-
// Wrap the script in an IIFE
|
|
26
|
-
const wrappedScript = `
|
|
27
|
-
(() => {
|
|
28
|
-
const print = (html) => output += html;
|
|
29
|
-
${transformedTemplate}
|
|
30
|
-
return output;
|
|
31
|
-
})();
|
|
32
|
-
`;
|
|
33
|
-
|
|
34
|
-
// Create a custom require function that resolves paths relative to the template
|
|
35
|
-
const customRequire = (modulePath) => {
|
|
36
|
-
const absolutePath = path.resolve(path.dirname(filePath), modulePath);
|
|
37
|
-
return require(absolutePath);
|
|
38
|
-
};
|
|
39
|
-
|
|
40
|
-
// Execute the script in a sandbox
|
|
41
|
-
const script = new vm.Script(wrappedScript);
|
|
42
|
-
const sandbox = {
|
|
43
|
-
output: '',
|
|
44
|
-
require: customRequire, // Add custom require
|
|
45
|
-
__dirname: path.dirname(filePath),
|
|
46
|
-
__filename: filePath,
|
|
47
|
-
};
|
|
48
|
-
vm.createContext(sandbox);
|
|
49
|
-
|
|
50
|
-
// Get the rendered output
|
|
51
|
-
let result = script.runInContext(sandbox);
|
|
52
|
-
|
|
53
|
-
// Normalize spaces but preserve those in content
|
|
54
|
-
result = result
|
|
55
|
-
.replace(/>\s+</g, '><') // Remove spaces between tags
|
|
56
|
-
.replace(/\s+/g, ' ') // Collapse multiple spaces to one
|
|
57
|
-
.replace(/>\s+/g, '>') // Remove spaces after tags
|
|
58
|
-
.replace(/\s+</g, '<') // Remove spaces before tags
|
|
59
|
-
.trim(); // Trim leading and trailing spaces
|
|
60
|
-
|
|
61
|
-
return result;
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
module.exports = HtmxRenderer;
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const vm = require('vm');
|
|
3
|
+
const path = require('path');
|
|
4
|
+
|
|
5
|
+
class HtmxRenderer {
|
|
6
|
+
/**
|
|
7
|
+
* Render an .htmx file as JavaScript with embedded print statements.
|
|
8
|
+
* @param {string} filePath - Path to the .htmx file.
|
|
9
|
+
* @returns {string} Rendered HTML string with normalized whitespace.
|
|
10
|
+
*/
|
|
11
|
+
static render(filePath) {
|
|
12
|
+
if (!fs.existsSync(filePath)) {
|
|
13
|
+
throw new Error(`Template file not found: ${filePath}`);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
let output = '';
|
|
17
|
+
const templateContent = fs.readFileSync(filePath, 'utf-8');
|
|
18
|
+
|
|
19
|
+
// Transform <@ ... @/> blocks into print() calls
|
|
20
|
+
const transformedTemplate = templateContent.replace(
|
|
21
|
+
/<@>([\s\S]*?)<@\/>/g,
|
|
22
|
+
(_, content) => `print(\`${content.replace(/{{\s*(.*?)\s*}}/g, '${$1}')}\`);`
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
// Wrap the script in an IIFE
|
|
26
|
+
const wrappedScript = `
|
|
27
|
+
(() => {
|
|
28
|
+
const print = (html) => output += html;
|
|
29
|
+
${transformedTemplate}
|
|
30
|
+
return output;
|
|
31
|
+
})();
|
|
32
|
+
`;
|
|
33
|
+
|
|
34
|
+
// Create a custom require function that resolves paths relative to the template
|
|
35
|
+
const customRequire = (modulePath) => {
|
|
36
|
+
const absolutePath = path.resolve(path.dirname(filePath), modulePath);
|
|
37
|
+
return require(absolutePath);
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
// Execute the script in a sandbox
|
|
41
|
+
const script = new vm.Script(wrappedScript);
|
|
42
|
+
const sandbox = {
|
|
43
|
+
output: '',
|
|
44
|
+
require: customRequire, // Add custom require
|
|
45
|
+
__dirname: path.dirname(filePath),
|
|
46
|
+
__filename: filePath,
|
|
47
|
+
};
|
|
48
|
+
vm.createContext(sandbox);
|
|
49
|
+
|
|
50
|
+
// Get the rendered output
|
|
51
|
+
let result = script.runInContext(sandbox);
|
|
52
|
+
|
|
53
|
+
// Normalize spaces but preserve those in content
|
|
54
|
+
result = result
|
|
55
|
+
.replace(/>\s+</g, '><') // Remove spaces between tags
|
|
56
|
+
.replace(/\s+/g, ' ') // Collapse multiple spaces to one
|
|
57
|
+
.replace(/>\s+/g, '>') // Remove spaces after tags
|
|
58
|
+
.replace(/\s+</g, '<') // Remove spaces before tags
|
|
59
|
+
.trim(); // Trim leading and trailing spaces
|
|
60
|
+
|
|
61
|
+
return result;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
module.exports = HtmxRenderer;
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
class RedWebHtmxComponent {
|
|
2
|
-
constructor(props = {}) {
|
|
3
|
-
this.props = props;
|
|
4
|
-
}
|
|
5
|
-
|
|
6
|
-
render() {
|
|
7
|
-
throw new Error('Render method must be implemented in derived components');
|
|
8
|
-
}
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
module.exports = RedWebHtmxComponent;
|
|
1
|
+
class RedWebHtmxComponent {
|
|
2
|
+
constructor(props = {}) {
|
|
3
|
+
this.props = props;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
render() {
|
|
7
|
+
throw new Error('Render method must be implemented in derived components');
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
module.exports = RedWebHtmxComponent;
|