mnfst-render 0.1.4 → 0.1.5
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 +75 -9
- package/package.json +1 -1
package/manifest.render.mjs
CHANGED
|
@@ -464,6 +464,39 @@ function buildCanonicalAndHreflang(pathSeg, locales, defaultLocale, base) {
|
|
|
464
464
|
return out;
|
|
465
465
|
}
|
|
466
466
|
|
|
467
|
+
/** Same alternate URLs as buildCanonicalAndHreflang; used for sitemap xhtml:link entries. */
|
|
468
|
+
function getAlternateLinksForPath(pathSeg, locales, defaultLocale, base) {
|
|
469
|
+
const baseClean = base.replace(/\/$/, '');
|
|
470
|
+
const defaultLoc = defaultLocale || locales[0];
|
|
471
|
+
if (!locales || locales.length <= 1) return [];
|
|
472
|
+
const currentLocale = locales.find((l) => pathSeg === l || pathSeg.startsWith(l + '/')) || defaultLoc;
|
|
473
|
+
const logicalRoute =
|
|
474
|
+
currentLocale === defaultLoc
|
|
475
|
+
? pathSeg === defaultLoc
|
|
476
|
+
? ''
|
|
477
|
+
: pathSeg.startsWith(defaultLoc + '/')
|
|
478
|
+
? pathSeg.slice(defaultLoc.length + 1)
|
|
479
|
+
: pathSeg
|
|
480
|
+
: pathSeg === currentLocale
|
|
481
|
+
? ''
|
|
482
|
+
: pathSeg.slice(currentLocale.length + 1);
|
|
483
|
+
const entries = [];
|
|
484
|
+
locales.forEach((loc) => {
|
|
485
|
+
const seg = loc === defaultLoc ? logicalRoute : (logicalRoute ? `${loc}/${logicalRoute}` : loc);
|
|
486
|
+
const href = baseClean + (seg ? `/${seg}` : '');
|
|
487
|
+
const hreflang = loc === defaultLoc ? 'x-default' : loc;
|
|
488
|
+
entries.push({ hreflang, href });
|
|
489
|
+
});
|
|
490
|
+
return entries;
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
function escapeXmlText(s) {
|
|
494
|
+
return String(s)
|
|
495
|
+
.replace(/&/g, '&')
|
|
496
|
+
.replace(/</g, '<')
|
|
497
|
+
.replace(/>/g, '>');
|
|
498
|
+
}
|
|
499
|
+
|
|
467
500
|
function buildOgLocale(pathSeg, locales, defaultLocale) {
|
|
468
501
|
if (locales.length <= 1) return '';
|
|
469
502
|
const defaultLoc = defaultLocale || locales[0];
|
|
@@ -530,7 +563,22 @@ function setNestedKey(obj, path, value) {
|
|
|
530
563
|
let cur = obj;
|
|
531
564
|
for (let i = 0; i < parts.length - 1; i++) {
|
|
532
565
|
const p = parts[i];
|
|
533
|
-
|
|
566
|
+
const next = parts[i + 1];
|
|
567
|
+
const nextIsIndex = /^\d+$/.test(next);
|
|
568
|
+
if (!(p in cur) || typeof cur[p] !== 'object') {
|
|
569
|
+
cur[p] = nextIsIndex ? [] : {};
|
|
570
|
+
} else if (nextIsIndex && !Array.isArray(cur[p]) && cur[p] && typeof cur[p] === 'object') {
|
|
571
|
+
const existing = cur[p];
|
|
572
|
+
const keys = Object.keys(existing);
|
|
573
|
+
const numericOnly = keys.every((k) => /^\d+$/.test(k));
|
|
574
|
+
if (numericOnly) {
|
|
575
|
+
const arr = [];
|
|
576
|
+
keys.forEach((k) => {
|
|
577
|
+
arr[parseInt(k, 10)] = existing[k];
|
|
578
|
+
});
|
|
579
|
+
cur[p] = arr;
|
|
580
|
+
}
|
|
581
|
+
}
|
|
534
582
|
cur = cur[p];
|
|
535
583
|
}
|
|
536
584
|
cur[parts[parts.length - 1]] = value;
|
|
@@ -568,9 +616,11 @@ function resolveHeadXBindings(html, xData) {
|
|
|
568
616
|
|
|
569
617
|
// --- SEO: robots.txt and sitemap.xml (written to output, use liveUrl for crawlers) ---
|
|
570
618
|
|
|
571
|
-
function writeSeoFiles(outputDir, pathList, liveUrl) {
|
|
619
|
+
function writeSeoFiles(outputDir, pathList, liveUrl, locales, defaultLocale) {
|
|
572
620
|
const base = liveUrl.replace(/\/$/, '');
|
|
573
621
|
const today = new Date().toISOString().slice(0, 10);
|
|
622
|
+
const localeList = Array.isArray(locales) ? locales : [];
|
|
623
|
+
const multiLocale = localeList.length > 1;
|
|
574
624
|
|
|
575
625
|
writeFileSync(
|
|
576
626
|
join(outputDir, 'robots.txt'),
|
|
@@ -582,22 +632,32 @@ Sitemap: ${base}/sitemap.xml
|
|
|
582
632
|
'utf8'
|
|
583
633
|
);
|
|
584
634
|
|
|
635
|
+
const urlsetNs = multiLocale
|
|
636
|
+
? '<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:xhtml="http://www.w3.org/1999/xhtml">'
|
|
637
|
+
: '<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">';
|
|
638
|
+
|
|
585
639
|
const urlEntries = pathList.map((pathSeg) => {
|
|
586
640
|
const path = pathSeg === '' ? '' : '/' + pathSeg.replace(/\/$/, '');
|
|
587
641
|
const loc = path ? `${base}${path}` : base + '/';
|
|
588
|
-
const
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
642
|
+
const escapedLoc = escapeXmlText(loc);
|
|
643
|
+
let body = ` <loc>${escapedLoc}</loc>`;
|
|
644
|
+
if (multiLocale) {
|
|
645
|
+
for (const { hreflang, href } of getAlternateLinksForPath(pathSeg, localeList, defaultLocale, liveUrl)) {
|
|
646
|
+
body += `\n <xhtml:link rel="alternate" hreflang="${escapeXmlText(hreflang)}" href="${escapeXmlText(href)}" />`;
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
body += `\n <lastmod>${today}</lastmod>
|
|
592
650
|
<changefreq>monthly</changefreq>
|
|
593
|
-
<priority>${path === '' ? '1.0' : '0.8'}</priority
|
|
651
|
+
<priority>${path === '' ? '1.0' : '0.8'}</priority>`;
|
|
652
|
+
return ` <url>
|
|
653
|
+
${body}
|
|
594
654
|
</url>`;
|
|
595
655
|
});
|
|
596
656
|
|
|
597
657
|
writeFileSync(
|
|
598
658
|
join(outputDir, 'sitemap.xml'),
|
|
599
659
|
`<?xml version="1.0" encoding="UTF-8"?>
|
|
600
|
-
|
|
660
|
+
${urlsetNs}
|
|
601
661
|
${urlEntries.join('\n')}
|
|
602
662
|
</urlset>
|
|
603
663
|
`,
|
|
@@ -1063,7 +1123,13 @@ async function runPrerender(config) {
|
|
|
1063
1123
|
await browser.close();
|
|
1064
1124
|
}
|
|
1065
1125
|
|
|
1066
|
-
writeSeoFiles(
|
|
1126
|
+
writeSeoFiles(
|
|
1127
|
+
config.output,
|
|
1128
|
+
pathList.filter((p) => p !== NOT_FOUND_PATH),
|
|
1129
|
+
config.liveUrl,
|
|
1130
|
+
locales,
|
|
1131
|
+
defaultLocale
|
|
1132
|
+
);
|
|
1067
1133
|
|
|
1068
1134
|
if (config.redirects.length > 0) {
|
|
1069
1135
|
const lines = config.redirects.map((r) => {
|