@aerobuilt/core 0.2.9 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/dist/build-script-analysis-Bd9EyItC.mjs +193 -0
  2. package/dist/{entry-dev.d.ts → entry-dev.d.mts} +6 -16
  3. package/dist/entry-dev.mjs +127 -0
  4. package/dist/{entry-editor.d.ts → entry-editor.d.mts} +24 -22
  5. package/dist/entry-editor.mjs +3 -0
  6. package/dist/entry-prod.d.mts +10 -0
  7. package/dist/entry-prod.mjs +15 -0
  8. package/dist/routing-Bai79LCq.mjs +198 -0
  9. package/dist/runtime/index.d.mts +65 -0
  10. package/dist/runtime/index.mjs +207 -0
  11. package/dist/runtime/instance.d.mts +19 -0
  12. package/dist/runtime/instance.mjs +45 -0
  13. package/dist/types-CLHGhnGA.d.mts +209 -0
  14. package/dist/types.d.mts +2 -0
  15. package/dist/types.mjs +1 -0
  16. package/dist/utils/aliases.d.mts +38 -0
  17. package/dist/utils/aliases.mjs +117 -0
  18. package/dist/utils/{redirects.d.ts → redirects.d.mts} +7 -12
  19. package/dist/utils/redirects.mjs +21 -0
  20. package/dist/vite/{index.d.ts → index.d.mts} +5 -12
  21. package/dist/vite/index.mjs +1890 -0
  22. package/package.json +25 -20
  23. package/dist/chunk-4DAK56WB.js +0 -36
  24. package/dist/chunk-5ZNUGZOW.js +0 -238
  25. package/dist/chunk-F7MXQXLM.js +0 -15
  26. package/dist/chunk-JAMYN2VX.js +0 -133
  27. package/dist/chunk-VTEG2UU3.js +0 -184
  28. package/dist/entry-dev.js +0 -101
  29. package/dist/entry-editor.js +0 -14
  30. package/dist/entry-prod.d.ts +0 -19
  31. package/dist/entry-prod.js +0 -19
  32. package/dist/runtime/index.d.ts +0 -74
  33. package/dist/runtime/index.js +0 -7
  34. package/dist/runtime/instance.d.ts +0 -31
  35. package/dist/runtime/instance.js +0 -10
  36. package/dist/types.d.ts +0 -202
  37. package/dist/types.js +0 -0
  38. package/dist/utils/redirects.js +0 -6
  39. package/dist/vite/index.js +0 -1991
