elit 1.1.0 → 2.0.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 (50) hide show
  1. package/README.md +267 -101
  2. package/dist/build.d.mts +11 -0
  3. package/dist/build.d.ts +11 -0
  4. package/dist/build.js +1 -0
  5. package/dist/build.mjs +1 -0
  6. package/dist/cli.js +2307 -0
  7. package/dist/client.d.mts +9 -0
  8. package/dist/client.d.ts +9 -0
  9. package/dist/client.js +1 -0
  10. package/dist/client.mjs +1 -0
  11. package/dist/dom.d.mts +80 -0
  12. package/dist/dom.d.ts +80 -0
  13. package/dist/dom.js +1 -0
  14. package/dist/dom.mjs +1 -0
  15. package/dist/el.d.mts +227 -0
  16. package/dist/el.d.ts +227 -0
  17. package/dist/el.js +1 -0
  18. package/dist/el.mjs +1 -0
  19. package/dist/hmr.d.mts +38 -0
  20. package/dist/hmr.d.ts +38 -0
  21. package/dist/hmr.js +1 -0
  22. package/dist/hmr.mjs +1 -0
  23. package/dist/index.d.mts +38 -619
  24. package/dist/index.d.ts +38 -619
  25. package/dist/index.js +1 -35
  26. package/dist/index.mjs +1 -35
  27. package/dist/router.d.mts +45 -0
  28. package/dist/router.d.ts +45 -0
  29. package/dist/router.js +1 -0
  30. package/dist/router.mjs +1 -0
  31. package/dist/server.d.mts +3 -0
  32. package/dist/server.d.ts +3 -0
  33. package/dist/server.js +1 -0
  34. package/dist/server.mjs +1 -0
  35. package/dist/state.d.mts +109 -0
  36. package/dist/state.d.ts +109 -0
  37. package/dist/state.js +1 -0
  38. package/dist/state.mjs +1 -0
  39. package/dist/style.d.mts +113 -0
  40. package/dist/style.d.ts +113 -0
  41. package/dist/style.js +1 -0
  42. package/dist/style.mjs +1 -0
  43. package/dist/types-DOAdFFJB.d.mts +330 -0
  44. package/dist/types-DOAdFFJB.d.ts +330 -0
  45. package/dist/types.d.mts +3 -0
  46. package/dist/types.d.ts +3 -0
  47. package/dist/types.js +1 -0
  48. package/dist/types.mjs +0 -0
  49. package/package.json +65 -2
  50. package/dist/index.global.js +0 -35
