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