@@ -1,1991 +0,0 @@
1
- import {
2
- analyzeBuildScript,
3
- compileInterpolationFromSegments,
4
- isDirectiveAttr,
5
- tokenizeCurlyInterpolation
6
- } from "../chunk-VTEG2UU3.js";
7
- import {
8
- expandRoutePattern,
9
- isDynamicRoutePattern,
10
- pagePathToKey,
11
- resolvePageName,
12
- toPosix,
13
- toPosixRelative
14
- } from "../chunk-JAMYN2VX.js";
15
- import {
16
- redirectsToRouteRules
17
- } from "../chunk-F7MXQXLM.js";
18
-
19
- // src/utils/parse.ts
20
- function extractObjectKeys(expr) {
21
- let inner = expr.trim();
22
- while (inner.startsWith("{") && inner.endsWith("}")) {
23
- inner = inner.slice(1, -1).trim();
24
- }
25
- if (!inner) return [];
26
- const keys = [];
27
- let depth = 0;
28
- let current = "";
29
- for (let i = 0; i < inner.length; i++) {
30
- const char = inner[i];
31
- if (char === "{" || char === "[" || char === "(") {
32
- depth++;
33
- current += char;
34
- } else if (char === "}" || char === "]" || char === ")") {
35
- depth--;
36
- current += char;
37
- } else if (char === "," && depth === 0) {
38
- const key = extractKeyFromEntry(current.trim());
39
- if (key) keys.push(key);
40
- current = "";
41
- } else {
42
- current += char;
43
- }
44
- }
45
- const lastKey = extractKeyFromEntry(current.trim());
46
- if (lastKey) keys.push(lastKey);
47
- return keys;
48
- }
49
- function extractKeyFromEntry(entry) {
50
- if (!entry) return null;
51
- if (entry.startsWith("...")) return null;
52
- const colonIdx = entry.indexOf(":");
53
- if (colonIdx > 0) {
54
- return entry.slice(0, colonIdx).trim();
55
- }
56
- if (/^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(entry)) {
57
- return entry;
58
- }
59
- return null;
60
- }
61
-
62
- // src/vite/index.ts
63
- import { ViteImageOptimizer } from "vite-plugin-image-optimizer";
64
- import { nitro } from "nitro/vite";
65
-
66
- // src/vite/defaults.ts
67
- var CLIENT_SCRIPT_PREFIX = "/@aero/client/";
68
- function getClientScriptVirtualUrl(baseName, index, total) {
69
- const suffix = total === 1 ? ".js" : `.${index}.js`;
70
- return CLIENT_SCRIPT_PREFIX + baseName + suffix;
71
- }
72
- var RUNTIME_INSTANCE_MODULE_ID = "virtual:aero/runtime-instance";
73
- var RESOLVED_RUNTIME_INSTANCE_MODULE_ID = "\0virtual:aero/runtime-instance";
74
- var AERO_EMPTY_INLINE_CSS_PREFIX = "\0aero:empty-inline-css:";
75
- var AERO_HTML_VIRTUAL_PREFIX = "\0aero-html:";
76
- var DEFAULT_DIRS = {
77
- client: "client",
78
- server: "server",
79
- dist: "dist"
80
- };
81
- var DEFAULT_API_PREFIX = "/api";
82
- var LINK_ATTRS = [
83
- "href",
84
- "src",
85
- "action",
86
- "hx-get",
87
- "hx-post",
88
- "hx-put",
89
- "hx-patch",
90
- "hx-delete"
91
- ];
92
- var SKIP_PROTOCOL_REGEX = /^(?:https?:\/\/|\/\/|mailto:|tel:|data:|javascript:|#|blob:|file:\/\/)/i;
93
- function getServerDir(dirs) {
94
- return dirs?.server ?? dirs?.serverDir ?? DEFAULT_DIRS.server;
95
- }
96
- function resolveDirs(dirs) {
97
- return {
98
- client: dirs?.client ?? DEFAULT_DIRS.client,
99
- server: getServerDir(dirs),
100
- dist: dirs?.dist ?? DEFAULT_DIRS.dist
101
- };
102
- }
103
-
104
- // src/compiler/parser.ts
105
- import { parseHTML } from "linkedom";
106
-
107
- // src/compiler/constants.ts
108
- var ATTR_PREFIX = "data-";
109
- var ATTR_PROPS = "props";
110
- var ATTR_EACH = "each";
111
- var ATTR_IF = "if";
112
- var ATTR_ELSE_IF = "else-if";
113
- var ATTR_ELSE = "else";
114
- var ATTR_NAME = "name";
115
- var ATTR_SLOT = "slot";
116
- var ATTR_IS_BUILD = "is:build";
117
- var ATTR_IS_INLINE = "is:inline";
118
- var ATTR_IS_BLOCKING = "is:blocking";
119
- var ATTR_PASS_DATA = "pass:data";
120
- var ATTR_SRC = "src";
121
- var TAG_SLOT = "slot";
122
- var SLOT_NAME_DEFAULT = "default";
123
- var EACH_REGEX = /^(\w+)\s+in\s+(.+)$/;
124
- var COMPONENT_SUFFIX_REGEX = /-(component|layout)$/;
125
- var SELF_CLOSING_TAG_REGEX = /<([a-z0-9-]+)([^>]*?)\/>/gi;
126
- var SELF_CLOSING_TAIL_REGEX = /\/>$/;
127
- var VOID_TAGS = /* @__PURE__ */ new Set([
128
- "area",
129
- "base",
130
- "br",
131
- "col",
132
- "embed",
133
- "hr",
134
- "img",
135
- "input",
136
- "link",
137
- "meta",
138
- "param",
139
- "source",
140
- "track",
141
- "wbr"
142
- ]);
143
-
144
- // src/compiler/parser.ts
145
- function getAttrsString(element, exclude) {
146
- const parts = [];
147
- const attrs = element.attributes;
148
- if (!attrs) return "";
149
- const excludeLower = new Set([...exclude].map((s) => s.toLowerCase()));
150
- for (let i = 0; i < attrs.length; i++) {
151
- const a = attrs[i];
152
- if (!a || excludeLower.has(a.name.toLowerCase())) continue;
153
- const value = a.value;
154
- const escaped = value.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
155
- parts.push(`${a.name}="${escaped}"`);
156
- }
157
- return parts.join(" ").trim();
158
- }
159
- function isInForeignNamespace(el) {
160
- let parent = el.parentElement;
161
- while (parent) {
162
- const tag = parent.tagName?.toLowerCase();
163
- if (tag === "svg" || tag === "math") return true;
164
- parent = parent.parentElement;
165
- }
166
- return false;
167
- }
168
- function isInHead(el) {
169
- let parent = el.parentElement;
170
- while (parent) {
171
- if (parent.tagName?.toLowerCase() === "head") return true;
172
- parent = parent.parentElement;
173
- }
174
- return false;
175
- }
176
- function collectScriptElements(doc) {
177
- const scripts = [];
178
- const walk = (node) => {
179
- if (node.nodeType === 1) {
180
- const el = node;
181
- if (el.tagName?.toLowerCase() === "script") {
182
- if (!isInForeignNamespace(el)) scripts.push(el);
183
- return;
184
- }
185
- }
186
- for (let i = 0; i < node.childNodes.length; i++) walk(node.childNodes[i]);
187
- };
188
- walk(doc);
189
- return scripts;
190
- }
191
- function parse(html) {
192
- html = html.replace(/^\uFEFF/, "");
193
- html = html.replace(SELF_CLOSING_TAG_REGEX, (match, tagName, attrs) => {
194
- const tag = String(tagName).toLowerCase();
195
- if (VOID_TAGS.has(tag)) return match;
196
- return `<${tagName}${attrs}></${tagName}>`;
197
- });
198
- const isFullDocument = /<\s*html[\s>]/i.test(html);
199
- let doc;
200
- if (isFullDocument) {
201
- const parsed = parseHTML(html);
202
- doc = parsed.document;
203
- } else {
204
- const parsed = parseHTML(`<html><head></head><body>${html}</body></html>`);
205
- doc = parsed.document;
206
- }
207
- const buildContent = [];
208
- const clientScripts = [];
209
- const inlineScripts = [];
210
- const blockingScripts = [];
211
- const scriptElements = collectScriptElements(doc);
212
- const toRemove = [];
213
- for (const scriptEl of scriptElements) {
214
- const inHead = isInHead(scriptEl);
215
- const hasBuild = scriptEl.hasAttribute(ATTR_IS_BUILD);
216
- const hasInline = scriptEl.hasAttribute(ATTR_IS_INLINE);
217
- const hasBlocking = scriptEl.hasAttribute(ATTR_IS_BLOCKING);
218
- const src = scriptEl.getAttribute(ATTR_SRC) ?? "";
219
- const passData = scriptEl.getAttribute(ATTR_PASS_DATA) ?? void 0;
220
- const attrsExcludeTaxonomy = /* @__PURE__ */ new Set([ATTR_IS_BUILD, ATTR_IS_INLINE, ATTR_IS_BLOCKING]);
221
- let cleanedAttrs = getAttrsString(scriptEl, attrsExcludeTaxonomy);
222
- if (!hasInline && !inHead) {
223
- cleanedAttrs = getAttrsString(scriptEl, /* @__PURE__ */ new Set([...attrsExcludeTaxonomy, ATTR_PASS_DATA]));
224
- }
225
- cleanedAttrs = cleanedAttrs.replace(/\s+/g, " ").trim();
226
- const content = (scriptEl.textContent ?? "").trim();
227
- if (hasBuild) {
228
- buildContent.push(content);
229
- toRemove.push(scriptEl);
230
- continue;
231
- }
232
- if (hasInline) {
233
- inlineScripts.push({
234
- attrs: cleanedAttrs,
235
- content,
236
- passDataExpr: passData
237
- });
238
- scriptEl.removeAttribute(ATTR_IS_INLINE);
239
- continue;
240
- }
241
- if (hasBlocking) {
242
- blockingScripts.push({
243
- attrs: cleanedAttrs,
244
- content,
245
- passDataExpr: passData
246
- });
247
- toRemove.push(scriptEl);
248
- continue;
249
- }
250
- if (src) {
251
- const isLocal = !src.startsWith("http://") && !src.startsWith("https://");
252
- const hasType = scriptEl.hasAttribute("type");
253
- if (isLocal && !hasType) {
254
- scriptEl.setAttribute("type", "module");
255
- scriptEl.removeAttribute("defer");
256
- }
257
- continue;
258
- }
259
- if (inHead && scriptEl.attributes.length > 0) continue;
260
- clientScripts.push({
261
- attrs: cleanedAttrs,
262
- content,
263
- passDataExpr: passData,
264
- injectInHead: inHead
265
- });
266
- toRemove.push(scriptEl);
267
- }
268
- for (const el of toRemove) el.remove();
269
- const buildScript = buildContent.length > 0 ? { content: buildContent.join("\n") } : null;
270
- let template;
271
- if (isFullDocument) {
272
- template = doc.documentElement ? doc.documentElement.outerHTML : String(doc);
273
- } else {
274
- template = doc.body ? doc.body.innerHTML : "";
275
- }
276
- return {
277
- buildScript,
278
- clientScripts,
279
- inlineScripts,
280
- blockingScripts,
281
- template: template.trim()
282
- };
283
- }
284
-
285
- // src/compiler/helpers.ts
286
- function validateSingleBracedExpression(value, options = {}) {
287
- const trimmed = value.trim();
288
- const segments = tokenizeCurlyInterpolation(trimmed, { attributeMode: true });
289
- const ok = segments.length === 1 && segments[0].kind === "interpolation" && segments[0].start === 0 && segments[0].end === trimmed.length;
290
- if (!ok) {
291
- const directive = options.directive ?? "directive";
292
- const tagName = options.tagName ?? "element";
293
- throw new Error(
294
- `Directive \`${directive}\` on <${tagName}> must use a braced expression, e.g. ${directive}="{ expression }".`
295
- );
296
- }
297
- return trimmed;
298
- }
299
- function compileInterpolation(text) {
300
- if (!text) return "";
301
- const segments = tokenizeCurlyInterpolation(text, { attributeMode: false });
302
- return compileInterpolationFromSegments(segments);
303
- }
304
- function compileAttributeInterpolation(text) {
305
- if (!text) return "";
306
- const segments = tokenizeCurlyInterpolation(text, { attributeMode: true });
307
- return compileInterpolationFromSegments(segments);
308
- }
309
- function isAttr(name, attr, prefix) {
310
- return name === attr || name === prefix + attr;
311
- }
312
- function stripBraces(s) {
313
- const trimmed = s.trim();
314
- if (trimmed.startsWith("{") && trimmed.endsWith("}")) {
315
- return trimmed.slice(1, -1).trim();
316
- }
317
- return trimmed;
318
- }
319
- function kebabToCamelCase(s) {
320
- return s.replace(/-([a-z])/g, (_, char) => char.toUpperCase());
321
- }
322
- function buildPropsString(entries, spreadExpr) {
323
- if (spreadExpr) {
324
- return entries.length > 0 ? `{ ${spreadExpr}, ${entries.join(", ")} }` : `{ ${spreadExpr} }`;
325
- }
326
- return `{ ${entries.join(", ")} }`;
327
- }
328
- function escapeBackticks(s) {
329
- return s.replace(/`/g, "\\`");
330
- }
331
- function emitSlotsObjectVars(slotsMap) {
332
- const entries = Object.entries(slotsMap).map(([k, varName]) => `"${k}": ${varName}`).join(", ");
333
- return "{ " + entries + " }";
334
- }
335
- function emitRenderFunction(script, body, options = {}) {
336
- const {
337
- getStaticPathsFn,
338
- rootStyles,
339
- rootScripts,
340
- styleCode = "",
341
- rootScriptsLines = [],
342
- headScriptsLines = []
343
- } = options;
344
- const stylesCode = rootStyles && rootStyles.length > 0 ? rootStyles.map((s) => `styles?.add(${JSON.stringify(s)});`).join("\n ") : "";
345
- const scriptsCode = rootScripts && rootScripts.length > 0 ? rootScripts.map((s) => `scripts?.add(${JSON.stringify(s)});`).join("\n ") : "";
346
- const rootScriptsBlock = rootScriptsLines.length > 0 ? rootScriptsLines.join("\n ") : "";
347
- const headScriptsBlock = headScriptsLines.length > 0 ? headScriptsLines.map((s) => `injectedHeadScripts?.add(${s});`).join("\n ") : "";
348
- const renderFn = `export default async function(Aero) {
349
- const { ${getRenderContextDestructurePattern()} } = Aero;
350
- ${script}
351
- ${styleCode}
352
- ${stylesCode}
353
- ${scriptsCode}
354
- ${rootScriptsBlock}
355
- ${headScriptsBlock}
356
- let __out = '';
357
- ${body}return __out;
358
- }`;
359
- if (getStaticPathsFn) {
360
- return `${getStaticPathsFn}
361
-
362
- ${renderFn}`.trim();
363
- }
364
- return renderFn.trim();
365
- }
366
- var RENDER_COMPONENT_CONTEXT_PAIRS = [
367
- ["request", "request"],
368
- ["url", "url"],
369
- ["params", "params"],
370
- ["site", "__aero_site"],
371
- ["styles", "styles"],
372
- ["scripts", "scripts"],
373
- ["headScripts", "injectedHeadScripts"]
374
- ];
375
- function getRenderComponentContextArg() {
376
- const entries = RENDER_COMPONENT_CONTEXT_PAIRS.map(
377
- ([key, varName]) => key === varName ? key : `${key}: ${varName}`
378
- );
379
- return `{ ${entries.join(", ")} }`;
380
- }
381
- function getRenderContextDestructurePattern() {
382
- const entries = RENDER_COMPONENT_CONTEXT_PAIRS.map(
383
- ([key, varName]) => key === varName ? key : `${key}: ${varName}`
384
- );
385
- return `slots = {}, renderComponent, ${entries.join(", ")}`;
386
- }
387
- function emitSlotVar(varName) {
388
- return `let ${varName} = '';
389
- `;
390
- }
391
- function emitAppend(content, outVar = "__out") {
392
- return `${outVar} += \`${content}\`;
393
- `;
394
- }
395
- function emitIf(condition) {
396
- return `if (${condition}) {
397
- `;
398
- }
399
- function emitElseIf(condition) {
400
- return `} else if (${condition}) {
401
- `;
402
- }
403
- function emitElse() {
404
- return `} else {
405
- `;
406
- }
407
- function emitEnd() {
408
- return `}
409
- `;
410
- }
411
- function emitForOf(item, items) {
412
- return `for (const ${item} of ${items}) {
413
- `;
414
- }
415
- function emitSlotOutput(name, defaultContent, outVar = "__out") {
416
- return `${outVar} += slots['${name}'] ?? \`${defaultContent}\`;
417
- `;
418
- }
419
-
420
- // src/compiler/emit.ts
421
- var DEFAULT_OUT = "__out";
422
- function outVarFor(node, defaultVar) {
423
- switch (node.kind) {
424
- case "Append":
425
- case "Slot":
426
- case "Component":
427
- return node.outVar ?? defaultVar;
428
- case "ScriptPassData":
429
- case "StylePassData":
430
- return node.outVar;
431
- default:
432
- return defaultVar;
433
- }
434
- }
435
- function emitToJS(ir, outVar = DEFAULT_OUT) {
436
- let out = "";
437
- for (const node of ir) {
438
- out += emitNode(node, outVar);
439
- }
440
- return out;
441
- }
442
- function emitNode(node, outVar) {
443
- switch (node.kind) {
444
- case "Append":
445
- return emitAppend(node.content, outVarFor(node, outVar));
446
- case "For":
447
- return emitForOf(node.item, node.items) + emitToJS(node.body, outVar) + emitEnd();
448
- case "If": {
449
- let code = emitIf(node.condition) + emitToJS(node.body, outVar);
450
- if (node.elseIf?.length) {
451
- for (const branch of node.elseIf) {
452
- code = code.slice(0, -2) + emitElseIf(branch.condition);
453
- code += emitToJS(branch.body, outVar);
454
- }
455
- }
456
- if (node.else?.length) {
457
- code = code.slice(0, -2) + emitElse();
458
- code += emitToJS(node.else, outVar);
459
- }
460
- return code + emitEnd();
461
- }
462
- case "Slot":
463
- return emitSlotOutput(node.name, node.defaultContent, outVarFor(node, outVar));
464
- case "SlotVar":
465
- return emitSlotVar(node.varName);
466
- case "Component": {
467
- let code = "";
468
- for (const [slotName, slotIR] of Object.entries(node.slots)) {
469
- const slotVar = node.slotVarMap[slotName];
470
- if (slotVar === void 0) continue;
471
- code += emitSlotVar(slotVar);
472
- code += emitToJS(slotIR, slotVar);
473
- }
474
- const slotsString = emitSlotsObjectVars(node.slotVarMap);
475
- const targetVar = outVarFor(node, outVar);
476
- code += `${targetVar} += await Aero.renderComponent(${node.baseName}, ${node.propsString}, ${slotsString}, ${getRenderComponentContextArg()});
477
- `;
478
- return code;
479
- }
480
- case "ScriptPassData": {
481
- let code = "";
482
- if (!node.isModule) {
483
- code += emitAppend("\\n{\\n", node.outVar);
484
- }
485
- const jsMapExpr = `Object.entries(${node.passDataExpr}).map(([k, v]) => "\\nconst " + k + " = " + JSON.stringify(v) + ";").join("")`;
486
- code += emitAppend(`\${${jsMapExpr}}\\n`, node.outVar);
487
- return code;
488
- }
489
- case "StylePassData": {
490
- const cssMapExpr = `Object.entries(${node.passDataExpr}).map(([k, v]) => "\\n --" + k + ": " + String(v) + ";").join("")`;
491
- return emitAppend(`
492
- :root {\${${cssMapExpr}}
493
- }
494
- `, node.outVar);
495
- }
496
- default: {
497
- const _ = node;
498
- return "";
499
- }
500
- }
501
- }
502
- function emitBodyAndStyle(ir) {
503
- const bodyCode = emitToJS(ir.body, DEFAULT_OUT);
504
- let styleCode = "";
505
- if (ir.style.length > 0) {
506
- const styleVar = `__out_style_${Math.random().toString(36).slice(2)}`;
507
- styleCode += `let ${styleVar} = '';
508
- `;
509
- styleCode += emitToJS(ir.style, styleVar);
510
- styleCode += `styles?.add(${styleVar});
511
- `;
512
- }
513
- return { bodyCode, styleCode };
514
- }
515
-
516
- // src/compiler/codegen.ts
517
- import { parseHTML as parseHTML2 } from "linkedom";
518
-
519
- // src/compiler/resolver.ts
520
- import path from "path";
521
- var Resolver = class {
522
- root;
523
- rootAbs;
524
- resolvePathFn;
525
- constructor(options) {
526
- this.root = options.root;
527
- this.rootAbs = path.resolve(this.root);
528
- this.resolvePathFn = options.resolvePath || ((v) => v);
529
- }
530
- /** Normalize resolved path: root-relative with `/` if under root, else posix-normalized; always forward slashes. */
531
- normalizeResolved(next) {
532
- if (path.isAbsolute(next)) {
533
- const absolute = path.resolve(next);
534
- const isWithinRoot = absolute === this.rootAbs || absolute.startsWith(this.rootAbs + path.sep);
535
- if (isWithinRoot) {
536
- next = "/" + path.relative(this.rootAbs, absolute);
537
- } else {
538
- next = path.posix.normalize(toPosix(next));
539
- }
540
- }
541
- return toPosix(next);
542
- }
543
- /**
544
- * Resolve an import specifier (e.g. `@components/header`) to a path.
545
- * Returns the original specifier if the resolved value does not look like a path.
546
- *
547
- * @param specifier - Import specifier from the source file.
548
- * @returns Resolved path or unchanged specifier.
549
- */
550
- resolveImport(specifier) {
551
- let next = this.resolvePathFn(specifier);
552
- const looksPath = /^(\.{1,2}\/|\/|@|~)/.test(next);
553
- if (!looksPath) return specifier;
554
- next = this.normalizeResolved(next);
555
- return next;
556
- }
557
- /**
558
- * Resolve an attribute value that may be a path (e.g. `src` on script).
559
- * Same rules as `resolveImport`; returns unchanged value if not path-like.
560
- *
561
- * @param value - Raw attribute value.
562
- * @returns Resolved path or unchanged value.
563
- */
564
- resolveAttrValue(value) {
565
- let next = this.resolvePathFn(value);
566
- const looksPath = /^(\.{1,2}\/|\/|@|~)/.test(next);
567
- if (!looksPath) return value;
568
- next = this.normalizeResolved(next);
569
- return next;
570
- }
571
- };
572
-
573
- // src/compiler/codegen.ts
574
- var Lowerer = class {
575
- resolver;
576
- slotCounter = 0;
577
- constructor(resolver) {
578
- this.resolver = resolver;
579
- }
580
- // =========================================================================
581
- // Conditional chain helpers
582
- // =========================================================================
583
- /** Checks if node has if/data-if attribute */
584
- hasIfAttr(node) {
585
- return node.nodeType === 1 && (node.hasAttribute(ATTR_IF) || node.hasAttribute(ATTR_PREFIX + ATTR_IF));
586
- }
587
- /** Checks if node has else-if/data-else-if attribute */
588
- hasElseIfAttr(node) {
589
- return node.nodeType === 1 && (node.hasAttribute(ATTR_ELSE_IF) || node.hasAttribute(ATTR_PREFIX + ATTR_ELSE_IF));
590
- }
591
- /** Checks if node has else/data-else attribute */
592
- hasElseAttr(node) {
593
- return node.nodeType === 1 && (node.hasAttribute(ATTR_ELSE) || node.hasAttribute(ATTR_PREFIX + ATTR_ELSE));
594
- }
595
- /** Gets the condition value from if/else-if attribute */
596
- getCondition(node, attr) {
597
- const plainValue = node.getAttribute(attr);
598
- if (plainValue !== null) {
599
- return this.requireBracedExpression(plainValue, attr, node);
600
- }
601
- const dataAttr = ATTR_PREFIX + attr;
602
- const dataValue = node.getAttribute(dataAttr);
603
- if (dataValue !== null) {
604
- return this.requireBracedExpression(dataValue, dataAttr, node);
605
- }
606
- return null;
607
- }
608
- /**
609
- * Require directive value to be a braced expression; optionally strip outer braces.
610
- *
611
- * @param value - Raw attribute value.
612
- * @param directive - Attribute name for error message (e.g. `each`, `pass:data`).
613
- * @param node - DOM node for error message (tag name).
614
- * @param options - `strip: true` (default) returns inner expression; `strip: false` returns trimmed value including braces (e.g. for pass:data).
615
- * @returns Trimmed value, with or without outer braces per options.
616
- */
617
- requireBracedExpression(value, directive, node, options) {
618
- const strip = options?.strip !== false;
619
- const trimmed = value.trim();
620
- if (!trimmed.startsWith("{") || !trimmed.endsWith("}")) {
621
- const tagName = node?.tagName?.toLowerCase?.() || "element";
622
- throw new Error(
623
- `Directive \`${directive}\` on <${tagName}> must use a braced expression, e.g. ${directive}="{ expression }".`
624
- );
625
- }
626
- return strip ? stripBraces(trimmed) : trimmed;
627
- }
628
- isSingleWrappedExpression(value) {
629
- const trimmed = value.trim();
630
- if (!trimmed.startsWith("{") || !trimmed.endsWith("}")) return false;
631
- if (trimmed.startsWith("{{") || trimmed.endsWith("}}")) return false;
632
- let depth = 0;
633
- for (let i = 0; i < trimmed.length; i++) {
634
- const char = trimmed[i];
635
- if (char === "{") depth++;
636
- if (char === "}") {
637
- depth--;
638
- if (depth < 0) return false;
639
- if (depth === 0 && i !== trimmed.length - 1) {
640
- return false;
641
- }
642
- }
643
- }
644
- return depth === 0;
645
- }
646
- /** Parses component attributes, extracting props and data-props */
647
- parseComponentAttributes(node) {
648
- const propsEntries = [];
649
- let dataPropsExpression = null;
650
- if (node.attributes) {
651
- for (let i = 0; i < node.attributes.length; i++) {
652
- const attr = node.attributes[i];
653
- if (isAttr(attr.name, ATTR_EACH, ATTR_PREFIX)) continue;
654
- if (isAttr(attr.name, ATTR_IF, ATTR_PREFIX)) continue;
655
- if (isAttr(attr.name, ATTR_ELSE_IF, ATTR_PREFIX)) continue;
656
- if (isAttr(attr.name, ATTR_ELSE, ATTR_PREFIX)) continue;
657
- if (isAttr(attr.name, ATTR_PROPS, ATTR_PREFIX)) {
658
- const value = attr.value?.trim() || "";
659
- if (!value) {
660
- dataPropsExpression = "...props";
661
- } else {
662
- dataPropsExpression = this.requireBracedExpression(value, attr.name, node);
663
- }
664
- continue;
665
- }
666
- const rawValue = attr.value ?? "";
667
- const escapedLiteral = escapeBackticks(rawValue);
668
- let propVal;
669
- if (this.isSingleWrappedExpression(rawValue)) {
670
- propVal = stripBraces(escapedLiteral);
671
- } else {
672
- const compiled = compileAttributeInterpolation(rawValue);
673
- const hasInterpolation = compiled.includes("${") || rawValue.includes("{{") || rawValue.includes("}}");
674
- propVal = hasInterpolation ? `\`${compiled}\`` : `"${escapedLiteral}"`;
675
- }
676
- propsEntries.push(`${attr.name}: ${propVal}`);
677
- }
678
- }
679
- const propsString = buildPropsString(propsEntries, dataPropsExpression);
680
- return { propsString };
681
- }
682
- /** Parses element attributes, extracting data-each and building the attribute string */
683
- parseElementAttributes(node) {
684
- const attributes = [];
685
- let loopData = null;
686
- let passDataExpr = null;
687
- if (node.attributes) {
688
- for (let i = 0; i < node.attributes.length; i++) {
689
- const attr = node.attributes[i];
690
- if (isAttr(attr.name, ATTR_EACH, ATTR_PREFIX)) {
691
- const content = this.requireBracedExpression(attr.value || "", attr.name, node);
692
- const match = content.match(EACH_REGEX);
693
- if (!match) {
694
- const tagName = node?.tagName?.toLowerCase?.() || "element";
695
- throw new Error(
696
- `Directive \`${attr.name}\` on <${tagName}> must match "{ item in items }".`
697
- );
698
- }
699
- loopData = { item: match[1], items: match[2] };
700
- continue;
701
- }
702
- if (isAttr(attr.name, ATTR_IF, ATTR_PREFIX)) continue;
703
- if (isAttr(attr.name, ATTR_ELSE_IF, ATTR_PREFIX)) continue;
704
- if (isAttr(attr.name, ATTR_ELSE, ATTR_PREFIX)) continue;
705
- if (isAttr(attr.name, ATTR_PASS_DATA, ATTR_PREFIX)) {
706
- passDataExpr = validateSingleBracedExpression(attr.value || "", {
707
- directive: attr.name,
708
- tagName: node?.tagName?.toLowerCase?.() || "element"
709
- });
710
- continue;
711
- }
712
- if (attr.name === ATTR_IS_INLINE) {
713
- continue;
714
- }
715
- let val = this.resolver.resolveAttrValue(attr.value ?? "");
716
- if (!isDirectiveAttr(attr.name)) {
717
- val = compileAttributeInterpolation(val);
718
- }
719
- attributes.push(`${attr.name}="${val}"`);
720
- }
721
- }
722
- const attrString = attributes.length ? " " + attributes.join(" ") : "";
723
- return { attrString, loopData, passDataExpr };
724
- }
725
- /** Dispatch by node type: text (3) → compileText, element (1) → compileElement; other types return []. */
726
- compileNode(node, skipInterpolation = false, outVar = "__out") {
727
- switch (node.nodeType) {
728
- case 3:
729
- return this.compileText(node, skipInterpolation, outVar);
730
- case 1:
731
- return this.compileElement(node, skipInterpolation, outVar);
732
- default:
733
- return [];
734
- }
735
- }
736
- /** Lower a list of nodes (e.g. body children) to IR. */
737
- compileFragment(nodes) {
738
- return this.compileChildNodes(nodes, false, "__out");
739
- }
740
- compileChildNodes(nodes, skipInterpolation, outVar) {
741
- if (!nodes) return [];
742
- const out = [];
743
- let i = 0;
744
- while (i < nodes.length) {
745
- const node = nodes[i];
746
- if (this.hasIfAttr(node)) {
747
- const { nodes: chainNodes, consumed } = this.compileConditionalChain(
748
- nodes,
749
- i,
750
- skipInterpolation,
751
- outVar
752
- );
753
- out.push(...chainNodes);
754
- i += consumed;
755
- continue;
756
- }
757
- out.push(...this.compileNode(node, skipInterpolation, outVar));
758
- i++;
759
- }
760
- return out;
761
- }
762
- /**
763
- * Lowers a conditional chain (if/else-if/else siblings) into one IR If node.
764
- * Returns the IR and how many DOM nodes were consumed.
765
- */
766
- compileConditionalChain(nodes, startIndex, skipInterpolation, outVar) {
767
- let i = startIndex;
768
- let condition = null;
769
- let body = [];
770
- const elseIf = [];
771
- let elseBody;
772
- while (i < nodes.length) {
773
- const node = nodes[i];
774
- if (!node || node.nodeType !== 1) {
775
- if (node?.nodeType === 3 && node.textContent?.trim() === "") {
776
- i++;
777
- continue;
778
- }
779
- break;
780
- }
781
- if (condition === null) {
782
- if (!this.hasIfAttr(node)) break;
783
- condition = this.getCondition(node, ATTR_IF);
784
- body = this.compileElement(node, skipInterpolation, outVar);
785
- i++;
786
- } else if (this.hasElseIfAttr(node)) {
787
- const elseIfCondition = this.getCondition(node, ATTR_ELSE_IF);
788
- elseIf.push({ condition: elseIfCondition, body: this.compileElement(node, skipInterpolation, outVar) });
789
- i++;
790
- } else if (this.hasElseAttr(node)) {
791
- elseBody = this.compileElement(node, skipInterpolation, outVar);
792
- i++;
793
- break;
794
- } else {
795
- break;
796
- }
797
- }
798
- const ifNode = {
799
- kind: "If",
800
- condition,
801
- body,
802
- ...elseIf.length > 0 && { elseIf },
803
- ...elseBody && elseBody.length > 0 && { else: elseBody }
804
- };
805
- return { nodes: [ifNode], consumed: i - startIndex };
806
- }
807
- compileText(node, skipInterpolation, outVar) {
808
- const text = node.textContent || "";
809
- if (!text) return [];
810
- const content = skipInterpolation ? escapeBackticks(text) : compileInterpolation(text);
811
- return [{ kind: "Append", content, outVar }];
812
- }
813
- /** Lower one element: slot, component (-component/-layout), or regular HTML (with optional data-each, pass:data). */
814
- compileElement(node, skipInterpolation, outVar) {
815
- const tagName = node.tagName.toLowerCase();
816
- if (tagName === TAG_SLOT) {
817
- return this.compileSlot(node, skipInterpolation, outVar);
818
- }
819
- if (COMPONENT_SUFFIX_REGEX.test(tagName)) {
820
- return this.compileComponent(node, tagName, skipInterpolation, outVar);
821
- }
822
- const { attrString, loopData, passDataExpr } = this.parseElementAttributes(node);
823
- const childSkip = skipInterpolation || tagName === "style" || tagName === "script" && !passDataExpr;
824
- const inner = [];
825
- if (VOID_TAGS.has(tagName)) {
826
- inner.push({ kind: "Append", content: `<${tagName}${attrString}>`, outVar });
827
- } else {
828
- inner.push({ kind: "Append", content: `<${tagName}${attrString}>`, outVar });
829
- const isScript = tagName === "script";
830
- const isStyle = tagName === "style";
831
- let closeBlock = false;
832
- if (isScript && passDataExpr) {
833
- const result = this.emitScriptPassDataIR(passDataExpr, node, outVar);
834
- inner.push(...result.nodes);
835
- closeBlock = result.closeBlock;
836
- } else if (isStyle && passDataExpr) {
837
- inner.push({ kind: "StylePassData", passDataExpr, outVar });
838
- }
839
- inner.push(...this.compileChildNodes(node.childNodes, childSkip, outVar));
840
- if (closeBlock) {
841
- inner.push({ kind: "Append", content: "\\n}\\n", outVar });
842
- }
843
- inner.push({ kind: "Append", content: `</${tagName}>`, outVar });
844
- }
845
- if (loopData) {
846
- return [{ kind: "For", item: loopData.item, items: loopData.items, body: inner }];
847
- }
848
- return inner;
849
- }
850
- /**
851
- * Builds IR for injecting `pass:data` into a `<script>` tag.
852
- * For non-module scripts, the emitter emits the opening `{\n`; caller must append closing `}\n` (Append IR).
853
- */
854
- emitScriptPassDataIR(passDataExpr, node, outVar) {
855
- const isModule = node.getAttribute("type") === "module";
856
- const nodes = [{ kind: "ScriptPassData", passDataExpr, isModule, outVar }];
857
- return { nodes, closeBlock: !isModule };
858
- }
859
- compileSlot(node, skipInterpolation, outVar) {
860
- const slotName = node.getAttribute(ATTR_NAME) || SLOT_NAME_DEFAULT;
861
- const defaultContent = this.compileSlotDefaultContent(node.childNodes, skipInterpolation);
862
- return [{ kind: "Slot", name: slotName, defaultContent, outVar }];
863
- }
864
- /** Compiles slot default content into template literal content (for simple fallbacks). */
865
- compileSlotDefaultContent(nodes, skipInterpolation) {
866
- if (!nodes) return "";
867
- let out = "";
868
- for (let i = 0; i < nodes.length; i++) {
869
- const node = nodes[i];
870
- if (!node) continue;
871
- if (node.nodeType === 3) {
872
- const text = node.textContent || "";
873
- if (text) {
874
- out += skipInterpolation ? escapeBackticks(text) : compileInterpolation(text);
875
- }
876
- } else if (node.nodeType === 1) {
877
- out += this.compileElementDefaultContent(node, skipInterpolation);
878
- }
879
- }
880
- return out;
881
- }
882
- /** Compiles an element for slot default content (template literal format). */
883
- compileElementDefaultContent(node, skipInterpolation) {
884
- const tagName = node.tagName.toLowerCase();
885
- if (tagName === TAG_SLOT) {
886
- const slotName = node.getAttribute(ATTR_NAME) || SLOT_NAME_DEFAULT;
887
- const defaultContent = this.compileSlotDefaultContent(node.childNodes, skipInterpolation);
888
- return `\${ slots['${slotName}'] ?? ${defaultContent} }`;
889
- }
890
- if (COMPONENT_SUFFIX_REGEX.test(tagName)) {
891
- const kebabBase = tagName.replace(COMPONENT_SUFFIX_REGEX, "");
892
- const baseName = kebabToCamelCase(kebabBase);
893
- const { propsString } = this.parseComponentAttributes(node);
894
- return `\${ await Aero.renderComponent(${baseName}, ${propsString}, {}, ${getRenderComponentContextArg()}) }`;
895
- }
896
- const { attrString } = this.parseElementAttributes(node);
897
- if (VOID_TAGS.has(tagName)) {
898
- return `<${tagName}${attrString}>`;
899
- }
900
- const childSkip = skipInterpolation || tagName === "style" || tagName === "script";
901
- const children = this.compileSlotDefaultContent(node.childNodes, childSkip);
902
- return `<${tagName}${attrString}>${children}</${tagName}>`;
903
- }
904
- compileComponent(node, tagName, skipInterpolation, outVar) {
905
- const kebabBase = tagName.replace(COMPONENT_SUFFIX_REGEX, "");
906
- const baseName = kebabToCamelCase(kebabBase);
907
- const { propsString } = this.parseComponentAttributes(node);
908
- const slotVarMap = {};
909
- const slotContentMap = { [SLOT_NAME_DEFAULT]: [] };
910
- if (node.childNodes) {
911
- for (let i = 0; i < node.childNodes.length; i++) {
912
- const child = node.childNodes[i];
913
- let slotName = SLOT_NAME_DEFAULT;
914
- if (child.nodeType === 1) {
915
- const slotAttr = child.getAttribute(ATTR_SLOT);
916
- if (slotAttr) slotName = slotAttr;
917
- }
918
- slotContentMap[slotName] = slotContentMap[slotName] || [];
919
- slotContentMap[slotName].push(child);
920
- }
921
- }
922
- const slots = {};
923
- for (const [slotName, children] of Object.entries(slotContentMap)) {
924
- const slotVar = `__slot_${this.slotCounter++}`;
925
- slotVarMap[slotName] = slotVar;
926
- const slotIR = [];
927
- for (const child of children) {
928
- if (child.nodeType === 1) {
929
- const childTagName = child.tagName?.toLowerCase();
930
- if (childTagName === TAG_SLOT && child.hasAttribute(ATTR_NAME) && child.hasAttribute(ATTR_SLOT)) {
931
- const passthroughName = child.getAttribute(ATTR_NAME);
932
- const defaultContent = this.compileSlotDefaultContent(
933
- child.childNodes,
934
- skipInterpolation
935
- );
936
- slotIR.push({
937
- kind: "Slot",
938
- name: passthroughName,
939
- defaultContent,
940
- outVar: slotVar
941
- });
942
- continue;
943
- }
944
- }
945
- slotIR.push(...this.compileNode(child, skipInterpolation, slotVar));
946
- }
947
- slots[slotName] = slotIR;
948
- }
949
- return [
950
- {
951
- kind: "Component",
952
- baseName,
953
- propsString,
954
- slots,
955
- slotVarMap,
956
- outVar
957
- }
958
- ];
959
- }
960
- };
961
- function compile(parsed, options) {
962
- const inlineScripts = options.inlineScripts ?? parsed.inlineScripts;
963
- const resolver = new Resolver({
964
- root: options.root,
965
- resolvePath: options.resolvePath
966
- });
967
- const lowerer = new Lowerer(resolver);
968
- let script = parsed.buildScript ? parsed.buildScript.content : "";
969
- const analysis = analyzeBuildScript(script);
970
- script = analysis.scriptWithoutImportsAndGetStaticPaths;
971
- const getStaticPathsFn = analysis.getStaticPathsFn;
972
- const importsLines = [];
973
- const quote = '"';
974
- for (const imp of analysis.imports) {
975
- const resolved = resolver.resolveImport(imp.specifier);
976
- const modExpr = `await import(${quote}${resolved}${quote})`;
977
- if (imp.defaultBinding) {
978
- importsLines.push(`const ${imp.defaultBinding} = (${modExpr}).default`);
979
- } else if (imp.namedBindings.length > 0) {
980
- const names = imp.namedBindings.map((b) => b.imported === b.local ? b.local : `${b.imported} as ${b.local}`).join(", ");
981
- importsLines.push(`const {${names}} = ${modExpr}`);
982
- } else if (imp.namespaceBinding) {
983
- importsLines.push(`const ${imp.namespaceBinding} = ${modExpr}`);
984
- }
985
- }
986
- const importsCode = importsLines.join("\n");
987
- const expandedTemplate = parsed.template.replace(
988
- SELF_CLOSING_TAG_REGEX,
989
- (match, tagName, attrs) => {
990
- const tag = String(tagName).toLowerCase();
991
- if (VOID_TAGS.has(tag)) {
992
- return match.replace(SELF_CLOSING_TAIL_REGEX, ">");
993
- }
994
- return `<${tagName}${attrs}></${tagName}>`;
995
- }
996
- );
997
- const { document } = parseHTML2(`
998
- <html lang="en">
999
- <body>${expandedTemplate}</body>
1000
- </html>
1001
- `);
1002
- let styleCode = "";
1003
- if (document.body) {
1004
- const children = Array.from(document.body.childNodes);
1005
- for (const node of children) {
1006
- if (node.nodeType === 1 && node.tagName === "STYLE") {
1007
- const styleVar = `__out_style_${Math.random().toString(36).slice(2)}`;
1008
- const styleIR = lowerer.compileNode(node, false, styleVar);
1009
- styleCode += `let ${styleVar} = '';
1010
- `;
1011
- styleCode += emitToJS(styleIR, styleVar);
1012
- styleCode += `styles?.add(${styleVar});
1013
- `;
1014
- node.remove();
1015
- }
1016
- }
1017
- }
1018
- const bodyIR = document.body ? lowerer.compileFragment(document.body.childNodes) : [];
1019
- const { bodyCode } = emitBodyAndStyle({ body: bodyIR, style: [] });
1020
- const rootScripts = [];
1021
- const headScripts = [];
1022
- const virtualPrefix = "/@aero/client/";
1023
- const hasVirtualClientScripts = options.clientScripts?.some((c) => c.content.startsWith(virtualPrefix)) ?? false;
1024
- if (hasVirtualClientScripts) {
1025
- script = `function __aeroScriptUrl(p){return '/'+'@aero/client/'+p}
1026
- ` + script;
1027
- }
1028
- if (options.clientScripts && options.clientScripts.length > 0) {
1029
- for (const clientScript of options.clientScripts) {
1030
- const attrs = clientScript.attrs ?? "";
1031
- const hasType = attrs.includes("type=");
1032
- const baseAttrs = hasType ? attrs : `type="module"${attrs ? " " + attrs : ""}`;
1033
- const urlExpr = clientScript.content.startsWith(virtualPrefix) ? `__aeroScriptUrl(${JSON.stringify(clientScript.content.slice(virtualPrefix.length))})` : JSON.stringify(clientScript.content);
1034
- const baseAttrsEscaped = baseAttrs.replace(/'/g, "\\'");
1035
- const tagExpr = `'<script ${baseAttrsEscaped} src="'+${urlExpr}+'"></script>'`;
1036
- const isHead = clientScript.injectInHead;
1037
- if (clientScript.passDataExpr) {
1038
- const jsonExpr = `JSON.stringify(${clientScript.passDataExpr})`;
1039
- if (isHead) {
1040
- headScripts.push(
1041
- `(function(){const __pid=Aero.nextPassDataId();\`<\`+'script type="application/json" id="'+__pid+'" class="__aero_data">'+${jsonExpr}+'</'+'script>';window.__aero_data_next=JSON.parse(document.getElementById("'+__pid+'").textContent);})();${tagExpr}`
1042
- );
1043
- } else {
1044
- rootScripts.push(
1045
- `(function(){const __pid=Aero.nextPassDataId();scripts?.add(\`<script type="application/json" id="\${__pid}" class="__aero_data">\${${jsonExpr}}</script>\`);scripts?.add(\`<script>window.__aero_data_next=JSON.parse(document.getElementById("\${__pid}").textContent);</script>\`);scripts?.add(${tagExpr});})();`
1046
- );
1047
- }
1048
- } else {
1049
- if (isHead) {
1050
- headScripts.push(tagExpr);
1051
- } else {
1052
- rootScripts.push(`scripts?.add(${tagExpr});`);
1053
- }
1054
- }
1055
- }
1056
- }
1057
- if (options.blockingScripts) {
1058
- for (const blockingScript of options.blockingScripts) {
1059
- if (blockingScript.passDataExpr) {
1060
- const passDataExpr = validateSingleBracedExpression(blockingScript.passDataExpr, {
1061
- directive: "pass:data",
1062
- tagName: "script"
1063
- });
1064
- const jsMapExpr = `Object.entries(${passDataExpr}).map(([k, v]) => "\\nconst " + k + " = " + JSON.stringify(v) + ";").join("")`;
1065
- headScripts.push(
1066
- `\`<script${blockingScript.attrs ? " " + blockingScript.attrs : ""}>\${${jsMapExpr}}${blockingScript.content.replace(/`/g, "\\`")}</script>\``
1067
- );
1068
- } else {
1069
- const escapedContent = blockingScript.content.replace(/'/g, "\\'");
1070
- headScripts.push(
1071
- `'<script${blockingScript.attrs ? " " + blockingScript.attrs : ""}>${escapedContent}</script>'`
1072
- );
1073
- }
1074
- }
1075
- }
1076
- const renderFn = emitRenderFunction(script, bodyCode, {
1077
- getStaticPathsFn: getStaticPathsFn || void 0,
1078
- styleCode,
1079
- rootScriptsLines: rootScripts,
1080
- headScriptsLines: headScripts
1081
- });
1082
- return importsCode + "\n" + renderFn;
1083
- }
1084
-
1085
- // src/utils/aliases.ts
1086
- import path2 from "path";
1087
- import { getTsconfig } from "get-tsconfig";
1088
- function loadTsconfigAliases(root) {
1089
- const result = getTsconfig(root);
1090
- if (!result) return { aliases: [], resolvePath: void 0 };
1091
- const config = result.config;
1092
- const options = config.compilerOptions;
1093
- const paths = options?.paths || {};
1094
- const baseUrl = options?.baseUrl || ".";
1095
- const baseDir = path2.resolve(path2.dirname(result.path || root), baseUrl);
1096
- const aliases = [];
1097
- for (const [key, values] of Object.entries(paths)) {
1098
- const valueArr = Array.from(values);
1099
- const first = valueArr[0];
1100
- if (typeof first !== "string" || first.length === 0) continue;
1101
- const find = key.replace(/\/*$/, "").replace("/*", "");
1102
- const target = first.replace(/\/*$/, "").replace("/*", "");
1103
- const replacement = path2.resolve(baseDir, target);
1104
- aliases.push({ find, replacement });
1105
- }
1106
- const resolvePath = (id) => {
1107
- for (const entry of aliases) {
1108
- if (id === entry.find || id.startsWith(`${entry.find}/`)) {
1109
- const rest = id.slice(entry.find.length);
1110
- return path2.join(entry.replacement, rest);
1111
- }
1112
- }
1113
- return id;
1114
- };
1115
- return { aliases, resolvePath };
1116
- }
1117
-
1118
- // src/vite/build.ts
1119
- import { minify as minifyHTML } from "html-minifier-next";
1120
- import fs from "fs";
1121
- import path3 from "path";
1122
- import { parseHTML as parseHTML3 } from "linkedom";
1123
- import { createServer } from "vite";
1124
- function registerClientScriptsToMap(parsed, baseName, target) {
1125
- const total = parsed.clientScripts.length;
1126
- for (let i = 0; i < total; i++) {
1127
- const clientScript = parsed.clientScripts[i];
1128
- const clientScriptUrl = getClientScriptVirtualUrl(baseName, i, total);
1129
- target.set(clientScriptUrl, {
1130
- content: clientScript.content,
1131
- passDataExpr: clientScript.passDataExpr,
1132
- injectInHead: clientScript.injectInHead
1133
- });
1134
- }
1135
- }
1136
- function isDynamicPage(page) {
1137
- return isDynamicRoutePattern(page.pageName);
1138
- }
1139
- function expandPattern(pattern, params) {
1140
- return expandRoutePattern(pattern, params);
1141
- }
1142
- function walkHtmlFiles(dir) {
1143
- if (!fs.existsSync(dir)) return [];
1144
- const files = [];
1145
- for (const item of fs.readdirSync(dir, { withFileTypes: true })) {
1146
- const fullPath = path3.join(dir, item.name);
1147
- if (item.isDirectory()) {
1148
- files.push(...walkHtmlFiles(fullPath));
1149
- continue;
1150
- }
1151
- if (item.isFile() && item.name.endsWith(".html")) {
1152
- files.push(fullPath);
1153
- }
1154
- }
1155
- return files;
1156
- }
1157
- function toRouteFromPageName(pageName) {
1158
- if (pageName === "index") return "";
1159
- if (pageName.endsWith("/index")) return pageName.slice(0, -"/index".length);
1160
- return pageName;
1161
- }
1162
- function toOutputFile(routePath) {
1163
- if (routePath === "") return "index.html";
1164
- if (routePath === "404") return "404.html";
1165
- return toPosix(path3.join(routePath, "index.html"));
1166
- }
1167
- function writeSitemap(routePaths, site, distDir) {
1168
- const base = site.replace(/\/$/, "");
1169
- const urls = routePaths.filter((r) => r !== "404").map((routePath) => {
1170
- const pathSegment = routePath === "" ? "" : `/${routePath}/`;
1171
- const loc = `${base}${pathSegment || "/"}`;
1172
- return ` <url>
1173
- <loc>${escapeXml(loc)}</loc>
1174
- </url>`;
1175
- });
1176
- const xml = '<?xml version="1.0" encoding="UTF-8"?>\n<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">\n' + urls.join("\n") + "\n</urlset>\n";
1177
- fs.writeFileSync(path3.join(distDir, "sitemap.xml"), xml, "utf-8");
1178
- }
1179
- function escapeXml(s) {
1180
- return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&apos;");
1181
- }
1182
- function normalizeRelativeLink(fromDir, targetPath) {
1183
- const rel = path3.posix.relative(fromDir, targetPath);
1184
- if (!rel) return "./";
1185
- if (rel.startsWith(".")) return rel;
1186
- return `./${rel}`;
1187
- }
1188
- function normalizeRelativeRouteLink(fromDir, routePath) {
1189
- const targetDir = routePath === "" ? "" : routePath;
1190
- const rel = path3.posix.relative(fromDir, targetDir);
1191
- let res = !rel ? "./" : rel.startsWith(".") ? rel : `./${rel}`;
1192
- if (routePath !== "" && routePath !== "404" && !res.endsWith("/")) {
1193
- res += "/";
1194
- }
1195
- return res;
1196
- }
1197
- function normalizeRoutePathFromHref(value) {
1198
- if (value === "/") return "";
1199
- return value.replace(/^\/+/, "").replace(/\/+$/, "");
1200
- }
1201
- function isSkippableUrl(value) {
1202
- if (!value) return true;
1203
- return SKIP_PROTOCOL_REGEX.test(value);
1204
- }
1205
- function toManifestKey(root, filePath) {
1206
- return toPosixRelative(filePath, root);
1207
- }
1208
- function resolveTemplateAssetPath(rawValue, templateFile, root, resolvePath) {
1209
- if (!rawValue || isSkippableUrl(rawValue)) return null;
1210
- if (rawValue.startsWith("/")) return path3.resolve(root, rawValue.slice(1));
1211
- if (rawValue.startsWith("@") || rawValue.startsWith("~")) {
1212
- const resolved = resolvePath ? resolvePath(rawValue) : rawValue;
1213
- return path3.isAbsolute(resolved) ? resolved : path3.resolve(root, resolved);
1214
- }
1215
- if (rawValue.startsWith("./") || rawValue.startsWith("../")) {
1216
- return path3.resolve(path3.dirname(templateFile), rawValue);
1217
- }
1218
- return null;
1219
- }
1220
- function discoverTemplates(root, templateRoot) {
1221
- return walkHtmlFiles(path3.resolve(root, templateRoot));
1222
- }
1223
- function discoverPages(root, pagesRoot) {
1224
- const pagesDir = path3.resolve(root, pagesRoot);
1225
- const pageFiles = walkHtmlFiles(pagesDir);
1226
- const allPageNames = new Set(
1227
- pageFiles.map((f) => pagePathToKey(toPosixRelative(f, root)))
1228
- );
1229
- return pageFiles.map((file) => {
1230
- const relFromRoot = toPosixRelative(file, root);
1231
- let pageName = pagePathToKey(relFromRoot);
1232
- if (pageName === "home" && !allPageNames.has("index")) {
1233
- pageName = "index";
1234
- } else if (pageName.endsWith("/home")) {
1235
- const siblingIndex = pageName.slice(0, -"/home".length) + "/index";
1236
- if (!allPageNames.has(siblingIndex)) {
1237
- pageName = siblingIndex;
1238
- }
1239
- }
1240
- const routePath = toRouteFromPageName(pageName);
1241
- return {
1242
- pageName,
1243
- routePath,
1244
- sourceFile: file,
1245
- outputFile: toOutputFile(routePath)
1246
- };
1247
- });
1248
- }
1249
- function discoverClientScriptContentMap(root, templateRoot) {
1250
- const map = /* @__PURE__ */ new Map();
1251
- for (const file of discoverTemplates(root, templateRoot)) {
1252
- const source = fs.readFileSync(file, "utf-8");
1253
- const parsed = parse(source);
1254
- if (parsed.clientScripts.length === 0) continue;
1255
- const rel = toPosixRelative(file, root);
1256
- const baseName = rel.replace(/\.html$/i, "");
1257
- registerClientScriptsToMap(parsed, baseName, map);
1258
- }
1259
- return map;
1260
- }
1261
- function discoverClientScriptVirtualInputs(root, templateRoot) {
1262
- const entries = {};
1263
- for (const file of discoverTemplates(root, templateRoot)) {
1264
- const source = fs.readFileSync(file, "utf-8");
1265
- const parsed = parse(source);
1266
- if (parsed.clientScripts.length === 0) continue;
1267
- const rel = toPosixRelative(file, root);
1268
- const baseName = rel.replace(/\.html$/i, "");
1269
- const { clientScripts } = parsed;
1270
- const total = clientScripts.length;
1271
- for (let i = 0; i < total; i++) {
1272
- const virtualPath = getClientScriptVirtualUrl(baseName, i, total);
1273
- const manifestKey = virtualPath.replace(/^\//, "");
1274
- entries[manifestKey] = virtualPath;
1275
- }
1276
- }
1277
- return entries;
1278
- }
1279
- function discoverAssetInputs(root, resolvePath, templateRoot = "client") {
1280
- const entries = /* @__PURE__ */ new Map();
1281
- for (const templateFile of discoverTemplates(root, templateRoot)) {
1282
- const source = fs.readFileSync(templateFile, "utf-8");
1283
- const { document } = parseHTML3(source);
1284
- const scripts = Array.from(document.querySelectorAll("script[src]"));
1285
- const styles = Array.from(document.querySelectorAll('link[rel="stylesheet"][href]'));
1286
- const refs = [...scripts, ...styles];
1287
- for (const el of refs) {
1288
- const attr = el.hasAttribute("src") ? "src" : "href";
1289
- const raw = el.getAttribute(attr) || "";
1290
- const resolved = resolveTemplateAssetPath(raw, templateFile, root, resolvePath);
1291
- if (!resolved || !fs.existsSync(resolved)) continue;
1292
- const ext = path3.extname(resolved).toLowerCase();
1293
- if (![".js", ".mjs", ".ts", ".tsx", ".css"].includes(ext)) continue;
1294
- entries.set(toManifestKey(root, resolved), resolved);
1295
- }
1296
- }
1297
- const defaultClientEntry = path3.resolve(root, `${templateRoot}/index.ts`);
1298
- if (fs.existsSync(defaultClientEntry)) {
1299
- entries.set(toManifestKey(root, defaultClientEntry), defaultClientEntry);
1300
- }
1301
- const imagesDir = path3.resolve(root, templateRoot, "assets/images");
1302
- if (fs.existsSync(imagesDir)) {
1303
- const imageFiles = walkFiles(imagesDir);
1304
- for (const file of imageFiles) {
1305
- const key = toManifestKey(root, file);
1306
- if (entries.has(key)) continue;
1307
- if (path3.basename(file).startsWith(".")) continue;
1308
- entries.set(key, file);
1309
- }
1310
- }
1311
- return Object.fromEntries(entries);
1312
- }
1313
- function walkFiles(dir) {
1314
- if (!fs.existsSync(dir)) return [];
1315
- const files = [];
1316
- for (const item of fs.readdirSync(dir, { withFileTypes: true })) {
1317
- const fullPath = path3.join(dir, item.name);
1318
- if (item.isDirectory()) {
1319
- files.push(...walkFiles(fullPath));
1320
- continue;
1321
- }
1322
- if (item.isFile()) {
1323
- files.push(fullPath);
1324
- }
1325
- }
1326
- return files;
1327
- }
1328
- function addDoctype(html) {
1329
- return /^\s*<!doctype\s+html/i.test(html) ? html : `<!doctype html>
1330
- ${html}`;
1331
- }
1332
- var ASSET_IMAGE_EXT = /\.(jpg|jpeg|png|gif|webp|svg|ico)(\?|$)/i;
1333
- function rewriteAbsoluteUrl(value, fromDir, manifest, routeSet, apiPrefix = DEFAULT_API_PREFIX) {
1334
- if (value.startsWith(apiPrefix)) return value;
1335
- const noQuery = value.split(/[?#]/)[0] || value;
1336
- const suffix = value.slice(noQuery.length);
1337
- const manifestKey = noQuery.replace(/^\//, "");
1338
- let manifestEntry = manifest[noQuery] ?? manifest[manifestKey];
1339
- if (!manifestEntry && noQuery.startsWith("assets/")) {
1340
- const entry = Object.values(manifest).find(
1341
- (e) => e?.file === noQuery || e?.file === manifestKey
1342
- );
1343
- if (entry) manifestEntry = entry;
1344
- }
1345
- if (manifestEntry?.file) {
1346
- const entryWithAssets = manifestEntry;
1347
- const imageAsset = entryWithAssets.assets?.find((a) => ASSET_IMAGE_EXT.test(a));
1348
- const fileToUse = imageAsset ?? manifestEntry.file;
1349
- const rel2 = normalizeRelativeLink(fromDir, fileToUse);
1350
- return rel2 + suffix;
1351
- }
1352
- if (noQuery.startsWith("/assets/")) {
1353
- const rel2 = normalizeRelativeLink(fromDir, noQuery.replace(/^\//, ""));
1354
- return rel2 + suffix;
1355
- }
1356
- const route = normalizeRoutePathFromHref(noQuery);
1357
- if (routeSet.has(route) || route === "") {
1358
- const rel2 = route === "404" ? normalizeRelativeLink(fromDir, toOutputFile(route)) : normalizeRelativeRouteLink(fromDir, route);
1359
- return rel2 + suffix;
1360
- }
1361
- const rel = normalizeRelativeLink(fromDir, noQuery.replace(/^\//, ""));
1362
- return rel + suffix;
1363
- }
1364
- function rewriteRenderedHtml(html, outputFile, manifest, routeSet, apiPrefix = DEFAULT_API_PREFIX) {
1365
- const fromDir = path3.posix.dirname(outputFile);
1366
- const { document } = parseHTML3(html);
1367
- for (const script of Array.from(document.querySelectorAll("script[src]"))) {
1368
- const src = script.getAttribute("src") || "";
1369
- if (src.startsWith(CLIENT_SCRIPT_PREFIX)) {
1370
- const newSrc = rewriteAbsoluteUrl(src, fromDir, manifest, routeSet, apiPrefix);
1371
- script.setAttribute("src", newSrc);
1372
- script.setAttribute("type", "module");
1373
- script.removeAttribute("defer");
1374
- continue;
1375
- }
1376
- if (script.getAttribute("type") === "module") {
1377
- script.removeAttribute("defer");
1378
- }
1379
- }
1380
- for (const el of Array.from(document.querySelectorAll("*"))) {
1381
- for (const attrName of LINK_ATTRS) {
1382
- if (!el.hasAttribute(attrName)) continue;
1383
- const current = (el.getAttribute(attrName) || "").trim();
1384
- if (!current || isSkippableUrl(current)) continue;
1385
- if (!current.startsWith("/")) continue;
1386
- el.setAttribute(
1387
- attrName,
1388
- rewriteAbsoluteUrl(current, fromDir, manifest, routeSet, apiPrefix)
1389
- );
1390
- }
1391
- }
1392
- const htmlTag = document.documentElement;
1393
- if (htmlTag) return addDoctype(htmlTag.outerHTML);
1394
- return addDoctype(document.toString());
1395
- }
1396
- function readManifest(distDir) {
1397
- const manifestPath = path3.join(distDir, ".vite", "manifest.json");
1398
- if (!fs.existsSync(manifestPath)) return {};
1399
- return JSON.parse(fs.readFileSync(manifestPath, "utf-8"));
1400
- }
1401
- async function renderStaticPages(options, outDir) {
1402
- const root = options.root;
1403
- const dirs = resolveDirs(options.dirs);
1404
- const apiPrefix = options.apiPrefix || DEFAULT_API_PREFIX;
1405
- const discoveredPages = discoverPages(root, path3.join(dirs.client, "pages"));
1406
- const distDir = path3.resolve(root, outDir);
1407
- const manifest = readManifest(distDir);
1408
- const previousAeroServer = process.env.AERO_SERVER;
1409
- process.env.AERO_SERVER = "false";
1410
- const staticCacheDir = path3.join(root, ".aero", "vite-ssr");
1411
- const server = await createServer({
1412
- configFile: false,
1413
- root,
1414
- cacheDir: staticCacheDir,
1415
- appType: "custom",
1416
- logLevel: "error",
1417
- plugins: options.vitePlugins ?? [],
1418
- server: {
1419
- middlewareMode: true,
1420
- hmr: false,
1421
- watch: { ignored: ["**/*"] }
1422
- }
1423
- });
1424
- try {
1425
- const runtime = await server.ssrLoadModule(RUNTIME_INSTANCE_MODULE_ID);
1426
- const pages = [];
1427
- for (const page of discoveredPages) {
1428
- if (!isDynamicPage(page)) {
1429
- pages.push(page);
1430
- continue;
1431
- }
1432
- const mod = await server.ssrLoadModule(page.sourceFile);
1433
- if (typeof mod.getStaticPaths !== "function") {
1434
- console.warn(
1435
- `[aero] \u26A0 Skipping dynamic page "${path3.relative(root, page.sourceFile)}" \u2014 no getStaticPaths() exported. Page will not be pre-rendered.`
1436
- );
1437
- continue;
1438
- }
1439
- const staticPaths = await mod.getStaticPaths();
1440
- if (!Array.isArray(staticPaths) || staticPaths.length === 0) {
1441
- console.warn(
1442
- `[aero] \u26A0 getStaticPaths() for "${path3.relative(root, page.sourceFile)}" returned no paths. Page will not be pre-rendered.`
1443
- );
1444
- continue;
1445
- }
1446
- for (const entry of staticPaths) {
1447
- const expandedPageName = expandPattern(page.pageName, entry.params);
1448
- const expandedRoute = toRouteFromPageName(expandedPageName);
1449
- pages.push({
1450
- pageName: expandedPageName,
1451
- routePath: expandedRoute,
1452
- sourceFile: page.sourceFile,
1453
- outputFile: toOutputFile(expandedRoute),
1454
- params: entry.params,
1455
- props: entry.props
1456
- });
1457
- }
1458
- }
1459
- const redirectFromSet = new Set(
1460
- (options.redirects ?? []).map(
1461
- (r) => r.from.replace(/^\/+|\/+$/g, "").trim() || ""
1462
- )
1463
- );
1464
- const pathMatchesRedirect = (page) => {
1465
- const pathSegment = page.routePath === "" ? "" : page.routePath;
1466
- return redirectFromSet.has(pathSegment);
1467
- };
1468
- const pagesToRender = options.redirects?.length ? pages.filter((p) => !pathMatchesRedirect(p)) : pages;
1469
- const routeSet = new Set(pagesToRender.map((p) => p.routePath));
1470
- for (const page of pagesToRender) {
1471
- const routePath = page.routePath ? `/${page.routePath}` : "/";
1472
- const pageUrl = new URL(routePath, "http://localhost");
1473
- const renderTarget = isDynamicPage(page) ? toPosixRelative(page.sourceFile, path3.resolve(root, dirs.client, "pages")).replace(/\.html$/i, "") : page.pageName;
1474
- let rendered = await runtime.aero.render(renderTarget, {
1475
- url: pageUrl,
1476
- request: new Request(pageUrl.toString(), { method: "GET" }),
1477
- routePath,
1478
- params: page.params || {},
1479
- props: page.props || {},
1480
- site: options.site
1481
- });
1482
- rendered = rewriteRenderedHtml(
1483
- addDoctype(rendered),
1484
- page.outputFile,
1485
- manifest,
1486
- routeSet,
1487
- apiPrefix
1488
- );
1489
- const isProd = typeof import.meta !== "undefined" && import.meta.env?.PROD;
1490
- if (options.minify && isProd) {
1491
- rendered = await minifyHTML(rendered, {
1492
- collapseWhitespace: true,
1493
- removeComments: true,
1494
- minifyCSS: true,
1495
- minifyJS: true
1496
- });
1497
- }
1498
- const outPath = path3.join(distDir, page.outputFile);
1499
- fs.mkdirSync(path3.dirname(outPath), { recursive: true });
1500
- fs.writeFileSync(outPath, rendered, "utf-8");
1501
- }
1502
- if (options.site && options.site.trim() !== "") {
1503
- const routePaths = [...new Set(pagesToRender.map((p) => p.routePath))];
1504
- writeSitemap(routePaths, options.site.trim(), distDir);
1505
- }
1506
- } finally {
1507
- await server.close();
1508
- if (previousAeroServer === void 0) {
1509
- delete process.env.AERO_SERVER;
1510
- } else {
1511
- process.env.AERO_SERVER = previousAeroServer;
1512
- }
1513
- }
1514
- }
1515
- function createBuildConfig(options = {}, root = process.cwd()) {
1516
- const dirs = resolveDirs(options.dirs);
1517
- const assetInputs = discoverAssetInputs(root, options.resolvePath, dirs.client);
1518
- const virtualClientInputs = discoverClientScriptVirtualInputs(root, dirs.client);
1519
- const inputs = { ...assetInputs, ...virtualClientInputs };
1520
- return {
1521
- outDir: dirs.dist,
1522
- manifest: true,
1523
- emptyOutDir: true,
1524
- rollupOptions: {
1525
- input: inputs,
1526
- output: {
1527
- entryFileNames(chunkInfo) {
1528
- const name = path3.basename(chunkInfo.name);
1529
- return `assets/${name}-[hash].js`;
1530
- },
1531
- chunkFileNames(chunkInfo) {
1532
- const facade = chunkInfo.facadeModuleId ?? "";
1533
- if (facade.includes("aero-html")) {
1534
- return `assets/aero-[hash].js`;
1535
- }
1536
- const name = path3.basename(chunkInfo.name);
1537
- return `assets/${name}-[hash].js`;
1538
- },
1539
- assetFileNames(assetInfo) {
1540
- const name = path3.basename(assetInfo.name || "");
1541
- return `assets/${name}-[hash][extname]`;
1542
- }
1543
- }
1544
- }
1545
- };
1546
- }
1547
-
1548
- // src/vite/index.ts
1549
- import { spawn } from "child_process";
1550
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
1551
- import { createRequire } from "module";
1552
- import { fileURLToPath } from "url";
1553
- import path4 from "path";
1554
- var require2 = createRequire(import.meta.url);
1555
- var AERO_DIR = ".aero";
1556
- var NITRO_CONFIG_FILENAME = "nitro.config.mjs";
1557
- function writeGeneratedNitroConfig(root, serverDir, redirects) {
1558
- const aeroDir = path4.join(root, AERO_DIR);
1559
- mkdirSync(aeroDir, { recursive: true });
1560
- const routeRules = redirectsToRouteRules(redirects ?? []);
1561
- const nitroConfig = {
1562
- rootDir: "..",
1563
- output: { dir: path4.join(root, ".output") },
1564
- scanDirs: [path4.join(root, serverDir)],
1565
- routeRules,
1566
- noPublicDir: true
1567
- };
1568
- const content = `// Generated by Aero \u2014 do not edit
1569
- export default ${JSON.stringify(nitroConfig, null, 2)}
1570
- `;
1571
- writeFileSync(path4.join(aeroDir, NITRO_CONFIG_FILENAME), content);
1572
- return aeroDir;
1573
- }
1574
- async function runNitroBuild(_root, configCwd) {
1575
- const nitroBin = process.platform === "win32" ? "nitro.cmd" : "nitro";
1576
- await new Promise((resolve, reject) => {
1577
- const child = spawn(nitroBin, ["build"], {
1578
- cwd: configCwd,
1579
- stdio: "inherit",
1580
- env: process.env
1581
- });
1582
- child.on("error", reject);
1583
- child.on("exit", (code) => {
1584
- if (code === 0) {
1585
- resolve();
1586
- return;
1587
- }
1588
- reject(new Error(`[aero] nitro build failed with exit code ${code ?? "null"}`));
1589
- });
1590
- });
1591
- }
1592
- function createAeroConfigPlugin(state) {
1593
- return {
1594
- name: "vite-plugin-aero-config",
1595
- enforce: "pre",
1596
- config(userConfig, env) {
1597
- const root = userConfig.root || process.cwd();
1598
- state.aliasResult = loadTsconfigAliases(root);
1599
- const site = state.options.site ?? "";
1600
- const alias = env?.command === "build" ? [
1601
- ...state.aliasResult.aliases,
1602
- {
1603
- find: "@aerobuilt/core",
1604
- replacement: require2.resolve("@aerobuilt/core/entry-prod")
1605
- }
1606
- ] : state.aliasResult.aliases;
1607
- return {
1608
- base: "./",
1609
- resolve: { alias },
1610
- define: {
1611
- "import.meta.env.SITE": JSON.stringify(site)
1612
- },
1613
- build: createBuildConfig(
1614
- { resolvePath: state.aliasResult.resolvePath, dirs: state.options.dirs },
1615
- root
1616
- )
1617
- };
1618
- },
1619
- configResolved(resolvedConfig) {
1620
- state.config = resolvedConfig;
1621
- }
1622
- };
1623
- }
1624
- function isAeroTemplateHtml(filePath, root, dirs) {
1625
- const clientBase = path4.join(root, dirs.client);
1626
- const rel = path4.relative(clientBase, filePath);
1627
- if (rel.startsWith("..") || path4.isAbsolute(rel)) return false;
1628
- const sep = path4.sep;
1629
- return rel.startsWith("pages" + sep) || rel.startsWith("components" + sep) || rel.startsWith("layouts" + sep);
1630
- }
1631
- function createAeroVirtualsPlugin(state) {
1632
- return {
1633
- name: "vite-plugin-aero-virtuals",
1634
- enforce: "pre",
1635
- buildStart() {
1636
- if (!state.config) return;
1637
- const contentMap = discoverClientScriptContentMap(state.config.root, state.dirs.client);
1638
- contentMap.forEach((entry, url) => state.clientScripts.set(url, entry));
1639
- },
1640
- async resolveId(id, importer) {
1641
- if (id === RUNTIME_INSTANCE_MODULE_ID) {
1642
- return RESOLVED_RUNTIME_INSTANCE_MODULE_ID;
1643
- }
1644
- if (id.startsWith(CLIENT_SCRIPT_PREFIX)) {
1645
- return "\0" + id;
1646
- }
1647
- if (id.startsWith("\0" + CLIENT_SCRIPT_PREFIX)) {
1648
- return id;
1649
- }
1650
- if (id.startsWith(AERO_HTML_VIRTUAL_PREFIX)) {
1651
- return id;
1652
- }
1653
- if (id.includes("html-proxy") && id.includes("inline-css")) {
1654
- return AERO_EMPTY_INLINE_CSS_PREFIX + id;
1655
- }
1656
- if (id.startsWith("aero:content")) {
1657
- return null;
1658
- }
1659
- const resolved = await this.resolve(id, importer, { skipSelf: true });
1660
- if (resolved && resolved.id.endsWith(".html")) {
1661
- if (state.config?.command === "build" && state.aliasResult && isAeroTemplateHtml(resolved.id, state.config.root, state.dirs)) {
1662
- return AERO_HTML_VIRTUAL_PREFIX + resolved.id.replace(/\.html$/i, ".aero");
1663
- }
1664
- return resolved;
1665
- }
1666
- const isPathLike = (id.startsWith("./") || id.startsWith("../") || id.startsWith("/") || id.startsWith("@") && !id.slice(1).split("/")[0].includes("-") && id.split("/").length < 3) && !id.includes(".") && !id.startsWith("\0") && !resolved?.id.includes("node_modules");
1667
- if (isPathLike) {
1668
- const resolvedHtml = await this.resolve(id + ".html", importer, { skipSelf: true });
1669
- if (resolvedHtml) {
1670
- if (state.config?.command === "build" && state.aliasResult && isAeroTemplateHtml(resolvedHtml.id, state.config.root, state.dirs)) {
1671
- return AERO_HTML_VIRTUAL_PREFIX + resolvedHtml.id.replace(/\.html$/i, ".aero");
1672
- }
1673
- return resolvedHtml;
1674
- }
1675
- }
1676
- return null;
1677
- },
1678
- load(id) {
1679
- if (id === RESOLVED_RUNTIME_INSTANCE_MODULE_ID) {
1680
- return `export { aero, onUpdate } from ${JSON.stringify(state.runtimeInstancePath)}`;
1681
- }
1682
- if (id.startsWith(AERO_EMPTY_INLINE_CSS_PREFIX)) {
1683
- return "/* aero: no inline styles */";
1684
- }
1685
- if (id.startsWith(AERO_HTML_VIRTUAL_PREFIX)) {
1686
- const filePath = id.slice(AERO_HTML_VIRTUAL_PREFIX.length).replace(/\.aero$/i, ".html");
1687
- if (!state.config || !state.aliasResult) return null;
1688
- this.addWatchFile(filePath);
1689
- try {
1690
- const code = readFileSync(filePath, "utf-8");
1691
- const parsed = parse(code);
1692
- const relativePath = toPosixRelative(filePath, state.config.root);
1693
- const baseName = relativePath.replace(/\.html$/i, "");
1694
- registerClientScriptsToMap(parsed, baseName, state.clientScripts);
1695
- for (let i = 0; i < parsed.clientScripts.length; i++) {
1696
- parsed.clientScripts[i].content = getClientScriptVirtualUrl(
1697
- baseName,
1698
- i,
1699
- parsed.clientScripts.length
1700
- );
1701
- }
1702
- const generated = compile(parsed, {
1703
- root: state.config.root,
1704
- clientScripts: parsed.clientScripts,
1705
- blockingScripts: parsed.blockingScripts,
1706
- inlineScripts: parsed.inlineScripts,
1707
- resolvePath: state.aliasResult.resolvePath
1708
- });
1709
- return { code: generated, map: null };
1710
- } catch {
1711
- return null;
1712
- }
1713
- }
1714
- if (id.startsWith("\0" + CLIENT_SCRIPT_PREFIX)) {
1715
- const virtualId = id.slice(1);
1716
- const entry = state.clientScripts.get(virtualId);
1717
- if (!entry) return "";
1718
- if (entry.passDataExpr) {
1719
- const keys = extractObjectKeys(entry.passDataExpr);
1720
- if (keys.length > 0) {
1721
- const preamble = `var __aero_data=(typeof window!=='undefined'&&window.__aero_data_next!==undefined)?window.__aero_data_next:{};if(typeof window!=='undefined')delete window.__aero_data_next;const { ${keys.join(", ")} } = __aero_data;
1722
- `;
1723
- return preamble + entry.content;
1724
- }
1725
- }
1726
- return entry.content;
1727
- }
1728
- return null;
1729
- }
1730
- };
1731
- }
1732
- function createAeroTransformPlugin(state) {
1733
- return {
1734
- name: "vite-plugin-aero-transform",
1735
- enforce: "pre",
1736
- transform(code, id) {
1737
- if (id.startsWith(AERO_HTML_VIRTUAL_PREFIX)) return null;
1738
- if (!id.endsWith(".html")) return null;
1739
- if (!state.config || !state.aliasResult) return null;
1740
- try {
1741
- const parsed = parse(code);
1742
- if (parsed.clientScripts.length > 0) {
1743
- const relativePath = toPosixRelative(id, state.config.root);
1744
- const baseName = relativePath.replace(/\.html$/i, "");
1745
- registerClientScriptsToMap(parsed, baseName, state.clientScripts);
1746
- for (let i = 0; i < parsed.clientScripts.length; i++) {
1747
- parsed.clientScripts[i].content = getClientScriptVirtualUrl(
1748
- baseName,
1749
- i,
1750
- parsed.clientScripts.length
1751
- );
1752
- }
1753
- }
1754
- const generated = compile(parsed, {
1755
- root: state.config.root,
1756
- clientScripts: parsed.clientScripts,
1757
- blockingScripts: parsed.blockingScripts,
1758
- inlineScripts: parsed.inlineScripts,
1759
- resolvePath: state.aliasResult.resolvePath
1760
- });
1761
- return {
1762
- code: generated,
1763
- map: null
1764
- };
1765
- } catch (err) {
1766
- const relativePath = path4.relative(state.config.root, id);
1767
- this.error(`[aero] Error compiling ${relativePath}: ${err.message}`);
1768
- }
1769
- }
1770
- };
1771
- }
1772
- function createAeroSsrPlugin(state) {
1773
- return {
1774
- name: "vite-plugin-aero-ssr",
1775
- configureServer(server) {
1776
- server.middlewares.use(async (req, res, next) => {
1777
- if (!req.url) return next();
1778
- if (req.method && req.method.toUpperCase() !== "GET") return next();
1779
- const acceptsHtml = req.headers.accept?.includes("text/html");
1780
- if (!acceptsHtml) return next();
1781
- const pathname = req.url.split("?")[0] || "/";
1782
- if (pathname.startsWith(state.apiPrefix) || pathname.startsWith("/@fs") || pathname.startsWith("/@id")) {
1783
- return next();
1784
- }
1785
- const ext = path4.extname(pathname);
1786
- if (ext && ext !== ".html") return next();
1787
- const redirects = state.options.redirects;
1788
- if (redirects?.length) {
1789
- for (const rule of redirects) {
1790
- if (pathname === rule.from) {
1791
- res.statusCode = rule.status ?? 302;
1792
- res.setHeader("Location", rule.to);
1793
- res.end();
1794
- return;
1795
- }
1796
- }
1797
- }
1798
- try {
1799
- const pageName = resolvePageName(req.url);
1800
- const mod = await server.ssrLoadModule(RUNTIME_INSTANCE_MODULE_ID);
1801
- const requestUrl = new URL(req.url, "http://localhost");
1802
- const requestHeaders = new Headers();
1803
- for (const [name, value] of Object.entries(req.headers)) {
1804
- if (value === void 0) continue;
1805
- if (Array.isArray(value)) {
1806
- for (const item of value) requestHeaders.append(name, item);
1807
- continue;
1808
- }
1809
- requestHeaders.set(name, value);
1810
- }
1811
- const request = new Request(requestUrl.toString(), {
1812
- method: req.method || "GET",
1813
- headers: requestHeaders
1814
- });
1815
- let renderPageName = pageName;
1816
- let renderInput = {
1817
- url: requestUrl,
1818
- request,
1819
- routePath: pathname,
1820
- site: state.options.site
1821
- };
1822
- const middleware = state.options.middleware;
1823
- if (middleware?.length) {
1824
- const ctx = {
1825
- url: requestUrl,
1826
- request,
1827
- routePath: pathname,
1828
- pageName,
1829
- site: state.options.site
1830
- };
1831
- for (const handler of middleware) {
1832
- const result = await Promise.resolve(handler(ctx));
1833
- if (result && "redirect" in result) {
1834
- res.statusCode = result.redirect.status ?? 302;
1835
- res.setHeader("Location", result.redirect.url);
1836
- res.end();
1837
- return;
1838
- }
1839
- if (result && "response" in result) {
1840
- res.statusCode = result.response.status;
1841
- result.response.headers.forEach((v, k) => res.setHeader(k, v));
1842
- const body = await result.response.arrayBuffer();
1843
- res.end(Buffer.from(body));
1844
- return;
1845
- }
1846
- if (result && "rewrite" in result) {
1847
- if (result.rewrite.pageName !== void 0)
1848
- renderPageName = result.rewrite.pageName;
1849
- const { pageName: _pn, ...rest } = result.rewrite;
1850
- renderInput = { ...renderInput, ...rest };
1851
- }
1852
- }
1853
- }
1854
- let rendered = await mod.aero.render(renderPageName, renderInput);
1855
- if (rendered === null) {
1856
- res.statusCode = 404;
1857
- rendered = await mod.aero.render("404", renderInput);
1858
- }
1859
- if (rendered === null) {
1860
- res.statusCode = 404;
1861
- res.setHeader("Content-Type", "text/html; charset=utf-8");
1862
- res.end("<h1>404 \u2014 Not Found</h1>");
1863
- return;
1864
- }
1865
- rendered = addDoctype(rendered);
1866
- const transformed = await server.transformIndexHtml(req.url, rendered);
1867
- res.setHeader("Content-Type", "text/html; charset=utf-8");
1868
- res.end(transformed);
1869
- } catch (err) {
1870
- next(err);
1871
- }
1872
- });
1873
- }
1874
- };
1875
- }
1876
- function aero(options = {}) {
1877
- const dirs = resolveDirs(options.dirs);
1878
- const apiPrefix = options.apiPrefix || DEFAULT_API_PREFIX;
1879
- const enableNitro = options.server === true && process.env.AERO_SERVER !== "false";
1880
- const runtimeInstanceJsPath = fileURLToPath(
1881
- new URL("../runtime/instance.js", import.meta.url)
1882
- );
1883
- const runtimeInstanceTsPath = fileURLToPath(
1884
- new URL("../runtime/instance.ts", import.meta.url)
1885
- );
1886
- const runtimeInstancePath = existsSync(runtimeInstanceJsPath) ? runtimeInstanceJsPath : runtimeInstanceTsPath;
1887
- const state = {
1888
- config: null,
1889
- aliasResult: null,
1890
- clientScripts: /* @__PURE__ */ new Map(),
1891
- runtimeInstancePath,
1892
- dirs,
1893
- apiPrefix,
1894
- options
1895
- };
1896
- const aeroConfigPlugin = createAeroConfigPlugin(state);
1897
- const aeroVirtualsPlugin = createAeroVirtualsPlugin(state);
1898
- const aeroTransformPlugin = createAeroTransformPlugin(state);
1899
- const aeroSsrPlugin = createAeroSsrPlugin(state);
1900
- const aeroCorePlugins = [aeroConfigPlugin, aeroVirtualsPlugin, aeroTransformPlugin];
1901
- const staticBuildPlugin = {
1902
- name: "vite-plugin-aero-static",
1903
- apply: "build",
1904
- async closeBundle() {
1905
- const root = state.config.root;
1906
- const outDir = state.config.build.outDir;
1907
- const shouldMinifyHtml = state.config.build.minify !== false && typeof import.meta !== "undefined" && import.meta.env?.PROD;
1908
- const staticPlugins = options.staticServerPlugins?.length ? [...aeroCorePlugins, ...options.staticServerPlugins] : aeroCorePlugins;
1909
- await renderStaticPages(
1910
- {
1911
- root,
1912
- resolvePath: state.aliasResult.resolvePath,
1913
- dirs: options.dirs,
1914
- apiPrefix,
1915
- vitePlugins: staticPlugins,
1916
- minify: shouldMinifyHtml,
1917
- site: options.site,
1918
- redirects: options.redirects
1919
- },
1920
- outDir
1921
- );
1922
- if (enableNitro) {
1923
- const configCwd = writeGeneratedNitroConfig(root, dirs.server, options.redirects);
1924
- await runNitroBuild(root, configCwd);
1925
- }
1926
- }
1927
- };
1928
- const plugins = [
1929
- aeroConfigPlugin,
1930
- aeroVirtualsPlugin,
1931
- aeroTransformPlugin,
1932
- aeroSsrPlugin,
1933
- staticBuildPlugin,
1934
- ViteImageOptimizer({
1935
- exclude: void 0,
1936
- include: void 0,
1937
- includePublic: true,
1938
- logStats: true,
1939
- ansiColors: true,
1940
- svg: {
1941
- multipass: true,
1942
- plugins: [
1943
- {
1944
- name: "preset-default",
1945
- params: {
1946
- overrides: {
1947
- cleanupNumericValues: false
1948
- },
1949
- cleanupIDs: {
1950
- minify: false,
1951
- remove: false
1952
- },
1953
- convertPathData: false
1954
- }
1955
- }
1956
- ]
1957
- },
1958
- png: { quality: 80 },
1959
- jpeg: { quality: 80 },
1960
- jpg: { quality: 80 },
1961
- tiff: { quality: 80 },
1962
- gif: {},
1963
- webp: { lossless: true },
1964
- avif: { lossless: true }
1965
- })
1966
- ];
1967
- if (enableNitro) {
1968
- const rawNitroPlugins = nitro({ serverDir: dirs.server });
1969
- const nitroPlugins = Array.isArray(rawNitroPlugins) ? rawNitroPlugins : [rawNitroPlugins];
1970
- for (const nitroPlugin of nitroPlugins) {
1971
- if (!nitroPlugin || typeof nitroPlugin !== "object") continue;
1972
- const originalApply = nitroPlugin.apply;
1973
- plugins.push({
1974
- ...nitroPlugin,
1975
- apply(pluginConfig, env) {
1976
- if (env.command !== "serve") return false;
1977
- if (env.isPreview) return false;
1978
- if (typeof originalApply === "function") {
1979
- return originalApply(pluginConfig, env);
1980
- }
1981
- if (originalApply) return originalApply === "serve";
1982
- return true;
1983
- }
1984
- });
1985
- }
1986
- }
1987
- return plugins;
1988
- }
1989
- export {
1990
- aero
1991
- };