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.
- package/dist/build.mjs +23 -974
- package/dist/client/client.mjs +20 -13
- package/dist/compile_docs.mjs +29 -980
- package/dist/global.d.ts +18 -5
- package/dist/index.mjs +9 -6
- package/dist/page_compiler.d.ts +5 -0
- package/dist/page_compiler.mjs +658 -109
- package/dist/server/generateHTMLTemplate.d.ts +1 -1
- package/dist/server/generateHTMLTemplate.mjs +5 -4
- package/dist/server/server.mjs +874 -317
- package/dist/server/state.d.ts +17 -0
- package/dist/server/state.mjs +9 -6
- package/package.json +2 -2
- package/scripts/prod.js +1 -0
- package/dist/components/Breakpoint.d.ts +0 -3
- package/dist/components/Breakpoint.mjs +0 -20
- package/dist/dynamic_page.d.ts +0 -2
- package/dist/dynamic_page.mjs +0 -591
- package/dist/server/createReference.d.ts +0 -6
- package/dist/server/createReference.mjs +0 -18
- package/dist/server/createState.d.ts +0 -64
- package/dist/server/createState.mjs +0 -65
- package/dist/server/packModule.d.ts +0 -4
- package/dist/server/packModule.mjs +0 -30
package/dist/server/server.mjs
CHANGED
|
@@ -1,21 +1,65 @@
|
|
|
1
|
-
// src/
|
|
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 (
|
|
53
|
+
return (options2, ...children) => ({
|
|
10
54
|
tag,
|
|
11
|
-
options:
|
|
55
|
+
options: options2 || {},
|
|
12
56
|
children
|
|
13
57
|
});
|
|
14
58
|
};
|
|
15
59
|
var createChildrenlessBuildableElement = (tag) => {
|
|
16
|
-
return (
|
|
60
|
+
return (options2) => ({
|
|
17
61
|
tag,
|
|
18
|
-
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
|
|
227
|
-
StartTemplate += `<script data-module="true" src="/shipped/${
|
|
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
|
-
|
|
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/
|
|
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/
|
|
265
|
-
var
|
|
266
|
-
|
|
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/
|
|
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
|
|
279
|
-
let key =
|
|
390
|
+
const options2 = element.options;
|
|
391
|
+
let key = options2.key;
|
|
280
392
|
if (key == void 0) {
|
|
281
393
|
key = elementKey += 1;
|
|
282
|
-
|
|
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
|
|
404
|
+
delete options2[optionName];
|
|
293
405
|
break;
|
|
294
406
|
}
|
|
295
407
|
if (lcOptionName === "innertext" || lcOptionName === "innerhtml") {
|
|
296
408
|
element.children = [SOA.value];
|
|
297
|
-
delete
|
|
409
|
+
delete options2[optionName];
|
|
298
410
|
} else {
|
|
299
|
-
delete
|
|
300
|
-
|
|
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
|
|
420
|
+
delete options2[optionName];
|
|
309
421
|
} else {
|
|
310
|
-
delete
|
|
311
|
-
|
|
422
|
+
delete options2[optionName];
|
|
423
|
+
options2[lcOptionName] = firstValue;
|
|
312
424
|
}
|
|
313
425
|
optionFinal = optionName;
|
|
314
426
|
break;
|
|
315
427
|
case 4 /* REFERENCE */:
|
|
316
|
-
|
|
428
|
+
options2["ref"] = optionValue.value;
|
|
317
429
|
break;
|
|
318
430
|
}
|
|
319
431
|
objectAttributes.push({ ...optionValue, key, attribute: optionFinal });
|
|
320
432
|
};
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
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
|
-
|
|
340
|
-
|
|
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
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
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
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
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
|
-
|
|
537
|
+
;
|
|
538
|
+
processOptionAsObjectAttribute(element, optionName, optionValue, objectAttributes);
|
|
374
539
|
}
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
393
|
-
|
|
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
|
|
399
|
-
pageURL:
|
|
572
|
+
const { internals, builtMetadata } = await generateHTMLTemplate({
|
|
573
|
+
pageURL: pathname,
|
|
400
574
|
head: metadata,
|
|
401
|
-
addPageScriptTag:
|
|
575
|
+
addPageScriptTag: doWrite,
|
|
402
576
|
name: pageName,
|
|
403
577
|
requiredClientModules,
|
|
404
|
-
environment:
|
|
578
|
+
environment: options.environment
|
|
405
579
|
});
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
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,
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
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
|
|
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
|
|
490
|
-
|
|
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
|
-
|
|
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:
|
|
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.
|
|
505
|
-
|
|
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
|
|
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
|
-
|
|
710
|
+
let layoutElements;
|
|
711
|
+
let metadataElements;
|
|
515
712
|
let modules = [];
|
|
713
|
+
let isDynamicLayout = false;
|
|
516
714
|
try {
|
|
517
715
|
const {
|
|
518
|
-
|
|
716
|
+
layout,
|
|
717
|
+
metadata,
|
|
718
|
+
isDynamic,
|
|
719
|
+
shippedModules: shippedModules2
|
|
519
720
|
} = await import("file://" + filePath);
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
}
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
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
|
-
|
|
538
|
-
|
|
539
|
-
|
|
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 (
|
|
546
|
-
|
|
941
|
+
if (isDynamic === true) {
|
|
942
|
+
isDynamicPage = isDynamic;
|
|
547
943
|
}
|
|
548
944
|
} catch (e) {
|
|
549
|
-
throw
|
|
550
|
-
|
|
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:
|
|
958
|
+
console.warn(`WARNING: ${filePath} does not export a metadata function.`);
|
|
556
959
|
}
|
|
557
960
|
if (!pageElements) {
|
|
558
|
-
console.warn(`WARNING:
|
|
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
|
|
571
|
-
|
|
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
|
-
|
|
575
|
-
|
|
576
|
-
|
|
981
|
+
DIST_DIR2,
|
|
982
|
+
name,
|
|
983
|
+
true,
|
|
984
|
+
modules,
|
|
985
|
+
layout,
|
|
986
|
+
directory
|
|
577
987
|
);
|
|
578
|
-
|
|
579
|
-
|
|
988
|
+
const {
|
|
989
|
+
sendHardReloadInstruction
|
|
990
|
+
} = await generateClientPageData(
|
|
991
|
+
directory,
|
|
580
992
|
state || {},
|
|
581
|
-
[...objectAttributes, ...foundObjectAttributes
|
|
993
|
+
[...objectAttributes, ...foundObjectAttributes],
|
|
582
994
|
pageLoadHooks || [],
|
|
583
|
-
|
|
584
|
-
|
|
995
|
+
DIST_DIR2,
|
|
996
|
+
name
|
|
585
997
|
);
|
|
586
|
-
return
|
|
998
|
+
return sendHardReloadInstruction === true;
|
|
587
999
|
};
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
}
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
}
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
}
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
}
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
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,
|
|
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
|
|
1243
|
+
async function getTargetInfo(root, pathname) {
|
|
703
1244
|
const originalPathname = pathname;
|
|
704
|
-
|
|
1245
|
+
const filePath = normalize(join(root, decodeURIComponent(pathname))).replace(/[\\/]+$/, "");
|
|
705
1246
|
if (!filePath.startsWith(root)) {
|
|
706
|
-
|
|
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
|
|
1254
|
+
let targetDir;
|
|
715
1255
|
if (stats) {
|
|
716
|
-
|
|
717
|
-
pageDir = filePath;
|
|
718
|
-
} else {
|
|
719
|
-
pageDir = dirname(filePath);
|
|
720
|
-
}
|
|
1256
|
+
targetDir = stats.isDirectory() ? filePath : dirname(filePath);
|
|
721
1257
|
} else {
|
|
722
|
-
|
|
723
|
-
pageDir = filePath;
|
|
724
|
-
} else {
|
|
725
|
-
pageDir = dirname(filePath);
|
|
726
|
-
}
|
|
1258
|
+
targetDir = originalPathname.endsWith("/") ? filePath : dirname(filePath);
|
|
727
1259
|
}
|
|
728
|
-
|
|
729
|
-
|
|
1260
|
+
return { filePath, targetDir, stats };
|
|
1261
|
+
}
|
|
1262
|
+
function getMiddlewareDirs(base, parts) {
|
|
730
1263
|
const middlewareDirs = [];
|
|
731
|
-
let current =
|
|
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
|
|
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
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
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(
|
|
762
|
-
|
|
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
|
-
|
|
769
|
-
|
|
770
|
-
|
|
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
|
-
|
|
1344
|
+
}
|
|
1345
|
+
async function handleStaticRequest(root, pathname, req, res, DIST_DIR2) {
|
|
773
1346
|
try {
|
|
774
|
-
await
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
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
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
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
|
|
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,
|
|
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 (
|
|
1426
|
+
if (options2.isApi) {
|
|
870
1427
|
return respondWithJsonError(req, res, 500, err.message || "Internal Server Error");
|
|
871
1428
|
} else {
|
|
872
|
-
return await respondWithErrorPage(
|
|
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
|
|
934
|
-
await sendResponse(req, res, code, { "Content-Type": "text/html; charset=utf-8" },
|
|
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
|
}
|