redweb 0.9.0 → 0.10.0

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 (41) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/README.md +138 -23
  3. package/docs/LIVE_HTML.md +313 -0
  4. package/examples/live-html/cards.css +36 -0
  5. package/examples/live-html/cards.html +11 -0
  6. package/examples/live-html/cards.js +91 -0
  7. package/examples/live-html/cards.ts +35 -0
  8. package/examples/live-html/chatroom.css +156 -0
  9. package/examples/live-html/chatroom.js +268 -0
  10. package/examples/live-html/chatroom.ts +217 -0
  11. package/examples/live-html/components.css +7 -0
  12. package/examples/live-html/components.js +113 -0
  13. package/examples/live-html/components.ts +41 -0
  14. package/examples/live-html/counter.css +24 -0
  15. package/examples/live-html/counter.html +10 -0
  16. package/examples/live-html/counter.js +73 -0
  17. package/examples/live-html/counter.ts +21 -0
  18. package/examples/live-html/tsconfig.json +16 -0
  19. package/index.d.ts +219 -1
  20. package/index.js +18 -1
  21. package/package.json +14 -3
  22. package/src/htmx/Html.js +133 -0
  23. package/src/htmx/HtmlRenderer.js +88 -0
  24. package/src/htmx/HtmlSyntax.js +168 -0
  25. package/src/htmx/LiveHtmlServer.js +91 -0
  26. package/src/htmx/LivePage.js +232 -0
  27. package/src/htmx/PageAssetLoader.js +34 -0
  28. package/src/htmx/PageManager.js +435 -0
  29. package/src/htmx/StaticExporter.js +78 -0
  30. package/src/htmx/StaticSite.js +182 -0
  31. package/src/htmx/TemplateRenderer.js +231 -0
  32. package/src/htmx/browserRuntime.js +97 -0
  33. package/src/htmx/index.js +10 -0
  34. package/src/htmx/metadata.js +349 -0
  35. package/src/htmx/sourceRoot.js +28 -0
  36. package/src/htmx/start.js +17 -0
  37. package/src/htmx/synchronous.js +9 -0
  38. package/src/http/BaseHttpServer.js +0 -35
  39. package/src/ws/BaseSocketServer.js +4 -0
  40. package/src/htmx/HtmxRenderer.js +0 -73
  41. package/src/htmx/RedWebHtmxComponent.js +0 -11
