elegance-js 2.1.23 → 2.1.26
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/client/effect.d.ts +27 -0
- package/dist/client/effect.js +37 -0
- package/dist/client/eventListener.d.ts +39 -0
- package/dist/client/eventListener.js +52 -0
- package/dist/client/loadHook.d.ts +34 -0
- package/dist/client/loadHook.js +52 -0
- package/dist/client/observer.d.ts +36 -0
- package/dist/client/observer.js +66 -0
- package/dist/client/runtime.d.ts +105 -0
- package/dist/client/runtime.js +620 -0
- package/dist/client/state.d.ts +40 -0
- package/dist/client/state.js +110 -0
- package/dist/compilation/compiler.d.ts +155 -0
- package/dist/compilation/compiler.js +1153 -0
- package/dist/components/ClientComponent.d.ts +22 -0
- package/dist/components/ClientComponent.js +55 -0
- package/dist/components/Link.d.ts +16 -1
- package/dist/components/Link.js +22 -0
- package/dist/components/Portal.d.ts +2 -0
- package/dist/components/Portal.js +2 -0
- package/dist/elements/element.d.ts +87 -0
- package/dist/elements/element.js +33 -0
- package/dist/elements/element_list.d.ts +7 -0
- package/dist/elements/element_list.js +65 -0
- package/dist/elements/raw.d.ts +14 -0
- package/dist/elements/raw.js +78 -0
- package/dist/elements/specific_props.d.ts +750 -0
- package/dist/global.d.ts +221 -327
- package/dist/index.d.ts +15 -3
- package/dist/index.js +11 -0
- package/dist/server/layout.d.ts +34 -3
- package/dist/server/layout.js +6 -0
- package/dist/server/log.d.ts +12 -0
- package/dist/server/log.js +64 -0
- package/dist/server/page.d.ts +32 -0
- package/dist/server/page.js +6 -0
- package/dist/server/runtime.d.ts +6 -0
- package/dist/server/runtime.js +72 -0
- package/dist/server/server.d.ts +103 -11
- package/dist/server/server.js +709 -0
- package/package.json +13 -13
- package/scripts/bootstrap.js +37 -273
- package/scripts/bootstrap_files/elegance.txt +40 -0
- package/scripts/bootstrap_files/index.txt +3 -0
- package/scripts/bootstrap_files/layout.txt +46 -0
- package/scripts/bootstrap_files/middleware.txt +18 -0
- package/scripts/bootstrap_files/page.txt +123 -0
- package/scripts/bootstrap_files/route.txt +6 -0
- package/scripts/elegance_dev.ts +40 -0
- package/scripts/elegance_prod.ts +40 -0
- package/scripts/elegance_static.ts +24 -0
- package/scripts/prod.js +9 -26
- package/scripts/run.js +13 -0
- package/scripts/static.js +13 -0
- package/dist/build.d.ts +0 -2
- package/dist/build.mjs +0 -202
- package/dist/client/client.d.ts +0 -1
- package/dist/client/client.mjs +0 -574
- package/dist/client/processPageElements.d.ts +0 -1
- package/dist/client/processPageElements.mjs +0 -117
- package/dist/client/render.d.ts +0 -1
- package/dist/client/render.mjs +0 -40
- package/dist/client/watcher.d.ts +0 -1
- package/dist/client/watcher.mjs +0 -26
- package/dist/compilation/compilation.d.ts +0 -139
- package/dist/compilation/compilation.mjs +0 -751
- package/dist/compilation/compiler_process.d.ts +0 -3
- package/dist/compilation/compiler_process.mjs +0 -102
- package/dist/compilation/dynamic_compiler.d.ts +0 -10
- package/dist/compilation/dynamic_compiler.mjs +0 -93
- package/dist/compile_docs.mjs +0 -34
- package/dist/components/Link.mjs +0 -65
- package/dist/global.mjs +0 -0
- package/dist/helpers/ObjectAttributeType.d.ts +0 -7
- package/dist/helpers/ObjectAttributeType.mjs +0 -11
- package/dist/helpers/camelToKebab.d.ts +0 -1
- package/dist/helpers/camelToKebab.mjs +0 -6
- package/dist/index.mjs +0 -3
- package/dist/internal/deprecate.d.ts +0 -1
- package/dist/internal/deprecate.mjs +0 -7
- package/dist/log.d.ts +0 -10
- package/dist/log.mjs +0 -38
- package/dist/server/generateHTMLTemplate.d.ts +0 -12
- package/dist/server/generateHTMLTemplate.mjs +0 -41
- package/dist/server/layout.mjs +0 -19
- package/dist/server/loadHook.d.ts +0 -30
- package/dist/server/loadHook.mjs +0 -50
- package/dist/server/observe.d.ts +0 -19
- package/dist/server/observe.mjs +0 -16
- package/dist/server/render.d.ts +0 -5
- package/dist/server/render.mjs +0 -61
- package/dist/server/server.mjs +0 -429
- package/dist/server/state.d.ts +0 -61
- package/dist/server/state.mjs +0 -146
- package/dist/shared/bindServerElements.mjs +0 -3
- package/dist/shared/serverElements.d.ts +0 -11
- package/dist/shared/serverElements.mjs +0 -164
- package/scripts/dev.js +0 -33
- package/scripts/export.js +0 -20
- package/scripts/ts-arc-dev.js +0 -9
- package/scripts/ts-arc-prod.js +0 -9
- /package/dist/{compile_docs.d.ts → elements/specific_props.js} +0 -0
- /package/dist/{shared/bindServerElements.d.ts → global.js} +0 -0
|
@@ -0,0 +1,1153 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This file contains the functions used by compiler_process to compile pages.
|
|
3
|
+
*/
|
|
4
|
+
import path from "path";
|
|
5
|
+
import crypto from "crypto";
|
|
6
|
+
import { EleganceElement, SpecialElementOption } from "../elements/element.js";
|
|
7
|
+
import { cpSync, existsSync, lstatSync, mkdirSync, readdirSync, watch, writeFileSync } from "fs";
|
|
8
|
+
import esbuild from "esbuild";
|
|
9
|
+
import { invalidPageError } from "../server/page.js";
|
|
10
|
+
import { invalidLayoutError } from "../server/layout.js";
|
|
11
|
+
import { allElements } from "../elements/element_list.js";
|
|
12
|
+
import { ObserverOption, ServerObserver } from "../client/observer.js";
|
|
13
|
+
import { fileURLToPath } from "url";
|
|
14
|
+
import util from "util";
|
|
15
|
+
import { AsyncLocalStorage } from "async_hooks";
|
|
16
|
+
import { ServerSubject } from "../client/state.js";
|
|
17
|
+
import { EventListenerOption, EventListener } from "../client/eventListener.js";
|
|
18
|
+
import { LoadHook } from "../client/loadHook.js";
|
|
19
|
+
import { formattedLog, LogLevel } from "../server/log.js";
|
|
20
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
21
|
+
const __dirname = path.dirname(__filename);
|
|
22
|
+
import { raw, unwrapAllRaw, } from "../elements/raw.js";
|
|
23
|
+
import { Effect } from "../client/effect.js";
|
|
24
|
+
let compilerOptions;
|
|
25
|
+
const compilerStore = new AsyncLocalStorage();
|
|
26
|
+
/**
|
|
27
|
+
* Get the current dist dir. If it does not exist, it will be created.
|
|
28
|
+
*/
|
|
29
|
+
function getDistDir() {
|
|
30
|
+
const fullPath = path.join(compilerOptions.outputDirectory, "DIST");
|
|
31
|
+
if (existsSync(fullPath) === false) {
|
|
32
|
+
mkdirSync(fullPath, { recursive: true, });
|
|
33
|
+
}
|
|
34
|
+
return fullPath;
|
|
35
|
+
}
|
|
36
|
+
function setCompilerOptions(newOptions) {
|
|
37
|
+
newOptions.pagesDirectory = path.resolve(newOptions.pagesDirectory);
|
|
38
|
+
newOptions.outputDirectory = path.resolve(newOptions.outputDirectory);
|
|
39
|
+
newOptions.publicDirectory = path.resolve(newOptions.publicDirectory);
|
|
40
|
+
if (existsSync(newOptions.pagesDirectory) === false) {
|
|
41
|
+
throw new Error("The directory: " + newOptions.pagesDirectory + " does not exist, and thus cannot be used as the pagesDirectory.");
|
|
42
|
+
}
|
|
43
|
+
if (existsSync(newOptions.publicDirectory) === false) {
|
|
44
|
+
throw new Error("The directory: " + newOptions.publicDirectory + " does not exist, and thus cannot be used as the publicDirectory");
|
|
45
|
+
}
|
|
46
|
+
if (existsSync(newOptions.outputDirectory) === false) {
|
|
47
|
+
mkdirSync(newOptions.outputDirectory, { recursive: true, });
|
|
48
|
+
}
|
|
49
|
+
compilerOptions = newOptions;
|
|
50
|
+
}
|
|
51
|
+
function invalidElementError(element, fullPath, reason) {
|
|
52
|
+
const stacktrace = formatStacktrace(fullPath);
|
|
53
|
+
return ("Direct Parent Trace:\n" + stacktrace + "\n" + "The element \"" + util.inspect(element, { depth: 1, colors: true, }) + "\" is an invalid element. Reason:\n" + reason);
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Generate a stable, page-specific & order-dependent unique-identifier.
|
|
57
|
+
* NOTE: This function will throw if it fails to generate a hash after 100+ attempts.
|
|
58
|
+
* However, this is not technically possible.
|
|
59
|
+
*/
|
|
60
|
+
function generateId(compilationContext) {
|
|
61
|
+
let id;
|
|
62
|
+
let tries = 0;
|
|
63
|
+
while (true) {
|
|
64
|
+
compilationContext.idCounter += 1;
|
|
65
|
+
id = crypto
|
|
66
|
+
.createHash('sha256')
|
|
67
|
+
.update(compilationContext.pathname + compilationContext.kind.toString() + ':' + compilationContext.idCounter.toString())
|
|
68
|
+
.digest('base64url')
|
|
69
|
+
.slice(0, 11); // 66 bits of entropy
|
|
70
|
+
if (!compilationContext.usedHashes.includes(id)) {
|
|
71
|
+
break;
|
|
72
|
+
}
|
|
73
|
+
tries += 1;
|
|
74
|
+
if (tries > 100) {
|
|
75
|
+
throw new Error("Failed to generate a unique id after 100+ attempts");
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
compilationContext.usedHashes.push(id);
|
|
79
|
+
return id;
|
|
80
|
+
}
|
|
81
|
+
function generateLayoutId(layoutInformation) {
|
|
82
|
+
return crypto
|
|
83
|
+
.createHash('sha256')
|
|
84
|
+
.update(layoutInformation.pathname)
|
|
85
|
+
.digest('base64url')
|
|
86
|
+
.slice(0, 11); // 66 bits of entropy
|
|
87
|
+
}
|
|
88
|
+
function internalCompilerError(reason) {
|
|
89
|
+
return new Error(`The compiler has encountered an internal error.\n${reason}`);
|
|
90
|
+
}
|
|
91
|
+
/** a simple path sanitizer that just ensures no repeat-slashes and no trailing slash */
|
|
92
|
+
function sanitizePathname(pathname = "") {
|
|
93
|
+
if (!pathname)
|
|
94
|
+
return "/";
|
|
95
|
+
pathname = "/" + pathname;
|
|
96
|
+
pathname = pathname.replace(/\/+/g, "/");
|
|
97
|
+
if (pathname.length > 1 && pathname.endsWith("/")) {
|
|
98
|
+
pathname = pathname.slice(0, -1);
|
|
99
|
+
}
|
|
100
|
+
return pathname;
|
|
101
|
+
}
|
|
102
|
+
function getElementKey(compilationContext, element) {
|
|
103
|
+
if (element.key)
|
|
104
|
+
return element.key;
|
|
105
|
+
element.key = generateId(compilationContext);
|
|
106
|
+
return element.key;
|
|
107
|
+
}
|
|
108
|
+
function generatePageCompilationContext(pathname) {
|
|
109
|
+
pathname = sanitizePathname(pathname);
|
|
110
|
+
return {
|
|
111
|
+
pathname: pathname,
|
|
112
|
+
idCounter: 0,
|
|
113
|
+
usedHashes: [],
|
|
114
|
+
kind: "page",
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
function generateLayoutCompilationContext(pathname) {
|
|
118
|
+
pathname = sanitizePathname(pathname);
|
|
119
|
+
const absPath = path.join(getDistDir(), pathname);
|
|
120
|
+
if (!existsSync(absPath))
|
|
121
|
+
mkdirSync(absPath, { recursive: true });
|
|
122
|
+
return {
|
|
123
|
+
pathname: pathname,
|
|
124
|
+
idCounter: 0,
|
|
125
|
+
usedHashes: [],
|
|
126
|
+
kind: "layout",
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
const builtPackages = new Map();
|
|
130
|
+
/**
|
|
131
|
+
* Make a set list of package available in the browser.
|
|
132
|
+
* Each package will be bundled individually, and placed in publicDirectory/DIST/__packages/, under their respective globalname + ".js".
|
|
133
|
+
*
|
|
134
|
+
*
|
|
135
|
+
* Note that this will bundle the *entire* library, which can be quite large.
|
|
136
|
+
*
|
|
137
|
+
* Check if the library you're shipping has a browser-friendly version for use.
|
|
138
|
+
* Each package is only ever built once, but every file that wants to use the package must re-call clientPackages(), to ensure the appropriate script-tag is added to the page's HTML.
|
|
139
|
+
*
|
|
140
|
+
* If a layout calls clientPackages, thus generating the <script> tag, you should not call it in the page, since it's unnecessary.
|
|
141
|
+
*
|
|
142
|
+
* **NOTE:** This currently only works with JS files and node_modules packages. ESBuild cannot resolve local typescript files.
|
|
143
|
+
* @param packages Key-value pair of globalName and packagePath.
|
|
144
|
+
*/
|
|
145
|
+
class ShippedPackage {
|
|
146
|
+
constructor(globalName, packagePath) {
|
|
147
|
+
this.globalName = globalName;
|
|
148
|
+
this.packagePath = packagePath;
|
|
149
|
+
}
|
|
150
|
+
serialize() {
|
|
151
|
+
return `<script data-package="true" src="/__packages/${this.globalName}.js"></script>`;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Ship any `node_modules` package to the browser.
|
|
156
|
+
* @param packages The packages to register for shipping to the browser.
|
|
157
|
+
*/
|
|
158
|
+
function clientPackages(packages) {
|
|
159
|
+
for (const [globalName, packagePath] of Object.entries(packages)) {
|
|
160
|
+
const store = compilerStore.getStore();
|
|
161
|
+
if (!store) {
|
|
162
|
+
throw formattedLog(LogLevel.ERROR, "Invalid invocation of clientPackages(). Ensure this function is never called outside of a page or layout constructor.");
|
|
163
|
+
}
|
|
164
|
+
const shippedPackage = new ShippedPackage(globalName, packagePath);
|
|
165
|
+
store?.addClientToken(shippedPackage);
|
|
166
|
+
if (builtPackages.has(globalName + packagePath)) {
|
|
167
|
+
continue;
|
|
168
|
+
}
|
|
169
|
+
const fullPath = packagePath;
|
|
170
|
+
esbuild.build({
|
|
171
|
+
entryPoints: [fullPath],
|
|
172
|
+
bundle: true,
|
|
173
|
+
outfile: path.join(getDistDir(), "__packages", globalName + ".js"),
|
|
174
|
+
format: "iife",
|
|
175
|
+
platform: "browser",
|
|
176
|
+
globalName: globalName,
|
|
177
|
+
loader: {
|
|
178
|
+
".ts": "ts",
|
|
179
|
+
".js": "js",
|
|
180
|
+
".cjs": "js",
|
|
181
|
+
".mjs": "js",
|
|
182
|
+
},
|
|
183
|
+
footer: {
|
|
184
|
+
"js": `;window["${globalName}"}=${globalName};`,
|
|
185
|
+
},
|
|
186
|
+
minify: true,
|
|
187
|
+
treeShaking: true,
|
|
188
|
+
}).catch((error) => {
|
|
189
|
+
formattedLog(LogLevel.ERROR, "Failed to ship package \"", globalName, "\".");
|
|
190
|
+
console.error(error);
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Convert any option of an element into a string,
|
|
196
|
+
* for style attributes, it will inline them into 1 string if they're an object.
|
|
197
|
+
*
|
|
198
|
+
* Will turn className into class.
|
|
199
|
+
* @param key The key of the KV pair
|
|
200
|
+
* @param value The value of the KV pair
|
|
201
|
+
* @returns Serialized value
|
|
202
|
+
*/
|
|
203
|
+
function serializeProp(key, value) {
|
|
204
|
+
if (key === "class" || key === "className") {
|
|
205
|
+
if (!value)
|
|
206
|
+
return "";
|
|
207
|
+
return ` class="${String(value)}"`;
|
|
208
|
+
}
|
|
209
|
+
if (key === "style") {
|
|
210
|
+
if (!value)
|
|
211
|
+
return "";
|
|
212
|
+
if (typeof value === "string") {
|
|
213
|
+
return ` style="${value}"`;
|
|
214
|
+
}
|
|
215
|
+
if (typeof value === "object") {
|
|
216
|
+
const styleString = Object.entries(value)
|
|
217
|
+
.map(([k, v]) => `${k.replace(/[A-Z]/g, m => "-" + m.toLowerCase())}:${v}`)
|
|
218
|
+
.join(";");
|
|
219
|
+
return styleString ? ` style="${styleString}"` : "";
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
if (typeof value === "function") {
|
|
223
|
+
return "";
|
|
224
|
+
}
|
|
225
|
+
if (typeof value === "boolean") {
|
|
226
|
+
return value ? ` ${key}` : "";
|
|
227
|
+
}
|
|
228
|
+
if (value == null) {
|
|
229
|
+
return "";
|
|
230
|
+
}
|
|
231
|
+
return ` ${key}="${String(value)}"`;
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* Prefere to call `serializeElement()`, which calls this function if it encounters an elegance element.
|
|
235
|
+
*/
|
|
236
|
+
function serializeEleganceElement(compilationContext, element, path = []) {
|
|
237
|
+
let serializedElement = "";
|
|
238
|
+
let specialElementOptions = [];
|
|
239
|
+
serializedElement += `<${element.tag}`;
|
|
240
|
+
{
|
|
241
|
+
const entries = Object.entries(element.options);
|
|
242
|
+
for (const [optionName, optionValue] of entries) {
|
|
243
|
+
if (optionValue instanceof SpecialElementOption) {
|
|
244
|
+
optionValue.mutate(element, optionName);
|
|
245
|
+
const elementKey = getElementKey(compilationContext, element);
|
|
246
|
+
specialElementOptions.push({ elementKey, optionName, optionValue });
|
|
247
|
+
}
|
|
248
|
+
else {
|
|
249
|
+
serializedElement += serializeProp(optionName, optionValue);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
if (element.key) {
|
|
254
|
+
serializedElement += ` key="${element.key}"`;
|
|
255
|
+
}
|
|
256
|
+
serializedElement += ">";
|
|
257
|
+
{
|
|
258
|
+
if (element.children === null) {
|
|
259
|
+
return { serializedElement, specialElementOptions };
|
|
260
|
+
}
|
|
261
|
+
if (element.children.length > 0) {
|
|
262
|
+
for (const child of element.children) {
|
|
263
|
+
const result = serializeElement(compilationContext, child, path);
|
|
264
|
+
serializedElement += result.serializedElement;
|
|
265
|
+
specialElementOptions.push(...result.specialElementOptions);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
serializedElement += `</${element.tag}>`;
|
|
270
|
+
return { serializedElement, specialElementOptions };
|
|
271
|
+
}
|
|
272
|
+
/**
|
|
273
|
+
* Take any element, and turn it into a valid HTML string.
|
|
274
|
+
* Throw an error whenever an element is considered invalid.
|
|
275
|
+
* @param compilationContext The context of the page or layout that we're compiling
|
|
276
|
+
* @param element The element to serialize.
|
|
277
|
+
* @returns The serialized element, and any special options that were encountered.
|
|
278
|
+
*/
|
|
279
|
+
function serializeElement(compilationContext, element, path = []) {
|
|
280
|
+
const currentRepr = getElementRepr(element);
|
|
281
|
+
const fullPath = [...path, currentRepr];
|
|
282
|
+
let serializedElement;
|
|
283
|
+
let specialElementOptions = [];
|
|
284
|
+
if (element === undefined) {
|
|
285
|
+
return { serializedElement: "", specialElementOptions };
|
|
286
|
+
}
|
|
287
|
+
switch (typeof element) {
|
|
288
|
+
case "object":
|
|
289
|
+
if (Array.isArray(element)) {
|
|
290
|
+
let serializedElements = "";
|
|
291
|
+
for (const [index, subElement] of element.entries()) {
|
|
292
|
+
const childPath = [...path, `${currentRepr} at index ${index}`];
|
|
293
|
+
const serializationResult = serializeElement(compilationContext, subElement, childPath);
|
|
294
|
+
serializedElements += serializationResult.serializedElement;
|
|
295
|
+
specialElementOptions.push(...serializationResult.specialElementOptions);
|
|
296
|
+
}
|
|
297
|
+
serializedElement = serializedElements;
|
|
298
|
+
break;
|
|
299
|
+
}
|
|
300
|
+
if (element instanceof EleganceElement) {
|
|
301
|
+
const result = serializeEleganceElement(compilationContext, element, fullPath);
|
|
302
|
+
serializedElement = result.serializedElement;
|
|
303
|
+
specialElementOptions.push(...result.specialElementOptions);
|
|
304
|
+
break;
|
|
305
|
+
}
|
|
306
|
+
if (element instanceof ServerSubject) {
|
|
307
|
+
serializedElement = unwrapAllRaw(element.generateObserverNode());
|
|
308
|
+
break;
|
|
309
|
+
}
|
|
310
|
+
throw invalidElementError(element, fullPath, `This element is an arbitrary object, and arbitrary objects are not valid children. Please make sure all elements are one of: EleganceElement, boolean, number, string or Array.`);
|
|
311
|
+
case "boolean":
|
|
312
|
+
serializedElement = `${element}`;
|
|
313
|
+
break;
|
|
314
|
+
case "number":
|
|
315
|
+
serializedElement = element.toString();
|
|
316
|
+
break;
|
|
317
|
+
case "string":
|
|
318
|
+
serializedElement = unwrapAllRaw(element);
|
|
319
|
+
break;
|
|
320
|
+
default:
|
|
321
|
+
throw invalidElementError(element, fullPath, `The typeof of this element is not one of EleganceElement, boolean, number, string or Array. Please convert it into one of these types.`);
|
|
322
|
+
}
|
|
323
|
+
return { serializedElement, specialElementOptions };
|
|
324
|
+
}
|
|
325
|
+
/**
|
|
326
|
+
* Prettify any value
|
|
327
|
+
* @param obj The value you want to prettify
|
|
328
|
+
* @param level Indentation level of the value
|
|
329
|
+
* @returns Prettified value
|
|
330
|
+
*/
|
|
331
|
+
function prettyObj(obj, level = 0) {
|
|
332
|
+
const ind = ' '.repeat(level);
|
|
333
|
+
const entries = Object.entries(obj);
|
|
334
|
+
if (entries.length === 0)
|
|
335
|
+
return '{}';
|
|
336
|
+
let str = '{\n';
|
|
337
|
+
for (const [key, value] of entries) {
|
|
338
|
+
let valRepr;
|
|
339
|
+
if (typeof value === 'object' && value !== null) {
|
|
340
|
+
valRepr = prettyObj(value, level + 1);
|
|
341
|
+
}
|
|
342
|
+
else {
|
|
343
|
+
valRepr = JSON.stringify(value);
|
|
344
|
+
}
|
|
345
|
+
str += `${ind} ${key}: ${valRepr},\n`;
|
|
346
|
+
}
|
|
347
|
+
str = str.slice(0, -2); // remove trailing commas
|
|
348
|
+
str += `\n${ind}}`;
|
|
349
|
+
return str;
|
|
350
|
+
}
|
|
351
|
+
function getElementRepr(element) {
|
|
352
|
+
if (element === null)
|
|
353
|
+
return 'null';
|
|
354
|
+
if (element === undefined)
|
|
355
|
+
return 'undefined';
|
|
356
|
+
if (typeof element === 'string')
|
|
357
|
+
return `"${element.replace(/"/g, '\\"')}"`;
|
|
358
|
+
if (typeof element === 'number')
|
|
359
|
+
return element.toString();
|
|
360
|
+
if (typeof element === 'boolean')
|
|
361
|
+
return element.toString();
|
|
362
|
+
if (Array.isArray(element)) {
|
|
363
|
+
return `Array(length: ${element.length})`;
|
|
364
|
+
}
|
|
365
|
+
if (element instanceof EleganceElement) {
|
|
366
|
+
const tag = element.tag || 'unknown';
|
|
367
|
+
const options = element.options || {};
|
|
368
|
+
if (Object.keys(options).length === 0) {
|
|
369
|
+
return `${tag}({})`;
|
|
370
|
+
}
|
|
371
|
+
return `${tag}(${prettyObj(options)})`;
|
|
372
|
+
}
|
|
373
|
+
return `Unknown(${typeof element})`;
|
|
374
|
+
}
|
|
375
|
+
function formatStacktrace(path) {
|
|
376
|
+
let lastReprColumn = 0;
|
|
377
|
+
const result = [];
|
|
378
|
+
path.forEach((repr, index) => {
|
|
379
|
+
const isLast = index === path.length - 1;
|
|
380
|
+
const lines = repr.split('\n');
|
|
381
|
+
let prefix = '';
|
|
382
|
+
if (index > 0) {
|
|
383
|
+
prefix = ' '.repeat(lastReprColumn + 1) + '∟> ';
|
|
384
|
+
}
|
|
385
|
+
let firstLine = prefix + lines[0];
|
|
386
|
+
if (isLast) {
|
|
387
|
+
firstLine = prefix + `\x1b[41;30m${lines[0]}`;
|
|
388
|
+
}
|
|
389
|
+
result.push(firstLine);
|
|
390
|
+
const reprColumn = lines[0].search(/\S|$/);
|
|
391
|
+
lastReprColumn = prefix.length + reprColumn;
|
|
392
|
+
for (let i = 1; i < lines.length; i++) {
|
|
393
|
+
const subLine = ' '.repeat(prefix.length) + lines[i];
|
|
394
|
+
result.push(subLine);
|
|
395
|
+
}
|
|
396
|
+
if (isLast) {
|
|
397
|
+
result[result.length - 1] += `\x1b[0m`;
|
|
398
|
+
}
|
|
399
|
+
});
|
|
400
|
+
return result.join('\n');
|
|
401
|
+
}
|
|
402
|
+
/**
|
|
403
|
+
* This function uses string interpolation to transform client tokens and special element options into a script tag that is then sent to the client.
|
|
404
|
+
* The client tokens are not *fully* serialized, but the necessary components to re-create them as client versions of the corresponding thing *are* serialized.
|
|
405
|
+
* For example, the serialize() method of EventListener is not sent, but it's callback, id, and dependencies (as ids) are.
|
|
406
|
+
* String interpolation is dangerous and error-prone, so if you're going to add something to this function, ensure you know what you're doing,
|
|
407
|
+
* and make sure to also edit runtime.ts to handle the clientTokens that you send to the browser. I did not create separate datatypes in the runtime for the intermediary forms of things like EventListeners,
|
|
408
|
+
* LoadHooks, etc, for I did not feel it necessary, but do note that these types do not exactly line up 100%.
|
|
409
|
+
* @param compilationContext The current context of what we're compiling, can be either layout or page compilation context.
|
|
410
|
+
* @param specialElementOptions An array of special element options that were found during serialization of the elements of whatever we're currently compiling
|
|
411
|
+
* @param clientTokens An array of tokens that will be serialized and shipped within the pageDataScript.
|
|
412
|
+
* @returns A string containing the page data <script> tag.
|
|
413
|
+
*/
|
|
414
|
+
async function generatePageDataScript(compilationContext, specialElementOptions, clientTokens) {
|
|
415
|
+
let dataScriptContent = `export const data = {`;
|
|
416
|
+
const serverSubjects = clientTokens.filter(t => t instanceof ServerSubject);
|
|
417
|
+
const loadHooks = clientTokens.filter(t => t instanceof LoadHook);
|
|
418
|
+
const eventListeners = clientTokens.filter(t => t instanceof EventListener);
|
|
419
|
+
const serverObservers = clientTokens.filter(t => t instanceof ServerObserver);
|
|
420
|
+
const effects = clientTokens.filter(t => t instanceof Effect);
|
|
421
|
+
{
|
|
422
|
+
dataScriptContent += "subjects:[";
|
|
423
|
+
for (const serverSubject of serverSubjects) {
|
|
424
|
+
dataScriptContent += serverSubject.serialize() + ",";
|
|
425
|
+
}
|
|
426
|
+
dataScriptContent += "],";
|
|
427
|
+
}
|
|
428
|
+
{
|
|
429
|
+
dataScriptContent += "loadHooks:[";
|
|
430
|
+
for (const loadHook of loadHooks) {
|
|
431
|
+
dataScriptContent += loadHook.serialize();
|
|
432
|
+
dataScriptContent += ",";
|
|
433
|
+
}
|
|
434
|
+
dataScriptContent += "],";
|
|
435
|
+
}
|
|
436
|
+
{
|
|
437
|
+
dataScriptContent += "eventListeners:[";
|
|
438
|
+
for (const eventListener of eventListeners) {
|
|
439
|
+
dataScriptContent += eventListener.serialize();
|
|
440
|
+
dataScriptContent += ",";
|
|
441
|
+
}
|
|
442
|
+
dataScriptContent += "],";
|
|
443
|
+
}
|
|
444
|
+
{
|
|
445
|
+
dataScriptContent += "observers:[";
|
|
446
|
+
for (const serverObserver of serverObservers) {
|
|
447
|
+
dataScriptContent += serverObserver.serialize();
|
|
448
|
+
dataScriptContent += ",";
|
|
449
|
+
}
|
|
450
|
+
dataScriptContent += "],";
|
|
451
|
+
}
|
|
452
|
+
{
|
|
453
|
+
dataScriptContent += "effects:[";
|
|
454
|
+
for (const effect of effects) {
|
|
455
|
+
dataScriptContent += effect.serialize();
|
|
456
|
+
dataScriptContent += ",";
|
|
457
|
+
}
|
|
458
|
+
dataScriptContent += "],";
|
|
459
|
+
}
|
|
460
|
+
{
|
|
461
|
+
const eventListenerOptions = specialElementOptions.filter(seo => seo.optionValue instanceof EventListenerOption);
|
|
462
|
+
dataScriptContent += "eventListenerOptions:[";
|
|
463
|
+
for (const eventListenerOption of eventListenerOptions) {
|
|
464
|
+
const eventListener = eventListenerOption.optionValue;
|
|
465
|
+
const optionName = eventListenerOption.optionName;
|
|
466
|
+
const elementKey = eventListenerOption.elementKey;
|
|
467
|
+
const result = eventListener.serialize(optionName, elementKey);
|
|
468
|
+
dataScriptContent += `${result},`;
|
|
469
|
+
}
|
|
470
|
+
dataScriptContent += "],";
|
|
471
|
+
}
|
|
472
|
+
{
|
|
473
|
+
const observerOptions = specialElementOptions.filter(seo => seo.optionValue instanceof ObserverOption);
|
|
474
|
+
dataScriptContent += "observerOptions:[";
|
|
475
|
+
for (const observerOption of observerOptions) {
|
|
476
|
+
const observer = observerOption.optionValue;
|
|
477
|
+
const optionName = observerOption.optionName;
|
|
478
|
+
const elementKey = observerOption.elementKey;
|
|
479
|
+
const result = observer.serialize(optionName, elementKey);
|
|
480
|
+
dataScriptContent += `${result},`;
|
|
481
|
+
}
|
|
482
|
+
dataScriptContent += "]";
|
|
483
|
+
}
|
|
484
|
+
dataScriptContent += "};\n";
|
|
485
|
+
const transformedResult = await esbuild.transform(dataScriptContent, { minify: true, });
|
|
486
|
+
dataScriptContent = transformedResult.code;
|
|
487
|
+
let dataScript = `<script data-hook="true" data-pathname="${compilationContext.pathname}" type="text/plain">${dataScriptContent}</script>`;
|
|
488
|
+
let dataLoaderScript = `<script>
|
|
489
|
+
const dataScript = document.querySelector('[data-hook="true"][data-pathname="${compilationContext.pathname}"][type="text/plain"');
|
|
490
|
+
const text = dataScript.textContent;
|
|
491
|
+
dataScript.remove();
|
|
492
|
+
const blob = new Blob([text], { type: 'text/javascript' });
|
|
493
|
+
const url = URL.createObjectURL(blob);
|
|
494
|
+
|
|
495
|
+
const script = document.createElement("script");
|
|
496
|
+
script.src = url;
|
|
497
|
+
script.type = "module";
|
|
498
|
+
script.setAttribute("data-page", "true");
|
|
499
|
+
script.setAttribute("data-pathname", "${compilationContext.pathname}");
|
|
500
|
+
|
|
501
|
+
document.head.appendChild(script);
|
|
502
|
+
|
|
503
|
+
document.currentScript.remove();
|
|
504
|
+
</script>`.replace(" ", "").replace("\n", "");
|
|
505
|
+
const shippedPackages = clientTokens.filter(t => t instanceof ShippedPackage);
|
|
506
|
+
let packagesString = "";
|
|
507
|
+
for (const shippedPackage of shippedPackages) {
|
|
508
|
+
packagesString += shippedPackage.serialize();
|
|
509
|
+
}
|
|
510
|
+
return packagesString + dataScript + dataLoaderScript;
|
|
511
|
+
}
|
|
512
|
+
/**
|
|
513
|
+
* Go through a directory, including all it's subdirectories,
|
|
514
|
+
* and call callback() for each file.
|
|
515
|
+
*/
|
|
516
|
+
async function walkDirectory(fullPath, callback) {
|
|
517
|
+
const stack = [];
|
|
518
|
+
stack.push(...readdirSync(fullPath, { withFileTypes: true, }));
|
|
519
|
+
while (true) {
|
|
520
|
+
const entry = stack.pop();
|
|
521
|
+
if (!entry)
|
|
522
|
+
break;
|
|
523
|
+
if (entry.isDirectory()) {
|
|
524
|
+
const fullPath = path.join(entry.parentPath, entry.name);
|
|
525
|
+
stack.push(...readdirSync(fullPath, { withFileTypes: true, }));
|
|
526
|
+
continue;
|
|
527
|
+
}
|
|
528
|
+
if (!entry.isFile())
|
|
529
|
+
continue;
|
|
530
|
+
await callback(entry);
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
/**
|
|
534
|
+
* Get a structured, validated and formatted version of the exports of a page.ts file
|
|
535
|
+
* This file *should* be the first thing that imports a page.
|
|
536
|
+
*/
|
|
537
|
+
async function getPageExports(modulePath) {
|
|
538
|
+
const rawExports = await import("file://" + modulePath).catch((err) => {
|
|
539
|
+
console.error(`Encountered an error in file:\n ${modulePath}`);
|
|
540
|
+
throw err;
|
|
541
|
+
});
|
|
542
|
+
let isDynamic = rawExports?.isDynamic === true;
|
|
543
|
+
const pageConstructor = rawExports.page;
|
|
544
|
+
{
|
|
545
|
+
if (pageConstructor === undefined) {
|
|
546
|
+
throw invalidPageError(compilerOptions, modulePath, "This page does note export a `page` function. Did you forget the keyword `export`?");
|
|
547
|
+
}
|
|
548
|
+
if (typeof pageConstructor !== "function") {
|
|
549
|
+
throw invalidPageError(compilerOptions, modulePath, "The type of the export \"page\" is not a function, and is instead of type: " + typeof pageConstructor);
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
const pageMetadataConstructor = rawExports.metadata;
|
|
553
|
+
{
|
|
554
|
+
if (pageMetadataConstructor === undefined) {
|
|
555
|
+
throw invalidPageError(compilerOptions, modulePath, "This page does note export a `metadata` function. Did you forget the keyword `export`?");
|
|
556
|
+
}
|
|
557
|
+
if (typeof pageMetadataConstructor !== "function") {
|
|
558
|
+
throw invalidPageError(compilerOptions, modulePath, "The type of the export \"metadata\" is not a function, and is instead of type: " + typeof pageMetadataConstructor);
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
const enumerateRoutes = rawExports.enumerateRoutes ?? null;
|
|
562
|
+
return {
|
|
563
|
+
isDynamic,
|
|
564
|
+
pageConstructor,
|
|
565
|
+
pageMetadataConstructor,
|
|
566
|
+
enumerateRoutes
|
|
567
|
+
};
|
|
568
|
+
}
|
|
569
|
+
/**
|
|
570
|
+
* Get a structured, validated and formatted version of the exports of a layout.ts file
|
|
571
|
+
* This file *should* be the first thing that imports a page.
|
|
572
|
+
*/
|
|
573
|
+
async function getLayoutExports(modulePath) {
|
|
574
|
+
const rawExports = await import("file://" + modulePath);
|
|
575
|
+
let isDynamic = rawExports?.isDynamic === true;
|
|
576
|
+
const layoutConstructor = rawExports.layout;
|
|
577
|
+
{
|
|
578
|
+
if (layoutConstructor === undefined) {
|
|
579
|
+
throw invalidPageError(compilerOptions, modulePath, "This layout does note export a `layout` function. Did you forget the keyword `export`?");
|
|
580
|
+
}
|
|
581
|
+
if (typeof layoutConstructor !== "function") {
|
|
582
|
+
throw invalidLayoutError(compilerOptions, modulePath, "The type of the export \"layout\" is not a function, and is instead of type: " + typeof layoutConstructor);
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
const layoutMetadataConstructor = rawExports.metadata;
|
|
586
|
+
{
|
|
587
|
+
if (layoutMetadataConstructor === undefined) {
|
|
588
|
+
throw invalidPageError(compilerOptions, modulePath, "This layout does note export a `metadata` function. Did you forget the keyword `export`?");
|
|
589
|
+
}
|
|
590
|
+
if (typeof layoutMetadataConstructor !== "function") {
|
|
591
|
+
throw invalidLayoutError(compilerOptions, modulePath, "The type of the export \"metadata\" is not a function, and is instead of type: " + typeof layoutMetadataConstructor);
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
return {
|
|
595
|
+
isDynamic,
|
|
596
|
+
layoutConstructor,
|
|
597
|
+
layoutMetadataConstructor,
|
|
598
|
+
};
|
|
599
|
+
}
|
|
600
|
+
async function getApplicablePageLayouts(allLayouts, pagePathname) {
|
|
601
|
+
const pageLayouts = [];
|
|
602
|
+
for (const [_, layoutInformation] of allLayouts.entries()) {
|
|
603
|
+
const doesLayoutApply = pagePathname.startsWith(layoutInformation.pathname);
|
|
604
|
+
if (!doesLayoutApply)
|
|
605
|
+
continue;
|
|
606
|
+
pageLayouts.push(layoutInformation);
|
|
607
|
+
}
|
|
608
|
+
pageLayouts.sort((a, b) => a.pathname.length - b.pathname.length);
|
|
609
|
+
return pageLayouts;
|
|
610
|
+
}
|
|
611
|
+
function isPathPartDynamic(part) {
|
|
612
|
+
if (part.startsWith("[") && part.endsWith("]"))
|
|
613
|
+
return true;
|
|
614
|
+
if (part.startsWith("*") && part.endsWith("*"))
|
|
615
|
+
return true;
|
|
616
|
+
if (part.startsWith(":*") && part.endsWith("*"))
|
|
617
|
+
return true;
|
|
618
|
+
if (part.startsWith(":[") && part.endsWith("]"))
|
|
619
|
+
return true;
|
|
620
|
+
return false;
|
|
621
|
+
}
|
|
622
|
+
async function generatePageInformation(file, allLayouts) {
|
|
623
|
+
const fullPath = path.join(file.parentPath, file.name);
|
|
624
|
+
const pathname = sanitizePathname(path.relative(compilerOptions.pagesDirectory, file.parentPath));
|
|
625
|
+
const exports = await getPageExports(fullPath);
|
|
626
|
+
const applicablePageLayouts = await getApplicablePageLayouts(allLayouts, pathname);
|
|
627
|
+
const parts = pathname === "/" ? [""] : pathname.split("/");
|
|
628
|
+
let containsCatchAllParts = false;
|
|
629
|
+
for (const part of parts) {
|
|
630
|
+
if (isPathPartDynamic(part)) {
|
|
631
|
+
containsCatchAllParts = true;
|
|
632
|
+
break;
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
// static pages are only allowed on pages that have statically determinable possible routes.
|
|
636
|
+
if (containsCatchAllParts && exports.isDynamic === false) {
|
|
637
|
+
if (exports.enumerateRoutes === null) {
|
|
638
|
+
throw invalidPageError(compilerOptions, fullPath, "A page that uses a catch-all route, eg. /[product] | /*product* must either be a dynamic page, or must specify an enumerateRoutes() function.");
|
|
639
|
+
}
|
|
640
|
+
const enumeratedRoutes = exports.enumerateRoutes();
|
|
641
|
+
const pageInformationArray = [];
|
|
642
|
+
for (const route of enumeratedRoutes) {
|
|
643
|
+
const staticParts = route === "/" ? [""] : route.split("/");
|
|
644
|
+
const pageInformation = {
|
|
645
|
+
modulePath: fullPath,
|
|
646
|
+
exports: exports,
|
|
647
|
+
pathname: route,
|
|
648
|
+
applicableLayouts: applicablePageLayouts,
|
|
649
|
+
pathnameParts: staticParts,
|
|
650
|
+
};
|
|
651
|
+
pageInformationArray.push(pageInformation);
|
|
652
|
+
}
|
|
653
|
+
return pageInformationArray;
|
|
654
|
+
}
|
|
655
|
+
const pageInformation = {
|
|
656
|
+
modulePath: fullPath,
|
|
657
|
+
exports: exports,
|
|
658
|
+
pathname: pathname,
|
|
659
|
+
applicableLayouts: applicablePageLayouts,
|
|
660
|
+
pathnameParts: parts,
|
|
661
|
+
};
|
|
662
|
+
return pageInformation;
|
|
663
|
+
}
|
|
664
|
+
async function gatherAllPages(allLayouts) {
|
|
665
|
+
const pageMap = new Map();
|
|
666
|
+
await walkDirectory(compilerOptions.pagesDirectory, async (file) => {
|
|
667
|
+
if (file.name !== "page.ts")
|
|
668
|
+
return;
|
|
669
|
+
const pageInformation = await generatePageInformation(file, allLayouts);
|
|
670
|
+
if (Array.isArray(pageInformation)) {
|
|
671
|
+
for (const info of pageInformation) {
|
|
672
|
+
pageMap.set(info.pathname, info);
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
else {
|
|
676
|
+
pageMap.set(pageInformation.pathname, pageInformation);
|
|
677
|
+
}
|
|
678
|
+
});
|
|
679
|
+
return pageMap;
|
|
680
|
+
}
|
|
681
|
+
async function gatherAllStatusCodePages(allLayouts) {
|
|
682
|
+
const pageMap = new Map();
|
|
683
|
+
await walkDirectory(compilerOptions.pagesDirectory, async (file) => {
|
|
684
|
+
const re = /\b\d{3}\.ts\b/;
|
|
685
|
+
if (re.test(file.name) === false)
|
|
686
|
+
return;
|
|
687
|
+
const code = file.name.slice(0, file.name.length - 3);
|
|
688
|
+
const fullPath = path.join(file.parentPath, file.name);
|
|
689
|
+
const pathname = sanitizePathname(path.relative(compilerOptions.pagesDirectory, file.parentPath));
|
|
690
|
+
const exports = await getPageExports(fullPath);
|
|
691
|
+
const applicablePageLayouts = await getApplicablePageLayouts(allLayouts, pathname);
|
|
692
|
+
const parts = pathname === "/" ? [""] : pathname.split("/");
|
|
693
|
+
let containsCatchAllParts = false;
|
|
694
|
+
for (const part of parts) {
|
|
695
|
+
if (isPathPartDynamic(part)) {
|
|
696
|
+
containsCatchAllParts = true;
|
|
697
|
+
break;
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
if (containsCatchAllParts && exports.isDynamic === false) {
|
|
701
|
+
throw invalidPageError(compilerOptions, fullPath, "A page that uses a catch-all route, eg. [product] | *product* must be dynamic, since it depends on the request pathname. Set `export const isDynamic` to true.");
|
|
702
|
+
}
|
|
703
|
+
const pageInformation = {
|
|
704
|
+
modulePath: fullPath,
|
|
705
|
+
exports: exports,
|
|
706
|
+
pathname: pathname + code,
|
|
707
|
+
applicableLayouts: applicablePageLayouts,
|
|
708
|
+
pathnameParts: parts,
|
|
709
|
+
};
|
|
710
|
+
pageMap.set(pathname + code, pageInformation);
|
|
711
|
+
});
|
|
712
|
+
return pageMap;
|
|
713
|
+
}
|
|
714
|
+
async function gatherAllLayouts() {
|
|
715
|
+
const layoutMap = new Map();
|
|
716
|
+
await walkDirectory(compilerOptions.pagesDirectory, async (file) => {
|
|
717
|
+
if (file.name !== "layout.ts")
|
|
718
|
+
return;
|
|
719
|
+
const fullPath = path.join(file.parentPath, file.name);
|
|
720
|
+
const pathname = sanitizePathname(path.relative(compilerOptions.pagesDirectory, file.parentPath));
|
|
721
|
+
const exports = await getLayoutExports(fullPath);
|
|
722
|
+
const layoutInformation = {
|
|
723
|
+
modulePath: fullPath,
|
|
724
|
+
exports: exports,
|
|
725
|
+
pathname: pathname,
|
|
726
|
+
};
|
|
727
|
+
layoutMap.set(pathname, layoutInformation);
|
|
728
|
+
});
|
|
729
|
+
return layoutMap;
|
|
730
|
+
}
|
|
731
|
+
const compiledStaticLayouts = new Map();
|
|
732
|
+
async function getCompiledLayout(layoutInformation, allLayouts, reqRes = {}) {
|
|
733
|
+
if (layoutInformation.exports.isDynamic === true) {
|
|
734
|
+
return await compileLayout(layoutInformation, allLayouts, reqRes);
|
|
735
|
+
}
|
|
736
|
+
if (compiledStaticLayouts.has(layoutInformation.pathname)) {
|
|
737
|
+
return compiledStaticLayouts.get(layoutInformation.pathname);
|
|
738
|
+
}
|
|
739
|
+
const compiledLayout = await compileLayout(layoutInformation, allLayouts, reqRes);
|
|
740
|
+
compiledStaticLayouts.set(layoutInformation.pathname, compiledLayout);
|
|
741
|
+
return compiledLayout;
|
|
742
|
+
}
|
|
743
|
+
async function compilePageToDisk(allLayouts, pageInformation) {
|
|
744
|
+
const compiledPage = await compilePage(allLayouts, pageInformation);
|
|
745
|
+
const absPath = path.join(getDistDir(), pageInformation.pathname);
|
|
746
|
+
if (!existsSync(absPath))
|
|
747
|
+
mkdirSync(absPath, { recursive: true });
|
|
748
|
+
const targetPath = path.join(getDistDir(), pageInformation.pathname, "index.html");
|
|
749
|
+
writeFileSync(targetPath, compiledPage.pageHTML);
|
|
750
|
+
return compiledPage;
|
|
751
|
+
}
|
|
752
|
+
/** Returns the standard children of <head> that must exist on every page independent of it's content. */
|
|
753
|
+
function getEnforcedMetadata() {
|
|
754
|
+
return `
|
|
755
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
756
|
+
<meta charset="utf-8"><script defer="true" src="/client.js"></script>`;
|
|
757
|
+
}
|
|
758
|
+
async function compilePage(allLayouts, pageInformation, reqRes = {}, extraParams = {}) {
|
|
759
|
+
const compilationContext = generatePageCompilationContext(pageInformation.pathname);
|
|
760
|
+
const exports = pageInformation.exports;
|
|
761
|
+
const pageConstructor = exports.pageConstructor;
|
|
762
|
+
const pageMetadataConstructor = exports.pageMetadataConstructor;
|
|
763
|
+
/**
|
|
764
|
+
* Populate compilerStore.
|
|
765
|
+
* Usage of AsyncLocalStorage allows us to have "globals" without globals,
|
|
766
|
+
* as each call to run has it's own "context",
|
|
767
|
+
* removing concurrency issues.
|
|
768
|
+
*/
|
|
769
|
+
const clientTokens = [];
|
|
770
|
+
const storeTools = {
|
|
771
|
+
generateId: () => generateId(compilationContext),
|
|
772
|
+
addClientToken: (value) => {
|
|
773
|
+
clientTokens.push(value);
|
|
774
|
+
},
|
|
775
|
+
compilationContext,
|
|
776
|
+
req: reqRes.req,
|
|
777
|
+
res: reqRes.res,
|
|
778
|
+
};
|
|
779
|
+
// Pre-compile all applicable layouts so we can accumulate their props first
|
|
780
|
+
let compiledLayouts = [];
|
|
781
|
+
let allLayoutProps = {};
|
|
782
|
+
if (pageInformation.applicableLayouts.length > 0) {
|
|
783
|
+
for (const layout of pageInformation.applicableLayouts) {
|
|
784
|
+
compiledLayouts.push(await getCompiledLayout(layout, allLayouts, reqRes));
|
|
785
|
+
}
|
|
786
|
+
for (const compiledLayout of compiledLayouts) {
|
|
787
|
+
allLayoutProps = { ...allLayoutProps, ...compiledLayout.layoutProps };
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
const pageProps = { allLayoutProps, params: { page: pageInformation.pathname, extraParams, }, };
|
|
791
|
+
let pageRootElement = await compilerStore.run(storeTools, async () => {
|
|
792
|
+
return await pageConstructor(pageProps);
|
|
793
|
+
});
|
|
794
|
+
let pageRootMetadataElement = await compilerStore.run(storeTools, async () => {
|
|
795
|
+
return await pageMetadataConstructor(pageProps);
|
|
796
|
+
});
|
|
797
|
+
let pageSerializationResult;
|
|
798
|
+
let pageMetadataSerializationResult;
|
|
799
|
+
try {
|
|
800
|
+
pageSerializationResult = serializeElement(compilationContext, pageRootElement);
|
|
801
|
+
pageMetadataSerializationResult = serializeElement(compilationContext, pageRootMetadataElement);
|
|
802
|
+
}
|
|
803
|
+
catch (e) {
|
|
804
|
+
formattedLog(LogLevel.ERROR, `${pageInformation.pathname}/page.ts - Element serialization failed.`);
|
|
805
|
+
throw e;
|
|
806
|
+
}
|
|
807
|
+
const allClientTokens = [...clientTokens];
|
|
808
|
+
const allSpecialElementOptions = [
|
|
809
|
+
...pageSerializationResult.specialElementOptions,
|
|
810
|
+
...pageMetadataSerializationResult.specialElementOptions,
|
|
811
|
+
];
|
|
812
|
+
for (const compiledLayout of compiledLayouts) {
|
|
813
|
+
allClientTokens.push(...compiledLayout.clientTokens);
|
|
814
|
+
allSpecialElementOptions.push(...compiledLayout.specialElementOptions);
|
|
815
|
+
}
|
|
816
|
+
const pageHTML = pageSerializationResult.serializedElement;
|
|
817
|
+
const pageMetadataHTML = pageMetadataSerializationResult.serializedElement;
|
|
818
|
+
let finalHTML = "<!DOCTYPE html>";
|
|
819
|
+
if (pageInformation.applicableLayouts.length > 0) {
|
|
820
|
+
const compiledRootLayout = compiledLayouts[0];
|
|
821
|
+
const htmlTagIndex = compiledRootLayout.layoutHTMLStart.indexOf("<html");
|
|
822
|
+
const htmlTagEndIndex = compiledRootLayout.layoutHTMLStart.indexOf(">");
|
|
823
|
+
if (htmlTagIndex === -1 || htmlTagEndIndex === -1) {
|
|
824
|
+
throw invalidLayoutError(compilerOptions, pageInformation.applicableLayouts[0].modulePath, "The root layout must start with an html() element.");
|
|
825
|
+
}
|
|
826
|
+
if (compiledRootLayout.layoutHTMLStart.includes("<body") === false) {
|
|
827
|
+
throw invalidLayoutError(compilerOptions, pageInformation.applicableLayouts[0].modulePath, "The root layout must contain the body() element.");
|
|
828
|
+
}
|
|
829
|
+
const beforeHead = compiledRootLayout.layoutHTMLStart.substring(0, htmlTagEndIndex + 1);
|
|
830
|
+
const afterHead = compiledRootLayout.layoutHTMLStart.substring(htmlTagEndIndex + 1);
|
|
831
|
+
// head content is (enforced + all layouts' metadata + page metadata)
|
|
832
|
+
let headContent = "";
|
|
833
|
+
{
|
|
834
|
+
headContent += "<head>";
|
|
835
|
+
headContent += getEnforcedMetadata();
|
|
836
|
+
for (const compiledLayout of compiledLayouts) {
|
|
837
|
+
headContent += compiledLayout.layoutMetadataHTML;
|
|
838
|
+
}
|
|
839
|
+
headContent += pageMetadataHTML;
|
|
840
|
+
headContent += "</head>";
|
|
841
|
+
}
|
|
842
|
+
finalHTML += beforeHead + headContent + afterHead;
|
|
843
|
+
// (skip root obviously)
|
|
844
|
+
for (let i = 1; i < compiledLayouts.length; i++) {
|
|
845
|
+
finalHTML += compiledLayouts[i].layoutHTMLStart;
|
|
846
|
+
}
|
|
847
|
+
finalHTML += pageHTML;
|
|
848
|
+
let endString = "";
|
|
849
|
+
for (const compiledLayout of [...compiledLayouts].reverse()) {
|
|
850
|
+
endString += compiledLayout.layoutHTMLEnd;
|
|
851
|
+
}
|
|
852
|
+
const htmlEndTagIndex = endString.indexOf("</body>");
|
|
853
|
+
if (htmlEndTagIndex === -1) {
|
|
854
|
+
throw internalCompilerError("Failed to find </body> tag whilst compiling a page");
|
|
855
|
+
}
|
|
856
|
+
const beforeEndTag = endString.substring(0, htmlEndTagIndex);
|
|
857
|
+
const afterEndTag = endString.substring(htmlEndTagIndex);
|
|
858
|
+
const pageDataScript = await generatePageDataScript(compilationContext, allSpecialElementOptions, allClientTokens);
|
|
859
|
+
finalHTML += beforeEndTag + pageDataScript + afterEndTag;
|
|
860
|
+
}
|
|
861
|
+
else {
|
|
862
|
+
finalHTML += `<html lang="en-us">`;
|
|
863
|
+
finalHTML += "<head>";
|
|
864
|
+
finalHTML += getEnforcedMetadata();
|
|
865
|
+
finalHTML += pageMetadataHTML;
|
|
866
|
+
finalHTML += "</head>";
|
|
867
|
+
finalHTML += "<body>";
|
|
868
|
+
finalHTML += pageHTML;
|
|
869
|
+
const pageDataScript = await generatePageDataScript(compilationContext, allSpecialElementOptions, allClientTokens);
|
|
870
|
+
finalHTML += pageDataScript;
|
|
871
|
+
finalHTML += "</body>";
|
|
872
|
+
finalHTML += `</html>`;
|
|
873
|
+
}
|
|
874
|
+
const compiledPage = {
|
|
875
|
+
pageHTML: finalHTML,
|
|
876
|
+
};
|
|
877
|
+
return compiledPage;
|
|
878
|
+
}
|
|
879
|
+
async function compileStaticPages(allLayouts, allPages) {
|
|
880
|
+
const compiledPages = new Map();
|
|
881
|
+
for (const [pagePathname, pageInformation] of allPages) {
|
|
882
|
+
if (pageInformation.exports.isDynamic === true)
|
|
883
|
+
continue;
|
|
884
|
+
const compiledPage = await compilePage(allLayouts, pageInformation);
|
|
885
|
+
compiledPages.set(pagePathname, compiledPage);
|
|
886
|
+
}
|
|
887
|
+
return compiledPages;
|
|
888
|
+
}
|
|
889
|
+
async function compileStaticPagesToDisk(allLayouts, allPages) {
|
|
890
|
+
const compiledPages = new Map();
|
|
891
|
+
for (const [pagePathname, pageInformation] of allPages) {
|
|
892
|
+
if (pageInformation.exports.isDynamic === true)
|
|
893
|
+
continue;
|
|
894
|
+
const compiledPage = await compilePageToDisk(allLayouts, pageInformation);
|
|
895
|
+
compiledPages.set(pagePathname, compiledPage);
|
|
896
|
+
}
|
|
897
|
+
return compiledPages;
|
|
898
|
+
}
|
|
899
|
+
async function compileLayoutToDisk(layoutInformation, allLayouts) {
|
|
900
|
+
const compiledLayout = await compileLayout(layoutInformation, allLayouts);
|
|
901
|
+
const directory = path.join(getDistDir(), layoutInformation.pathname);
|
|
902
|
+
const htmlFullPath = path.join(directory, "layout.html");
|
|
903
|
+
const htmlMetadataFullPath = path.join(directory, "layout_metadata.html");
|
|
904
|
+
const jsFullPath = path.join(directory, "layout_data.js");
|
|
905
|
+
writeFileSync(htmlFullPath, compiledLayout.layoutHTMLStart + compiledLayout.layoutHTMLEnd);
|
|
906
|
+
writeFileSync(htmlMetadataFullPath, compiledLayout.layoutMetadataHTML);
|
|
907
|
+
writeFileSync(jsFullPath, compiledLayout.specialElementOptions.join(","));
|
|
908
|
+
}
|
|
909
|
+
async function compileLayout(layoutInformation, allLayouts, reqRes = {}) {
|
|
910
|
+
const compilationContext = generateLayoutCompilationContext(layoutInformation.pathname);
|
|
911
|
+
/**
|
|
912
|
+
* Get this layout's parent layout's props.
|
|
913
|
+
* Note that this happens recursively, so if you have a layout at:
|
|
914
|
+
* /my-site/blog/layout.ts, it will get the parent props from /my-site/layout.ts,
|
|
915
|
+
* which will get *it's* parent props from /layout.ts.
|
|
916
|
+
*
|
|
917
|
+
* This *could* be inefficient if layouts are not rendered in-order, meaning least-depth to most depth,
|
|
918
|
+
* but they are, so the performance hit is negligeble.
|
|
919
|
+
*/
|
|
920
|
+
let parentLayoutProps = {};
|
|
921
|
+
{
|
|
922
|
+
const parentLayout = sanitizePathname(path.dirname(layoutInformation.pathname));
|
|
923
|
+
// prevents infinite recursion, because the pathname /'s dirname is /.
|
|
924
|
+
const isSameLayout = layoutInformation.pathname === parentLayout;
|
|
925
|
+
if (!isSameLayout && allLayouts.has(parentLayout)) {
|
|
926
|
+
const compiledParent = await getCompiledLayout(allLayouts.get(parentLayout), allLayouts, reqRes);
|
|
927
|
+
parentLayoutProps = compiledParent.layoutProps;
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
const exports = layoutInformation.exports;
|
|
931
|
+
const layoutConstructor = exports.layoutConstructor;
|
|
932
|
+
const layoutMetadataConstructor = exports.layoutMetadataConstructor;
|
|
933
|
+
/**
|
|
934
|
+
* Use a marker element to denote the separation point of the start and end of the layout.
|
|
935
|
+
*/
|
|
936
|
+
const layoutId = generateLayoutId(layoutInformation);
|
|
937
|
+
const markerElement = `<template layout-id="${layoutId}"></template>`;
|
|
938
|
+
const wrappedElement = raw(markerElement);
|
|
939
|
+
/**
|
|
940
|
+
* Populate compilerStore.
|
|
941
|
+
* Usage of AsyncLocalStorage allows us to have "globals" without globals,
|
|
942
|
+
* as each call to run has it's own "context", removing concurrency issues.
|
|
943
|
+
*/
|
|
944
|
+
const clientTokens = [];
|
|
945
|
+
const storeTools = {
|
|
946
|
+
generateId: () => generateId(compilationContext),
|
|
947
|
+
addClientToken: (value) => {
|
|
948
|
+
clientTokens.push(value);
|
|
949
|
+
},
|
|
950
|
+
compilationContext,
|
|
951
|
+
req: reqRes.req,
|
|
952
|
+
res: reqRes.res,
|
|
953
|
+
};
|
|
954
|
+
let layoutProps = {};
|
|
955
|
+
const propPasser = (props) => {
|
|
956
|
+
layoutProps = {
|
|
957
|
+
...layoutProps,
|
|
958
|
+
...props,
|
|
959
|
+
};
|
|
960
|
+
return wrappedElement;
|
|
961
|
+
};
|
|
962
|
+
let layoutRootElement = await compilerStore.run(storeTools, async () => await layoutConstructor({ props: parentLayoutProps, child: propPasser, }));
|
|
963
|
+
let layoutRootMetadataElement = await compilerStore.run(storeTools, async () => await layoutMetadataConstructor());
|
|
964
|
+
let layoutSerializationResult;
|
|
965
|
+
let layoutMetadataSerializationResult;
|
|
966
|
+
try {
|
|
967
|
+
layoutSerializationResult = serializeElement(compilationContext, layoutRootElement);
|
|
968
|
+
layoutMetadataSerializationResult = serializeElement(compilationContext, layoutRootMetadataElement);
|
|
969
|
+
}
|
|
970
|
+
catch (e) {
|
|
971
|
+
console.error(`${layoutInformation.pathname}/layout.ts - Failed to serialize elements.`);
|
|
972
|
+
throw e;
|
|
973
|
+
}
|
|
974
|
+
const layoutHTML = layoutSerializationResult.serializedElement;
|
|
975
|
+
const layoutMetadataHTML = layoutMetadataSerializationResult.serializedElement;
|
|
976
|
+
const layoutHTMLMarkerElementIndex = layoutHTML.indexOf(markerElement);
|
|
977
|
+
// Ensure marker element is present.
|
|
978
|
+
if (layoutHTMLMarkerElementIndex === -1) {
|
|
979
|
+
throw invalidLayoutError(compilerOptions, layoutInformation.modulePath, "The marker element was not found in the compiled layout HTML. Make sure to use the \"child\" paramater passed into the layout function.");
|
|
980
|
+
}
|
|
981
|
+
/**
|
|
982
|
+
* Split *after* the marker element, always leaving it at the layoutHTML start,
|
|
983
|
+
* this let's us replace all the children *after* it, for client-side navigation.
|
|
984
|
+
*/
|
|
985
|
+
const layoutHTMLStart = layoutHTML.slice(0, layoutHTMLMarkerElementIndex + markerElement.length);
|
|
986
|
+
const layoutHTMLEnd = layoutHTML.slice(layoutHTMLMarkerElementIndex + markerElement.length);
|
|
987
|
+
const specialElementOptions = [
|
|
988
|
+
...layoutSerializationResult.specialElementOptions,
|
|
989
|
+
...layoutMetadataSerializationResult.specialElementOptions
|
|
990
|
+
];
|
|
991
|
+
const compiledLayout = {
|
|
992
|
+
layoutHTMLStart: layoutHTMLStart,
|
|
993
|
+
layoutHTMLEnd: layoutHTMLEnd,
|
|
994
|
+
layoutMetadataHTML: layoutMetadataHTML,
|
|
995
|
+
specialElementOptions: specialElementOptions,
|
|
996
|
+
clientTokens: clientTokens,
|
|
997
|
+
layoutProps: layoutProps
|
|
998
|
+
};
|
|
999
|
+
return compiledLayout;
|
|
1000
|
+
}
|
|
1001
|
+
/**
|
|
1002
|
+
* Transpile the client runtime from typescript into javascript and place it into the dist directory
|
|
1003
|
+
*/
|
|
1004
|
+
async function transpileClientRuntime() {
|
|
1005
|
+
const clientTsPath = path.join(__dirname, "..", "client", "runtime.js");
|
|
1006
|
+
if (!existsSync(clientTsPath)) {
|
|
1007
|
+
throw internalCompilerError("Failed to find the client runtime at path:" + clientTsPath);
|
|
1008
|
+
}
|
|
1009
|
+
const targetPath = path.join(getDistDir(), "client.js");
|
|
1010
|
+
await esbuild.build({
|
|
1011
|
+
bundle: true,
|
|
1012
|
+
entryPoints: [clientTsPath],
|
|
1013
|
+
outfile: targetPath,
|
|
1014
|
+
format: "iife",
|
|
1015
|
+
platform: "browser",
|
|
1016
|
+
target: "esnext",
|
|
1017
|
+
minify: compilerOptions.environment === "production",
|
|
1018
|
+
external: ["util"],
|
|
1019
|
+
treeShaking: true,
|
|
1020
|
+
loader: {
|
|
1021
|
+
".ts": "ts",
|
|
1022
|
+
},
|
|
1023
|
+
dropLabels: [
|
|
1024
|
+
compilerOptions.environment === "production" ? "DEV_BUILD" : "PROD_BUILD"
|
|
1025
|
+
],
|
|
1026
|
+
define: compilerOptions.environment === "production" ? {
|
|
1027
|
+
"DEV_BUILD": "false",
|
|
1028
|
+
"PROD_BUILD": "true",
|
|
1029
|
+
} : {
|
|
1030
|
+
"PROD_BUILD": "false",
|
|
1031
|
+
"DEV_BUILD": "true",
|
|
1032
|
+
},
|
|
1033
|
+
sourcemap: compilerOptions.environment === "production" ? undefined : "inline",
|
|
1034
|
+
});
|
|
1035
|
+
}
|
|
1036
|
+
function createRecursiveWatcher(targetDir, callback) {
|
|
1037
|
+
const watchers = new Map();
|
|
1038
|
+
const timeouts = new Map();
|
|
1039
|
+
function debouncedCallback(fullPath) {
|
|
1040
|
+
const existingTimeout = timeouts.get(fullPath);
|
|
1041
|
+
if (existingTimeout) {
|
|
1042
|
+
clearTimeout(existingTimeout);
|
|
1043
|
+
}
|
|
1044
|
+
const timeout = setTimeout(async function () {
|
|
1045
|
+
try {
|
|
1046
|
+
await callback(fullPath);
|
|
1047
|
+
}
|
|
1048
|
+
catch (err) {
|
|
1049
|
+
console.error(err);
|
|
1050
|
+
}
|
|
1051
|
+
finally {
|
|
1052
|
+
timeouts.delete(fullPath);
|
|
1053
|
+
}
|
|
1054
|
+
}, 100);
|
|
1055
|
+
timeouts.set(fullPath, timeout);
|
|
1056
|
+
}
|
|
1057
|
+
function unregisterWatcher(path) {
|
|
1058
|
+
for (const [dir, watcher] of watchers.entries()) {
|
|
1059
|
+
if (dir === path || dir.startsWith(path + '/')) {
|
|
1060
|
+
watcher.close();
|
|
1061
|
+
watchers.delete(dir);
|
|
1062
|
+
const timeout = timeouts.get(dir);
|
|
1063
|
+
if (timeout) {
|
|
1064
|
+
clearTimeout(timeout);
|
|
1065
|
+
timeouts.delete(dir);
|
|
1066
|
+
}
|
|
1067
|
+
}
|
|
1068
|
+
}
|
|
1069
|
+
}
|
|
1070
|
+
function registerWatcher(dirPath) {
|
|
1071
|
+
if (watchers.has(dirPath))
|
|
1072
|
+
return;
|
|
1073
|
+
try {
|
|
1074
|
+
const watcher = watch(dirPath, { recursive: false }, function (event, filename) {
|
|
1075
|
+
if (!filename)
|
|
1076
|
+
return;
|
|
1077
|
+
const fullPath = path.join(dirPath, filename);
|
|
1078
|
+
try {
|
|
1079
|
+
const stats = lstatSync(fullPath);
|
|
1080
|
+
if (stats.isDirectory()) {
|
|
1081
|
+
registerWatcher(fullPath);
|
|
1082
|
+
}
|
|
1083
|
+
debouncedCallback(fullPath);
|
|
1084
|
+
}
|
|
1085
|
+
catch (err) {
|
|
1086
|
+
if (err.code === 'ENOENT') {
|
|
1087
|
+
unregisterWatcher(fullPath);
|
|
1088
|
+
debouncedCallback(fullPath);
|
|
1089
|
+
}
|
|
1090
|
+
}
|
|
1091
|
+
});
|
|
1092
|
+
watcher.on('error', function () {
|
|
1093
|
+
unregisterWatcher(dirPath);
|
|
1094
|
+
});
|
|
1095
|
+
watchers.set(dirPath, watcher);
|
|
1096
|
+
const files = readdirSync(dirPath);
|
|
1097
|
+
for (const file of files) {
|
|
1098
|
+
const childPath = path.join(dirPath, file);
|
|
1099
|
+
try {
|
|
1100
|
+
if (lstatSync(childPath).isDirectory()) {
|
|
1101
|
+
registerWatcher(childPath);
|
|
1102
|
+
}
|
|
1103
|
+
}
|
|
1104
|
+
catch (e) { }
|
|
1105
|
+
}
|
|
1106
|
+
}
|
|
1107
|
+
catch (e) { }
|
|
1108
|
+
}
|
|
1109
|
+
registerWatcher(targetDir);
|
|
1110
|
+
}
|
|
1111
|
+
/**
|
|
1112
|
+
* Run the general compilation process for the project.
|
|
1113
|
+
* This compiles all static-pages & static-layouts, as well as gathers a list of every page (dynamic and static) & layout (dynamic and static).
|
|
1114
|
+
* It also recursively copies your public directory into the distribution directory.
|
|
1115
|
+
* If doHotReload is true, it will also enable hot-reloading.
|
|
1116
|
+
*/
|
|
1117
|
+
async function compileEntireProject() {
|
|
1118
|
+
// make sure to hook every element builder into the global scope
|
|
1119
|
+
Object.assign(globalThis, allElements);
|
|
1120
|
+
const gracefulErr = (err) => { console.error(err); };
|
|
1121
|
+
process.on("uncaughtException", gracefulErr);
|
|
1122
|
+
process.on("unhandledRejection", gracefulErr);
|
|
1123
|
+
if (compilerOptions.doHotReload) {
|
|
1124
|
+
createRecursiveWatcher(compilerOptions.pagesDirectory, async (path) => {
|
|
1125
|
+
process.send?.(`restart-me`);
|
|
1126
|
+
});
|
|
1127
|
+
}
|
|
1128
|
+
const allLayouts = await gatherAllLayouts();
|
|
1129
|
+
const allPages = await gatherAllPages(allLayouts);
|
|
1130
|
+
const allStatusCodePages = await gatherAllStatusCodePages(allLayouts);
|
|
1131
|
+
const compiledStaticPages = await compileStaticPagesToDisk(allLayouts, allPages);
|
|
1132
|
+
await transpileClientRuntime();
|
|
1133
|
+
cpSync(compilerOptions.publicDirectory, getDistDir(), { recursive: true, });
|
|
1134
|
+
process.off("uncaughtException", gracefulErr);
|
|
1135
|
+
process.off("unhandledRejection", gracefulErr);
|
|
1136
|
+
return {
|
|
1137
|
+
allPages,
|
|
1138
|
+
allLayouts,
|
|
1139
|
+
allStatusCodePages,
|
|
1140
|
+
compiledStaticPages,
|
|
1141
|
+
compiledStaticLayouts,
|
|
1142
|
+
};
|
|
1143
|
+
}
|
|
1144
|
+
/**
|
|
1145
|
+
* Run the general compilation process for the project.
|
|
1146
|
+
* This compiles all static-pages & static-layouts, as well as gathers a list of every page (dynamic and static) & layout (dynamic and static).
|
|
1147
|
+
* It then writes those values to compilerOptions -> outputDirectory/DIST
|
|
1148
|
+
* It also recursively copies your public directory into the distribution directory.
|
|
1149
|
+
*/
|
|
1150
|
+
async function compileEntireProjectToDisk() {
|
|
1151
|
+
throw new Error("Not yet implemented.");
|
|
1152
|
+
}
|
|
1153
|
+
export { setCompilerOptions, generatePageCompilationContext, generateLayoutCompilationContext, serializeElement, generatePageDataScript, compileEntireProject, compileEntireProjectToDisk, compilePageToDisk, compileLayoutToDisk, compilerStore, compilerOptions, compilePage, compileLayout, clientPackages, };
|