package/dist/cli.js ADDED
@@ -0,0 +1,2307 @@
1
+ #!/usr/bin/env node
2
+ #!/usr/bin/env node
3
+ "use strict";
4
+ var __create = Object.create;
5
+ var __defProp = Object.defineProperty;
6
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
7
+ var __getOwnPropNames = Object.getOwnPropertyNames;
8
+ var __getProtoOf = Object.getPrototypeOf;
9
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
10
+ var __commonJS = (cb, mod) => function __require() {
11
+ return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
12
+ };
13
+ var __copyProps = (to, from, except, desc) => {
14
+ if (from && typeof from === "object" || typeof from === "function") {
15
+ for (let key of __getOwnPropNames(from))
16
+ if (!__hasOwnProp.call(to, key) && key !== except)
17
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
18
+ }
19
+ return to;
20
+ };
21
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
22
+ // If the importer is in node compatibility mode or this is not an ESM
23
+ // file that has been converted to a CommonJS file using a Babel-
24
+ // compatible transform (i.e. "__esModule" has not been set), then set
25
+ // "default" to the CommonJS "module.exports" for node compatibility.
26
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
27
+ mod
28
+ ));
29
+
30
+ // package.json
31
+ var require_package = __commonJS({
32
+ "package.json"(exports2, module2) {
33
+ module2.exports = {
34
+ name: "elit",
35
+ version: "2.0.0",
36
+ description: "Optimized lightweight library for creating DOM elements with reactive state",
37
+ main: "dist/index.js",
38
+ module: "dist/index.mjs",
39
+ types: "dist/index.d.ts",
40
+ bin: {
41
+ elit: "./dist/cli.js"
42
+ },
43
+ exports: {
44
+ ".": {
45
+ types: "./dist/index.d.ts",
46
+ import: "./dist/index.mjs",
47
+ require: "./dist/index.js"
48
+ },
49
+ "./dom": {
50
+ types: "./dist/dom.d.ts",
51
+ import: "./dist/dom.mjs",
52
+ require: "./dist/dom.js"
53
+ },
54
+ "./el": {
55
+ types: "./dist/el.d.ts",
56
+ import: "./dist/el.mjs",
57
+ require: "./dist/el.js"
58
+ },
59
+ "./router": {
60
+ types: "./dist/router.d.ts",
61
+ import: "./dist/router.mjs",
62
+ require: "./dist/router.js"
63
+ },
64
+ "./state": {
65
+ types: "./dist/state.d.ts",
66
+ import: "./dist/state.mjs",
67
+ require: "./dist/state.js"
68
+ },
69
+ "./style": {
70
+ types: "./dist/style.d.ts",
71
+ import: "./dist/style.mjs",
72
+ require: "./dist/style.js"
73
+ },
74
+ "./server": {
75
+ types: "./dist/server.d.ts",
76
+ import: "./dist/server.mjs",
77
+ require: "./dist/server.js"
78
+ },
79
+ "./hmr": {
80
+ types: "./dist/hmr.d.ts",
81
+ import: "./dist/hmr.mjs",
82
+ require: "./dist/hmr.js"
83
+ },
84
+ "./build": {
85
+ types: "./dist/build.d.ts",
86
+ import: "./dist/build.mjs",
87
+ require: "./dist/build.js"
88
+ }
89
+ },
90
+ scripts: {
91
+ build: "tsup",
92
+ dev: "tsup --watch",
93
+ typecheck: "tsc --noEmit",
94
+ "docs:dev": "npm run --prefix docs dev",
95
+ "docs:build": "npm run --prefix docs build",
96
+ "docs:preview": "npm run --prefix docs preview",
97
+ "docs:deploy": "npm run docs:build && gh-pages -d docs/dist"
98
+ },
99
+ keywords: [
100
+ "dom",
101
+ "reactive",
102
+ "state-management",
103
+ "ui",
104
+ "framework",
105
+ "typescript",
106
+ "lightweight",
107
+ "ssr",
108
+ "router",
109
+ "css-in-js",
110
+ "virtual-scrolling",
111
+ "hmr",
112
+ "hot-module-replacement",
113
+ "dev-server",
114
+ "development",
115
+ "websocket",
116
+ "rest-api",
117
+ "shared-state",
118
+ "real-time",
119
+ "build",
120
+ "bundler",
121
+ "esbuild"
122
+ ],
123
+ author: "",
124
+ license: "MIT",
125
+ repository: {
126
+ type: "git",
127
+ url: "https://github.com/d-osc/elit.git"
128
+ },
129
+ bugs: {
130
+ url: "https://github.com/d-osc/elit/issues"
131
+ },
132
+ homepage: "https://github.com/d-osc/elit#readme",
133
+ dependencies: {
134
+ chokidar: "^4.0.3",
135
+ esbuild: "^0.24.2",
136
+ "mime-types": "^2.1.35",
137
+ open: "^11.0.0",
138
+ ws: "^8.18.0"
139
+ },
140
+ devDependencies: {
141
+ "@types/mime-types": "^2.1.4",
142
+ "@types/node": "^22.0.0",
143
+ "@types/ws": "^8.5.13",
144
+ terser: "^5.44.1",
145
+ tsup: "^8.0.0",
146
+ typescript: "^5.3.0"
147
+ },
148
+ files: [
149
+ "dist",
150
+ "README.md",
151
+ "LICENSE"
152
+ ]
153
+ };
154
+ }
155
+ });
156
+
157
+ // src/config.ts
158
+ var import_fs = require("fs");
159
+ var import_path = require("path");
160
+ var CONFIG_FILES = [
161
+ "elit.config.ts",
162
+ "elit.config.js",
163
+ "elit.config.mjs",
164
+ "elit.config.cjs",
165
+ "elit.config.json"
166
+ ];
167
+ function loadEnv(mode = "development", cwd = process.cwd()) {
168
+ const env = { MODE: mode };
169
+ const envFiles = [
170
+ `.env.${mode}.local`,
171
+ `.env.${mode}`,
172
+ `.env.local`,
173
+ `.env`
174
+ ];
175
+ for (const file of envFiles) {
176
+ const filePath = (0, import_path.resolve)(cwd, file);
177
+ if ((0, import_fs.existsSync)(filePath)) {
178
+ const content = (0, import_fs.readFileSync)(filePath, "utf-8");
179
+ const lines = content.split("\n");
180
+ for (const line of lines) {
181
+ const trimmed = line.trim();
182
+ if (!trimmed || trimmed.startsWith("#")) continue;
183
+ const match = trimmed.match(/^([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.*)$/);
184
+ if (match) {
185
+ const [, key, value] = match;
186
+ let cleanValue = value.trim();
187
+ if (cleanValue.startsWith('"') && cleanValue.endsWith('"') || cleanValue.startsWith("'") && cleanValue.endsWith("'")) {
188
+ cleanValue = cleanValue.slice(1, -1);
189
+ }
190
+ if (!(key in env)) {
191
+ env[key] = cleanValue;
192
+ }
193
+ }
194
+ }
195
+ }
196
+ }
197
+ return env;
198
+ }
199
+ async function loadConfig(cwd = process.cwd()) {
200
+ for (const configFile of CONFIG_FILES) {
201
+ const configPath = (0, import_path.resolve)(cwd, configFile);
202
+ if ((0, import_fs.existsSync)(configPath)) {
203
+ try {
204
+ return await loadConfigFile(configPath);
205
+ } catch (error) {
206
+ console.error(`Error loading config file: ${configFile}`);
207
+ console.error(error);
208
+ throw error;
209
+ }
210
+ }
211
+ }
212
+ return null;
213
+ }
214
+ async function loadConfigFile(configPath) {
215
+ const ext = configPath.split(".").pop();
216
+ if (ext === "json") {
217
+ const content = (0, import_fs.readFileSync)(configPath, "utf-8");
218
+ return JSON.parse(content);
219
+ } else {
220
+ if (ext === "ts") {
221
+ try {
222
+ const { pathToFileURL } = await import("url");
223
+ const configModule = await import(pathToFileURL(configPath).href);
224
+ return configModule.default || configModule;
225
+ } catch {
226
+ console.error("TypeScript config files require tsx or ts-node to be installed.");
227
+ console.error("Install with: npm install -D tsx");
228
+ console.error("Or use a .js or .json config file instead.");
229
+ throw new Error("Cannot load TypeScript config without tsx/ts-node");
230
+ }
231
+ } else {
232
+ const { pathToFileURL } = await import("url");
233
+ const configModule = await import(pathToFileURL(configPath).href);
234
+ return configModule.default || configModule;
235
+ }
236
+ }
237
+ }
238
+ function mergeConfig(config, cliArgs) {
239
+ if (!config) {
240
+ return cliArgs;
241
+ }
242
+ return {
243
+ ...config,
244
+ ...Object.fromEntries(
245
+ Object.entries(cliArgs).filter(([_, v]) => v !== void 0)
246
+ )
247
+ };
248
+ }
249
+
250
+ // src/server.ts
251
+ var import_http = require("http");
252
+ var import_https = require("https");
253
+ var import_ws = require("ws");
254
+ var import_chokidar = require("chokidar");
255
+ var import_promises = require("fs/promises");
256
+ var import_path2 = require("path");
257
+ var import_mime_types = require("mime-types");
258
+ var import_esbuild = require("esbuild");
259
+
260
+ // src/dom.ts
261
+ var DomNode = class {
262
+ constructor() {
263
+ this.elementCache = /* @__PURE__ */ new WeakMap();
264
+ this.reactiveNodes = /* @__PURE__ */ new Map();
265
+ }
266
+ createElement(tagName, props = {}, children = []) {
267
+ return { tagName, props, children };
268
+ }
269
+ renderToDOM(vNode, parent) {
270
+ if (vNode == null || vNode === false) return;
271
+ if (typeof vNode !== "object") {
272
+ parent.appendChild(document.createTextNode(String(vNode)));
273
+ return;
274
+ }
275
+ const { tagName, props, children } = vNode;
276
+ const isSVG = tagName === "svg" || tagName[0] === "s" && tagName[1] === "v" && tagName[2] === "g" || parent.namespaceURI === "http://www.w3.org/2000/svg";
277
+ const el = isSVG ? document.createElementNS("http://www.w3.org/2000/svg", tagName.replace("svg", "").toLowerCase() || tagName) : document.createElement(tagName);
278
+ for (const key in props) {
279
+ const value = props[key];
280
+ if (value == null || value === false) continue;
281
+ const c = key.charCodeAt(0);
282
+ if (c === 99 && (key.length < 6 || key[5] === "N")) {
283
+ const classValue = Array.isArray(value) ? value.join(" ") : value;
284
+ isSVG ? el.setAttribute("class", classValue) : el.className = classValue;
285
+ } else if (c === 115 && key.length === 5) {
286
+ if (typeof value === "string") {
287
+ el.style.cssText = value;
288
+ } else {
289
+ const s = el.style;
290
+ for (const k in value) s[k] = value[k];
291
+ }
292
+ } else if (c === 111 && key.charCodeAt(1) === 110) {
293
+ el[key.toLowerCase()] = value;
294
+ } else if (c === 100 && key.length > 20) {
295
+ el.innerHTML = value.__html;
296
+ } else if (c === 114 && key.length === 3) {
297
+ setTimeout(() => {
298
+ typeof value === "function" ? value(el) : value.current = el;
299
+ }, 0);
300
+ } else {
301
+ el.setAttribute(key, value === true ? "" : String(value));
302
+ }
303
+ }
304
+ const len = children.length;
305
+ if (!len) {
306
+ parent.appendChild(el);
307
+ return;
308
+ }
309
+ const renderChildren = (target) => {
310
+ for (let i = 0; i < len; i++) {
311
+ const child = children[i];
312
+ if (child == null || child === false) continue;
313
+ if (Array.isArray(child)) {
314
+ for (let j = 0, cLen = child.length; j < cLen; j++) {
315
+ const c = child[j];
316
+ c != null && c !== false && this.renderToDOM(c, target);
317
+ }
318
+ } else {
319
+ this.renderToDOM(child, target);
320
+ }
321
+ }
322
+ };
323
+ if (len > 30) {
324
+ const fragment = document.createDocumentFragment();
325
+ renderChildren(fragment);
326
+ el.appendChild(fragment);
327
+ } else {
328
+ renderChildren(el);
329
+ }
330
+ parent.appendChild(el);
331
+ }
332
+ render(rootElement, vNode) {
333
+ const el = typeof rootElement === "string" ? document.getElementById(rootElement.replace("#", "")) : rootElement;
334
+ if (!el) {
335
+ throw new Error(`Element not found: ${rootElement}`);
336
+ }
337
+ if (vNode.children && vNode.children.length > 500) {
338
+ const fragment = document.createDocumentFragment();
339
+ this.renderToDOM(vNode, fragment);
340
+ el.appendChild(fragment);
341
+ } else {
342
+ this.renderToDOM(vNode, el);
343
+ }
344
+ return el;
345
+ }
346
+ batchRender(rootElement, vNodes) {
347
+ const el = typeof rootElement === "string" ? document.getElementById(rootElement.replace("#", "")) : rootElement;
348
+ if (!el) {
349
+ throw new Error(`Element not found: ${rootElement}`);
350
+ }
351
+ const len = vNodes.length;
352
+ if (len > 3e3) {
353
+ const fragment = document.createDocumentFragment();
354
+ let processed = 0;
355
+ const chunkSize = 1500;
356
+ const processChunk = () => {
357
+ const end = Math.min(processed + chunkSize, len);
358
+ for (let i = processed; i < end; i++) {
359
+ this.renderToDOM(vNodes[i], fragment);
360
+ }
361
+ processed = end;
362
+ if (processed >= len) {
363
+ el.appendChild(fragment);
364
+ } else {
365
+ requestAnimationFrame(processChunk);
366
+ }
367
+ };
368
+ processChunk();
369
+ } else {
370
+ const fragment = document.createDocumentFragment();
371
+ for (let i = 0; i < len; i++) {
372
+ this.renderToDOM(vNodes[i], fragment);
373
+ }
374
+ el.appendChild(fragment);
375
+ }
376
+ return el;
377
+ }
378
+ renderChunked(rootElement, vNodes, chunkSize = 5e3, onProgress) {
379
+ const el = typeof rootElement === "string" ? document.getElementById(rootElement.replace("#", "")) : rootElement;
380
+ if (!el) {
381
+ throw new Error(`Element not found: ${rootElement}`);
382
+ }
383
+ const len = vNodes.length;
384
+ let index = 0;
385
+ const renderChunk = () => {
386
+ const end = Math.min(index + chunkSize, len);
387
+ const fragment = document.createDocumentFragment();
388
+ for (let i = index; i < end; i++) {
389
+ this.renderToDOM(vNodes[i], fragment);
390
+ }
391
+ el.appendChild(fragment);
392
+ index = end;
393
+ if (onProgress) onProgress(index, len);
394
+ if (index < len) {
395
+ requestAnimationFrame(renderChunk);
396
+ }
397
+ };
398
+ requestAnimationFrame(renderChunk);
399
+ return el;
400
+ }
401
+ renderToHead(...vNodes) {
402
+ const head = document.head;
403
+ if (head) {
404
+ for (const vNode of vNodes.flat()) {
405
+ vNode && this.renderToDOM(vNode, head);
406
+ }
407
+ }
408
+ return head;
409
+ }
410
+ addStyle(cssText) {
411
+ const el = document.createElement("style");
412
+ el.textContent = cssText;
413
+ return document.head.appendChild(el);
414
+ }
415
+ addMeta(attrs) {
416
+ const el = document.createElement("meta");
417
+ for (const k in attrs) el.setAttribute(k, attrs[k]);
418
+ return document.head.appendChild(el);
419
+ }
420
+ addLink(attrs) {
421
+ const el = document.createElement("link");
422
+ for (const k in attrs) el.setAttribute(k, attrs[k]);
423
+ return document.head.appendChild(el);
424
+ }
425
+ setTitle(text) {
426
+ return document.title = text;
427
+ }
428
+ // Reactive State Management
429
+ createState(initialValue, options = {}) {
430
+ let value = initialValue;
431
+ const listeners = /* @__PURE__ */ new Set();
432
+ let updateTimer = null;
433
+ const { throttle = 0, deep = false } = options;
434
+ const notify = () => listeners.forEach((fn) => fn(value));
435
+ const scheduleUpdate = () => {
436
+ if (throttle > 0) {
437
+ if (!updateTimer) {
438
+ updateTimer = setTimeout(() => {
439
+ updateTimer = null;
440
+ notify();
441
+ }, throttle);
442
+ }
443
+ } else {
444
+ notify();
445
+ }
446
+ };
447
+ return {
448
+ get value() {
449
+ return value;
450
+ },
451
+ set value(newValue) {
452
+ const changed = deep ? JSON.stringify(value) !== JSON.stringify(newValue) : value !== newValue;
453
+ if (changed) {
454
+ value = newValue;
455
+ scheduleUpdate();
456
+ }
457
+ },
458
+ subscribe(fn) {
459
+ listeners.add(fn);
460
+ return () => listeners.delete(fn);
461
+ },
462
+ destroy() {
463
+ listeners.clear();
464
+ updateTimer && clearTimeout(updateTimer);
465
+ }
466
+ };
467
+ }
468
+ computed(states, computeFn) {
469
+ const values = states.map((s) => s.value);
470
+ const result = this.createState(computeFn(...values));
471
+ states.forEach((state, index) => {
472
+ state.subscribe((newValue) => {
473
+ values[index] = newValue;
474
+ result.value = computeFn(...values);
475
+ });
476
+ });
477
+ return result;
478
+ }
479
+ effect(stateFn) {
480
+ stateFn();
481
+ }
482
+ // Virtual scrolling helper for large lists
483
+ createVirtualList(container, items, renderItem, itemHeight = 50, bufferSize = 5) {
484
+ const viewportHeight = container.clientHeight;
485
+ const totalHeight = items.length * itemHeight;
486
+ let scrollTop = 0;
487
+ const getVisibleRange = () => {
488
+ const start = Math.max(0, Math.floor(scrollTop / itemHeight) - bufferSize);
489
+ const end = Math.min(items.length, Math.ceil((scrollTop + viewportHeight) / itemHeight) + bufferSize);
490
+ return { start, end };
491
+ };
492
+ const render = () => {
493
+ const { start, end } = getVisibleRange();
494
+ const wrapper = document.createElement("div");
495
+ wrapper.style.cssText = `height:${totalHeight}px;position:relative`;
496
+ for (let i = start; i < end; i++) {
497
+ const itemEl = document.createElement("div");
498
+ itemEl.style.cssText = `position:absolute;top:${i * itemHeight}px;height:${itemHeight}px;width:100%`;
499
+ this.renderToDOM(renderItem(items[i], i), itemEl);
500
+ wrapper.appendChild(itemEl);
501
+ }
502
+ container.innerHTML = "";
503
+ container.appendChild(wrapper);
504
+ };
505
+ const scrollHandler = () => {
506
+ scrollTop = container.scrollTop;
507
+ requestAnimationFrame(render);
508
+ };
509
+ container.addEventListener("scroll", scrollHandler);
510
+ render();
511
+ return {
512
+ render,
513
+ destroy: () => {
514
+ container.removeEventListener("scroll", scrollHandler);
515
+ container.innerHTML = "";
516
+ }
517
+ };
518
+ }
519
+ // Lazy load components
520
+ lazy(loadFn) {
521
+ let component = null;
522
+ let loading = false;
523
+ return async (...args) => {
524
+ if (!component && !loading) {
525
+ loading = true;
526
+ component = await loadFn();
527
+ loading = false;
528
+ }
529
+ return component ? component(...args) : { tagName: "div", props: { class: "loading" }, children: ["Loading..."] };
530
+ };
531
+ }
532
+ // Memory management - cleanup unused elements
533
+ cleanupUnusedElements(root) {
534
+ const walker = document.createTreeWalker(root, NodeFilter.SHOW_ELEMENT);
535
+ const toRemove = [];
536
+ while (walker.nextNode()) {
537
+ const node = walker.currentNode;
538
+ if (node.id && node.id.startsWith("r") && !this.elementCache.has(node)) {
539
+ toRemove.push(node);
540
+ }
541
+ }
542
+ toRemove.forEach((el) => el.remove());
543
+ return toRemove.length;
544
+ }
545
+ // Server-Side Rendering - convert VNode to HTML string
546
+ renderToString(vNode, options = {}) {
547
+ const { pretty = false, indent = 0 } = options;
548
+ const indentStr = pretty ? " ".repeat(indent) : "";
549
+ const newLine = pretty ? "\n" : "";
550
+ let resolvedVNode = this.resolveStateValue(vNode);
551
+ resolvedVNode = this.unwrapReactive(resolvedVNode);
552
+ if (Array.isArray(resolvedVNode)) {
553
+ return resolvedVNode.map((child) => this.renderToString(child, options)).join("");
554
+ }
555
+ if (typeof resolvedVNode !== "object" || resolvedVNode === null) {
556
+ if (resolvedVNode === null || resolvedVNode === void 0 || resolvedVNode === false) {
557
+ return "";
558
+ }
559
+ return this.escapeHtml(String(resolvedVNode));
560
+ }
561
+ const { tagName, props, children } = resolvedVNode;
562
+ const isSelfClosing = this.isSelfClosingTag(tagName);
563
+ let html = `${indentStr}<${tagName}`;
564
+ const attrs = this.propsToAttributes(props);
565
+ if (attrs) {
566
+ html += ` ${attrs}`;
567
+ }
568
+ if (isSelfClosing) {
569
+ html += ` />${newLine}`;
570
+ return html;
571
+ }
572
+ html += ">";
573
+ if (props.dangerouslySetInnerHTML) {
574
+ html += props.dangerouslySetInnerHTML.__html;
575
+ html += `</${tagName}>${newLine}`;
576
+ return html;
577
+ }
578
+ if (children && children.length > 0) {
579
+ const resolvedChildren = children.map((c) => {
580
+ const resolved = this.resolveStateValue(c);
581
+ return this.unwrapReactive(resolved);
582
+ });
583
+ const hasComplexChildren = resolvedChildren.some(
584
+ (c) => typeof c === "object" && c !== null && !Array.isArray(c) && "tagName" in c
585
+ );
586
+ if (pretty && hasComplexChildren) {
587
+ html += newLine;
588
+ for (const child of resolvedChildren) {
589
+ if (child == null || child === false) continue;
590
+ if (Array.isArray(child)) {
591
+ for (const c of child) {
592
+ if (c != null && c !== false) {
593
+ html += this.renderToString(c, { pretty, indent: indent + 1 });
594
+ }
595
+ }
596
+ } else {
597
+ html += this.renderToString(child, { pretty, indent: indent + 1 });
598
+ }
599
+ }
600
+ html += indentStr;
601
+ } else {
602
+ for (const child of resolvedChildren) {
603
+ if (child == null || child === false) continue;
604
+ if (Array.isArray(child)) {
605
+ for (const c of child) {
606
+ if (c != null && c !== false) {
607
+ html += this.renderToString(c, { pretty: false, indent: 0 });
608
+ }
609
+ }
610
+ } else {
611
+ html += this.renderToString(child, { pretty: false, indent: 0 });
612
+ }
613
+ }
614
+ }
615
+ }
616
+ html += `</${tagName}>${newLine}`;
617
+ return html;
618
+ }
619
+ resolveStateValue(value) {
620
+ if (value && typeof value === "object" && "value" in value && "subscribe" in value) {
621
+ return value.value;
622
+ }
623
+ return value;
624
+ }
625
+ isReactiveWrapper(vNode) {
626
+ if (!vNode || typeof vNode !== "object" || !vNode.tagName) {
627
+ return false;
628
+ }
629
+ return vNode.tagName === "span" && vNode.props?.id && typeof vNode.props.id === "string" && vNode.props.id.match(/^r[a-z0-9]{9}$/);
630
+ }
631
+ unwrapReactive(vNode) {
632
+ if (!this.isReactiveWrapper(vNode)) {
633
+ return vNode;
634
+ }
635
+ const children = vNode.children;
636
+ if (!children || children.length === 0) {
637
+ return "";
638
+ }
639
+ if (children.length === 1) {
640
+ const child = children[0];
641
+ if (child && typeof child === "object" && child.tagName === "span") {
642
+ const props = child.props;
643
+ const hasNoProps = !props || Object.keys(props).length === 0;
644
+ const hasSingleStringChild = child.children && child.children.length === 1 && typeof child.children[0] === "string";
645
+ if (hasNoProps && hasSingleStringChild) {
646
+ return child.children[0];
647
+ }
648
+ }
649
+ return this.unwrapReactive(child);
650
+ }
651
+ return children.map((c) => this.unwrapReactive(c));
652
+ }
653
+ escapeHtml(text) {
654
+ const htmlEscapes = {
655
+ "&": "&amp;",
656
+ "<": "&lt;",
657
+ ">": "&gt;",
658
+ '"': "&quot;",
659
+ "'": "&#x27;"
660
+ };
661
+ return text.replace(/[&<>"']/g, (char) => htmlEscapes[char]);
662
+ }
663
+ isSelfClosingTag(tagName) {
664
+ const selfClosingTags = /* @__PURE__ */ new Set([
665
+ "area",
666
+ "base",
667
+ "br",
668
+ "col",
669
+ "embed",
670
+ "hr",
671
+ "img",
672
+ "input",
673
+ "link",
674
+ "meta",
675
+ "param",
676
+ "source",
677
+ "track",
678
+ "wbr"
679
+ ]);
680
+ return selfClosingTags.has(tagName.toLowerCase());
681
+ }
682
+ propsToAttributes(props) {
683
+ const attrs = [];
684
+ for (const key in props) {
685
+ if (key === "children" || key === "dangerouslySetInnerHTML" || key === "ref") {
686
+ continue;
687
+ }
688
+ let value = props[key];
689
+ value = this.resolveStateValue(value);
690
+ if (value == null || value === false) continue;
691
+ if (key.startsWith("on") && typeof value === "function") {
692
+ continue;
693
+ }
694
+ if (key === "className" || key === "class") {
695
+ const className = Array.isArray(value) ? value.join(" ") : value;
696
+ if (className) {
697
+ attrs.push(`class="${this.escapeHtml(String(className))}"`);
698
+ }
699
+ continue;
700
+ }
701
+ if (key === "style") {
702
+ const styleStr = this.styleToString(value);
703
+ if (styleStr) {
704
+ attrs.push(`style="${this.escapeHtml(styleStr)}"`);
705
+ }
706
+ continue;
707
+ }
708
+ if (value === true) {
709
+ attrs.push(key);
710
+ continue;
711
+ }
712
+ attrs.push(`${key}="${this.escapeHtml(String(value))}"`);
713
+ }
714
+ return attrs.join(" ");
715
+ }
716
+ styleToString(style) {
717
+ if (typeof style === "string") {
718
+ return style;
719
+ }
720
+ if (typeof style === "object" && style !== null) {
721
+ const styles = [];
722
+ for (const key in style) {
723
+ const cssKey = key.replace(/([A-Z])/g, "-$1").toLowerCase();
724
+ styles.push(`${cssKey}:${style[key]}`);
725
+ }
726
+ return styles.join(";");
727
+ }
728
+ return "";
729
+ }
730
+ isState(value) {
731
+ return value && typeof value === "object" && "value" in value && "subscribe" in value && typeof value.subscribe === "function";
732
+ }
733
+ createReactiveChild(state, renderFn) {
734
+ const currentValue = renderFn(state.value);
735
+ if (typeof window !== "undefined" && typeof document !== "undefined") {
736
+ const entry = { node: null, renderFn };
737
+ this.reactiveNodes.set(state, entry);
738
+ state.subscribe(() => {
739
+ if (entry.node && entry.node.parentNode) {
740
+ const newValue = renderFn(state.value);
741
+ entry.node.textContent = String(newValue ?? "");
742
+ }
743
+ });
744
+ }
745
+ return currentValue;
746
+ }
747
+ jsonToVNode(json) {
748
+ if (this.isState(json)) {
749
+ return this.createReactiveChild(json, (v) => v);
750
+ }
751
+ if (json == null || typeof json === "boolean") {
752
+ return json;
753
+ }
754
+ if (typeof json === "string" || typeof json === "number") {
755
+ return json;
756
+ }
757
+ const { tag, attributes = {}, children } = json;
758
+ const props = {};
759
+ for (const key in attributes) {
760
+ const value = attributes[key];
761
+ if (key === "class") {
762
+ props.className = this.isState(value) ? value.value : value;
763
+ } else {
764
+ props[key] = this.isState(value) ? value.value : value;
765
+ }
766
+ }
767
+ const childrenArray = [];
768
+ if (children != null) {
769
+ if (Array.isArray(children)) {
770
+ for (const child of children) {
771
+ if (this.isState(child)) {
772
+ childrenArray.push(this.createReactiveChild(child, (v) => v));
773
+ } else {
774
+ const converted = this.jsonToVNode(child);
775
+ if (converted != null && converted !== false) {
776
+ childrenArray.push(converted);
777
+ }
778
+ }
779
+ }
780
+ } else if (this.isState(children)) {
781
+ childrenArray.push(this.createReactiveChild(children, (v) => v));
782
+ } else if (typeof children === "object" && "tag" in children) {
783
+ const converted = this.jsonToVNode(children);
784
+ if (converted != null && converted !== false) {
785
+ childrenArray.push(converted);
786
+ }
787
+ } else {
788
+ childrenArray.push(children);
789
+ }
790
+ }
791
+ return { tagName: tag, props, children: childrenArray };
792
+ }
793
+ vNodeJsonToVNode(json) {
794
+ if (this.isState(json)) {
795
+ return this.createReactiveChild(json, (v) => v);
796
+ }
797
+ if (json == null || typeof json === "boolean") {
798
+ return json;
799
+ }
800
+ if (typeof json === "string" || typeof json === "number") {
801
+ return json;
802
+ }
803
+ const { tagName, props = {}, children = [] } = json;
804
+ const resolvedProps = {};
805
+ for (const key in props) {
806
+ const value = props[key];
807
+ resolvedProps[key] = this.isState(value) ? value.value : value;
808
+ }
809
+ const childrenArray = [];
810
+ for (const child of children) {
811
+ if (this.isState(child)) {
812
+ childrenArray.push(this.createReactiveChild(child, (v) => v));
813
+ } else {
814
+ const converted = this.vNodeJsonToVNode(child);
815
+ if (converted != null && converted !== false) {
816
+ childrenArray.push(converted);
817
+ }
818
+ }
819
+ }
820
+ return { tagName, props: resolvedProps, children: childrenArray };
821
+ }
822
+ renderJson(rootElement, json) {
823
+ const vNode = this.jsonToVNode(json);
824
+ if (!vNode || typeof vNode !== "object" || !("tagName" in vNode)) {
825
+ throw new Error("Invalid JSON structure");
826
+ }
827
+ return this.render(rootElement, vNode);
828
+ }
829
+ renderVNode(rootElement, json) {
830
+ const vNode = this.vNodeJsonToVNode(json);
831
+ if (!vNode || typeof vNode !== "object" || !("tagName" in vNode)) {
832
+ throw new Error("Invalid VNode JSON structure");
833
+ }
834
+ return this.render(rootElement, vNode);
835
+ }
836
+ renderJsonToString(json, options = {}) {
837
+ const vNode = this.jsonToVNode(json);
838
+ return this.renderToString(vNode, options);
839
+ }
840
+ renderVNodeToString(json, options = {}) {
841
+ const vNode = this.vNodeJsonToVNode(json);
842
+ return this.renderToString(vNode, options);
843
+ }
844
+ // Server-side rendering - Render complete HTML document VNode to document
845
+ renderServer(vNode) {
846
+ if (typeof vNode !== "object" || vNode === null || !("tagName" in vNode)) throw new Error("renderServer requires a VNode with html tag");
847
+ if (vNode.tagName !== "html") throw new Error("renderServer requires a VNode with html tag as root");
848
+ const htmlVNode = vNode;
849
+ let headVNode = null, bodyVNode = null;
850
+ for (const child of htmlVNode.children || []) {
851
+ if (typeof child === "object" && child !== null && "tagName" in child) {
852
+ if (child.tagName === "head") headVNode = child;
853
+ if (child.tagName === "body") bodyVNode = child;
854
+ }
855
+ }
856
+ if (htmlVNode.props) for (const k in htmlVNode.props) {
857
+ const v = htmlVNode.props[k];
858
+ if (v !== void 0 && v !== null && v !== false) document.documentElement.setAttribute(k, String(v));
859
+ }
860
+ if (headVNode) {
861
+ document.head.innerHTML = "";
862
+ for (const child of headVNode.children || []) this.renderToDOM(child, document.head);
863
+ }
864
+ if (bodyVNode) {
865
+ document.body.innerHTML = "";
866
+ if (bodyVNode.props) for (const k in bodyVNode.props) {
867
+ const v = bodyVNode.props[k];
868
+ if (v !== void 0 && v !== null && v !== false) document.body.setAttribute(k, String(v));
869
+ }
870
+ for (const child of bodyVNode.children || []) this.renderToDOM(child, document.body);
871
+ }
872
+ }
873
+ // Generate complete HTML document as string (for SSR)
874
+ renderToHTMLDocument(vNode, options = {}) {
875
+ const { title = "", meta = [], links = [], scripts = [], styles = [], lang = "en", head = "", bodyAttrs = {}, pretty = false } = options;
876
+ const nl = pretty ? "\n" : "";
877
+ const indent = pretty ? " " : "";
878
+ const indent2 = pretty ? " " : "";
879
+ let html = `<!DOCTYPE html>${nl}<html lang="${lang}">${nl}${indent}<head>${nl}${indent2}<meta charset="UTF-8">${nl}${indent2}<meta name="viewport" content="width=device-width, initial-scale=1.0">${nl}`;
880
+ if (title) html += `${indent2}<title>${this.escapeHtml(title)}</title>${nl}`;
881
+ for (const m of meta) {
882
+ html += `${indent2}<meta`;
883
+ for (const k in m) html += ` ${k}="${this.escapeHtml(m[k])}"`;
884
+ html += `>${nl}`;
885
+ }
886
+ for (const l of links) {
887
+ html += `${indent2}<link`;
888
+ for (const k in l) html += ` ${k}="${this.escapeHtml(l[k])}"`;
889
+ html += `>${nl}`;
890
+ }
891
+ for (const s of styles) {
892
+ if (s.href) {
893
+ html += `${indent2}<link rel="stylesheet" href="${this.escapeHtml(s.href)}">${nl}`;
894
+ } else if (s.content) {
895
+ html += `${indent2}<style>${s.content}</style>${nl}`;
896
+ }
897
+ }
898
+ if (head) html += head + nl;
899
+ html += `${indent}</head>${nl}${indent}<body`;
900
+ for (const k in bodyAttrs) html += ` ${k}="${this.escapeHtml(bodyAttrs[k])}"`;
901
+ html += `>${nl}`;
902
+ html += this.renderToString(vNode, { pretty, indent: 2 });
903
+ for (const script of scripts) {
904
+ html += `${indent2}<script`;
905
+ if (script.type) html += ` type="${this.escapeHtml(script.type)}"`;
906
+ if (script.async) html += ` async`;
907
+ if (script.defer) html += ` defer`;
908
+ if (script.src) {
909
+ html += ` src="${this.escapeHtml(script.src)}"></script>${nl}`;
910
+ } else if (script.content) {
911
+ html += `>${script.content}</script>${nl}`;
912
+ } else {
913
+ html += `></script>${nl}`;
914
+ }
915
+ }
916
+ html += `${indent}</body>${nl}</html>`;
917
+ return html;
918
+ }
919
+ // Expose elementCache for reactive updates
920
+ getElementCache() {
921
+ return this.elementCache;
922
+ }
923
+ };
924
+ var dom = new DomNode();
925
+
926
+ // src/server.ts
927
+ function rewritePath(path, pathRewrite) {
928
+ if (!pathRewrite) return path;
929
+ for (const [from, to] of Object.entries(pathRewrite)) {
930
+ const regex = new RegExp(from);
931
+ if (regex.test(path)) {
932
+ return path.replace(regex, to);
933
+ }
934
+ }
935
+ return path;
936
+ }
937
+ function createProxyHandler(proxyConfigs) {
938
+ return async (req, res) => {
939
+ const url = req.url || "/";
940
+ const path = url.split("?")[0];
941
+ const proxy = proxyConfigs.find((p) => path.startsWith(p.context));
942
+ if (!proxy) return false;
943
+ const { target, changeOrigin, pathRewrite, headers } = proxy;
944
+ try {
945
+ const targetUrl = new URL(target);
946
+ const isHttps = targetUrl.protocol === "https:";
947
+ const requestLib = isHttps ? import_https.request : import_http.request;
948
+ let proxyPath = rewritePath(url, pathRewrite);
949
+ const proxyReqOptions = {
950
+ hostname: targetUrl.hostname,
951
+ port: targetUrl.port || (isHttps ? 443 : 80),
952
+ path: proxyPath,
953
+ method: req.method,
954
+ headers: {
955
+ ...req.headers,
956
+ ...headers || {}
957
+ }
958
+ };
959
+ if (changeOrigin) {
960
+ proxyReqOptions.headers.host = targetUrl.host;
961
+ }
962
+ delete proxyReqOptions.headers["host"];
963
+ const proxyReq = requestLib(proxyReqOptions, (proxyRes) => {
964
+ res.writeHead(proxyRes.statusCode || 200, proxyRes.headers);
965
+ proxyRes.pipe(res);
966
+ });
967
+ proxyReq.on("error", (error) => {
968
+ console.error("[Proxy] Error proxying %s to %s:", url, target, error.message);
969
+ if (!res.headersSent) {
970
+ res.writeHead(502, { "Content-Type": "application/json" });
971
+ res.end(JSON.stringify({ error: "Bad Gateway", message: "Proxy error" }));
972
+ }
973
+ });
974
+ req.pipe(proxyReq);
975
+ return true;
976
+ } catch (error) {
977
+ console.error("[Proxy] Invalid proxy configuration for %s:", path, error);
978
+ return false;
979
+ }
980
+ };
981
+ }
982
+ var SharedState = class {
983
+ constructor(key, options) {
984
+ this.key = key;
985
+ this.listeners = /* @__PURE__ */ new Set();
986
+ this.changeHandlers = /* @__PURE__ */ new Set();
987
+ this.options = options;
988
+ this._value = options.initial;
989
+ }
990
+ get value() {
991
+ return this._value;
992
+ }
993
+ set value(newValue) {
994
+ if (this.options.validate && !this.options.validate(newValue)) {
995
+ throw new Error(`Invalid state value for "${this.key}"`);
996
+ }
997
+ const oldValue = this._value;
998
+ this._value = newValue;
999
+ this.changeHandlers.forEach((handler) => {
1000
+ handler(newValue, oldValue);
1001
+ });
1002
+ this.broadcast();
1003
+ }
1004
+ update(updater) {
1005
+ this.value = updater(this._value);
1006
+ }
1007
+ subscribe(ws) {
1008
+ this.listeners.add(ws);
1009
+ this.sendTo(ws);
1010
+ }
1011
+ unsubscribe(ws) {
1012
+ this.listeners.delete(ws);
1013
+ }
1014
+ onChange(handler) {
1015
+ this.changeHandlers.add(handler);
1016
+ return () => this.changeHandlers.delete(handler);
1017
+ }
1018
+ broadcast() {
1019
+ const message = JSON.stringify({ type: "state:update", key: this.key, value: this._value, timestamp: Date.now() });
1020
+ this.listeners.forEach((ws) => ws.readyState === import_ws.WebSocket.OPEN && ws.send(message));
1021
+ }
1022
+ sendTo(ws) {
1023
+ if (ws.readyState === import_ws.WebSocket.OPEN) {
1024
+ ws.send(JSON.stringify({ type: "state:init", key: this.key, value: this._value, timestamp: Date.now() }));
1025
+ }
1026
+ }
1027
+ get subscriberCount() {
1028
+ return this.listeners.size;
1029
+ }
1030
+ clear() {
1031
+ this.listeners.clear();
1032
+ this.changeHandlers.clear();
1033
+ }
1034
+ };
1035
+ var StateManager = class {
1036
+ constructor() {
1037
+ this.states = /* @__PURE__ */ new Map();
1038
+ }
1039
+ create(key, options) {
1040
+ if (this.states.has(key)) return this.states.get(key);
1041
+ const state = new SharedState(key, options);
1042
+ this.states.set(key, state);
1043
+ return state;
1044
+ }
1045
+ get(key) {
1046
+ return this.states.get(key);
1047
+ }
1048
+ has(key) {
1049
+ return this.states.has(key);
1050
+ }
1051
+ delete(key) {
1052
+ const state = this.states.get(key);
1053
+ if (state) {
1054
+ state.clear();
1055
+ return this.states.delete(key);
1056
+ }
1057
+ return false;
1058
+ }
1059
+ subscribe(key, ws) {
1060
+ this.states.get(key)?.subscribe(ws);
1061
+ }
1062
+ unsubscribe(key, ws) {
1063
+ this.states.get(key)?.unsubscribe(ws);
1064
+ }
1065
+ unsubscribeAll(ws) {
1066
+ this.states.forEach((state) => state.unsubscribe(ws));
1067
+ }
1068
+ handleStateChange(key, value) {
1069
+ const state = this.states.get(key);
1070
+ if (state) state.value = value;
1071
+ }
1072
+ keys() {
1073
+ return Array.from(this.states.keys());
1074
+ }
1075
+ clear() {
1076
+ this.states.forEach((state) => state.clear());
1077
+ this.states.clear();
1078
+ }
1079
+ };
1080
+ var defaultOptions = {
1081
+ port: 3e3,
1082
+ host: "localhost",
1083
+ https: false,
1084
+ open: true,
1085
+ watch: ["**/*.ts", "**/*.js", "**/*.html", "**/*.css"],
1086
+ ignore: ["node_modules/**", "dist/**", ".git/**", "**/*.d.ts"],
1087
+ logging: true,
1088
+ middleware: [],
1089
+ worker: []
1090
+ };
1091
+ function createDevServer(options) {
1092
+ const config = { ...defaultOptions, ...options };
1093
+ const wsClients = /* @__PURE__ */ new Set();
1094
+ const stateManager = new StateManager();
1095
+ const clientsToNormalize = config.clients?.length ? config.clients : config.root ? [{ root: config.root, basePath: config.basePath || "", ssr: config.ssr, proxy: config.proxy }] : null;
1096
+ if (!clientsToNormalize) throw new Error('DevServerOptions must include either "clients" array or "root" directory');
1097
+ const normalizedClients = clientsToNormalize.map((client) => {
1098
+ let basePath = client.basePath || "";
1099
+ if (basePath) {
1100
+ while (basePath.startsWith("/")) basePath = basePath.slice(1);
1101
+ while (basePath.endsWith("/")) basePath = basePath.slice(0, -1);
1102
+ basePath = basePath ? "/" + basePath : "";
1103
+ }
1104
+ return {
1105
+ root: client.root,
1106
+ basePath,
1107
+ ssr: client.ssr,
1108
+ proxyHandler: client.proxy ? createProxyHandler(client.proxy) : void 0
1109
+ };
1110
+ });
1111
+ const globalProxyHandler = config.proxy ? createProxyHandler(config.proxy) : null;
1112
+ const server = (0, import_http.createServer)(async (req, res) => {
1113
+ const originalUrl = req.url || "/";
1114
+ const matchedClient = normalizedClients.find((c) => c.basePath && originalUrl.startsWith(c.basePath)) || normalizedClients.find((c) => !c.basePath);
1115
+ if (!matchedClient) {
1116
+ res.writeHead(404, { "Content-Type": "text/plain" });
1117
+ res.end("404 Not Found");
1118
+ return;
1119
+ }
1120
+ if (matchedClient.proxyHandler) {
1121
+ try {
1122
+ const proxied = await matchedClient.proxyHandler(req, res);
1123
+ if (proxied) {
1124
+ if (config.logging) console.log(`[Proxy] ${req.method} ${originalUrl} -> proxied (client-specific)`);
1125
+ return;
1126
+ }
1127
+ } catch (error) {
1128
+ console.error("[Proxy] Error (client-specific):", error);
1129
+ }
1130
+ }
1131
+ if (globalProxyHandler) {
1132
+ try {
1133
+ const proxied = await globalProxyHandler(req, res);
1134
+ if (proxied) {
1135
+ if (config.logging) console.log(`[Proxy] ${req.method} ${originalUrl} -> proxied (global)`);
1136
+ return;
1137
+ }
1138
+ } catch (error) {
1139
+ console.error("[Proxy] Error (global):", error);
1140
+ }
1141
+ }
1142
+ const url = matchedClient.basePath ? originalUrl.slice(matchedClient.basePath.length) || "/" : originalUrl;
1143
+ if (config.api && url.startsWith("/api")) {
1144
+ const handled = await config.api.handle(req, res);
1145
+ if (handled) return;
1146
+ }
1147
+ let filePath = url === "/" ? "/index.html" : url;
1148
+ filePath = filePath.split("?")[0];
1149
+ if (config.logging && filePath === "/src/pages") {
1150
+ console.log(`[DEBUG] Request for /src/pages received`);
1151
+ }
1152
+ if (filePath.includes("\0")) {
1153
+ if (config.logging) console.log(`[403] Rejected path with null byte: ${filePath}`);
1154
+ res.writeHead(403, { "Content-Type": "text/plain" });
1155
+ res.end("403 Forbidden");
1156
+ return;
1157
+ }
1158
+ const isDistRequest = filePath.startsWith("/dist/");
1159
+ let normalizedPath;
1160
+ const tempPath = (0, import_path2.normalize)(filePath).replace(/\\/g, "/").replace(/^\/+/, "");
1161
+ if (tempPath.includes("..")) {
1162
+ if (config.logging) console.log(`[403] Path traversal attempt: ${filePath}`);
1163
+ res.writeHead(403, { "Content-Type": "text/plain" });
1164
+ res.end("403 Forbidden");
1165
+ return;
1166
+ }
1167
+ normalizedPath = tempPath;
1168
+ const rootDir = await (0, import_promises.realpath)((0, import_path2.resolve)(matchedClient.root));
1169
+ const baseDir = isDistRequest ? await (0, import_promises.realpath)((0, import_path2.resolve)(matchedClient.root, "..")) : rootDir;
1170
+ let fullPath;
1171
+ try {
1172
+ fullPath = await (0, import_promises.realpath)((0, import_path2.resolve)((0, import_path2.join)(baseDir, normalizedPath)));
1173
+ if (!fullPath.startsWith(baseDir.endsWith(import_path2.sep) ? baseDir : baseDir + import_path2.sep)) {
1174
+ if (config.logging) console.log(`[403] File access outside of root: ${fullPath}`);
1175
+ res.writeHead(403, { "Content-Type": "text/plain" });
1176
+ res.end("403 Forbidden");
1177
+ return;
1178
+ }
1179
+ if (config.logging && filePath === "/src/pages") {
1180
+ console.log(`[DEBUG] Initial resolve succeeded: ${fullPath}`);
1181
+ }
1182
+ } catch (firstError) {
1183
+ let resolvedPath;
1184
+ if (config.logging && !normalizedPath.includes(".")) {
1185
+ console.log(`[DEBUG] File not found: ${normalizedPath}, trying extensions...`);
1186
+ }
1187
+ if (normalizedPath.endsWith(".js")) {
1188
+ const tsPath = normalizedPath.replace(/\.js$/, ".ts");
1189
+ try {
1190
+ const tsFullPath = await (0, import_promises.realpath)((0, import_path2.resolve)((0, import_path2.join)(baseDir, tsPath)));
1191
+ if (!tsFullPath.startsWith(baseDir.endsWith(import_path2.sep) ? baseDir : baseDir + import_path2.sep)) {
1192
+ if (config.logging) console.log(`[403] Fallback TS path outside of root: ${tsFullPath}`);
1193
+ res.writeHead(403, { "Content-Type": "text/plain" });
1194
+ res.end("403 Forbidden");
1195
+ return;
1196
+ }
1197
+ resolvedPath = tsFullPath;
1198
+ } catch {
1199
+ }
1200
+ }
1201
+ if (!resolvedPath && !normalizedPath.includes(".")) {
1202
+ try {
1203
+ resolvedPath = await (0, import_promises.realpath)((0, import_path2.resolve)((0, import_path2.join)(baseDir, normalizedPath + ".ts")));
1204
+ if (config.logging) console.log(`[DEBUG] Found: ${normalizedPath}.ts`);
1205
+ } catch {
1206
+ try {
1207
+ resolvedPath = await (0, import_promises.realpath)((0, import_path2.resolve)((0, import_path2.join)(baseDir, normalizedPath + ".js")));
1208
+ if (config.logging) console.log(`[DEBUG] Found: ${normalizedPath}.js`);
1209
+ } catch {
1210
+ try {
1211
+ resolvedPath = await (0, import_promises.realpath)((0, import_path2.resolve)((0, import_path2.join)(baseDir, normalizedPath, "index.ts")));
1212
+ if (config.logging) console.log(`[DEBUG] Found: ${normalizedPath}/index.ts`);
1213
+ } catch {
1214
+ try {
1215
+ resolvedPath = await (0, import_promises.realpath)((0, import_path2.resolve)((0, import_path2.join)(baseDir, normalizedPath, "index.js")));
1216
+ if (config.logging) console.log(`[DEBUG] Found: ${normalizedPath}/index.js`);
1217
+ } catch {
1218
+ if (config.logging) console.log(`[DEBUG] Not found: all attempts failed for ${normalizedPath}`);
1219
+ }
1220
+ }
1221
+ }
1222
+ }
1223
+ }
1224
+ if (!resolvedPath) {
1225
+ res.writeHead(404, { "Content-Type": "text/plain" });
1226
+ res.end("404 Not Found");
1227
+ return;
1228
+ }
1229
+ fullPath = resolvedPath;
1230
+ }
1231
+ try {
1232
+ const stats = await (0, import_promises.stat)(fullPath);
1233
+ if (stats.isDirectory()) {
1234
+ if (config.logging) console.log(`[DEBUG] Path is directory: ${fullPath}, trying index files...`);
1235
+ let indexPath;
1236
+ try {
1237
+ indexPath = await (0, import_promises.realpath)((0, import_path2.resolve)((0, import_path2.join)(fullPath, "index.ts")));
1238
+ if (config.logging) console.log(`[DEBUG] Found index.ts in directory`);
1239
+ } catch {
1240
+ try {
1241
+ indexPath = await (0, import_promises.realpath)((0, import_path2.resolve)((0, import_path2.join)(fullPath, "index.js")));
1242
+ if (config.logging) console.log(`[DEBUG] Found index.js in directory`);
1243
+ } catch {
1244
+ if (config.logging) console.log(`[DEBUG] No index file found in directory`);
1245
+ res.writeHead(404, { "Content-Type": "text/plain" });
1246
+ res.end("404 Not Found");
1247
+ return;
1248
+ }
1249
+ }
1250
+ fullPath = indexPath;
1251
+ }
1252
+ } catch (statError) {
1253
+ res.writeHead(404, { "Content-Type": "text/plain" });
1254
+ res.end("404 Not Found");
1255
+ return;
1256
+ }
1257
+ const parentDir = await (0, import_promises.realpath)((0, import_path2.resolve)(matchedClient.root, ".."));
1258
+ const isInRoot = fullPath.startsWith(rootDir + import_path2.sep) || fullPath === rootDir;
1259
+ const isInParent = isDistRequest && (fullPath.startsWith(parentDir + import_path2.sep) || fullPath === parentDir);
1260
+ if (!isInRoot && !isInParent) {
1261
+ if (config.logging) console.log(`[403] Path outside allowed directories: ${filePath}`);
1262
+ res.writeHead(403, { "Content-Type": "text/plain" });
1263
+ res.end("403 Forbidden");
1264
+ return;
1265
+ }
1266
+ try {
1267
+ const stats = await (0, import_promises.stat)(fullPath);
1268
+ if (stats.isDirectory()) {
1269
+ try {
1270
+ const indexPath = await (0, import_promises.realpath)((0, import_path2.resolve)((0, import_path2.join)(fullPath, "index.html")));
1271
+ if (!indexPath.startsWith(rootDir + import_path2.sep) && indexPath !== rootDir) {
1272
+ res.writeHead(403, { "Content-Type": "text/plain" });
1273
+ res.end("403 Forbidden");
1274
+ return;
1275
+ }
1276
+ await (0, import_promises.stat)(indexPath);
1277
+ return serveFile(indexPath, res, matchedClient);
1278
+ } catch {
1279
+ res.writeHead(404, { "Content-Type": "text/plain" });
1280
+ res.end("404 Not Found");
1281
+ return;
1282
+ }
1283
+ }
1284
+ await serveFile(fullPath, res, matchedClient);
1285
+ } catch (error) {
1286
+ if (config.logging) console.log(`[404] ${filePath}`);
1287
+ res.writeHead(404, { "Content-Type": "text/plain" });
1288
+ res.end("404 Not Found");
1289
+ }
1290
+ });
1291
+ async function serveFile(filePath, res, client) {
1292
+ try {
1293
+ const rootDir = await (0, import_promises.realpath)((0, import_path2.resolve)(client.root));
1294
+ const parentDir = await (0, import_promises.realpath)((0, import_path2.resolve)(client.root, ".."));
1295
+ let resolvedPath;
1296
+ try {
1297
+ resolvedPath = await (0, import_promises.realpath)((0, import_path2.resolve)(filePath));
1298
+ } catch {
1299
+ if (filePath.endsWith("index.html") && client.ssr) {
1300
+ return serveSSR(res, client);
1301
+ }
1302
+ res.writeHead(404, { "Content-Type": "text/plain" });
1303
+ res.end("404 Not Found");
1304
+ return;
1305
+ }
1306
+ const isInRoot = resolvedPath.startsWith(rootDir + import_path2.sep) || resolvedPath === rootDir;
1307
+ const isInParentDist = resolvedPath.startsWith(parentDir + import_path2.sep + "dist" + import_path2.sep);
1308
+ if (!isInRoot && !isInParentDist) {
1309
+ if (config.logging) console.log(`[403] Attempted to serve file outside allowed directories: ${filePath}`);
1310
+ res.writeHead(403, { "Content-Type": "text/plain" });
1311
+ res.end("403 Forbidden");
1312
+ return;
1313
+ }
1314
+ let content = await (0, import_promises.readFile)(resolvedPath);
1315
+ const ext = (0, import_path2.extname)(resolvedPath);
1316
+ let mimeType = (0, import_mime_types.lookup)(resolvedPath) || "application/octet-stream";
1317
+ if (ext === ".ts" || ext === ".tsx") {
1318
+ try {
1319
+ const result = await (0, import_esbuild.build)({
1320
+ stdin: {
1321
+ contents: content.toString(),
1322
+ loader: ext === ".tsx" ? "tsx" : "ts",
1323
+ resolveDir: (0, import_path2.resolve)(resolvedPath, ".."),
1324
+ sourcefile: resolvedPath
1325
+ },
1326
+ format: "esm",
1327
+ target: "es2020",
1328
+ write: false,
1329
+ bundle: false,
1330
+ sourcemap: "inline"
1331
+ });
1332
+ content = Buffer.from(result.outputFiles[0].text);
1333
+ mimeType = "application/javascript";
1334
+ } catch (error) {
1335
+ res.writeHead(500, { "Content-Type": "text/plain" });
1336
+ res.end(`TypeScript compilation error:
1337
+ ${error}`);
1338
+ if (config.logging) console.error("[500] TypeScript compilation error:", error);
1339
+ return;
1340
+ }
1341
+ }
1342
+ if (ext === ".html") {
1343
+ const elitPath = client.basePath ? `${client.basePath}/dist/client.mjs` : "/dist/client.mjs";
1344
+ const importMap = `<script type="importmap">
1345
+ {
1346
+ "imports": {
1347
+ "elit": "${elitPath}"
1348
+ }
1349
+ }
1350
+ </script>`;
1351
+ const hmrScript = `<script>(function(){const ws=new WebSocket('ws://${config.host}:${config.port}${client.basePath}');ws.onopen=()=>console.log('[Elit HMR] Connected');ws.onmessage=(e)=>{const d=JSON.parse(e.data);if(d.type==='update'){console.log('[Elit HMR] File updated:',d.path);window.location.reload()}else if(d.type==='reload'){console.log('[Elit HMR] Reloading...');window.location.reload()}else if(d.type==='error')console.error('[Elit HMR] Error:',d.error)};ws.onclose=()=>{console.log('[Elit HMR] Disconnected - Retrying...');setTimeout(()=>window.location.reload(),1000)};ws.onerror=(e)=>console.error('[Elit HMR] WebSocket error:',e)})();</script>`;
1352
+ let html = content.toString();
1353
+ if (client.basePath && client.basePath !== "/") {
1354
+ const baseTag = `<base href="${client.basePath}/">`;
1355
+ if (!html.includes("<base")) {
1356
+ if (html.includes('<meta name="viewport"')) {
1357
+ html = html.replace(
1358
+ /<meta name="viewport"[^>]*>/,
1359
+ (match) => `${match}
1360
+ ${baseTag}`
1361
+ );
1362
+ } else if (html.includes("<head>")) {
1363
+ html = html.replace("<head>", `<head>
1364
+ ${baseTag}`);
1365
+ }
1366
+ }
1367
+ }
1368
+ html = html.includes("</head>") ? html.replace("</head>", `${importMap}</head>`) : html;
1369
+ html = html.includes("</body>") ? html.replace("</body>", `${hmrScript}</body>`) : html + hmrScript;
1370
+ content = Buffer.from(html);
1371
+ }
1372
+ const cacheControl = ext === ".html" || ext === ".ts" || ext === ".tsx" ? "no-cache, no-store, must-revalidate" : "public, max-age=31536000, immutable";
1373
+ const headers = {
1374
+ "Content-Type": mimeType,
1375
+ "Cache-Control": cacheControl
1376
+ };
1377
+ const compressible = /^(text\/|application\/(javascript|json|xml))/.test(mimeType);
1378
+ if (compressible && content.length > 1024) {
1379
+ const { gzipSync } = require("zlib");
1380
+ const compressed = gzipSync(content);
1381
+ headers["Content-Encoding"] = "gzip";
1382
+ headers["Content-Length"] = compressed.length;
1383
+ res.writeHead(200, headers);
1384
+ res.end(compressed);
1385
+ } else {
1386
+ res.writeHead(200, headers);
1387
+ res.end(content);
1388
+ }
1389
+ if (config.logging) console.log(`[200] ${(0, import_path2.relative)(client.root, filePath)}`);
1390
+ } catch (error) {
1391
+ res.writeHead(500, { "Content-Type": "text/plain" });
1392
+ res.end("500 Internal Server Error");
1393
+ if (config.logging) console.error("[500] Error reading file:", error);
1394
+ }
1395
+ }
1396
+ function serveSSR(res, client) {
1397
+ try {
1398
+ if (!client.ssr) {
1399
+ res.writeHead(500, { "Content-Type": "text/plain" });
1400
+ res.end("SSR function not configured");
1401
+ return;
1402
+ }
1403
+ const result = client.ssr();
1404
+ let html;
1405
+ if (typeof result === "string") {
1406
+ html = result;
1407
+ } else if (typeof result === "object" && result !== null && "tagName" in result) {
1408
+ const vnode = result;
1409
+ if (vnode.tagName === "html") {
1410
+ html = dom.renderToString(vnode);
1411
+ } else {
1412
+ html = `<!DOCTYPE html><html><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"></head><body>${dom.renderToString(vnode)}</body></html>`;
1413
+ }
1414
+ } else {
1415
+ html = String(result);
1416
+ }
1417
+ const hmrScript = `<script>(function(){const ws=new WebSocket('ws://${config.host}:${config.port}${client.basePath}');ws.onopen=()=>console.log('[Elit HMR] Connected');ws.onmessage=(e)=>{const d=JSON.parse(e.data);if(d.type==='update'){console.log('[Elit HMR] File updated:',d.path);window.location.reload()}else if(d.type==='reload'){console.log('[Elit HMR] Reloading...');window.location.reload()}else if(d.type==='error')console.error('[Elit HMR] Error:',d.error)};ws.onclose=()=>{console.log('[Elit HMR] Disconnected - Retrying...');setTimeout(()=>window.location.reload(),1000)};ws.onerror=(e)=>console.error('[Elit HMR] WebSocket error:',e)})();</script>`;
1418
+ html = html.includes("</body>") ? html.replace("</body>", `${hmrScript}</body>`) : html + hmrScript;
1419
+ res.writeHead(200, { "Content-Type": "text/html", "Cache-Control": "no-cache, no-store, must-revalidate" });
1420
+ res.end(html);
1421
+ if (config.logging) console.log(`[200] SSR rendered`);
1422
+ } catch (error) {
1423
+ res.writeHead(500, { "Content-Type": "text/plain" });
1424
+ res.end("500 SSR Error");
1425
+ if (config.logging) console.error("[500] SSR Error:", error);
1426
+ }
1427
+ }
1428
+ const wss = new import_ws.WebSocketServer({ server });
1429
+ wss.on("connection", (ws) => {
1430
+ wsClients.add(ws);
1431
+ const message = { type: "connected", timestamp: Date.now() };
1432
+ ws.send(JSON.stringify(message));
1433
+ if (config.logging) {
1434
+ console.log("[HMR] Client connected");
1435
+ }
1436
+ ws.on("message", (data) => {
1437
+ try {
1438
+ const msg = JSON.parse(data.toString());
1439
+ if (msg.type === "state:subscribe") {
1440
+ stateManager.subscribe(msg.key, ws);
1441
+ if (config.logging) {
1442
+ console.log(`[State] Client subscribed to "${msg.key}"`);
1443
+ }
1444
+ } else if (msg.type === "state:unsubscribe") {
1445
+ stateManager.unsubscribe(msg.key, ws);
1446
+ if (config.logging) {
1447
+ console.log(`[State] Client unsubscribed from "${msg.key}"`);
1448
+ }
1449
+ } else if (msg.type === "state:change") {
1450
+ stateManager.handleStateChange(msg.key, msg.value);
1451
+ if (config.logging) {
1452
+ console.log(`[State] Client updated "${msg.key}"`);
1453
+ }
1454
+ }
1455
+ } catch (error) {
1456
+ if (config.logging) {
1457
+ console.error("[WebSocket] Message parse error:", error);
1458
+ }
1459
+ }
1460
+ });
1461
+ ws.on("close", () => {
1462
+ wsClients.delete(ws);
1463
+ stateManager.unsubscribeAll(ws);
1464
+ if (config.logging) {
1465
+ console.log("[HMR] Client disconnected");
1466
+ }
1467
+ });
1468
+ });
1469
+ const watchPaths = normalizedClients.flatMap(
1470
+ (client) => config.watch.map((pattern) => (0, import_path2.join)(client.root, pattern))
1471
+ );
1472
+ const watcher = (0, import_chokidar.watch)(watchPaths, {
1473
+ ignored: config.ignore,
1474
+ ignoreInitial: true,
1475
+ persistent: true
1476
+ });
1477
+ watcher.on("change", (path) => {
1478
+ if (config.logging) console.log(`[HMR] File changed: ${path}`);
1479
+ const message = JSON.stringify({ type: "update", path, timestamp: Date.now() });
1480
+ wsClients.forEach((client) => client.readyState === import_ws.WebSocket.OPEN && client.send(message));
1481
+ });
1482
+ watcher.on("add", (path) => config.logging && console.log(`[HMR] File added: ${path}`));
1483
+ watcher.on("unlink", (path) => config.logging && console.log(`[HMR] File removed: ${path}`));
1484
+ server.setMaxListeners(20);
1485
+ server.listen(config.port, config.host, () => {
1486
+ if (config.logging) {
1487
+ console.log("\n\u{1F680} Elit Dev Server");
1488
+ console.log(`
1489
+ \u279C Local: http://${config.host}:${config.port}`);
1490
+ if (normalizedClients.length > 1) {
1491
+ console.log(` \u279C Clients:`);
1492
+ normalizedClients.forEach((client) => {
1493
+ const clientUrl = `http://${config.host}:${config.port}${client.basePath}`;
1494
+ console.log(` - ${clientUrl} \u2192 ${client.root}`);
1495
+ });
1496
+ } else {
1497
+ const client = normalizedClients[0];
1498
+ console.log(` \u279C Root: ${client.root}`);
1499
+ if (client.basePath) {
1500
+ console.log(` \u279C Base: ${client.basePath}`);
1501
+ }
1502
+ }
1503
+ console.log(`
1504
+ [HMR] Watching for file changes...
1505
+ `);
1506
+ }
1507
+ if (config.open && normalizedClients.length > 0) {
1508
+ const firstClient = normalizedClients[0];
1509
+ const url = `http://${config.host}:${config.port}${firstClient.basePath}`;
1510
+ const open = async () => {
1511
+ const { default: openBrowser } = await import("open");
1512
+ await openBrowser(url);
1513
+ };
1514
+ open().catch(() => {
1515
+ });
1516
+ }
1517
+ });
1518
+ let isClosing = false;
1519
+ const close = async () => {
1520
+ if (isClosing) return;
1521
+ isClosing = true;
1522
+ if (config.logging) console.log("\n[Server] Shutting down...");
1523
+ await watcher.close();
1524
+ wss.close();
1525
+ wsClients.forEach((client) => client.close());
1526
+ wsClients.clear();
1527
+ return new Promise((resolve4) => {
1528
+ server.close(() => {
1529
+ if (config.logging) console.log("[Server] Closed");
1530
+ resolve4();
1531
+ });
1532
+ });
1533
+ };
1534
+ const primaryClient = normalizedClients[0];
1535
+ const primaryUrl = `http://${config.host}:${config.port}${primaryClient.basePath}`;
1536
+ return {
1537
+ server,
1538
+ wss,
1539
+ url: primaryUrl,
1540
+ state: stateManager,
1541
+ close
1542
+ };
1543
+ }
1544
+
1545
+ // src/build.ts
1546
+ var import_esbuild2 = require("esbuild");
1547
+ var import_fs2 = require("fs");
1548
+ var import_path3 = require("path");
1549
+ var defaultOptions2 = {
1550
+ outDir: "dist",
1551
+ minify: true,
1552
+ sourcemap: false,
1553
+ target: "es2020",
1554
+ format: "esm",
1555
+ treeshake: true,
1556
+ logging: true,
1557
+ external: []
1558
+ };
1559
+ async function build2(options) {
1560
+ const config = { ...defaultOptions2, ...options };
1561
+ const startTime = Date.now();
1562
+ if (!config.entry) {
1563
+ throw new Error("Entry file is required");
1564
+ }
1565
+ const entryPath = (0, import_path3.resolve)(config.entry);
1566
+ const outDir = (0, import_path3.resolve)(config.outDir);
1567
+ let outFile = config.outFile;
1568
+ if (!outFile) {
1569
+ const baseName = (0, import_path3.basename)(config.entry, (0, import_path3.extname)(config.entry));
1570
+ const ext = config.format === "cjs" ? ".cjs" : ".js";
1571
+ outFile = baseName + ext;
1572
+ }
1573
+ const outputPath = (0, import_path3.join)(outDir, outFile);
1574
+ try {
1575
+ (0, import_fs2.mkdirSync)(outDir, { recursive: true });
1576
+ } catch (error) {
1577
+ }
1578
+ if (config.logging) {
1579
+ console.log("\n\u{1F528} Building...");
1580
+ console.log(` Entry: ${config.entry}`);
1581
+ console.log(` Output: ${outputPath}`);
1582
+ console.log(` Format: ${config.format}`);
1583
+ console.log(` Target: ${config.target}`);
1584
+ }
1585
+ const browserOnlyPlugin = {
1586
+ name: "browser-only",
1587
+ setup(build3) {
1588
+ build3.onResolve({ filter: /^(node:.*|fs|path|http|https|url|os|child_process|net|tls|crypto|stream|util|events|buffer|zlib|readline|process|assert|constants|dns|domain|punycode|querystring|repl|string_decoder|sys|timers|tty|v8|vm)$/ }, () => {
1589
+ return { path: "node-builtin", external: true, sideEffects: false };
1590
+ });
1591
+ build3.onResolve({ filter: /^(chokidar|esbuild|mime-types|open|ws|fs\/promises)$/ }, () => {
1592
+ return { path: "server-dep", external: true, sideEffects: false };
1593
+ });
1594
+ build3.onLoad({ filter: /[\\/](server|config|cli)\.ts$/ }, () => {
1595
+ return {
1596
+ contents: "export {}",
1597
+ loader: "js"
1598
+ };
1599
+ });
1600
+ }
1601
+ };
1602
+ try {
1603
+ const platform = config.platform || (config.format === "cjs" ? "node" : "browser");
1604
+ const plugins = platform === "browser" ? [browserOnlyPlugin] : [];
1605
+ const define = {};
1606
+ if (config.env) {
1607
+ Object.entries(config.env).forEach(([key, value]) => {
1608
+ if (key.startsWith("VITE_")) {
1609
+ define[`import.meta.env.${key}`] = JSON.stringify(value);
1610
+ }
1611
+ });
1612
+ if (config.env.MODE) {
1613
+ define["import.meta.env.MODE"] = JSON.stringify(config.env.MODE);
1614
+ }
1615
+ define["import.meta.env.DEV"] = JSON.stringify(config.env.MODE !== "production");
1616
+ define["import.meta.env.PROD"] = JSON.stringify(config.env.MODE === "production");
1617
+ }
1618
+ const result = await (0, import_esbuild2.build)({
1619
+ entryPoints: [entryPath],
1620
+ bundle: true,
1621
+ outfile: outputPath,
1622
+ format: config.format,
1623
+ target: config.target,
1624
+ minify: config.minify,
1625
+ sourcemap: config.sourcemap,
1626
+ external: config.external,
1627
+ treeShaking: config.treeshake,
1628
+ globalName: config.globalName,
1629
+ platform,
1630
+ plugins,
1631
+ define,
1632
+ logLevel: config.logging ? "info" : "silent",
1633
+ metafile: true,
1634
+ // Additional optimizations
1635
+ ...config.minify && {
1636
+ minifyWhitespace: true,
1637
+ minifyIdentifiers: true,
1638
+ minifySyntax: true,
1639
+ legalComments: "none",
1640
+ mangleProps: /^_/,
1641
+ // Mangle properties starting with _
1642
+ keepNames: false
1643
+ }
1644
+ });
1645
+ const buildTime = Date.now() - startTime;
1646
+ const stats = (0, import_fs2.statSync)(outputPath);
1647
+ const size = stats.size;
1648
+ if (config.logging) {
1649
+ console.log(`
1650
+ \u2705 Build successful!`);
1651
+ console.log(` Time: ${buildTime}ms`);
1652
+ console.log(` Size: ${formatBytes(size)}`);
1653
+ if (result.metafile) {
1654
+ const inputs = Object.keys(result.metafile.inputs).length;
1655
+ console.log(` Files: ${inputs} input(s)`);
1656
+ const outputKeys = Object.keys(result.metafile.outputs);
1657
+ if (outputKeys.length > 0) {
1658
+ const mainOutput = result.metafile.outputs[outputKeys[0]];
1659
+ if (mainOutput && mainOutput.inputs) {
1660
+ const sortedInputs = Object.entries(mainOutput.inputs).sort(([, a], [, b]) => b.bytesInOutput - a.bytesInOutput).slice(0, 5);
1661
+ if (sortedInputs.length > 0) {
1662
+ console.log("\n \u{1F4CA} Top 5 largest modules:");
1663
+ sortedInputs.forEach(([file, info]) => {
1664
+ const fileName = file.split(/[/\\]/).pop() || file;
1665
+ console.log(` ${fileName.padEnd(30)} ${formatBytes(info.bytesInOutput)}`);
1666
+ });
1667
+ }
1668
+ }
1669
+ }
1670
+ }
1671
+ }
1672
+ const buildResult = {
1673
+ outputPath,
1674
+ buildTime,
1675
+ size
1676
+ };
1677
+ if (config.copy && config.copy.length > 0) {
1678
+ if (config.logging) {
1679
+ console.log("\n\u{1F4E6} Copying files...");
1680
+ }
1681
+ for (const copyItem of config.copy) {
1682
+ const fromPath = (0, import_path3.resolve)(copyItem.from);
1683
+ const toPath = (0, import_path3.resolve)(outDir, copyItem.to);
1684
+ const targetDir = (0, import_path3.dirname)(toPath);
1685
+ if (!(0, import_fs2.existsSync)(targetDir)) {
1686
+ (0, import_fs2.mkdirSync)(targetDir, { recursive: true });
1687
+ }
1688
+ if ((0, import_fs2.existsSync)(fromPath)) {
1689
+ if (copyItem.transform) {
1690
+ const content = (0, import_fs2.readFileSync)(fromPath, "utf-8");
1691
+ const transformed = copyItem.transform(content, config);
1692
+ (0, import_fs2.writeFileSync)(toPath, transformed);
1693
+ } else {
1694
+ (0, import_fs2.copyFileSync)(fromPath, toPath);
1695
+ }
1696
+ if (config.logging) {
1697
+ console.log(` \u2713 ${copyItem.from} \u2192 ${copyItem.to}`);
1698
+ }
1699
+ } else if (config.logging) {
1700
+ console.warn(` \u26A0 File not found: ${copyItem.from}`);
1701
+ }
1702
+ }
1703
+ }
1704
+ if (config.onBuildEnd) {
1705
+ await config.onBuildEnd(buildResult);
1706
+ }
1707
+ if (config.logging) {
1708
+ console.log("");
1709
+ }
1710
+ return buildResult;
1711
+ } catch (error) {
1712
+ if (config.logging) {
1713
+ console.error("\n\u274C Build failed:");
1714
+ console.error(error);
1715
+ }
1716
+ throw error;
1717
+ }
1718
+ }
1719
+ function formatBytes(bytes) {
1720
+ if (bytes === 0) return "0 B";
1721
+ const k = 1024;
1722
+ const sizes = ["B", "KB", "MB", "GB"];
1723
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
1724
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + " " + sizes[i];
1725
+ }
1726
+
1727
+ // src/cli.ts
1728
+ var COMMANDS = ["dev", "build", "preview", "help", "version"];
1729
+ async function main() {
1730
+ const args = process.argv.slice(2);
1731
+ const command = args[0] || "help";
1732
+ if (!COMMANDS.includes(command)) {
1733
+ console.error(`Unknown command: ${command}`);
1734
+ printHelp();
1735
+ process.exit(1);
1736
+ }
1737
+ switch (command) {
1738
+ case "dev":
1739
+ await runDev(args.slice(1));
1740
+ break;
1741
+ case "build":
1742
+ await runBuild(args.slice(1));
1743
+ break;
1744
+ case "preview":
1745
+ await runPreview(args.slice(1));
1746
+ break;
1747
+ case "version":
1748
+ printVersion();
1749
+ break;
1750
+ case "help":
1751
+ default:
1752
+ printHelp();
1753
+ break;
1754
+ }
1755
+ }
1756
+ async function runDev(args) {
1757
+ const cliOptions = parseDevArgs(args);
1758
+ const config = await loadConfig();
1759
+ const options = config?.dev ? mergeConfig(config.dev, cliOptions) : cliOptions;
1760
+ if (!options.root && (!options.clients || options.clients.length === 0)) {
1761
+ options.root = process.cwd();
1762
+ }
1763
+ const devServer = createDevServer(options);
1764
+ const shutdown = async () => {
1765
+ console.log("\n[Server] Shutting down...");
1766
+ await devServer.close();
1767
+ process.exit(0);
1768
+ };
1769
+ process.on("SIGINT", shutdown);
1770
+ process.on("SIGTERM", shutdown);
1771
+ }
1772
+ async function runBuild(args) {
1773
+ const cliOptions = parseBuildArgs(args);
1774
+ const config = await loadConfig();
1775
+ const mode = process.env.MODE || "production";
1776
+ const env = loadEnv(mode);
1777
+ if (config?.build) {
1778
+ const builds = Array.isArray(config.build) ? config.build : [config.build];
1779
+ if (Object.keys(cliOptions).length > 0) {
1780
+ const options = mergeConfig(builds[0] || {}, cliOptions);
1781
+ if (!options.env) {
1782
+ options.env = env;
1783
+ }
1784
+ if (!options.entry) {
1785
+ console.error("Error: Entry file is required");
1786
+ console.error("Specify in config file or use --entry <file>");
1787
+ process.exit(1);
1788
+ }
1789
+ try {
1790
+ await build2(options);
1791
+ } catch (error) {
1792
+ process.exit(1);
1793
+ }
1794
+ } else {
1795
+ console.log(`Building ${builds.length} ${builds.length === 1 ? "entry" : "entries"}...
1796
+ `);
1797
+ for (let i = 0; i < builds.length; i++) {
1798
+ const buildConfig = builds[i];
1799
+ if (!buildConfig.env) {
1800
+ buildConfig.env = env;
1801
+ }
1802
+ if (!buildConfig.entry) {
1803
+ console.error(`Error: Entry file is required for build #${i + 1}`);
1804
+ process.exit(1);
1805
+ }
1806
+ console.log(`[${i + 1}/${builds.length}] Building ${buildConfig.entry}...`);
1807
+ try {
1808
+ await build2(buildConfig);
1809
+ } catch (error) {
1810
+ console.error(`Build #${i + 1} failed`);
1811
+ process.exit(1);
1812
+ }
1813
+ if (i < builds.length - 1) {
1814
+ console.log("");
1815
+ }
1816
+ }
1817
+ console.log(`
1818
+ \u2713 All ${builds.length} builds completed successfully`);
1819
+ }
1820
+ } else {
1821
+ const options = cliOptions;
1822
+ if (!options.env) {
1823
+ options.env = env;
1824
+ }
1825
+ if (!options.entry) {
1826
+ console.error("Error: Entry file is required");
1827
+ console.error("Specify in config file or use --entry <file>");
1828
+ process.exit(1);
1829
+ }
1830
+ try {
1831
+ await build2(options);
1832
+ } catch (error) {
1833
+ process.exit(1);
1834
+ }
1835
+ }
1836
+ }
1837
+ async function runPreview(args) {
1838
+ const cliOptions = parsePreviewArgs(args);
1839
+ const config = await loadConfig();
1840
+ const previewConfig = config?.preview || {};
1841
+ const mergedOptions = {
1842
+ ...previewConfig,
1843
+ ...Object.fromEntries(
1844
+ Object.entries(cliOptions).filter(([_, v]) => v !== void 0)
1845
+ )
1846
+ };
1847
+ const options = {
1848
+ port: mergedOptions.port || 4173,
1849
+ host: mergedOptions.host || "localhost",
1850
+ open: mergedOptions.open ?? true,
1851
+ logging: mergedOptions.logging ?? true
1852
+ };
1853
+ if (mergedOptions.clients && mergedOptions.clients.length > 0) {
1854
+ options.clients = mergedOptions.clients;
1855
+ console.log("Starting preview server with multiple clients...");
1856
+ console.log(` Clients: ${mergedOptions.clients.length}`);
1857
+ mergedOptions.clients.forEach((client, i) => {
1858
+ console.log(` ${i + 1}. ${client.basePath} -> ${client.root}`);
1859
+ });
1860
+ } else {
1861
+ const buildConfig = config?.build;
1862
+ const defaultOutDir = Array.isArray(buildConfig) ? buildConfig[0]?.outDir : buildConfig?.outDir;
1863
+ options.root = mergedOptions.root || defaultOutDir || "dist";
1864
+ options.basePath = mergedOptions.basePath;
1865
+ console.log("Starting preview server...");
1866
+ console.log(` Root: ${options.root}`);
1867
+ }
1868
+ if (mergedOptions.proxy && mergedOptions.proxy.length > 0) {
1869
+ options.proxy = mergedOptions.proxy;
1870
+ }
1871
+ if (mergedOptions.worker && mergedOptions.worker.length > 0) {
1872
+ options.worker = mergedOptions.worker;
1873
+ }
1874
+ if (mergedOptions.api) {
1875
+ options.api = mergedOptions.api;
1876
+ }
1877
+ if (mergedOptions.https) {
1878
+ options.https = mergedOptions.https;
1879
+ }
1880
+ if (mergedOptions.middleware) {
1881
+ options.middleware = mergedOptions.middleware;
1882
+ }
1883
+ if (mergedOptions.ssr) {
1884
+ options.ssr = mergedOptions.ssr;
1885
+ }
1886
+ const devServer = createDevServer(options);
1887
+ const shutdown = async () => {
1888
+ console.log("\n[Server] Shutting down...");
1889
+ await devServer.close();
1890
+ process.exit(0);
1891
+ };
1892
+ process.on("SIGINT", shutdown);
1893
+ process.on("SIGTERM", shutdown);
1894
+ }
1895
+ function parseDevArgs(args) {
1896
+ const options = {};
1897
+ for (let i = 0; i < args.length; i++) {
1898
+ const arg = args[i];
1899
+ const next = args[i + 1];
1900
+ switch (arg) {
1901
+ case "-p":
1902
+ case "--port":
1903
+ options.port = parseInt(next, 10);
1904
+ i++;
1905
+ break;
1906
+ case "-h":
1907
+ case "--host":
1908
+ options.host = next;
1909
+ i++;
1910
+ break;
1911
+ case "-r":
1912
+ case "--root":
1913
+ options.root = next;
1914
+ i++;
1915
+ break;
1916
+ case "--no-open":
1917
+ options.open = false;
1918
+ break;
1919
+ case "--silent":
1920
+ options.logging = false;
1921
+ break;
1922
+ }
1923
+ }
1924
+ return options;
1925
+ }
1926
+ function parseBuildArgs(args) {
1927
+ const options = {};
1928
+ for (let i = 0; i < args.length; i++) {
1929
+ const arg = args[i];
1930
+ const next = args[i + 1];
1931
+ switch (arg) {
1932
+ case "-e":
1933
+ case "--entry":
1934
+ options.entry = next;
1935
+ i++;
1936
+ break;
1937
+ case "-o":
1938
+ case "--out-dir":
1939
+ options.outDir = next;
1940
+ i++;
1941
+ break;
1942
+ case "--no-minify":
1943
+ options.minify = false;
1944
+ break;
1945
+ case "--sourcemap":
1946
+ options.sourcemap = true;
1947
+ break;
1948
+ case "-f":
1949
+ case "--format":
1950
+ options.format = next;
1951
+ i++;
1952
+ break;
1953
+ case "--silent":
1954
+ options.logging = false;
1955
+ break;
1956
+ }
1957
+ }
1958
+ return options;
1959
+ }
1960
+ function parsePreviewArgs(args) {
1961
+ const options = {};
1962
+ for (let i = 0; i < args.length; i++) {
1963
+ const arg = args[i];
1964
+ const next = args[i + 1];
1965
+ switch (arg) {
1966
+ case "-p":
1967
+ case "--port":
1968
+ options.port = parseInt(next, 10);
1969
+ i++;
1970
+ break;
1971
+ case "-h":
1972
+ case "--host":
1973
+ options.host = next;
1974
+ i++;
1975
+ break;
1976
+ case "-r":
1977
+ case "--root":
1978
+ options.root = next;
1979
+ i++;
1980
+ break;
1981
+ case "-b":
1982
+ case "--base-path":
1983
+ options.basePath = next;
1984
+ i++;
1985
+ break;
1986
+ case "--no-open":
1987
+ options.open = false;
1988
+ break;
1989
+ case "--silent":
1990
+ options.logging = false;
1991
+ break;
1992
+ }
1993
+ }
1994
+ return options;
1995
+ }
1996
+ function printHelp() {
1997
+ console.log(`
1998
+ Elit - Modern Web Development Toolkit
1999
+
2000
+ Usage:
2001
+ elit <command> [options]
2002
+
2003
+ Commands:
2004
+ dev Start development server
2005
+ build Build for production
2006
+ preview Preview production build
2007
+ version Show version number
2008
+ help Show this help message
2009
+
2010
+ Dev Options:
2011
+ -p, --port <number> Port to run server on (default: 3000)
2012
+ -h, --host <string> Host to bind to (default: localhost)
2013
+ -r, --root <path> Root directory to serve
2014
+ --no-open Don't open browser automatically
2015
+ --silent Disable logging
2016
+
2017
+ Build Options:
2018
+ -e, --entry <file> Entry file to build (required)
2019
+ -o, --out-dir <dir> Output directory (default: dist)
2020
+ -f, --format <format> Output format: esm, cjs, iife (default: esm)
2021
+ --no-minify Disable minification
2022
+ --sourcemap Generate sourcemap
2023
+ --silent Disable logging
2024
+
2025
+ Note: Build configuration supports both single and multiple builds:
2026
+ - Single build: build: { entry: 'src/app.ts', outDir: 'dist' }
2027
+ - Multiple builds: build: [{ entry: 'src/app1.ts' }, { entry: 'src/app2.ts' }]
2028
+ When using array, all builds run sequentially.
2029
+
2030
+ Preview Options:
2031
+ -p, --port <number> Port to run server on (default: 4173)
2032
+ -h, --host <string> Host to bind to (default: localhost)
2033
+ -r, --root <dir> Root directory to serve (default: dist or build.outDir)
2034
+ -b, --base-path <path> Base path for the application
2035
+ --no-open Don't open browser automatically
2036
+ --silent Disable logging
2037
+
2038
+ Note: Preview mode has full feature parity with dev mode:
2039
+ - Single root and multi-client configurations (use clients[] in config)
2040
+ - REST API endpoints (use api option in config)
2041
+ - Proxy forwarding and Web Workers
2042
+ - HTTPS support, custom middleware, and SSR
2043
+
2044
+ Config File:
2045
+ Create elit.config.ts, elit.config.js, or elit.config.json in project root
2046
+
2047
+ Proxy Configuration:
2048
+ Configure proxy in the config file to forward requests to backend servers.
2049
+ Supports both global proxy (applies to all clients) and client-specific proxy.
2050
+
2051
+ Options:
2052
+ - context: Path prefix to match (required, e.g., '/api', '/graphql')
2053
+ - target: Backend server URL (required, e.g., 'http://localhost:8080')
2054
+ - changeOrigin: Change the origin header to match target (default: false)
2055
+ - pathRewrite: Rewrite request paths (e.g., { '^/api': '/v1/api' })
2056
+ - headers: Add custom headers to proxied requests
2057
+ - ws: Enable WebSocket proxying (default: false)
2058
+
2059
+ Proxy Priority:
2060
+ 1. Client-specific proxy (defined in clients[].proxy)
2061
+ 2. Global proxy (defined in dev.proxy)
2062
+ The first matching proxy configuration will be used.
2063
+
2064
+ Worker Configuration:
2065
+ Configure Web Workers in the config file for background processing.
2066
+ Supports both global workers (applies to all clients) and client-specific workers.
2067
+
2068
+ Options:
2069
+ - path: Worker script path relative to root directory (required)
2070
+ - name: Worker name/identifier (optional, defaults to filename)
2071
+ - type: Worker type - 'module' (ESM) or 'classic' (default: 'module')
2072
+
2073
+ Worker Priority:
2074
+ 1. Client-specific workers (defined in clients[].worker)
2075
+ 2. Global workers (defined in dev.worker or preview.worker)
2076
+ Both global and client-specific workers will be loaded.
2077
+
2078
+ API and Middleware Configuration:
2079
+ Configure REST API endpoints and custom middleware per client or globally.
2080
+ Supports both global configuration and client-specific configuration.
2081
+
2082
+ Client-specific API and Middleware:
2083
+ - Each client can have its own API router (clients[].api)
2084
+ - Each client can have its own middleware chain (clients[].middleware)
2085
+ - Client-specific configuration is isolated to that client's routes
2086
+ - API paths are automatically prefixed with the client's basePath
2087
+ Example: If basePath is '/app1' and route is '/api/health',
2088
+ the full path will be '/app1/api/health'
2089
+
2090
+ Priority:
2091
+ 1. Client-specific middleware runs first (defined in clients[].middleware)
2092
+ 2. Global middleware runs second (defined in dev.middleware or preview.middleware)
2093
+ 3. Client-specific API routes are matched (defined in clients[].api)
2094
+ 4. Global API routes are matched (defined in dev.api or preview.api)
2095
+
2096
+ Examples:
2097
+ elit dev
2098
+ elit dev --port 8080
2099
+ elit build --entry src/app.ts
2100
+ elit preview
2101
+ elit preview --port 5000
2102
+
2103
+ Config file example (elit.config.ts):
2104
+ export default {
2105
+ dev: {
2106
+ port: 3000,
2107
+ clients: [
2108
+ {
2109
+ root: './app1',
2110
+ basePath: '/app1',
2111
+ proxy: [
2112
+ {
2113
+ context: '/api',
2114
+ target: 'http://localhost:8080',
2115
+ changeOrigin: true
2116
+ }
2117
+ ],
2118
+ worker: [
2119
+ {
2120
+ path: 'workers/data-processor.js',
2121
+ name: 'dataProcessor',
2122
+ type: 'module'
2123
+ }
2124
+ ],
2125
+ // API routes are prefixed with basePath
2126
+ // This route becomes: /app1/api/health
2127
+ api: router()
2128
+ .get('/api/health', (req, res) => {
2129
+ res.json({ status: 'ok', app: 'app1' });
2130
+ }),
2131
+ middleware: [
2132
+ (req, res, next) => {
2133
+ console.log('App1 middleware:', req.url);
2134
+ next();
2135
+ }
2136
+ ]
2137
+ },
2138
+ {
2139
+ root: './app2',
2140
+ basePath: '/app2',
2141
+ proxy: [
2142
+ {
2143
+ context: '/graphql',
2144
+ target: 'http://localhost:4000',
2145
+ changeOrigin: true
2146
+ }
2147
+ ],
2148
+ worker: [
2149
+ {
2150
+ path: 'workers/image-worker.js',
2151
+ type: 'module'
2152
+ }
2153
+ ],
2154
+ // API routes are prefixed with basePath
2155
+ // This route becomes: /app2/api/status
2156
+ api: router()
2157
+ .get('/api/status', (req, res) => {
2158
+ res.json({ status: 'running', app: 'app2' });
2159
+ }),
2160
+ middleware: [
2161
+ (req, res, next) => {
2162
+ console.log('App2 middleware:', req.url);
2163
+ next();
2164
+ }
2165
+ ]
2166
+ }
2167
+ ],
2168
+ // Global proxy (applies to all clients)
2169
+ proxy: [
2170
+ {
2171
+ context: '/shared-api',
2172
+ target: 'http://localhost:9000',
2173
+ changeOrigin: true
2174
+ }
2175
+ ],
2176
+ // Global workers (applies to all clients)
2177
+ worker: [
2178
+ {
2179
+ path: 'workers/shared-worker.js',
2180
+ name: 'sharedWorker',
2181
+ type: 'module'
2182
+ }
2183
+ ]
2184
+ },
2185
+ // Single build configuration
2186
+ build: {
2187
+ entry: 'src/app.ts',
2188
+ outDir: 'dist',
2189
+ format: 'esm'
2190
+ },
2191
+ // Alternative: Multiple builds
2192
+ // build: [
2193
+ // {
2194
+ // entry: 'src/app1.ts',
2195
+ // outDir: 'dist/app1',
2196
+ // outFile: 'app1.js',
2197
+ // format: 'esm',
2198
+ // minify: true
2199
+ // },
2200
+ // {
2201
+ // entry: 'src/app2.ts',
2202
+ // outDir: 'dist/app2',
2203
+ // outFile: 'app2.js',
2204
+ // format: 'esm',
2205
+ // minify: true
2206
+ // },
2207
+ // {
2208
+ // entry: 'src/worker.ts',
2209
+ // outDir: 'dist/workers',
2210
+ // outFile: 'worker.js',
2211
+ // format: 'esm',
2212
+ // platform: 'browser'
2213
+ // }
2214
+ // ],
2215
+ preview: {
2216
+ port: 4173,
2217
+ // Single client preview
2218
+ root: 'dist',
2219
+ basePath: '/app',
2220
+ https: false,
2221
+ // API router (import from elit/server)
2222
+ api: router()
2223
+ .get('/api/data', (req, res) => {
2224
+ res.json({ message: 'Hello from preview API' });
2225
+ }),
2226
+ // Custom middleware
2227
+ middleware: [
2228
+ (req, res, next) => {
2229
+ console.log('Preview request:', req.url);
2230
+ next();
2231
+ }
2232
+ ],
2233
+ // SSR render function
2234
+ ssr: () => '<h1>Server-rendered content</h1>',
2235
+ proxy: [
2236
+ {
2237
+ context: '/api',
2238
+ target: 'http://localhost:8080'
2239
+ }
2240
+ ],
2241
+ worker: [
2242
+ {
2243
+ path: 'workers/cache-worker.js',
2244
+ type: 'module'
2245
+ }
2246
+ ]
2247
+ // Multi-client preview (alternative)
2248
+ // clients: [
2249
+ // {
2250
+ // root: './dist/app1',
2251
+ // basePath: '/app1',
2252
+ // proxy: [
2253
+ // {
2254
+ // context: '/api',
2255
+ // target: 'http://localhost:8080'
2256
+ // }
2257
+ // ],
2258
+ // worker: [
2259
+ // {
2260
+ // path: 'workers/app1-worker.js',
2261
+ // type: 'module'
2262
+ // }
2263
+ // ],
2264
+ // api: router()
2265
+ // .get('/api/health', (req, res) => {
2266
+ // res.json({ status: 'ok', app: 'app1' });
2267
+ // }),
2268
+ // middleware: [
2269
+ // (req, res, next) => {
2270
+ // console.log('App1 request:', req.url);
2271
+ // next();
2272
+ // }
2273
+ // ]
2274
+ // },
2275
+ // {
2276
+ // root: './dist/app2',
2277
+ // basePath: '/app2',
2278
+ // worker: [
2279
+ // {
2280
+ // path: 'workers/app2-worker.js',
2281
+ // type: 'module'
2282
+ // }
2283
+ // ],
2284
+ // api: router()
2285
+ // .get('/api/status', (req, res) => {
2286
+ // res.json({ status: 'running', app: 'app2' });
2287
+ // }),
2288
+ // middleware: [
2289
+ // (req, res, next) => {
2290
+ // console.log('App2 request:', req.url);
2291
+ // next();
2292
+ // }
2293
+ // ]
2294
+ // }
2295
+ // ]
2296
+ }
2297
+ }
2298
+ `);
2299
+ }
2300
+ function printVersion() {
2301
+ const pkg = require_package();
2302
+ console.log(`elit v${pkg.version}`);
2303
+ }
2304
+ main().catch((error) => {
2305
+ console.error("Fatal error:", error);
2306
+ process.exit(1);
2307
+ });