elegance-js 2.0.18 → 2.1.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.
@@ -1,21 +1,65 @@
1
- // src/dynamic_page.ts
1
+ // src/server/server.ts
2
+ import { createServer as createHttpServer } from "http";
3
+ import { promises as fs2 } from "fs";
4
+ import { join, normalize, extname, dirname } from "path";
5
+ import { pathToFileURL } from "url";
6
+
7
+ // src/log.ts
8
+ var quiet = false;
9
+ function getTimestamp() {
10
+ const now = /* @__PURE__ */ new Date();
11
+ return now.toLocaleString(void 0, {
12
+ year: "2-digit",
13
+ month: "2-digit",
14
+ day: "2-digit",
15
+ hour: "2-digit",
16
+ minute: "2-digit",
17
+ second: "2-digit"
18
+ });
19
+ }
20
+ function color(text, code) {
21
+ return `\x1B[${code}m${text}\x1B[0m`;
22
+ }
23
+ function logInfo(...args) {
24
+ if (quiet) return;
25
+ console.info(`Elegance.JS: ${getTimestamp()} ${color("[INFO]:", 34)}`, ...args);
26
+ }
27
+ function logWarn(...args) {
28
+ if (quiet) return;
29
+ console.warn(`Elegance.JS: ${getTimestamp()} ${color("[WARN]:", 33)}`, ...args);
30
+ }
31
+ function logError(...args) {
32
+ console.error(`Elegance.JS: ${getTimestamp()} ${color("[ERROR]:", 31)}`, ...args);
33
+ }
34
+ var log = {
35
+ info: logInfo,
36
+ warn: logWarn,
37
+ error: logError
38
+ };
39
+
40
+ // src/server/server.ts
41
+ import { gzip, deflate } from "zlib";
42
+ import { promisify } from "util";
43
+
44
+ // src/page_compiler.ts
2
45
  import fs from "fs";
3
46
  import path from "path";
47
+ import { registerLoader, setArcTsConfig } from "ts-arc";
4
48
  import esbuild from "esbuild";
5
49
  import { fileURLToPath } from "url";
6
50
 
7
51
  // src/shared/serverElements.ts