@@ -0,0 +1,217 @@
1
+ import { action, component, each, html, page, start, state, type HtmlFragment } from 'redweb';
2
+
3
+ const MAX_VISIBLE_MEMBERS = 100;
4
+ const UNSAFE_TEXT = /[\p{Cc}\p{Cf}]/u;
5
+
6
+ interface JoinForm {
7
+ name?: string;
8
+ }
9
+
10
+ interface MessageForm {
11
+ message?: string;
12
+ }
13
+
14
+ interface StoredMessage {
15
+ sender: string;
16
+ text: string;
17
+ }
18
+
19
+ interface RoomParticipant {
20
+ readonly displayName: string;
21
+ updateMessages(messages: HtmlFragment): void;
22
+ updatePresence(presence: HtmlFragment): void;
23
+ }
24
+
25
+ function messageView(messages: readonly StoredMessage[]) {
26
+ return messages.length
27
+ ? each([...messages], entry => html`<li><strong>${entry.sender}</strong><p>${entry.text}</p></li>`)
28
+ : html`<li class="empty-message">No messages yet. Say hello.</li>`;
29
+ }
30
+
31
+ function presenceView(members: readonly string[]) {
32
+ const visible = members.slice(0, MAX_VISIBLE_MEMBERS);
33
+ const remaining = members.length - visible.length;
34
+ return html`
35
+ <p class="eyebrow">Online · ${members.length}</p>
36
+ <ul>
37
+ ${each([...visible], member => html`<li>${member}</li>`)}
38
+ ${remaining ? html`<li class="more-members">+${remaining} more</li>` : html``}
39
+ </ul>
40
+ `;
41
+ }
42
+
43
+ class ChatRoom {
44
+ private history: StoredMessage[] = [];
45
+ private readonly participants = new Set<RoomParticipant>();
46
+ private readonly online = new Set<RoomParticipant>();
47
+
48
+ join(participant: RoomParticipant) {
49
+ const name = participant.displayName.toLocaleLowerCase();
50
+ if ([...this.participants].some(member => member !== participant && member.displayName.toLocaleLowerCase() === name)) {
51
+ return false;
52
+ }
53
+ this.participants.add(participant);
54
+ this.online.add(participant);
55
+ participant.updateMessages(messageView(this.history));
56
+ this.publishPresence();
57
+ return true;
58
+ }
59
+
60
+ disconnect(participant: RoomParticipant) {
61
+ if (!this.online.delete(participant)) return;
62
+ this.publishPresence();
63
+ }
64
+
65
+ leave(participant: RoomParticipant) {
66
+ this.online.delete(participant);
67
+ if (!this.participants.delete(participant)) return;
68
+ this.publishPresence();
69
+ }
70
+
71
+ send(participant: RoomParticipant, text: string) {
72
+ if (!this.online.has(participant)) return false;
73
+ this.history = [...this.history, { sender: participant.displayName, text }].slice(-100);
74
+ const messages = messageView(this.history);
75
+ for (const member of this.participants) member.updateMessages(messages);
76
+ return true;
77
+ }
78
+
79
+ private publishPresence() {
80
+ const members = [...this.online].map(participant => participant.displayName);
81
+ const presence = presenceView(members);
82
+ for (const participant of this.participants) participant.updatePresence(presence);
83
+ }
84
+ }
85
+
86
+ @component()
87
+ export class ChatroomComponent implements RoomParticipant {
88
+ displayName = '';
89
+
90
+ @state()
91
+ screen = this.joinScreen();
92
+
93
+ @state()
94
+ messages = messageView([]);
95
+
96
+ @state()
97
+ presence = presenceView([]);
98
+
99
+ constructor(private readonly room: ChatRoom) {}
100
+
101
+ connected() {
102
+ if (this.displayName) this.room.join(this);
103
+ }
104
+
105
+ disconnected() {
106
+ this.room.disconnect(this);
107
+ }
108
+
109
+ disposed() {
110
+ this.room.leave(this);
111
+ }
112
+
113
+ @action()
114
+ join({ name }: JoinForm) {
115
+ if (this.displayName) return false;
116
+ if (typeof name !== 'string') {
117
+ this.screen = this.joinScreen('Display name must be text.');
118
+ return false;
119
+ }
120
+ const displayName = name.normalize('NFKC').trim();
121
+ if (!displayName || displayName.length > 40 || UNSAFE_TEXT.test(displayName)) {
122
+ this.screen = this.joinScreen('Choose a visible display name of at most 40 characters.');
123
+ return false;
124
+ }
125
+ this.displayName = displayName;
126
+ if (this.room.join(this)) {
127
+ this.screen = this.roomScreen();
128
+ return true;
129
+ }
130
+ this.displayName = '';
131
+ this.screen = this.joinScreen('That display name is already in use.');
132
+ return false;
133
+ }
134
+
135
+ @action()
136
+ send({ message }: MessageForm) {
137
+ if (typeof message !== 'string') return false;
138
+ const text = message.normalize('NFKC').trim();
139
+ if (!text || text.length > 500 || UNSAFE_TEXT.test(text)) return false;
140
+ return this.room.send(this, text);
141
+ }
142
+
143
+ @action()
144
+ leave() {
145
+ this.room.leave(this);
146
+ this.displayName = '';
147
+ this.screen = this.joinScreen();
148
+ }
149
+
150
+ updateMessages(messages: HtmlFragment) {
151
+ this.messages = messages;
152
+ }
153
+
154
+ updatePresence(presence: HtmlFragment) {
155
+ this.presence = presence;
156
+ }
157
+
158
+ render() {
159
+ return html`<section class="chatroom" data-rw-state="screen">${this.screen}</section>`;
160
+ }
161
+
162
+ private joinScreen(error = '') {
163
+ const feedback = error ? html`<p class="form-error" role="alert">${error}</p>` : html``;
164
+ return html`
165
+ <section class="join-panel">
166
+ <p class="eyebrow">Live room</p>
167
+ <h1>Join the chatroom</h1>
168
+ <p>Choose a name once, then chat in realtime with everyone currently in the room.</p>
169
+ ${feedback}
170
+ <form rw-submit="join" class="join-form">
171
+ <label for="display-name">Display name</label>
172
+ <div class="input-row">
173
+ <input id="display-name" name="name" maxlength="40" autocomplete="nickname" required autofocus>
174
+ <button type="submit">Join room</button>
175
+ </div>
176
+ </form>
177
+ </section>
178
+ `;
179
+ }
180
+
181
+ private roomScreen() {
182
+ return html`
183
+ <div class="room-layout">
184
+ <section class="conversation">
185
+ <header class="room-header">
186
+ <div><p class="eyebrow">Connected as</p><h1>${this.displayName}</h1></div>
187
+ <button type="button" class="quiet-button" rw-click="leave">Leave</button>
188
+ </header>
189
+ <ol class="message-list" aria-live="polite" data-rw-state="messages">${this.messages}</ol>
190
+ <form rw-submit="send" class="composer">
191
+ <label class="sr-only" for="chat-message">Message</label>
192
+ <input id="chat-message" name="message" maxlength="500" autocomplete="off" placeholder="Message the room…" required autofocus>
193
+ <button type="submit">Send</button>
194
+ </form>
195
+ </section>
196
+ <aside class="presence" aria-label="People in the room" data-rw-state="presence">${this.presence}</aside>
197
+ </div>
198
+ `;
199
+ }
200
+ }
201
+
202
+ export function createChatroomPage() {
203
+ const room = new ChatRoom();
204
+
205
+ @page('/', { css: 'chatroom.css' })
206
+ class ChatroomPage {
207
+ chat = new ChatroomComponent(room);
208
+
209
+ render() {
210
+ return html`<main>${this.chat}</main>`;
211
+ }
212
+ }
213
+
214
+ return ChatroomPage;
215
+ }
216
+
217
+ if (require.main === module) start(createChatroomPage(), { port: 8080 });
@@ -0,0 +1,7 @@
1
+ :root { color-scheme: dark; font-family: system-ui, sans-serif; background: #07111f; color: #e2e8f0; }
2
+ body { margin: 0; }
3
+ main { width: min(54rem, calc(100% - 2rem)); margin: 4rem auto; }
4
+ .counter-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr)); gap: 1rem; }
5
+ .counter-card { display: grid; gap: 1rem; padding: 1.5rem; border: 1px solid #334155; border-radius: 1rem; background: #111827; }
6
+ output { font-size: 2rem; font-weight: 700; color: #67e8f9; }
7
+ button { border: 0; border-radius: .65rem; padding: .75rem 1rem; background: #22d3ee; color: #082f49; font-weight: 700; cursor: pointer; }
@@ -0,0 +1,113 @@
1
+ "use strict";
2
+ var __runInitializers = (this && this.__runInitializers) || function (thisArg, initializers, value) {
3
+ var useValue = arguments.length > 2;
4
+ for (var i = 0; i < initializers.length; i++) {
5
+ value = useValue ? initializers[i].call(thisArg, value) : initializers[i].call(thisArg);
6
+ }
7
+ return useValue ? value : void 0;
8
+ };
9
+ var __esDecorate = (this && this.__esDecorate) || function (ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) {
10
+ function accept(f) { if (f !== void 0 && typeof f !== "function") throw new TypeError("Function expected"); return f; }
11
+ var kind = contextIn.kind, key = kind === "getter" ? "get" : kind === "setter" ? "set" : "value";
12
+ var target = !descriptorIn && ctor ? contextIn["static"] ? ctor : ctor.prototype : null;
13
+ var descriptor = descriptorIn || (target ? Object.getOwnPropertyDescriptor(target, contextIn.name) : {});
14
+ var _, done = false;
15
+ for (var i = decorators.length - 1; i >= 0; i--) {
16
+ var context = {};
17
+ for (var p in contextIn) context[p] = p === "access" ? {} : contextIn[p];
18
+ for (var p in contextIn.access) context.access[p] = contextIn.access[p];
19
+ context.addInitializer = function (f) { if (done) throw new TypeError("Cannot add initializers after decoration has completed"); extraInitializers.push(accept(f || null)); };
20
+ var result = (0, decorators[i])(kind === "accessor" ? { get: descriptor.get, set: descriptor.set } : descriptor[key], context);
21
+ if (kind === "accessor") {
22
+ if (result === void 0) continue;
23
+ if (result === null || typeof result !== "object") throw new TypeError("Object expected");
24
+ if (_ = accept(result.get)) descriptor.get = _;
25
+ if (_ = accept(result.set)) descriptor.set = _;
26
+ if (_ = accept(result.init)) initializers.unshift(_);
27
+ }
28
+ else if (_ = accept(result)) {
29
+ if (kind === "field") initializers.unshift(_);
30
+ else descriptor[key] = _;
31
+ }
32
+ }
33
+ if (target) Object.defineProperty(target, contextIn.name, descriptor);
34
+ done = true;
35
+ };
36
+ Object.defineProperty(exports, "__esModule", { value: true });
37
+ exports.ComponentsPage = exports.CounterComponent = void 0;
38
+ const redweb_1 = require('../..');
39
+ let CounterComponent = (() => {
40
+ let _classDecorators = [(0, redweb_1.component)()];
41
+ let _classDescriptor;
42
+ let _classExtraInitializers = [];
43
+ let _classThis;
44
+ let _instanceExtraInitializers = [];
45
+ let _count_decorators;
46
+ let _count_initializers = [];
47
+ let _count_extraInitializers = [];
48
+ let _increment_decorators;
49
+ var CounterComponent = class {
50
+ static { _classThis = this; }
51
+ static {
52
+ const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(null) : void 0;
53
+ _count_decorators = [(0, redweb_1.state)()];
54
+ _increment_decorators = [(0, redweb_1.action)()];
55
+ __esDecorate(this, null, _increment_decorators, { kind: "method", name: "increment", static: false, private: false, access: { has: obj => "increment" in obj, get: obj => obj.increment }, metadata: _metadata }, null, _instanceExtraInitializers);
56
+ __esDecorate(null, null, _count_decorators, { kind: "field", name: "count", static: false, private: false, access: { has: obj => "count" in obj, get: obj => obj.count, set: (obj, value) => { obj.count = value; } }, metadata: _metadata }, _count_initializers, _count_extraInitializers);
57
+ __esDecorate(null, _classDescriptor = { value: _classThis }, _classDecorators, { kind: "class", name: _classThis.name, metadata: _metadata }, null, _classExtraInitializers);
58
+ CounterComponent = _classThis = _classDescriptor.value;
59
+ if (_metadata) Object.defineProperty(_classThis, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
60
+ __runInitializers(_classThis, _classExtraInitializers);
61
+ }
62
+ label = __runInitializers(this, _instanceExtraInitializers);
63
+ count = __runInitializers(this, _count_initializers, 0);
64
+ constructor(label) {
65
+ __runInitializers(this, _count_extraInitializers);
66
+ this.label = label;
67
+ }
68
+ increment() {
69
+ this.count += 1;
70
+ }
71
+ render() {
72
+ return (0, redweb_1.html) `
73
+ <article class="counter-card">
74
+ <h2>${this.label}</h2>
75
+ <output data-rw-state="count">${this.count}</output>
76
+ <button type="button" rw-click="increment">Increment on the server</button>
77
+ </article>
78
+ `;
79
+ }
80
+ };
81
+ return CounterComponent = _classThis;
82
+ })();
83
+ exports.CounterComponent = CounterComponent;
84
+ let ComponentsPage = (() => {
85
+ let _classDecorators = [(0, redweb_1.page)('/', { css: 'components.css' })];
86
+ let _classDescriptor;
87
+ let _classExtraInitializers = [];
88
+ let _classThis;
89
+ var ComponentsPage = class {
90
+ static { _classThis = this; }
91
+ static {
92
+ const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(null) : void 0;
93
+ __esDecorate(null, _classDescriptor = { value: _classThis }, _classDecorators, { kind: "class", name: _classThis.name, metadata: _metadata }, null, _classExtraInitializers);
94
+ ComponentsPage = _classThis = _classDescriptor.value;
95
+ if (_metadata) Object.defineProperty(_classThis, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
96
+ __runInitializers(_classThis, _classExtraInitializers);
97
+ }
98
+ primary = new CounterComponent('Primary counter');
99
+ secondary = new CounterComponent('Independent counter');
100
+ render() {
101
+ return (0, redweb_1.html) `
102
+ <main>
103
+ <h1>Reusable server components</h1>
104
+ <section class="counter-grid">${this.primary}${this.secondary}</section>
105
+ </main>
106
+ `;
107
+ }
108
+ };
109
+ return ComponentsPage = _classThis;
110
+ })();
111
+ exports.ComponentsPage = ComponentsPage;
112
+ if (require.main === module)
113
+ (0, redweb_1.start)(ComponentsPage, { port: 8080 });
@@ -0,0 +1,41 @@
1
+ import { action, component, html, page, start, state } from 'redweb';
2
+
3
+ @component()
4
+ export class CounterComponent {
5
+ @state()
6
+ count = 0;
7
+
8
+ constructor(private readonly label: string) {}
9
+
10
+ @action()
11
+ increment() {
12
+ this.count += 1;
13
+ }
14
+
15
+ render() {
16
+ return html`
17
+ <article class="counter-card">
18
+ <h2>${this.label}</h2>
19
+ <output data-rw-state="count">${this.count}</output>
20
+ <button type="button" rw-click="increment">Increment on the server</button>
21
+ </article>
22
+ `;
23
+ }
24
+ }
25
+
26
+ @page('/', { css: 'components.css' })
27
+ export class ComponentsPage {
28
+ primary = new CounterComponent('Primary counter');
29
+ secondary = new CounterComponent('Independent counter');
30
+
31
+ render() {
32
+ return html`
33
+ <main>
34
+ <h1>Reusable server components</h1>
35
+ <section class="counter-grid">${this.primary}${this.secondary}</section>
36
+ </main>
37
+ `;
38
+ }
39
+ }
40
+
41
+ if (require.main === module) start(ComponentsPage, { port: 8080 });
@@ -0,0 +1,24 @@
1
+ :root {
2
+ color-scheme: dark;
3
+ font-family: system-ui, sans-serif;
4
+ background: #111827;
5
+ color: #f9fafb;
6
+ }
7
+
8
+ body {
9
+ min-height: 100vh;
10
+ margin: 0;
11
+ display: grid;
12
+ place-items: center;
13
+ }
14
+
15
+ main {
16
+ text-align: center;
17
+ }
18
+
19
+ output {
20
+ display: block;
21
+ color: #67e8f9;
22
+ font-size: 6rem;
23
+ font-weight: 700;
24
+ }
@@ -0,0 +1,10 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head><meta charset="utf-8"><title>Redweb server counter</title></head>
4
+ <body>
5
+ <main>
6
+ <h1>Server-side counter</h1>
7
+ <output aria-live="polite" data-rw-state="count"></output>
8
+ </main>
9
+ </body>
10
+ </html>
@@ -0,0 +1,73 @@
1
+ "use strict";
2
+ var __esDecorate = (this && this.__esDecorate) || function (ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) {
3
+ function accept(f) { if (f !== void 0 && typeof f !== "function") throw new TypeError("Function expected"); return f; }
4
+ var kind = contextIn.kind, key = kind === "getter" ? "get" : kind === "setter" ? "set" : "value";
5
+ var target = !descriptorIn && ctor ? contextIn["static"] ? ctor : ctor.prototype : null;
6
+ var descriptor = descriptorIn || (target ? Object.getOwnPropertyDescriptor(target, contextIn.name) : {});
7
+ var _, done = false;
8
+ for (var i = decorators.length - 1; i >= 0; i--) {
9
+ var context = {};
10
+ for (var p in contextIn) context[p] = p === "access" ? {} : contextIn[p];
11
+ for (var p in contextIn.access) context.access[p] = contextIn.access[p];
12
+ context.addInitializer = function (f) { if (done) throw new TypeError("Cannot add initializers after decoration has completed"); extraInitializers.push(accept(f || null)); };
13
+ var result = (0, decorators[i])(kind === "accessor" ? { get: descriptor.get, set: descriptor.set } : descriptor[key], context);
14
+ if (kind === "accessor") {
15
+ if (result === void 0) continue;
16
+ if (result === null || typeof result !== "object") throw new TypeError("Object expected");
17
+ if (_ = accept(result.get)) descriptor.get = _;
18
+ if (_ = accept(result.set)) descriptor.set = _;
19
+ if (_ = accept(result.init)) initializers.unshift(_);
20
+ }
21
+ else if (_ = accept(result)) {
22
+ if (kind === "field") initializers.unshift(_);
23
+ else descriptor[key] = _;
24
+ }
25
+ }
26
+ if (target) Object.defineProperty(target, contextIn.name, descriptor);
27
+ done = true;
28
+ };
29
+ var __runInitializers = (this && this.__runInitializers) || function (thisArg, initializers, value) {
30
+ var useValue = arguments.length > 2;
31
+ for (var i = 0; i < initializers.length; i++) {
32
+ value = useValue ? initializers[i].call(thisArg, value) : initializers[i].call(thisArg);
33
+ }
34
+ return useValue ? value : void 0;
35
+ };
36
+ Object.defineProperty(exports, "__esModule", { value: true });
37
+ exports.CounterPage = void 0;
38
+ const redweb_1 = require('../..');
39
+ let CounterPage = (() => {
40
+ let _classDecorators = [(0, redweb_1.page)('/', { template: 'counter.html', css: 'counter.css' })];
41
+ let _classDescriptor;
42
+ let _classExtraInitializers = [];
43
+ let _classThis;
44
+ let _count_decorators;
45
+ let _count_initializers = [];
46
+ let _count_extraInitializers = [];
47
+ var CounterPage = class {
48
+ static { _classThis = this; }
49
+ static {
50
+ const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(null) : void 0;
51
+ _count_decorators = [(0, redweb_1.state)()];
52
+ __esDecorate(null, null, _count_decorators, { kind: "field", name: "count", static: false, private: false, access: { has: obj => "count" in obj, get: obj => obj.count, set: (obj, value) => { obj.count = value; } }, metadata: _metadata }, _count_initializers, _count_extraInitializers);
53
+ __esDecorate(null, _classDescriptor = { value: _classThis }, _classDecorators, { kind: "class", name: _classThis.name, metadata: _metadata }, null, _classExtraInitializers);
54
+ CounterPage = _classThis = _classDescriptor.value;
55
+ if (_metadata) Object.defineProperty(_classThis, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
56
+ __runInitializers(_classThis, _classExtraInitializers);
57
+ }
58
+ count = __runInitializers(this, _count_initializers, 0);
59
+ ticker = (__runInitializers(this, _count_extraInitializers), null);
60
+ connected() {
61
+ this.ticker = setInterval(() => { this.count += 1; }, 1000);
62
+ }
63
+ disconnected() {
64
+ if (this.ticker)
65
+ clearInterval(this.ticker);
66
+ this.ticker = null;
67
+ }
68
+ };
69
+ return CounterPage = _classThis;
70
+ })();
71
+ exports.CounterPage = CounterPage;
72
+ if (require.main === module)
73
+ (0, redweb_1.start)(CounterPage, { port: 8080 });
@@ -0,0 +1,21 @@
1
+ import { page, start, state } from 'redweb';
2
+
3
+ @page('/', { template: 'counter.html', css: 'counter.css' })
4
+ export class CounterPage {
5
+ @state()
6
+ count = 0;
7
+
8
+ private ticker: NodeJS.Timeout | null = null;
9
+
10
+ connected() {
11
+ this.ticker = setInterval(() => { this.count += 1; }, 1000);
12
+ }
13
+
14
+ disconnected() {
15
+ if (this.ticker) clearInterval(this.ticker);
16
+ this.ticker = null;
17
+ }
18
+
19
+ }
20
+
21
+ if (require.main === module) start(CounterPage, { port: 8080 });
@@ -0,0 +1,16 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "CommonJS",
5
+ "moduleResolution": "Node",
6
+ "strict": true,
7
+ "experimentalDecorators": false,
8
+ "useDefineForClassFields": true,
9
+ "skipLibCheck": false,
10
+ "baseUrl": "../..",
11
+ "paths": {
12
+ "redweb": ["."]
13
+ }
14
+ },
15
+ "files": ["counter.ts", "chatroom.ts", "cards.ts", "components.ts"]
16
+ }