elit 1.0.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.
- package/LICENSE +21 -0
- package/README.md +552 -19
- package/dist/build.d.mts +11 -0
- package/dist/build.d.ts +11 -0
- package/dist/build.js +1 -0
- package/dist/build.mjs +1 -0
- package/dist/cli.js +2307 -0
- package/dist/client.d.mts +9 -0
- package/dist/client.d.ts +9 -0
- package/dist/client.js +1 -0
- package/dist/client.mjs +1 -0
- package/dist/dom.d.mts +80 -0
- package/dist/dom.d.ts +80 -0
- package/dist/dom.js +1 -0
- package/dist/dom.mjs +1 -0
- package/dist/el.d.mts +227 -0
- package/dist/el.d.ts +227 -0
- package/dist/el.js +1 -0
- package/dist/el.mjs +1 -0
- package/dist/hmr.d.mts +38 -0
- package/dist/hmr.d.ts +38 -0
- package/dist/hmr.js +1 -0
- package/dist/hmr.mjs +1 -0
- package/dist/index.d.mts +38 -490
- package/dist/index.d.ts +38 -490
- package/dist/index.js +1 -2266
- package/dist/index.mjs +1 -2039
- package/dist/router.d.mts +45 -0
- package/dist/router.d.ts +45 -0
- package/dist/router.js +1 -0
- package/dist/router.mjs +1 -0
- package/dist/server.d.mts +3 -0
- package/dist/server.d.ts +3 -0
- package/dist/server.js +1 -0
- package/dist/server.mjs +1 -0
- package/dist/state.d.mts +109 -0
- package/dist/state.d.ts +109 -0
- package/dist/state.js +1 -0
- package/dist/state.mjs +1 -0
- package/dist/style.d.mts +113 -0
- package/dist/style.d.ts +113 -0
- package/dist/style.js +1 -0
- package/dist/style.mjs +1 -0
- package/dist/types-DOAdFFJB.d.mts +330 -0
- package/dist/types-DOAdFFJB.d.ts +330 -0
- package/dist/types.d.mts +3 -0
- package/dist/types.d.ts +3 -0
- package/dist/types.js +1 -0
- package/dist/types.mjs +0 -0
- package/package.json +77 -7
- package/dist/index.global.js +0 -2064
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
|
+
"&": "&",
|
|
656
|
+
"<": "<",
|
|
657
|
+
">": ">",
|
|
658
|
+
'"': """,
|
|
659
|
+
"'": "'"
|
|
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
|
+
});
|