8
52
  var createBuildableElement = (tag) => {
9
- return (options, ...children) => ({
53
+ return (options2, ...children) => ({
10
54
  tag,
11
- options: options || {},
55
+ options: options2 || {},
12
56
  children
13
57
  });
14
58
  };
15
59
  var createChildrenlessBuildableElement = (tag) => {
16
- return (options) => ({
60
+ return (options2) => ({
17
61
  tag,
18
- options: options || {},
62
+ options: options2 || {},
19
63
  children: null
20
64
  });
21
65
  };
@@ -215,7 +259,7 @@ var generateHTMLTemplate = async ({
215
259
  serverData = null,
216
260
  addPageScriptTag = true,
217
261
  name,
218
- requiredClientModules = [],
262
+ requiredClientModules = {},
219
263
  environment
220
264
  }) => {
221
265
  let StartTemplate = `<meta name="viewport" content="width=device-width, initial-scale=1.0">`;
@@ -223,11 +267,12 @@ var generateHTMLTemplate = async ({
223
267
  StartTemplate += `<meta http-equiv="Content-Security-Policy" content="upgrade-insecure-requests">`;
224
268
  }
225
269
  StartTemplate += '<meta charset="UTF-8">';
226
- for (const module of requiredClientModules) {
227
- StartTemplate += `<script data-module="true" src="/shipped/${module}.js" defer="true"></script>`;
270
+ for (const [globalName] of Object.entries(requiredClientModules)) {
271
+ StartTemplate += `<script data-module="true" src="/shipped/${globalName}.js" defer="true"></script>`;
228
272
  }
229
273
  if (addPageScriptTag === true) {
230
- StartTemplate += `<script data-page="true" type="module" src="${pageURL === "" ? "" : "/"}${pageURL}/${name}_data.js" defer="true"></script>`;
274
+ const sanitized = pageURL === "" ? "/" : pageURL;
275
+ StartTemplate += `<script data-page="true" type="module" data-pathname="${sanitized}" src="${sanitized}${name}_data.js" defer="true"></script>`;
231
276
  }
232
277
  StartTemplate += `<script type="module" src="/client.js" defer="true"></script>`;
233
278
  let builtHead;
@@ -246,13 +291,15 @@ var generateHTMLTemplate = async ({
246
291
  };
247
292
  };
248
293
 
249
- // src/server/createState.ts
294
+ // src/server/loadHook.ts
295
+ var resetLoadHooks = () => globalThis.__SERVER_CURRENT_LOADHOOKS__ = [];
296
+ var getLoadHooks = () => globalThis.__SERVER_CURRENT_LOADHOOKS__;
297
+
298
+ // src/server/state.ts
250
299
  if (!globalThis.__SERVER_CURRENT_STATE_ID__) {
251
300
  globalThis.__SERVER_CURRENT_STATE_ID__ = 1;
252
301
  }
253
- var initializeState = () => {
254
- globalThis.__SERVER_CURRENT_STATE__ = [];
255
- };
302
+ var initializeState = () => globalThis.__SERVER_CURRENT_STATE__ = [];
256
303
  var getState = () => {
257
304
  return globalThis.__SERVER_CURRENT_STATE__;
258
305
  };
@@ -261,25 +308,90 @@ var getObjectAttributes = () => {
261
308
  return globalThis.__SERVER_CURRENT_OBJECT_ATTRIBUTES__;
262
309
  };
263
310
 
264
- // src/server/loadHook.ts
265
- var resetLoadHooks = () => globalThis.__SERVER_CURRENT_LOADHOOKS__ = [];
266
- var getLoadHooks = () => globalThis.__SERVER_CURRENT_LOADHOOKS__;
311
+ // src/server/layout.ts
312
+ var resetLayouts = () => globalThis.__SERVER_CURRENT_LAYOUTS__ = /* @__PURE__ */ new Map();
313
+ if (!globalThis.__SERVER_CURRENT_LAYOUT_ID__) globalThis.__SERVER_CURRENT_LAYOUT_ID__ = 1;
267
314
 
268
- // src/dynamic_page.ts
315
+ // src/page_compiler.ts
316
+ var __filename = fileURLToPath(import.meta.url);
317
+ var __dirname = path.dirname(__filename);
318
+ setArcTsConfig(__dirname);
319
+ registerLoader();
269
320
  var packageDir = process.env.PACKAGE_PATH;
270
321
  if (packageDir === void 0) {
271
- const __filename = fileURLToPath(import.meta.url);
272
- const __dirname = path.dirname(__filename);
273
322
  packageDir = path.resolve(__dirname, "..");
274
323
  }
324
+ var clientPath = path.resolve(packageDir, "./dist/client/client.mjs");
325
+ var watcherPath = path.resolve(packageDir, "./dist/client/watcher.mjs");
326
+ var shippedModules = /* @__PURE__ */ new Map();
327
+ var modulesToShip = [];
328
+ var yellow = (text) => {
329
+ return `\x1B[38;2;238;184;68m${text}`;
330
+ };
331
+ var black = (text) => {
332
+ return `\x1B[38;2;0;0;0m${text}`;
333
+ };
334
+ var bgYellow = (text) => {
335
+ return `\x1B[48;2;238;184;68m${text}`;
336
+ };
337
+ var bold = (text) => {
338
+ return `\x1B[1m${text}`;
339
+ };
340
+ var underline = (text) => {
341
+ return `\x1B[4m${text}`;
342
+ };
343
+ var white = (text) => {
344
+ return `\x1B[38;2;255;247;229m${text}`;
345
+ };
346
+ var log2 = (...text) => {
347
+ if (options.quiet) return;
348
+ return console.log(text.map((text2) => `${text2}\x1B[0m`).join(""));
349
+ };
350
+ var options = JSON.parse(process.env.OPTIONS);
351
+ var DIST_DIR = process.env.DIST_DIR;
352
+ var PAGE_MAP = /* @__PURE__ */ new Map();
353
+ var LAYOUT_MAP = /* @__PURE__ */ new Map();
354
+ var getAllSubdirectories = (dir, baseDir = dir) => {
355
+ let directories = [];
356
+ const items = fs.readdirSync(dir, { withFileTypes: true });
357
+ for (const item of items) {
358
+ if (item.isDirectory()) {
359
+ const fullPath = path.join(dir, item.name);
360
+ const relativePath = path.relative(baseDir, fullPath);
361
+ directories.push(relativePath);
362
+ directories = directories.concat(getAllSubdirectories(fullPath, baseDir));
363
+ }
364
+ }
365
+ return directories;
366
+ };
367
+ var buildClient = async (DIST_DIR2) => {
368
+ let clientString = "window.__name = (func) => func; ";
369
+ clientString += fs.readFileSync(clientPath, "utf-8");
370
+ if (options.hotReload !== void 0) {
371
+ clientString += `const watchServerPort = ${options.hotReload.port}`;
372
+ clientString += fs.readFileSync(watcherPath, "utf-8");
373
+ }
374
+ const transformedClient = await esbuild.transform(clientString, {
375
+ minify: options.environment === "production",
376
+ drop: options.environment === "production" ? ["console", "debugger"] : void 0,
377
+ keepNames: false,
378
+ format: "iife",
379
+ platform: "node",
380
+ loader: "ts"
381
+ });
382
+ fs.writeFileSync(
383
+ path.join(DIST_DIR2, "/client.js"),
384
+ transformedClient.code
385
+ );
386
+ };
275
387
  var elementKey = 0;
276
388
  var processOptionAsObjectAttribute = (element, optionName, optionValue, objectAttributes) => {
277
389
  const lcOptionName = optionName.toLowerCase();
278
- const options = element.options;
279
- let key = options.key;
390
+ const options2 = element.options;
391
+ let key = options2.key;
280
392
  if (key == void 0) {
281
393
  key = elementKey += 1;
282
- options.key = key;
394
+ options2.key = key;
283
395
  }
284
396
  if (!optionValue.type) {
285
397
  throw `ObjectAttributeType is missing from object attribute. ${element.tag}: ${optionName}/${optionValue}`;
@@ -289,15 +401,15 @@ var processOptionAsObjectAttribute = (element, optionName, optionValue, objectAt
289
401
  case 1 /* STATE */:
290
402
  const SOA = optionValue;
291
403
  if (typeof SOA.value === "function") {
292
- delete options[optionName];
404
+ delete options2[optionName];
293
405
  break;
294
406
  }
295
407
  if (lcOptionName === "innertext" || lcOptionName === "innerhtml") {
296
408
  element.children = [SOA.value];
297
- delete options[optionName];
409
+ delete options2[optionName];
298
410
  } else {
299
- delete options[optionName];
300
- options[lcOptionName] = SOA.value;
411
+ delete options2[optionName];
412
+ options2[lcOptionName] = SOA.value;
301
413
  }
302
414
  break;
303
415
  case 2 /* OBSERVER */:
@@ -305,121 +417,233 @@ var processOptionAsObjectAttribute = (element, optionName, optionValue, objectAt
305
417
  const firstValue = OOA.update(...OOA.initialValues);
306
418
  if (lcOptionName === "innertext" || lcOptionName === "innerhtml") {
307
419
  element.children = [firstValue];
308
- delete options[optionName];
420
+ delete options2[optionName];
309
421
  } else {
310
- delete options[optionName];
311
- options[lcOptionName] = firstValue;
422
+ delete options2[optionName];
423
+ options2[lcOptionName] = firstValue;
312
424
  }
313
425
  optionFinal = optionName;
314
426
  break;
315
427
  case 4 /* REFERENCE */:
316
- options["ref"] = optionValue.value;
428
+ options2["ref"] = optionValue.value;
317
429
  break;
318
430
  }
319
431
  objectAttributes.push({ ...optionValue, key, attribute: optionFinal });
320
432
  };
321
- var processPageElements = async (element, objectAttributes, parent) => {
322
- if (typeof element === "boolean" || typeof element === "number" || Array.isArray(element)) return element;
323
- if (typeof element === "string") {
324
- return element;
325
- }
326
- const processElementOptionsAsChildAndReturn = async () => {
327
- const children = element.children;
328
- element.children = [
329
- element.options,
330
- ...children
331
- ];
332
- element.options = {};
333
- for (let i = 0; i < children.length + 1; i++) {
334
- const child = element.children[i];
335
- const processedChild = await processPageElements(child, objectAttributes, element);
336
- element.children[i] = processedChild;
433
+ function buildTrace(stack, indent = 4) {
434
+ try {
435
+ if (!stack || stack.length === 0) return "[]";
436
+ let traceObj = stack[stack.length - 1] && typeof stack[stack.length - 1] === "object" ? JSON.parse(JSON.stringify(stack[stack.length - 1])) : { value: stack[stack.length - 1] };
437
+ traceObj._error = "This is the element where the error occurred";
438
+ for (let i = stack.length - 2; i >= 0; i--) {
439
+ const parent = stack[i];
440
+ const child = stack[i + 1];
441
+ if (!parent || typeof parent !== "object") {
442
+ traceObj = { value: parent, _errorChild: traceObj };
443
+ continue;
444
+ }
445
+ let parentClone;
446
+ try {
447
+ parentClone = JSON.parse(JSON.stringify(parent));
448
+ } catch {
449
+ parentClone = { value: parent };
450
+ }
451
+ let index = -1;
452
+ if (Array.isArray(parentClone.children)) {
453
+ index = parentClone.children.findIndex((c) => c === child);
454
+ }
455
+ if (index !== -1 && parentClone.children) {
456
+ parentClone.children = parentClone.children.slice(0, index + 1);
457
+ parentClone.children[index] = traceObj;
458
+ } else {
459
+ parentClone._errorChild = traceObj;
460
+ }
461
+ traceObj = parentClone;
337
462
  }
338
- return {
339
- ...element,
340
- options: {}
341
- };
342
- };
343
- if (typeof element.options !== "object") {
344
- return await processElementOptionsAsChildAndReturn();
463
+ return JSON.stringify(traceObj, null, indent).replace(/^/gm, " ".repeat(indent));
464
+ } catch {
465
+ return "Could not build stack-trace.";
345
466
  }
346
- const {
347
- tag: elementTag,
348
- options: elementOptions,
349
- children: elementChildren
350
- } = element.options;
351
- if (elementTag && elementOptions && elementChildren) {
352
- return await processElementOptionsAsChildAndReturn();
353
- }
354
- const options = element.options;
355
- for (const [optionName, optionValue] of Object.entries(options)) {
356
- const lcOptionName = optionName.toLowerCase();
357
- if (typeof optionValue !== "object") {
358
- if (lcOptionName === "innertext") {
359
- delete options[optionName];
360
- if (element.children === null) {
361
- throw `Cannot use innerText or innerHTML on childrenless elements.`;
467
+ }
468
+ var processPageElements = (element, objectAttributes, recursionLevel, stack = []) => {
469
+ stack.push(element);
470
+ try {
471
+ if (typeof element === "boolean" || typeof element === "number" || Array.isArray(element)) {
472
+ stack.pop();
473
+ return element;
474
+ }
475
+ if (typeof element === "string") {
476
+ stack.pop();
477
+ return element;
478
+ }
479
+ const processElementOptionsAsChildAndReturn = () => {
480
+ try {
481
+ const children = element.children;
482
+ element.children = [
483
+ element.options,
484
+ ...children
485
+ ];
486
+ element.options = {};
487
+ for (let i = 0; i < children.length + 1; i++) {
488
+ const child = element.children[i];
489
+ const processedChild = processPageElements(child, objectAttributes, recursionLevel + 1, stack);
490
+ element.children[i] = processedChild;
362
491
  }
363
- element.children = [optionValue, ...element.children];
364
- continue;
365
- } else if (lcOptionName === "innerhtml") {
366
- if (element.children === null) {
367
- throw `Cannot use innerText or innerHTML on childrenless elements.`;
492
+ return {
493
+ ...element,
494
+ options: {}
495
+ };
496
+ } catch (e) {
497
+ const errorString = `Could not process element options as a child. ${e}.`;
498
+ throw new Error(errorString);
499
+ }
500
+ };
501
+ if (typeof element.options !== "object") {
502
+ const result = processElementOptionsAsChildAndReturn();
503
+ stack.pop();
504
+ return result;
505
+ }
506
+ const {
507
+ tag: elementTag,
508
+ options: elementOptions,
509
+ children: elementChildren
510
+ } = element.options;
511
+ if (elementTag && elementOptions && elementChildren) {
512
+ const result = processElementOptionsAsChildAndReturn();
513
+ stack.pop();
514
+ return result;
515
+ }
516
+ const options2 = element.options;
517
+ for (const [optionName, optionValue] of Object.entries(options2)) {
518
+ const lcOptionName = optionName.toLowerCase();
519
+ if (typeof optionValue !== "object") {
520
+ if (lcOptionName === "innertext") {
521
+ delete options2[optionName];
522
+ if (element.children === null) {
523
+ throw `Cannot use innerText or innerHTML on childrenless elements.`;
524
+ }
525
+ element.children = [optionValue, ...element.children];
526
+ continue;
527
+ } else if (lcOptionName === "innerhtml") {
528
+ if (element.children === null) {
529
+ throw `Cannot use innerText or innerHTML on childrenless elements.`;
530
+ }
531
+ delete options2[optionName];
532
+ element.children = [optionValue];
533
+ continue;
368
534
  }
369
- delete options[optionName];
370
- element.children = [optionValue];
371
535
  continue;
372
536
  }
373
- continue;
537
+ ;
538
+ processOptionAsObjectAttribute(element, optionName, optionValue, objectAttributes);
374
539
  }
375
- ;
376
- processOptionAsObjectAttribute(element, optionName, optionValue, objectAttributes);
377
- }
378
- if (element.children) {
379
- for (let i = 0; i < element.children.length; i++) {
380
- const child = element.children[i];
381
- const processedChild = await processPageElements(child, objectAttributes, element);
382
- element.children[i] = processedChild;
540
+ if (element.children) {
541
+ for (let i = 0; i < element.children.length; i++) {
542
+ const child = element.children[i];
543
+ const processedChild = processPageElements(child, objectAttributes, recursionLevel + 1, stack);
544
+ element.children[i] = processedChild;
545
+ }
546
+ }
547
+ stack.pop();
548
+ return element;
549
+ } catch (e) {
550
+ const trace = buildTrace(stack);
551
+ if (recursionLevel === 0) {
552
+ throw new Error(`${e}
553
+
554
+ Trace:
555
+ ${trace}`);
556
+ } else {
557
+ throw e;
383
558
  }
384
559
  }
385
- return element;
386
560
  };
387
- var generateSuitablePageElements = async (pageLocation, pageElements, metadata, DIST_DIR, pageName, requiredClientModules) => {
561
+ var pageToHTML = async (pageLocation, pageElements, metadata, DIST_DIR2, pageName, doWrite = true, requiredClientModules = {}, layout, pathname = "") => {
388
562
  if (typeof pageElements === "string" || typeof pageElements === "boolean" || typeof pageElements === "number" || Array.isArray(pageElements)) {
389
- return [];
563
+ throw new Error(`The root element of a page / layout must be a built element, not just a Child. Received: ${typeof pageElements}.`);
390
564
  }
391
565
  const objectAttributes = [];
392
- const processedPageElements = await processPageElements(pageElements, objectAttributes, []);
393
- elementKey = 0;
566
+ const stack = [];
567
+ const processedPageElements = processPageElements(pageElements, objectAttributes, 0, stack);
394
568
  const renderedPage = await serverSideRenderPage(
395
569
  processedPageElements,
396
570
  pageLocation
397
571
  );
398
- const template = await generateHTMLTemplate({
399
- pageURL: path.relative(DIST_DIR, pageLocation),
572
+ const { internals, builtMetadata } = await generateHTMLTemplate({
573
+ pageURL: pathname,
400
574
  head: metadata,
401
- addPageScriptTag: true,
575
+ addPageScriptTag: doWrite,
402
576
  name: pageName,
403
577
  requiredClientModules,
404
- environment: "production"
578
+ environment: options.environment
405
579
  });
406
- const resultHTML = `<!DOCTYPE html>${template}${renderedPage.bodyHTML}`;
407
- return {
408
- objectAttributes,
409
- resultHTML
410
- };
580
+ let extraBodyHTML = "";
581
+ if (doWrite === false) {
582
+ const state = getState();
583
+ const pageLoadHooks = getLoadHooks();
584
+ const userObjectAttributes = getObjectAttributes();
585
+ const {
586
+ result
587
+ } = await generateClientPageData(
588
+ pathname,
589
+ state || {},
590
+ [...objectAttributes, ...userObjectAttributes],
591
+ pageLoadHooks || [],
592
+ DIST_DIR2,
593
+ "page",
594
+ "pd",
595
+ false
596
+ );
597
+ const sanitized = pathname === "" ? "/" : pathname;
598
+ extraBodyHTML = `<script data-hook="true" data-pathname="${sanitized}" type="text/plain">${result}</script>`;
599
+ extraBodyHTML += `<script>
600
+ const text = document.querySelector('[data-hook="true"][data-pathname="${sanitized}"][type="text/plain"').textContent;
601
+ const blob = new Blob([text], { type: 'text/javascript' });
602
+ const url = URL.createObjectURL(blob);
603
+
604
+ const script = document.createElement("script");
605
+ script.src = url;
606
+ script.type = "module";
607
+ script.setAttribute("data-page", "true");
608
+ script.setAttribute("data-pathname", "${sanitized}");
609
+
610
+ document.head.appendChild(script);
611
+
612
+ document.currentScript.remove();
613
+ </script>`;
614
+ extraBodyHTML = extraBodyHTML.replace(/\s+/g, " ").replace(/\s*([{}();,:])\s*/g, "$1").trim();
615
+ }
616
+ const headHTML = `<!DOCTYPE html>${layout.metadata.startHTML}${layout.scriptTag}${internals}${builtMetadata}${layout.metadata.endHTML}`;
617
+ const bodyHTML = `${layout.pageContent.startHTML}${renderedPage.bodyHTML}${extraBodyHTML}${layout.pageContent.endHTML}`;
618
+ const resultHTML = `${headHTML}${bodyHTML}`;
619
+ const htmlLocation = path.join(pageLocation, (pageName === "page" ? "index" : pageName) + ".html");
620
+ if (doWrite) {
621
+ const dirname2 = path.dirname(htmlLocation);
622
+ if (fs.existsSync(dirname2) === false) {
623
+ fs.mkdirSync(dirname2, { recursive: true });
624
+ }
625
+ fs.writeFileSync(
626
+ htmlLocation,
627
+ resultHTML,
628
+ {
629
+ encoding: "utf-8",
630
+ flag: "w"
631
+ }
632
+ );
633
+ return objectAttributes;
634
+ }
635
+ return resultHTML;
411
636
  };
412
- var generateClientPageData = async (pageLocation, state, objectAttributes, pageLoadHooks, DIST_DIR, pageName) => {
413
- const pageDiff = path.relative(DIST_DIR, pageLocation);
414
- let clientPageJSText = `${globalThis.__SERVER_PAGE_DATA_BANNER__}
415
- /*ELEGANCE_JS*/
416
- let url="${pageDiff === "" ? "/" : `/${pageDiff}`}";`;
637
+ var generateClientPageData = async (pageLocation, state, objectAttributes, pageLoadHooks, DIST_DIR2, pageName, globalVariableName = "pd", write = true) => {
638
+ let clientPageJSText = "";
639
+ {
640
+ clientPageJSText += `${globalThis.__SERVER_PAGE_DATA_BANNER__}`;
641
+ }
417
642
  {
418
643
  clientPageJSText += `export const data = {`;
419
644
  if (state) {
420
- const nonBoundState = state.filter((subj) => subj.bind === void 0);
421
645
  clientPageJSText += `state:[`;
422
- for (const subject of nonBoundState) {
646
+ for (const subject of state) {
423
647
  if (typeof subject.value === "string") {
424
648
  const stringified = JSON.stringify(subject.value);
425
649
  clientPageJSText += `{id:${subject.id},value:${stringified}},`;
@@ -430,34 +654,6 @@ let url="${pageDiff === "" ? "/" : `/${pageDiff}`}";`;
430
654
  }
431
655
  }
432
656
  clientPageJSText += `],`;
433
- const formattedBoundState = {};
434
- const stateBinds = state.map((subj) => subj.bind).filter((bind) => bind !== void 0);
435
- for (const bind of stateBinds) {
436
- formattedBoundState[bind] = [];
437
- }
438
- ;
439
- const boundState = state.filter((subj) => subj.bind !== void 0);
440
- for (const subject of boundState) {
441
- const bindingState = formattedBoundState[subject.bind];
442
- delete subject.bind;
443
- bindingState.push(subject);
444
- }
445
- const bindSubjectPairing = Object.entries(formattedBoundState);
446
- if (bindSubjectPairing.length > 0) {
447
- clientPageJSText += "binds:{";
448
- for (const [bind, subjects] of bindSubjectPairing) {
449
- clientPageJSText += `${bind}:[`;
450
- for (const subject of subjects) {
451
- if (typeof subject.value === "string") {
452
- clientPageJSText += `{id:${subject.id},value:${JSON.stringify(subject.value)}},`;
453
- } else {
454
- clientPageJSText += `{id:${subject.id},value:${JSON.stringify(subject.value)}},`;
455
- }
456
- }
457
- clientPageJSText += "]";
458
- }
459
- clientPageJSText += "},";
460
- }
461
657
  }
462
658
  const stateObjectAttributes = objectAttributes.filter((oa) => oa.type === 1 /* STATE */);
463
659
  if (stateObjectAttributes.length > 0) {
@@ -475,9 +671,7 @@ let url="${pageDiff === "" ? "/" : `/${pageDiff}`}";`;
475
671
  observerObjectAttributeString += `{key:${ooa.key},attribute:"${ooa.attribute}",update:${ooa.update.toString()},`;
476
672
  observerObjectAttributeString += `refs:[`;
477
673
  for (const ref of ooa.refs) {
478
- observerObjectAttributeString += `{id:${ref.id}`;
479
- if (ref.bind !== void 0) observerObjectAttributeString += `,bind:${ref.bind}`;
480
- observerObjectAttributeString += "},";
674
+ observerObjectAttributeString += `{id:${ref.id}},`;
481
675
  }
482
676
  observerObjectAttributeString += "]},";
483
677
  }
@@ -486,148 +680,493 @@ let url="${pageDiff === "" ? "/" : `/${pageDiff}`}";`;
486
680
  }
487
681
  if (pageLoadHooks.length > 0) {
488
682
  clientPageJSText += "lh:[";
489
- for (const loadHook of pageLoadHooks) {
490
- const key = loadHook.bind;
491
- clientPageJSText += `{fn:${loadHook.fn},bind:"${key || ""}"},`;
683
+ for (const loadHook2 of pageLoadHooks) {
684
+ clientPageJSText += `{fn:${loadHook2.fn}},`;
492
685
  }
493
686
  clientPageJSText += "],";
494
687
  }
495
688
  clientPageJSText += `};`;
496
689
  }
497
- clientPageJSText += "if(!globalThis.pd) { globalThis.pd = {}; globalThis.pd[url] = data}";
498
- const pageDataPath = path.join(pageLocation, `${pageName}_data.js`);
690
+ const pageDataPath = path.join(DIST_DIR2, pageLocation, `${pageName}_data.js`);
499
691
  let sendHardReloadInstruction = false;
500
- const transformedResult = await esbuild.transform(clientPageJSText, { minify: true }).catch((error) => {
692
+ const transformedResult = await esbuild.transform(clientPageJSText, { minify: options.environment === "production" }).catch((error) => {
501
693
  console.error("Failed to transform client page js!", error);
502
694
  });
503
695
  if (!transformedResult) return { sendHardReloadInstruction };
504
- fs.writeFileSync(pageDataPath, transformedResult.code, "utf-8");
505
- return { sendHardReloadInstruction };
696
+ if (fs.existsSync(pageDataPath)) {
697
+ const content = fs.readFileSync(pageDataPath).toString();
698
+ if (content !== transformedResult.code) {
699
+ sendHardReloadInstruction = true;
700
+ }
701
+ }
702
+ if (write) fs.writeFileSync(pageDataPath, transformedResult.code, "utf-8");
703
+ return { sendHardReloadInstruction, result: transformedResult.code };
506
704
  };
507
- var buildDynamicPage = async (filePath, DIST_DIR, req, res) => {
508
- let pageElements;
509
- let metadata;
705
+ var generateLayout = async (DIST_DIR2, filePath, directory, childIndicator, generateDynamic = false) => {
510
706
  initializeState();
511
707
  initializeObjectAttributes();
512
708
  resetLoadHooks();
513
709
  globalThis.__SERVER_PAGE_DATA_BANNER__ = "";
514
- globalThis.__SERVER_CURRENT_STATE_ID__ = 1;
710
+ let layoutElements;
711
+ let metadataElements;
515
712
  let modules = [];
713
+ let isDynamicLayout = false;
516
714
  try {
517
715
  const {
518
- construct
716
+ layout,
717
+ metadata,
718
+ isDynamic,
719
+ shippedModules: shippedModules2
519
720
  } = await import("file://" + filePath);
520
- const {
521
- page,
522
- metadata: pageMetadata,
523
- isDynamicPage,
524
- requestHook,
525
- requiredClientModules
526
- } = construct();
527
- if (requiredClientModules !== void 0) {
528
- modules = requiredClientModules;
529
- }
530
- if (typeof requestHook === "function") {
531
- if (requestHook.constructor.name === "AsyncFunction") {
532
- const doProcessRequest = await requestHook(req, res);
533
- if (doProcessRequest !== void 0 == doProcessRequest === false) {
534
- return false;
535
- }
721
+ if (shippedModules2 !== void 0) {
722
+ modules = shippedModules2;
723
+ }
724
+ layoutElements = layout;
725
+ metadataElements = metadata;
726
+ if (isDynamic === true) {
727
+ isDynamicLayout = isDynamic;
728
+ }
729
+ } catch (e) {
730
+ throw new Error(`Error in Page: ${directory === "" ? "/" : directory}layout.ts - ${e}`);
731
+ }
732
+ LAYOUT_MAP.set(directory === "" ? "/" : directory, {
733
+ isDynamic: isDynamicLayout,
734
+ filePath
735
+ });
736
+ if (isDynamicLayout === true && generateDynamic === false) return false;
737
+ {
738
+ if (!layoutElements) {
739
+ throw new Error(`WARNING: ${filePath} should export a const layout, which is of type Layout: (child: Child) => AnyBuiltElement.`);
740
+ }
741
+ if (typeof layoutElements === "function") {
742
+ if (layoutElements.constructor.name === "AsyncFunction") {
743
+ layoutElements = await layoutElements(childIndicator);
744
+ } else {
745
+ layoutElements = layoutElements(childIndicator);
746
+ }
747
+ }
748
+ }
749
+ {
750
+ if (!metadataElements) {
751
+ throw new Error(`WARNING: ${filePath} should export a const metadata, which is of type LayoutMetadata: (child: Child) => AnyBuiltElement.`);
752
+ }
753
+ if (typeof metadataElements === "function") {
754
+ if (metadataElements.constructor.name === "AsyncFunction") {
755
+ metadataElements = await metadataElements(childIndicator);
536
756
  } else {
537
- const doProcessRequest = requestHook(req, res);
538
- if (doProcessRequest !== void 0 == doProcessRequest === false) {
539
- return false;
757
+ metadataElements = metadataElements(childIndicator);
758
+ }
759
+ }
760
+ }
761
+ const state = getState();
762
+ const pageLoadHooks = getLoadHooks();
763
+ const objectAttributes = getObjectAttributes();
764
+ if (typeof layoutElements === "string" || typeof layoutElements === "boolean" || typeof layoutElements === "number" || Array.isArray(layoutElements)) {
765
+ throw new Error(`The root element of a page / layout must be a built element, not just a Child. Received: ${typeof layoutElements}.`);
766
+ }
767
+ const foundObjectAttributes = [];
768
+ const stack = [];
769
+ const processedPageElements = processPageElements(layoutElements, foundObjectAttributes, 0, stack);
770
+ const renderedPage = await serverSideRenderPage(
771
+ processedPageElements,
772
+ directory
773
+ );
774
+ const metadataHTML = metadataElements ? renderRecursively(metadataElements) : "";
775
+ await generateClientPageData(
776
+ directory,
777
+ state || {},
778
+ [...objectAttributes, ...foundObjectAttributes],
779
+ pageLoadHooks || [],
780
+ DIST_DIR2,
781
+ "layout",
782
+ "ld"
783
+ );
784
+ return { pageContentHTML: renderedPage.bodyHTML, metadataHTML };
785
+ };
786
+ var builtLayouts = /* @__PURE__ */ new Map();
787
+ var buildLayouts = async () => {
788
+ const pagesDirectory = path.resolve(options.pagesDirectory);
789
+ const subdirectories = [...getAllSubdirectories(pagesDirectory), ""];
790
+ let shouldClientHardReload = false;
791
+ for (const directory of subdirectories) {
792
+ const abs = path.resolve(path.join(pagesDirectory, directory));
793
+ const files = fs.readdirSync(abs, { withFileTypes: true }).filter((f) => f.name.endsWith(".ts"));
794
+ for (const file of files) {
795
+ const filePath = path.join(file.parentPath, file.name);
796
+ const name = file.name.slice(0, file.name.length - 3);
797
+ const isLayout = name === "layout";
798
+ if (isLayout == false) {
799
+ continue;
800
+ }
801
+ try {
802
+ const builtLayout = await buildLayout(filePath, directory);
803
+ if (!builtLayout) return { shouldClientHardReload: false };
804
+ builtLayouts.set(filePath, builtLayout);
805
+ } catch (e) {
806
+ console.error(e);
807
+ continue;
808
+ }
809
+ }
810
+ }
811
+ return { shouldClientHardReload };
812
+ };
813
+ var buildLayout = async (filePath, directory, generateDynamic = false) => {
814
+ const id = globalThis.__SERVER_CURRENT_STATE_ID__ += 1;
815
+ const childIndicator = `<template layout-id="${id}"></template>`;
816
+ const result = await generateLayout(
817
+ DIST_DIR,
818
+ filePath,
819
+ directory,
820
+ childIndicator,
821
+ generateDynamic
822
+ );
823
+ if (result === false) return false;
824
+ const { pageContentHTML, metadataHTML } = result;
825
+ const splitAround = (str, sub) => {
826
+ const i = str.indexOf(sub);
827
+ if (i === -1) throw new Error("substring does not exist in parent string");
828
+ return {
829
+ startHTML: str.substring(0, i),
830
+ endHTML: str.substring(i + sub.length)
831
+ };
832
+ };
833
+ const splitAt = (str, sub) => {
834
+ const i = str.indexOf(sub) + sub.length;
835
+ if (i === -1) throw new Error("substring does not exist in parent string");
836
+ return {
837
+ startHTML: str.substring(0, i),
838
+ endHTML: str.substring(i)
839
+ };
840
+ };
841
+ const pathname = directory === "" ? "/" : directory;
842
+ return {
843
+ pageContent: splitAt(pageContentHTML, childIndicator),
844
+ metadata: splitAround(metadataHTML, childIndicator),
845
+ scriptTag: `<script data-layout="true" type="module" src="${pathname}layout_data.js" data-pathname="${pathname}" defer="true"></script>`
846
+ };
847
+ };
848
+ var fetchPageLayoutHTML = async (dirname2) => {
849
+ const relative2 = path.relative(options.pagesDirectory, dirname2);
850
+ let split = relative2.split(path.sep).filter(Boolean);
851
+ split.push("/");
852
+ split.reverse();
853
+ let layouts = [];
854
+ for (const dir of split) {
855
+ if (LAYOUT_MAP.has(dir)) {
856
+ const filePath = path.join(path.resolve(options.pagesDirectory), dir, "layout.ts");
857
+ const layout = LAYOUT_MAP.get(dir);
858
+ if (layout.isDynamic) {
859
+ const builtLayout = await buildLayout(layout.filePath, dir, true);
860
+ if (!builtLayout) continue;
861
+ layouts.push(builtLayout);
862
+ } else {
863
+ layouts.push(builtLayouts.get(filePath));
864
+ }
865
+ }
866
+ }
867
+ const pageContent = {
868
+ startHTML: "",
869
+ endHTML: ""
870
+ };
871
+ const metadata = {
872
+ startHTML: "",
873
+ endHTML: ""
874
+ };
875
+ let scriptTags = "";
876
+ for (const layout of layouts) {
877
+ pageContent.startHTML += layout.pageContent.startHTML;
878
+ metadata.startHTML += layout.metadata.startHTML;
879
+ scriptTags += layout.scriptTag;
880
+ pageContent.endHTML += layout.pageContent.endHTML;
881
+ metadata.endHTML += layout.metadata.endHTML;
882
+ }
883
+ return { pageContent, metadata, scriptTag: scriptTags };
884
+ };
885
+ var buildPages = async (DIST_DIR2) => {
886
+ resetLayouts();
887
+ const pagesDirectory = path.resolve(options.pagesDirectory);
888
+ const subdirectories = [...getAllSubdirectories(pagesDirectory), ""];
889
+ let shouldClientHardReload = false;
890
+ for (const directory of subdirectories) {
891
+ const abs = path.resolve(path.join(pagesDirectory, directory));
892
+ const files = fs.readdirSync(abs, { withFileTypes: true }).filter((f) => f.name.endsWith(".ts"));
893
+ for (const file of files) {
894
+ const filePath = path.join(file.parentPath, file.name);
895
+ const name = file.name.slice(0, file.name.length - 3);
896
+ const isPage = name === "page";
897
+ if (isPage == false) {
898
+ continue;
899
+ }
900
+ try {
901
+ const hardReloadForPage = await buildPage(DIST_DIR2, directory, filePath, name);
902
+ if (hardReloadForPage) {
903
+ shouldClientHardReload = true;
540
904
  }
905
+ } catch (e) {
906
+ console.error(e);
907
+ continue;
541
908
  }
542
909
  }
910
+ }
911
+ return {
912
+ shouldClientHardReload
913
+ };
914
+ };
915
+ var buildPage = async (DIST_DIR2, directory, filePath, name) => {
916
+ initializeState();
917
+ initializeObjectAttributes();
918
+ resetLoadHooks();
919
+ globalThis.__SERVER_PAGE_DATA_BANNER__ = "";
920
+ let pageElements;
921
+ let metadata;
922
+ let modules = {};
923
+ let pageIgnoresLayout = false;
924
+ let isDynamicPage = false;
925
+ try {
926
+ const {
927
+ page,
928
+ metadata: pageMetadata,
929
+ isDynamic,
930
+ shippedModules: shippedModules2,
931
+ ignoreLayout
932
+ } = await import("file://" + filePath);
933
+ if (shippedModules2 !== void 0) {
934
+ modules = shippedModules2;
935
+ }
936
+ if (ignoreLayout) {
937
+ pageIgnoresLayout = true;
938
+ }
543
939
  pageElements = page;
544
940
  metadata = pageMetadata;
545
- if (isDynamicPage === false) {
546
- throw new Error("Cannot dynamically render a non-dynamic page.");
941
+ if (isDynamic === true) {
942
+ isDynamicPage = isDynamic;
547
943
  }
548
944
  } catch (e) {
549
- throw `${filePath} - ${e}
550
- ${e?.stack ?? "No stack."}
551
-
552
- `;
945
+ throw new Error(`Error in Page: ${directory}/${name}.ts - ${e}`);
946
+ }
947
+ PAGE_MAP.set(directory === "" ? "/" : directory, {
948
+ isDynamic: isDynamicPage,
949
+ filePath
950
+ });
951
+ if (isDynamicPage) return false;
952
+ if (modules !== void 0) {
953
+ for (const [globalName, path2] of Object.entries(modules)) {
954
+ modulesToShip.push({ globalName, path: path2 });
955
+ }
553
956
  }
554
957
  if (!metadata || metadata && typeof metadata !== "function") {
555
- console.warn(`WARNING: Dynamic ${filePath} does not export a metadata function. This is *highly* recommended.`);
958
+ console.warn(`WARNING: ${filePath} does not export a metadata function.`);
556
959
  }
557
960
  if (!pageElements) {
558
- console.warn(`WARNING: Dynamic ${filePath} should export a const page, which is of type BuiltElement<"body">.`);
961
+ console.warn(`WARNING: ${filePath} should export a const page, which is of type () => BuiltElement<"body">.`);
559
962
  }
963
+ const pageProps = {
964
+ pageName: directory
965
+ };
560
966
  if (typeof pageElements === "function") {
561
967
  if (pageElements.constructor.name === "AsyncFunction") {
562
- pageElements = await pageElements();
968
+ pageElements = await pageElements(pageProps);
563
969
  } else {
564
- pageElements = pageElements();
970
+ pageElements = pageElements(pageProps);
565
971
  }
566
972
  }
567
973
  const state = getState();
568
974
  const pageLoadHooks = getLoadHooks();
569
975
  const objectAttributes = getObjectAttributes();
570
- const foundObjectAttributes = await generateSuitablePageElements(
571
- path.dirname(filePath),
976
+ const layout = await fetchPageLayoutHTML(path.dirname(filePath));
977
+ const foundObjectAttributes = await pageToHTML(
978
+ path.join(DIST_DIR2, directory),
572
979
  pageElements || body(),
573
980
  metadata ?? (() => head()),
574
- DIST_DIR,
575
- "page",
576
- modules
981
+ DIST_DIR2,
982
+ name,
983
+ true,
984
+ modules,
985
+ layout,
986
+ directory
577
987
  );
578
- await generateClientPageData(
579
- path.dirname(filePath),
988
+ const {
989
+ sendHardReloadInstruction
990
+ } = await generateClientPageData(
991
+ directory,
580
992
  state || {},
581
- [...objectAttributes, ...foundObjectAttributes.objectAttributes],
993
+ [...objectAttributes, ...foundObjectAttributes],
582
994
  pageLoadHooks || [],
583
- DIST_DIR,
584
- "page"
995
+ DIST_DIR2,
996
+ name
585
997
  );
586
- return foundObjectAttributes.resultHTML;
998
+ return sendHardReloadInstruction === true;
587
999
  };
588
-
589
- // src/server/server.ts
590
- import { createServer as createHttpServer } from "http";
591
- import { promises as fs2 } from "fs";
592
- import { join, normalize, extname, dirname, resolve } from "path";
593
- import { pathToFileURL } from "url";
594
-
595
- // src/log.ts
596
- var quiet = false;
597
- function getTimestamp() {
598
- const now = /* @__PURE__ */ new Date();
599
- return now.toLocaleString(void 0, {
600
- year: "2-digit",
601
- month: "2-digit",
602
- day: "2-digit",
603
- hour: "2-digit",
604
- minute: "2-digit",
605
- second: "2-digit"
606
- });
607
- }
608
- function color(text, code) {
609
- return `\x1B[${code}m${text}\x1B[0m`;
610
- }
611
- function logInfo(...args) {
612
- if (quiet) return;
613
- console.info(`Elegance.JS: ${getTimestamp()} ${color("[INFO]:", 34)}`, ...args);
614
- }
615
- function logWarn(...args) {
616
- if (quiet) return;
617
- console.warn(`Elegance.JS: ${getTimestamp()} ${color("[WARN]:", 33)}`, ...args);
618
- }
619
- function logError(...args) {
620
- console.error(`Elegance.JS: ${getTimestamp()} ${color("[ERROR]:", 31)}`, ...args);
621
- }
622
- var log = {
623
- info: logInfo,
624
- warn: logWarn,
625
- error: logError
1000
+ var buildDynamicPage = async (DIST_DIR2, directory, pageInfo) => {
1001
+ directory = directory === "/" ? "" : directory;
1002
+ const filePath = pageInfo.filePath;
1003
+ initializeState();
1004
+ initializeObjectAttributes();
1005
+ resetLoadHooks();
1006
+ globalThis.__SERVER_PAGE_DATA_BANNER__ = "";
1007
+ let pageElements = async (props) => body();
1008
+ let metadata = async (props) => html();
1009
+ let modules = {};
1010
+ let pageIgnoresLayout = false;
1011
+ let isDynamicPage = false;
1012
+ try {
1013
+ const {
1014
+ page,
1015
+ metadata: pageMetadata,
1016
+ isDynamic,
1017
+ shippedModules: shippedModules2,
1018
+ ignoreLayout
1019
+ } = await import("file://" + filePath);
1020
+ if (shippedModules2 !== void 0) {
1021
+ modules = shippedModules2;
1022
+ }
1023
+ if (ignoreLayout) {
1024
+ pageIgnoresLayout = true;
1025
+ }
1026
+ pageElements = page;
1027
+ metadata = pageMetadata;
1028
+ if (isDynamic === true) {
1029
+ isDynamicPage = isDynamic;
1030
+ }
1031
+ } catch (e) {
1032
+ throw new Error(`Error in Page: ${directory}/page.ts - ${e}`);
1033
+ }
1034
+ if (modules !== void 0) {
1035
+ for (const [globalName, path2] of Object.entries(modules)) {
1036
+ modulesToShip.push({ globalName, path: path2 });
1037
+ }
1038
+ }
1039
+ if (!metadata || metadata && typeof metadata !== "function") {
1040
+ console.warn(`WARNING: ${filePath} does not export a metadata function.`);
1041
+ }
1042
+ if (!pageElements) {
1043
+ console.warn(`WARNING: ${filePath} should export a const page, which is of type () => BuiltElement<"body">.`);
1044
+ }
1045
+ const pageProps = {
1046
+ pageName: directory
1047
+ };
1048
+ if (typeof pageElements === "function") {
1049
+ if (pageElements.constructor.name === "AsyncFunction") {
1050
+ pageElements = await pageElements(pageProps);
1051
+ } else {
1052
+ pageElements = pageElements(pageProps);
1053
+ }
1054
+ }
1055
+ const layout = await fetchPageLayoutHTML(path.dirname(filePath));
1056
+ const resultHTML = await pageToHTML(
1057
+ path.join(DIST_DIR2, directory),
1058
+ pageElements,
1059
+ metadata,
1060
+ DIST_DIR2,
1061
+ "page",
1062
+ false,
1063
+ modules,
1064
+ layout,
1065
+ directory
1066
+ );
1067
+ await shipModules();
1068
+ return { resultHTML };
1069
+ };
1070
+ var shipModules = async () => {
1071
+ for (const plugin of modulesToShip) {
1072
+ {
1073
+ if (shippedModules.has(plugin.globalName)) continue;
1074
+ shippedModules.set(plugin.globalName, true);
1075
+ }
1076
+ esbuild.build({
1077
+ entryPoints: [plugin.path],
1078
+ bundle: true,
1079
+ outfile: path.join(DIST_DIR, "shipped", plugin.globalName + ".js"),
1080
+ format: "iife",
1081
+ platform: "browser",
1082
+ globalName: plugin.globalName,
1083
+ minify: true,
1084
+ treeShaking: true
1085
+ });
1086
+ }
1087
+ modulesToShip = [];
626
1088
  };
1089
+ var build = async () => {
1090
+ if (options.quiet === true) {
1091
+ console.log = function() {
1092
+ };
1093
+ console.error = function() {
1094
+ };
1095
+ console.warn = function() {
1096
+ };
1097
+ }
1098
+ try {
1099
+ {
1100
+ log2(bold(yellow(" -- Elegance.JS -- ")));
1101
+ if (options.environment === "production") {
1102
+ log2(
1103
+ " - ",
1104
+ bgYellow(bold(black(" NOTE "))),
1105
+ " : ",
1106
+ white("In production mode, no "),
1107
+ underline("console.log() "),
1108
+ white("statements will be shown on the client, and all code will be minified.")
1109
+ );
1110
+ log2("");
1111
+ }
1112
+ }
1113
+ if (options.preCompile) {
1114
+ options.preCompile();
1115
+ }
1116
+ const start = performance.now();
1117
+ let shouldClientHardReload;
1118
+ {
1119
+ const { shouldClientHardReload: doReload } = await buildLayouts();
1120
+ if (doReload) shouldClientHardReload = true;
1121
+ }
1122
+ {
1123
+ const { shouldClientHardReload: doReload } = await buildPages(path.resolve(DIST_DIR));
1124
+ if (doReload) shouldClientHardReload = true;
1125
+ }
1126
+ await shipModules();
1127
+ const pagesBuilt = performance.now();
1128
+ await buildClient(DIST_DIR);
1129
+ const end = performance.now();
1130
+ if (options.publicDirectory) {
1131
+ log2("Recursively copying public directory.. this may take a while.");
1132
+ const src = path.relative(process.cwd(), options.publicDirectory.path);
1133
+ if (fs.existsSync(src) === false) {
1134
+ console.warn("WARNING: Public directory not found, an attempt will be made create it..");
1135
+ fs.mkdirSync(src, { recursive: true });
1136
+ }
1137
+ await fs.promises.cp(src, path.join(DIST_DIR), { recursive: true });
1138
+ }
1139
+ {
1140
+ log2(`Took ${Math.round(pagesBuilt - start)}ms to Build Pages.`);
1141
+ log2(`Took ${Math.round(end - pagesBuilt)}ms to Build Client.`);
1142
+ }
1143
+ if (options.server != void 0 && options.server.runServer == true) {
1144
+ startServer({
1145
+ root: options.server.root ?? DIST_DIR,
1146
+ environment: options.environment,
1147
+ port: options.server.port ?? 3e3,
1148
+ host: options.server.host ?? "localhost",
1149
+ DIST_DIR
1150
+ });
1151
+ }
1152
+ process.send?.({ event: "message", data: "compile-finish" });
1153
+ if (shouldClientHardReload) {
1154
+ process.send({ event: "message", data: "hard-reload" });
1155
+ } else {
1156
+ process.send({ event: "message", data: "soft-reload" });
1157
+ }
1158
+ } catch (e) {
1159
+ console.error("Build Failed! Received Error:");
1160
+ console.error(e);
1161
+ return false;
1162
+ }
1163
+ return true;
1164
+ };
1165
+ (async () => {
1166
+ await build();
1167
+ })();
627
1168
 
628
1169
  // src/server/server.ts
629
- import { gzip, deflate } from "zlib";
630
- import { promisify } from "util";
631
1170
  var gzipAsync = promisify(gzip);
632
1171
  var deflateAsync = promisify(deflate);
633
1172
  var MIME_TYPES = {
@@ -648,7 +1187,7 @@ function startServer({
648
1187
  port = 3e3,
649
1188
  host = "localhost",
650
1189
  environment = "production",
651
- DIST_DIR
1190
+ DIST_DIR: DIST_DIR2
652
1191
  }) {
653
1192
  if (!root) throw new Error("Root directory must be specified.");
654
1193
  root = normalize(root).replace(/[\\/]+$/, "");
@@ -672,8 +1211,10 @@ function startServer({
672
1211
  const url = new URL(req.url, `http://${req.headers.host}`);
673
1212
  if (url.pathname.startsWith("/api/")) {
674
1213
  await handleApiRequest(root, url.pathname, req, res);
1214
+ } else if (PAGE_MAP.has(url.pathname)) {
1215
+ await handlePageRequest(root, url.pathname, req, res, DIST_DIR2, PAGE_MAP.get(url.pathname));
675
1216
  } else {
676
- await handleStaticRequest(root, url.pathname, req, res, DIST_DIR);
1217
+ await handleStaticRequest(root, url.pathname, req, res, DIST_DIR2);
677
1218
  }
678
1219
  if (environment === "development") {
679
1220
  log.info(req.method, "::", req.url, "-", res.statusCode);
@@ -699,43 +1240,38 @@ function startServer({
699
1240
  }
700
1241
  return attemptListen(port);
701
1242
  }
702
- async function handleStaticRequest(root, pathname, req, res, DIST_DIR) {
1243
+ async function getTargetInfo(root, pathname) {
703
1244
  const originalPathname = pathname;
704
- let filePath = normalize(join(root, decodeURIComponent(pathname))).replace(/[\\/]+$/, "");
1245
+ const filePath = normalize(join(root, decodeURIComponent(pathname))).replace(/[\\/]+$/, "");
705
1246
  if (!filePath.startsWith(root)) {
706
- await sendResponse(req, res, 403, { "Content-Type": "text/plain; charset=utf-8" }, "Forbidden");
707
- return;
1247
+ throw new Error("Forbidden");
708
1248
  }
709
1249
  let stats;
710
1250
  try {
711
1251
  stats = await fs2.stat(filePath);
712
1252
  } catch {
713
1253
  }
714
- let pageDir;
1254
+ let targetDir;
715
1255
  if (stats) {
716
- if (stats.isDirectory()) {
717
- pageDir = filePath;
718
- } else {
719
- pageDir = dirname(filePath);
720
- }
1256
+ targetDir = stats.isDirectory() ? filePath : dirname(filePath);
721
1257
  } else {
722
- if (originalPathname.endsWith("/")) {
723
- pageDir = filePath;
724
- } else {
725
- pageDir = dirname(filePath);
726
- }
1258
+ targetDir = originalPathname.endsWith("/") ? filePath : dirname(filePath);
727
1259
  }
728
- const relDir = pageDir.slice(root.length).replace(/^[\/\\]+/, "");
729
- const parts = relDir.split(/[\\/]/).filter(Boolean);
1260
+ return { filePath, targetDir, stats };
1261
+ }
1262
+ function getMiddlewareDirs(base, parts) {
730
1263
  const middlewareDirs = [];
731
- let current = root;
1264
+ let current = base;
732
1265
  middlewareDirs.push(current);
733
1266
  for (const part of parts) {
734
1267
  current = join(current, part);
735
1268
  middlewareDirs.push(current);
736
1269
  }
1270
+ return middlewareDirs;
1271
+ }
1272
+ async function collectMiddlewares(dirs) {
737
1273
  const middlewares = [];
738
- for (const dir of middlewareDirs) {
1274
+ for (const dir of dirs) {
739
1275
  const mwPath = join(dir, "middleware.mjs");
740
1276
  let mwModule;
741
1277
  try {
@@ -753,57 +1289,104 @@ async function handleStaticRequest(root, pathname, req, res, DIST_DIR) {
753
1289
  }
754
1290
  }
755
1291
  }
756
- let isDynamic = false;
757
- let handlerPath = filePath;
758
- if (stats && stats.isDirectory()) {
759
- const pageMjsPath = join(filePath, "page.mjs");
1292
+ return middlewares;
1293
+ }
1294
+ async function handlePageRequest(root, pathname, req, res, DIST_DIR2, pageInfo) {
1295
+ try {
1296
+ const { filePath, targetDir, stats } = await getTargetInfo(root, pathname);
1297
+ const relDir = targetDir.slice(root.length).replace(/^[\/\\]+/, "");
1298
+ const parts = relDir.split(/[\\/]/).filter(Boolean);
1299
+ const middlewareDirs = getMiddlewareDirs(root, parts);
1300
+ const middlewares = await collectMiddlewares(middlewareDirs);
1301
+ let isDynamic = pageInfo.isDynamic;
1302
+ const handlerPath = isDynamic ? pageInfo.filePath : join(filePath, "index.html");
1303
+ let hasHandler = false;
760
1304
  try {
761
- await fs2.access(pageMjsPath);
762
- handlerPath = pageMjsPath;
763
- isDynamic = true;
1305
+ await fs2.access(handlerPath);
1306
+ hasHandler = true;
764
1307
  } catch {
765
- handlerPath = join(filePath, "index.html");
766
- isDynamic = false;
767
1308
  }
768
- } else {
769
- handlerPath = filePath;
770
- isDynamic = false;
1309
+ const finalHandler = async (req2, res2) => {
1310
+ if (!hasHandler) {
1311
+ await respondWithErrorPage(root, pathname, 404, req2, res2);
1312
+ return;
1313
+ }
1314
+ if (isDynamic) {
1315
+ try {
1316
+ const { resultHTML } = await buildDynamicPage(
1317
+ DIST_DIR2,
1318
+ pathname,
1319
+ pageInfo
1320
+ );
1321
+ if (resultHTML === false) {
1322
+ return;
1323
+ }
1324
+ await sendResponse(req2, res2, 200, { "Content-Type": MIME_TYPES[".html"] }, resultHTML);
1325
+ } catch (err) {
1326
+ log.error("Error building dynamic page -", err);
1327
+ }
1328
+ } else {
1329
+ const ext = extname(handlerPath).toLowerCase();
1330
+ const contentType = MIME_TYPES[ext] || "application/octet-stream";
1331
+ const data = await fs2.readFile(handlerPath);
1332
+ await sendResponse(req2, res2, 200, { "Content-Type": contentType }, data);
1333
+ }
1334
+ };
1335
+ const composed = composeMiddlewares(middlewares, finalHandler, { isApi: false, root, pathname });
1336
+ await composed(req, res);
1337
+ } catch (err) {
1338
+ if (err.message === "Forbidden") {
1339
+ await sendResponse(req, res, 403, { "Content-Type": "text/plain; charset=utf-8" }, "Forbidden");
1340
+ } else {
1341
+ throw err;
1342
+ }
771
1343
  }
772
- let hasHandler = false;
1344
+ }
1345
+ async function handleStaticRequest(root, pathname, req, res, DIST_DIR2) {
773
1346
  try {
774
- await fs2.access(handlerPath);
775
- hasHandler = true;
776
- } catch {
777
- }
778
- const finalHandler = async (req2, res2) => {
779
- if (!hasHandler) {
780
- await respondWithErrorPage(root, pathname, 404, req2, res2);
781
- return;
1347
+ const { filePath, targetDir, stats } = await getTargetInfo(root, pathname);
1348
+ const relDir = targetDir.slice(root.length).replace(/^[\/\\]+/, "");
1349
+ const parts = relDir.split(/[\\/]/).filter(Boolean);
1350
+ const middlewareDirs = getMiddlewareDirs(root, parts);
1351
+ const middlewares = await collectMiddlewares(middlewareDirs);
1352
+ let handlerPath = filePath;
1353
+ if (stats && stats.isDirectory()) {
1354
+ handlerPath = join(filePath, "index.html");
1355
+ } else {
1356
+ handlerPath = filePath;
782
1357
  }
783
- if (isDynamic) {
784
- try {
785
- const resultHTML = await buildDynamicPage(resolve(handlerPath), DIST_DIR, req2, res2);
786
- if (resultHTML === false) {
787
- return;
788
- }
789
- await sendResponse(req2, res2, 200, { "Content-Type": MIME_TYPES[".html"] }, resultHTML);
790
- } catch (err) {
791
- log.error("Error building dynamic page -", err);
1358
+ let hasHandler = false;
1359
+ try {
1360
+ await fs2.access(handlerPath);
1361
+ hasHandler = true;
1362
+ } catch {
1363
+ }
1364
+ const finalHandler = async (req2, res2) => {
1365
+ if (!hasHandler) {
1366
+ await respondWithErrorPage(root, pathname, 404, req2, res2);
1367
+ return;
792
1368
  }
793
- } else {
794
1369
  const ext = extname(handlerPath).toLowerCase();
795
1370
  const contentType = MIME_TYPES[ext] || "application/octet-stream";
796
1371
  const data = await fs2.readFile(handlerPath);
797
1372
  await sendResponse(req2, res2, 200, { "Content-Type": contentType }, data);
1373
+ };
1374
+ const composed = composeMiddlewares(middlewares, finalHandler, { isApi: false, root, pathname });
1375
+ await composed(req, res);
1376
+ } catch (err) {
1377
+ if (err.message === "Forbidden") {
1378
+ await sendResponse(req, res, 403, { "Content-Type": "text/plain; charset=utf-8" }, "Forbidden");
1379
+ } else {
1380
+ throw err;
798
1381
  }
799
- };
800
- const composed = composeMiddlewares(middlewares, finalHandler, { isApi: false, root, pathname });
801
- await composed(req, res);
1382
+ }
802
1383
  }
803
1384
  async function handleApiRequest(root, pathname, req, res) {
804
1385
  const apiSubPath = pathname.slice("/api/".length);
805
1386
  const parts = apiSubPath.split("/").filter(Boolean);
806
- const routeDir = join(root, pathname);
1387
+ const middlewareDirs = getMiddlewareDirs(join(root, "api"), parts);
1388
+ const middlewares = await collectMiddlewares(middlewareDirs);
1389
+ const routeDir = middlewareDirs[middlewareDirs.length - 1];
807
1390
  const routePath = join(routeDir, "route.mjs");
808
1391
  let hasRoute = false;
809
1392
  try {
@@ -823,32 +1406,6 @@ async function handleApiRequest(root, pathname, req, res) {
823
1406
  return respondWithJsonError(req, res, 500, "Internal Server Error");
824
1407
  }
825
1408
  }
826
- const middlewareDirs = [];
827
- let current = join(root, "api");
828
- middlewareDirs.push(current);
829
- for (const part of parts) {
830
- current = join(current, part);
831
- middlewareDirs.push(current);
832
- }
833
- const middlewares = [];
834
- for (const dir of middlewareDirs) {
835
- const mwPath = join(dir, "middleware.mjs");
836
- let mwModule;
837
- try {
838
- await fs2.access(mwPath);
839
- const url = pathToFileURL(mwPath).href;
840
- mwModule = await import(url);
841
- } catch {
842
- continue;
843
- }
844
- const mwKeys = Object.keys(mwModule).sort();
845
- for (const key of mwKeys) {
846
- const f = mwModule[key];
847
- if (typeof f === "function" && !middlewares.some((existing) => existing === f)) {
848
- middlewares.push(f);
849
- }
850
- }
851
- }
852
1409
  const finalHandler = async (req2, res2) => {
853
1410
  if (!hasRoute) {
854
1411
  return respondWithJsonError(req2, res2, 404, "Not Found");
@@ -861,15 +1418,15 @@ async function handleApiRequest(root, pathname, req, res) {
861
1418
  const composed = composeMiddlewares(middlewares, finalHandler, { isApi: true });
862
1419
  await composed(req, res);
863
1420
  }
864
- function composeMiddlewares(mws, final, options) {
1421
+ function composeMiddlewares(mws, final, options2) {
865
1422
  return async function(req, res) {
866
1423
  let index = 0;
867
1424
  async function dispatch(err) {
868
1425
  if (err) {
869
- if (options.isApi) {
1426
+ if (options2.isApi) {
870
1427
  return respondWithJsonError(req, res, 500, err.message || "Internal Server Error");
871
1428
  } else {
872
- return await respondWithErrorPage(options.root, options.pathname, 500, req, res);
1429
+ return await respondWithErrorPage(options2.root, options2.pathname, 500, req, res);
873
1430
  }
874
1431
  }
875
1432
  if (index >= mws.length) {
@@ -930,8 +1487,8 @@ async function respondWithErrorPage(root, pathname, code, req, res) {
930
1487
  }
931
1488
  if (errorFilePath) {
932
1489
  try {
933
- const html = await fs2.readFile(errorFilePath, "utf8");
934
- await sendResponse(req, res, code, { "Content-Type": "text/html; charset=utf-8" }, html);
1490
+ const html2 = await fs2.readFile(errorFilePath, "utf8");
1491
+ await sendResponse(req, res, code, { "Content-Type": "text/html; charset=utf-8" }, html2);
935
1492
  return;
936
1493
  } catch {
937
1494
  }