mnfst-render 0.1.5 → 0.1.6
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/manifest.render.mjs +68 -12
- package/package.json +1 -1
package/manifest.render.mjs
CHANGED
|
@@ -144,16 +144,37 @@ function extractXRouteConditions(html) {
|
|
|
144
144
|
return conditions;
|
|
145
145
|
}
|
|
146
146
|
|
|
147
|
+
function normalizeRouteCondition(cond) {
|
|
148
|
+
const raw = String(cond || '').trim();
|
|
149
|
+
if (!raw) return { kind: 'all', path: '' };
|
|
150
|
+
if (raw.startsWith('!')) {
|
|
151
|
+
const omitted = raw.slice(1).trim();
|
|
152
|
+
if (!omitted || omitted === '*') return { kind: 'not-found', path: '' }; // !*
|
|
153
|
+
return { kind: 'omit', path: omitted };
|
|
154
|
+
}
|
|
155
|
+
if (raw === '*') return { kind: 'all', path: '' };
|
|
156
|
+
const withoutExact = raw.startsWith('=') ? raw.slice(1) : raw;
|
|
157
|
+
const trimmed = withoutExact.replace(/^\/+|\/+$/g, '');
|
|
158
|
+
if (!trimmed) return { kind: 'root', path: '' };
|
|
159
|
+
if (trimmed.endsWith('/*')) {
|
|
160
|
+
const base = trimmed.slice(0, -2).replace(/^\/+|\/+$/g, '');
|
|
161
|
+
return base ? { kind: 'wildcard-prefix', path: base } : { kind: 'all', path: '' };
|
|
162
|
+
}
|
|
163
|
+
if (trimmed.includes('*')) return { kind: 'unsupported-pattern', path: trimmed };
|
|
164
|
+
return { kind: 'path', path: trimmed };
|
|
165
|
+
}
|
|
166
|
+
|
|
147
167
|
function conditionsToPaths(conditions) {
|
|
148
168
|
const paths = new Set();
|
|
149
169
|
paths.add('/');
|
|
150
170
|
for (const c of conditions) {
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
171
|
+
const parsed = normalizeRouteCondition(c);
|
|
172
|
+
// Discovery rules aligned with router docs:
|
|
173
|
+
// - "*" and omitted routes do not define concrete paths.
|
|
174
|
+
// - "!*" is handled separately via explicit NOT_FOUND path.
|
|
175
|
+
// - "about/*" does not include "/about" by itself; concrete children come from data paths.
|
|
176
|
+
if (parsed.kind === 'path') paths.add('/' + parsed.path);
|
|
177
|
+
else if (parsed.kind === 'root') paths.add('/');
|
|
157
178
|
}
|
|
158
179
|
return paths;
|
|
159
180
|
}
|
|
@@ -177,6 +198,13 @@ function parseYamlPaths(filePath) {
|
|
|
177
198
|
const segment = pathMatch[1].trim();
|
|
178
199
|
paths.push(`${currentGroup}/${segment}`);
|
|
179
200
|
}
|
|
201
|
+
const genericPathMatch = line.match(/^\s*(?:-\s*)?(?:path|slug):\s*["']?([^"'\n#]+)["']?/);
|
|
202
|
+
if (genericPathMatch) {
|
|
203
|
+
const v = genericPathMatch[1].trim().replace(/^\/+|\/+$/g, '');
|
|
204
|
+
if (v && !v.includes('*') && !/\.[a-z0-9]+$/i.test(v)) {
|
|
205
|
+
paths.push(v);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
180
208
|
}
|
|
181
209
|
return paths;
|
|
182
210
|
}
|
|
@@ -309,10 +337,8 @@ function discoverRoutes(manifest, rootDir) {
|
|
|
309
337
|
const compPath = join(rootDir, rel);
|
|
310
338
|
if (existsSync(compPath)) {
|
|
311
339
|
const html = readFileSync(compPath, 'utf8');
|
|
312
|
-
extractXRouteConditions(html)
|
|
313
|
-
|
|
314
|
-
else if (c) pathSet.add('/' + c);
|
|
315
|
-
});
|
|
340
|
+
const conditions = extractXRouteConditions(html);
|
|
341
|
+
conditionsToPaths(conditions).forEach((p) => pathSet.add(p));
|
|
316
342
|
}
|
|
317
343
|
}
|
|
318
344
|
|
|
@@ -329,6 +355,29 @@ function pathToFileSegments(pathname) {
|
|
|
329
355
|
return normalized ? normalized.split('/') : [];
|
|
330
356
|
}
|
|
331
357
|
|
|
358
|
+
function validatePrerenderedOutput(outputDir, pathList) {
|
|
359
|
+
const invalidPathTokens = pathList.filter((p) => /(^|\/)[*=]/.test(p) || p.includes('/*') || p.includes('*'));
|
|
360
|
+
if (invalidPathTokens.length > 0) {
|
|
361
|
+
throw new Error(`prerender validation failed: invalid discovered route token(s): ${invalidPathTokens.join(', ')}`);
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
const badFolders = [];
|
|
365
|
+
function walk(dir, rel = '') {
|
|
366
|
+
const entries = readdirSync(dir, { withFileTypes: true });
|
|
367
|
+
for (const ent of entries) {
|
|
368
|
+
if (!ent.isDirectory()) continue;
|
|
369
|
+
const seg = ent.name;
|
|
370
|
+
const nextRel = rel ? `${rel}/${seg}` : seg;
|
|
371
|
+
if (seg.includes('*') || seg.startsWith('=')) badFolders.push(nextRel);
|
|
372
|
+
walk(join(dir, seg), nextRel);
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
if (existsSync(outputDir)) walk(outputDir, '');
|
|
376
|
+
if (badFolders.length > 0) {
|
|
377
|
+
throw new Error(`prerender validation failed: invalid output folder(s): ${badFolders.join(', ')}`);
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
|
|
332
381
|
// --- Strip dev-only injected content (e.g. browser-sync) so dist works under any server -
|
|
333
382
|
|
|
334
383
|
function stripDevOnlyContent(html) {
|
|
@@ -990,7 +1039,11 @@ async function runPrerender(config) {
|
|
|
990
1039
|
xFor.includes('$url') || xFor.includes('$auth') ||
|
|
991
1040
|
/\bin\s+(filtered\w*|results|searchResults)\b/.test(xFor);
|
|
992
1041
|
const forceCollapse = explicit || inferred;
|
|
993
|
-
if (!forceCollapse)
|
|
1042
|
+
if (!forceCollapse) {
|
|
1043
|
+
tpl.removeAttribute('data-prerender-collapsed');
|
|
1044
|
+
return; // keep prerendered list for SEO
|
|
1045
|
+
}
|
|
1046
|
+
tpl.setAttribute('data-prerender-collapsed', '1');
|
|
994
1047
|
const first = tpl.content?.firstElementChild;
|
|
995
1048
|
if (!first) return;
|
|
996
1049
|
const tag = first.tagName;
|
|
@@ -1028,7 +1081,9 @@ async function runPrerender(config) {
|
|
|
1028
1081
|
return false;
|
|
1029
1082
|
};
|
|
1030
1083
|
|
|
1031
|
-
|
|
1084
|
+
// Only clean up templates we intentionally collapsed above.
|
|
1085
|
+
// Running this on all x-for templates can remove valid prerendered list items.
|
|
1086
|
+
document.querySelectorAll('template[x-for][data-prerender-collapsed="1"]').forEach((tpl) => {
|
|
1032
1087
|
const xFor = (tpl.getAttribute('x-for') || '').trim();
|
|
1033
1088
|
const m = xFor.match(loopVarRegex);
|
|
1034
1089
|
const itemVar = m ? (m[1] || m[3] || '') : '';
|
|
@@ -1130,6 +1185,7 @@ async function runPrerender(config) {
|
|
|
1130
1185
|
locales,
|
|
1131
1186
|
defaultLocale
|
|
1132
1187
|
);
|
|
1188
|
+
validatePrerenderedOutput(config.output, pathList.filter((p) => p !== NOT_FOUND_PATH));
|
|
1133
1189
|
|
|
1134
1190
|
if (config.redirects.length > 0) {
|
|
1135
1191
|
const lines = config.redirects.map((r) => {
|