@nuxtjs/sitemap 7.2.10 → 7.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/client/200.html +10 -9
- package/dist/client/404.html +10 -9
- package/dist/client/_nuxt/{DDzCRRw4.js → 5vafBU9X.js} +1 -1
- package/dist/client/_nuxt/BIHI7g3E.js +1 -0
- package/dist/client/_nuxt/Bn78IMkz.js +172 -0
- package/dist/client/_nuxt/{DdvkoY3I.js → CT3BV8Rj.js} +1 -1
- package/dist/client/_nuxt/Cp-IABpG.js +1 -0
- package/dist/client/_nuxt/builds/latest.json +1 -1
- package/dist/client/_nuxt/builds/meta/5ecca6e1-2b8a-4fc5-a128-35cbc27bf6d7.json +1 -0
- package/dist/client/_nuxt/{entry.BrROiVtQ.css → entry.BzbtAPc0.css} +1 -1
- package/dist/client/_nuxt/error-404.D_zhMyJm.css +1 -0
- package/dist/client/_nuxt/error-500.rdOYVbxo.css +1 -0
- package/dist/client/index.html +10 -9
- package/dist/module.cjs +59 -20
- package/dist/module.json +2 -2
- package/dist/module.mjs +60 -21
- package/dist/runtime/server/plugins/warm-up.js +20 -4
- package/dist/runtime/server/routes/__sitemap__/debug.js +2 -2
- package/dist/runtime/server/routes/sitemap/[sitemap].xml.js +37 -7
- package/dist/runtime/server/routes/sitemap_index.xml.js +11 -3
- package/dist/runtime/server/sitemap/builder/sitemap-index.d.ts +1 -1
- package/dist/runtime/server/sitemap/builder/sitemap-index.js +110 -16
- package/dist/runtime/server/sitemap/builder/sitemap.d.ts +1 -1
- package/dist/runtime/server/sitemap/builder/sitemap.js +62 -36
- package/dist/runtime/server/sitemap/builder/xml.d.ts +2 -3
- package/dist/runtime/server/sitemap/builder/xml.js +182 -80
- package/dist/runtime/server/sitemap/nitro.js +68 -20
- package/dist/runtime/server/sitemap/urlset/normalise.js +21 -19
- package/dist/runtime/server/sitemap/urlset/sort.d.ts +1 -1
- package/dist/runtime/server/sitemap/urlset/sort.js +9 -15
- package/dist/runtime/server/sitemap/utils/chunk.d.ts +10 -0
- package/dist/runtime/server/sitemap/utils/chunk.js +66 -0
- package/dist/runtime/types.d.ts +44 -0
- package/dist/runtime/utils-pure.js +13 -5
- package/package.json +23 -22
- package/dist/client/_nuxt/BLmTiKMJ.js +0 -1
- package/dist/client/_nuxt/BUP090M8.js +0 -172
- package/dist/client/_nuxt/builds/meta/3c351607-eab3-459a-b743-aba04e49a80e.json +0 -1
- package/dist/client/_nuxt/error-404.BMkETmdU.css +0 -1
- package/dist/client/_nuxt/error-500.C3_I-O7u.css +0 -1
|
@@ -1,86 +1,188 @@
|
|
|
1
|
-
function
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
case "videos":
|
|
6
|
-
return "video";
|
|
7
|
-
// news & others?
|
|
8
|
-
case "news":
|
|
9
|
-
return "news";
|
|
10
|
-
default:
|
|
11
|
-
return k;
|
|
12
|
-
}
|
|
1
|
+
export function escapeValueForXml(value) {
|
|
2
|
+
if (value === true || value === false)
|
|
3
|
+
return value ? "yes" : "no";
|
|
4
|
+
return String(value).replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
13
5
|
}
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
6
|
+
const URLSET_OPENING_TAG = '<urlset xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:video="http://www.google.com/schemas/sitemap-video/1.1" xmlns:xhtml="http://www.w3.org/1999/xhtml" xmlns:image="http://www.google.com/schemas/sitemap-image/1.1" xmlns:news="http://www.google.com/schemas/sitemap-news/0.9" xsi:schemaLocation="http://www.sitemaps.org/schemas/sitemap/0.9 http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd http://www.google.com/schemas/sitemap-image/1.1 http://www.google.com/schemas/sitemap-image/1.1/sitemap-image.xsd" xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">';
|
|
7
|
+
function buildUrlXml(url) {
|
|
8
|
+
const capacity = 50;
|
|
9
|
+
const parts = Array.from({ length: capacity });
|
|
10
|
+
let partIndex = 0;
|
|
11
|
+
parts[partIndex++] = " <url>";
|
|
12
|
+
if (url.loc) {
|
|
13
|
+
parts[partIndex++] = ` <loc>${escapeValueForXml(url.loc)}</loc>`;
|
|
14
|
+
}
|
|
15
|
+
if (url.lastmod) {
|
|
16
|
+
parts[partIndex++] = ` <lastmod>${url.lastmod}</lastmod>`;
|
|
17
|
+
}
|
|
18
|
+
if (url.changefreq) {
|
|
19
|
+
parts[partIndex++] = ` <changefreq>${url.changefreq}</changefreq>`;
|
|
20
|
+
}
|
|
21
|
+
if (url.priority !== void 0) {
|
|
22
|
+
const priorityValue = Number.parseFloat(String(url.priority));
|
|
23
|
+
const formattedPriority = priorityValue % 1 === 0 ? String(priorityValue) : priorityValue.toFixed(1);
|
|
24
|
+
parts[partIndex++] = ` <priority>${formattedPriority}</priority>`;
|
|
25
|
+
}
|
|
26
|
+
const keys = Object.keys(url).filter((k) => !k.startsWith("_") && !["loc", "lastmod", "changefreq", "priority"].includes(k));
|
|
27
|
+
for (const key of keys) {
|
|
28
|
+
const value = url[key];
|
|
29
|
+
if (value === void 0 || value === null) continue;
|
|
30
|
+
switch (key) {
|
|
31
|
+
case "alternatives":
|
|
32
|
+
if (Array.isArray(value) && value.length > 0) {
|
|
33
|
+
for (const alt of value) {
|
|
34
|
+
const attrs = Object.entries(alt).map(([k, v]) => `${k}="${escapeValueForXml(v)}"`).join(" ");
|
|
35
|
+
parts[partIndex++] = ` <xhtml:link rel="alternate" ${attrs} />`;
|
|
27
36
|
}
|
|
28
|
-
const attributes = Object.entries(v).filter(([ssk]) => ssk !== sk).map(([ssk, ssv]) => `${ssk}="${escapeValueForXml(ssv)}"`).join(" ");
|
|
29
|
-
return [
|
|
30
|
-
` <${key}:${sk} ${attributes}>`,
|
|
31
|
-
// value is the same sk
|
|
32
|
-
v[sk],
|
|
33
|
-
`</${key}:${sk}>`
|
|
34
|
-
].join("");
|
|
35
|
-
}).join("\n");
|
|
36
|
-
}
|
|
37
|
-
if (typeof sv === "object") {
|
|
38
|
-
if (key === "video") {
|
|
39
|
-
const attributes = Object.entries(sv).filter(([ssk]) => ssk !== sk).map(([ssk, ssv]) => `${ssk}="${escapeValueForXml(ssv)}"`).join(" ");
|
|
40
|
-
return [
|
|
41
|
-
` <${key}:${sk} ${attributes}>`,
|
|
42
|
-
// value is the same sk
|
|
43
|
-
sv[sk],
|
|
44
|
-
`</${key}:${sk}>`
|
|
45
|
-
].join("");
|
|
46
37
|
}
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
38
|
+
break;
|
|
39
|
+
case "images":
|
|
40
|
+
if (Array.isArray(value) && value.length > 0) {
|
|
41
|
+
for (const img of value) {
|
|
42
|
+
parts[partIndex++] = " <image:image>";
|
|
43
|
+
parts[partIndex++] = ` <image:loc>${escapeValueForXml(img.loc)}</image:loc>`;
|
|
44
|
+
if (img.title) parts[partIndex++] = ` <image:title>${escapeValueForXml(img.title)}</image:title>`;
|
|
45
|
+
if (img.caption) parts[partIndex++] = ` <image:caption>${escapeValueForXml(img.caption)}</image:caption>`;
|
|
46
|
+
if (img.geo_location) parts[partIndex++] = ` <image:geo_location>${escapeValueForXml(img.geo_location)}</image:geo_location>`;
|
|
47
|
+
if (img.license) parts[partIndex++] = ` <image:license>${escapeValueForXml(img.license)}</image:license>`;
|
|
48
|
+
parts[partIndex++] = " </image:image>";
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
break;
|
|
52
|
+
case "videos":
|
|
53
|
+
if (Array.isArray(value) && value.length > 0) {
|
|
54
|
+
for (const video of value) {
|
|
55
|
+
parts[partIndex++] = " <video:video>";
|
|
56
|
+
parts[partIndex++] = ` <video:title>${escapeValueForXml(video.title)}</video:title>`;
|
|
57
|
+
if (video.thumbnail_loc) {
|
|
58
|
+
parts[partIndex++] = ` <video:thumbnail_loc>${escapeValueForXml(video.thumbnail_loc)}</video:thumbnail_loc>`;
|
|
59
|
+
}
|
|
60
|
+
parts[partIndex++] = ` <video:description>${escapeValueForXml(video.description)}</video:description>`;
|
|
61
|
+
if (video.content_loc) {
|
|
62
|
+
parts[partIndex++] = ` <video:content_loc>${escapeValueForXml(video.content_loc)}</video:content_loc>`;
|
|
63
|
+
}
|
|
64
|
+
if (video.player_loc) {
|
|
65
|
+
const attrs = video.player_loc.allow_embed ? ' allow_embed="yes"' : "";
|
|
66
|
+
const autoplay = video.player_loc.autoplay ? ' autoplay="yes"' : "";
|
|
67
|
+
parts[partIndex++] = ` <video:player_loc${attrs}${autoplay}>${escapeValueForXml(video.player_loc)}</video:player_loc>`;
|
|
68
|
+
}
|
|
69
|
+
if (video.duration !== void 0) {
|
|
70
|
+
parts[partIndex++] = ` <video:duration>${video.duration}</video:duration>`;
|
|
71
|
+
}
|
|
72
|
+
if (video.expiration_date) {
|
|
73
|
+
parts[partIndex++] = ` <video:expiration_date>${video.expiration_date}</video:expiration_date>`;
|
|
74
|
+
}
|
|
75
|
+
if (video.rating !== void 0) {
|
|
76
|
+
parts[partIndex++] = ` <video:rating>${video.rating}</video:rating>`;
|
|
77
|
+
}
|
|
78
|
+
if (video.view_count !== void 0) {
|
|
79
|
+
parts[partIndex++] = ` <video:view_count>${video.view_count}</video:view_count>`;
|
|
80
|
+
}
|
|
81
|
+
if (video.publication_date) {
|
|
82
|
+
parts[partIndex++] = ` <video:publication_date>${video.publication_date}</video:publication_date>`;
|
|
83
|
+
}
|
|
84
|
+
if (video.family_friendly !== void 0) {
|
|
85
|
+
parts[partIndex++] = ` <video:family_friendly>${video.family_friendly === "yes" || video.family_friendly === true ? "yes" : "no"}</video:family_friendly>`;
|
|
86
|
+
}
|
|
87
|
+
if (video.restriction) {
|
|
88
|
+
const relationship = video.restriction.relationship || "allow";
|
|
89
|
+
parts[partIndex++] = ` <video:restriction relationship="${relationship}">${escapeValueForXml(video.restriction.restriction)}</video:restriction>`;
|
|
90
|
+
}
|
|
91
|
+
if (video.platform) {
|
|
92
|
+
const relationship = video.platform.relationship || "allow";
|
|
93
|
+
parts[partIndex++] = ` <video:platform relationship="${relationship}">${escapeValueForXml(video.platform.platform)}</video:platform>`;
|
|
94
|
+
}
|
|
95
|
+
if (video.requires_subscription !== void 0) {
|
|
96
|
+
parts[partIndex++] = ` <video:requires_subscription>${video.requires_subscription === "yes" || video.requires_subscription === true ? "yes" : "no"}</video:requires_subscription>`;
|
|
97
|
+
}
|
|
98
|
+
if (video.price) {
|
|
99
|
+
const prices = Array.isArray(video.price) ? video.price : [video.price];
|
|
100
|
+
for (const price of prices) {
|
|
101
|
+
const attrs = [];
|
|
102
|
+
if (price.currency) attrs.push(`currency="${price.currency}"`);
|
|
103
|
+
if (price.type) attrs.push(`type="${price.type}"`);
|
|
104
|
+
const attrsStr = attrs.length > 0 ? " " + attrs.join(" ") : "";
|
|
105
|
+
parts[partIndex++] = ` <video:price${attrsStr}>${escapeValueForXml(price.price)}</video:price>`;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
if (video.uploader) {
|
|
109
|
+
const info = video.uploader.info ? ` info="${escapeValueForXml(video.uploader.info)}"` : "";
|
|
110
|
+
parts[partIndex++] = ` <video:uploader${info}>${escapeValueForXml(video.uploader.uploader)}</video:uploader>`;
|
|
111
|
+
}
|
|
112
|
+
if (video.live !== void 0) {
|
|
113
|
+
parts[partIndex++] = ` <video:live>${video.live === "yes" || video.live === true ? "yes" : "no"}</video:live>`;
|
|
114
|
+
}
|
|
115
|
+
if (video.tag) {
|
|
116
|
+
const tags = Array.isArray(video.tag) ? video.tag : [video.tag];
|
|
117
|
+
for (const tag of tags) {
|
|
118
|
+
parts[partIndex++] = ` <video:tag>${escapeValueForXml(tag)}</video:tag>`;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
if (video.category) {
|
|
122
|
+
parts[partIndex++] = ` <video:category>${escapeValueForXml(video.category)}</video:category>`;
|
|
123
|
+
}
|
|
124
|
+
if (video.gallery_loc) {
|
|
125
|
+
const title = video.gallery_loc.title ? ` title="${escapeValueForXml(video.gallery_loc.title)}"` : "";
|
|
126
|
+
parts[partIndex++] = ` <video:gallery_loc${title}>${escapeValueForXml(video.gallery_loc)}</video:gallery_loc>`;
|
|
127
|
+
}
|
|
128
|
+
parts[partIndex++] = " </video:video>";
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
break;
|
|
132
|
+
case "news":
|
|
133
|
+
if (value) {
|
|
134
|
+
parts[partIndex++] = " <news:news>";
|
|
135
|
+
parts[partIndex++] = " <news:publication>";
|
|
136
|
+
parts[partIndex++] = ` <news:name>${escapeValueForXml(value.publication.name)}</news:name>`;
|
|
137
|
+
parts[partIndex++] = ` <news:language>${escapeValueForXml(value.publication.language)}</news:language>`;
|
|
138
|
+
parts[partIndex++] = " </news:publication>";
|
|
139
|
+
if (value.title) {
|
|
140
|
+
parts[partIndex++] = ` <news:title>${escapeValueForXml(value.title)}</news:title>`;
|
|
141
|
+
}
|
|
142
|
+
if (value.publication_date) {
|
|
143
|
+
parts[partIndex++] = ` <news:publication_date>${value.publication_date}</news:publication_date>`;
|
|
144
|
+
}
|
|
145
|
+
if (value.access) {
|
|
146
|
+
parts[partIndex++] = ` <news:access>${value.access}</news:access>`;
|
|
147
|
+
}
|
|
148
|
+
if (value.genres) {
|
|
149
|
+
parts[partIndex++] = ` <news:genres>${escapeValueForXml(value.genres)}</news:genres>`;
|
|
150
|
+
}
|
|
151
|
+
if (value.keywords) {
|
|
152
|
+
parts[partIndex++] = ` <news:keywords>${escapeValueForXml(value.keywords)}</news:keywords>`;
|
|
153
|
+
}
|
|
154
|
+
if (value.stock_tickers) {
|
|
155
|
+
parts[partIndex++] = ` <news:stock_tickers>${escapeValueForXml(value.stock_tickers)}</news:stock_tickers>`;
|
|
156
|
+
}
|
|
157
|
+
parts[partIndex++] = " </news:news>";
|
|
158
|
+
}
|
|
159
|
+
break;
|
|
160
|
+
}
|
|
66
161
|
}
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
export function handleEntry(k, e) {
|
|
70
|
-
return Array.isArray(e[k]) ? handleArray(k, e[k]) : typeof e[k] === "object" ? handleObject(k, e[k]) : ` <${k}>${escapeValueForXml(e[k])}</${k}>`;
|
|
162
|
+
parts[partIndex++] = " </url>";
|
|
163
|
+
return parts.slice(0, partIndex).join("\n");
|
|
71
164
|
}
|
|
72
|
-
export function
|
|
73
|
-
const
|
|
74
|
-
const
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
165
|
+
export function urlsToXml(urls, resolvers, { version, xsl, credits, minify }) {
|
|
166
|
+
const estimatedSize = urls.length + 5;
|
|
167
|
+
const xmlParts = Array.from({ length: estimatedSize });
|
|
168
|
+
let partIndex = 0;
|
|
169
|
+
const xslHref = xsl ? resolvers.relativeBaseUrlResolver(xsl) : false;
|
|
170
|
+
if (xslHref) {
|
|
171
|
+
xmlParts[partIndex++] = `<?xml version="1.0" encoding="UTF-8"?><?xml-stylesheet type="text/xsl" href="${xslHref}"?>`;
|
|
172
|
+
} else {
|
|
173
|
+
xmlParts[partIndex++] = '<?xml version="1.0" encoding="UTF-8"?>';
|
|
174
|
+
}
|
|
175
|
+
xmlParts[partIndex++] = URLSET_OPENING_TAG;
|
|
176
|
+
for (const url of urls) {
|
|
177
|
+
xmlParts[partIndex++] = buildUrlXml(url);
|
|
178
|
+
}
|
|
179
|
+
xmlParts[partIndex++] = "</urlset>";
|
|
180
|
+
if (credits) {
|
|
181
|
+
xmlParts[partIndex++] = `<!-- XML Sitemap generated by @nuxtjs/sitemap v${version} at ${(/* @__PURE__ */ new Date()).toISOString()} -->`;
|
|
182
|
+
}
|
|
183
|
+
const xmlContent = xmlParts.slice(0, partIndex);
|
|
184
|
+
if (minify) {
|
|
185
|
+
return xmlContent.join("").replace(/(?<!<[^>]*)\s(?![^<]*>)/g, "");
|
|
186
|
+
}
|
|
187
|
+
return xmlContent.join("\n");
|
|
86
188
|
}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import { getQuery, setHeader, createError } from "h3";
|
|
1
|
+
import { getQuery, setHeader, createError, getHeader } from "h3";
|
|
2
2
|
import { fixSlashes } from "nuxt-site-config/urls";
|
|
3
3
|
import { defu } from "defu";
|
|
4
|
-
import { useNitroApp } from "nitropack/runtime";
|
|
4
|
+
import { useNitroApp, defineCachedFunction } from "nitropack/runtime";
|
|
5
5
|
import { logger, mergeOnKey, splitForLocales } from "../../utils-pure.js";
|
|
6
6
|
import { createNitroRouteRuleMatcher } from "../kit.js";
|
|
7
7
|
import { buildSitemapUrls, urlsToXml } from "./builder/sitemap.js";
|
|
8
8
|
import { normaliseEntry, preNormalizeEntry } from "./urlset/normalise.js";
|
|
9
|
-
import {
|
|
9
|
+
import { sortInPlace } from "./urlset/sort.js";
|
|
10
10
|
import { getPathRobotConfig } from "#imports";
|
|
11
11
|
import { useSiteConfig } from "#site-config/server/composables/useSiteConfig";
|
|
12
12
|
import { createSitePathResolver } from "#site-config/server/composables/utils";
|
|
@@ -26,7 +26,7 @@ export function useNitroUrlResolvers(e) {
|
|
|
26
26
|
relativeBaseUrlResolver: createSitePathResolver(e, { absolute: false, withBase: true })
|
|
27
27
|
};
|
|
28
28
|
}
|
|
29
|
-
|
|
29
|
+
async function buildSitemapXml(event, definition, resolvers, runtimeConfig) {
|
|
30
30
|
const { sitemapName } = definition;
|
|
31
31
|
const nitro = useNitroApp();
|
|
32
32
|
if (import.meta.prerender) {
|
|
@@ -41,14 +41,15 @@ export async function createSitemap(event, definition, runtimeConfig) {
|
|
|
41
41
|
});
|
|
42
42
|
}
|
|
43
43
|
}
|
|
44
|
-
const
|
|
45
|
-
let sitemapUrls = await buildSitemapUrls(definition, resolvers, runtimeConfig, nitro);
|
|
44
|
+
const sitemapUrls = await buildSitemapUrls(definition, resolvers, runtimeConfig, nitro);
|
|
46
45
|
const routeRuleMatcher = createNitroRouteRuleMatcher();
|
|
47
46
|
const { autoI18n } = runtimeConfig;
|
|
48
|
-
|
|
47
|
+
let validCount = 0;
|
|
48
|
+
for (let i = 0; i < sitemapUrls.length; i++) {
|
|
49
|
+
const u = sitemapUrls[i];
|
|
49
50
|
const path = u._path?.pathname || u.loc;
|
|
50
51
|
if (!getPathRobotConfig(event, { path, skipSiteIndexable: true }).indexable)
|
|
51
|
-
|
|
52
|
+
continue;
|
|
52
53
|
let routeRules = routeRuleMatcher(path);
|
|
53
54
|
if (autoI18n?.locales && autoI18n?.strategy !== "no_prefix") {
|
|
54
55
|
const match = splitForLocales(path, autoI18n.locales.map((l) => l.code));
|
|
@@ -57,15 +58,15 @@ export async function createSitemap(event, definition, runtimeConfig) {
|
|
|
57
58
|
routeRules = defu(routeRules, routeRuleMatcher(pathWithoutPrefix));
|
|
58
59
|
}
|
|
59
60
|
if (routeRules.sitemap === false)
|
|
60
|
-
|
|
61
|
-
if (typeof routeRules.robots !== "undefined" && !routeRules.robots)
|
|
62
|
-
|
|
63
|
-
}
|
|
61
|
+
continue;
|
|
62
|
+
if (typeof routeRules.robots !== "undefined" && !routeRules.robots)
|
|
63
|
+
continue;
|
|
64
64
|
const hasRobotsDisabled = Object.entries(routeRules.headers || {}).some(([name, value]) => name.toLowerCase() === "x-robots-tag" && value.toLowerCase().includes("noindex"));
|
|
65
65
|
if (routeRules.redirect || hasRobotsDisabled)
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
}
|
|
66
|
+
continue;
|
|
67
|
+
sitemapUrls[validCount++] = routeRules.sitemap ? defu(u, routeRules.sitemap) : u;
|
|
68
|
+
}
|
|
69
|
+
sitemapUrls.length = validCount;
|
|
69
70
|
const locSize = sitemapUrls.length;
|
|
70
71
|
const resolvedCtx = {
|
|
71
72
|
urls: sitemapUrls,
|
|
@@ -76,17 +77,64 @@ export async function createSitemap(event, definition, runtimeConfig) {
|
|
|
76
77
|
if (resolvedCtx.urls.length !== locSize) {
|
|
77
78
|
resolvedCtx.urls = resolvedCtx.urls.map((e) => preNormalizeEntry(e, resolvers));
|
|
78
79
|
}
|
|
79
|
-
const maybeSort = (urls2) => runtimeConfig.sortEntries ?
|
|
80
|
+
const maybeSort = (urls2) => runtimeConfig.sortEntries ? sortInPlace(urls2) : urls2;
|
|
80
81
|
const normalizedPreDedupe = resolvedCtx.urls.map((e) => normaliseEntry(e, definition.defaults, resolvers));
|
|
81
82
|
const urls = maybeSort(mergeOnKey(normalizedPreDedupe, "_key").map((e) => normaliseEntry(e, definition.defaults, resolvers)));
|
|
83
|
+
if (definition._isChunking && definition.sitemapName.includes("-")) {
|
|
84
|
+
const parts = definition.sitemapName.split("-");
|
|
85
|
+
const lastPart = parts.pop();
|
|
86
|
+
if (!Number.isNaN(Number(lastPart))) {
|
|
87
|
+
const chunkIndex = Number(lastPart);
|
|
88
|
+
const baseSitemapName = parts.join("-");
|
|
89
|
+
if (urls.length === 0 && chunkIndex > 0) {
|
|
90
|
+
throw createError({
|
|
91
|
+
statusCode: 404,
|
|
92
|
+
message: `Sitemap chunk ${chunkIndex} for "${baseSitemapName}" does not exist.`
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
82
97
|
const sitemap = urlsToXml(urls, resolvers, runtimeConfig);
|
|
83
98
|
const ctx = { sitemap, sitemapName, event };
|
|
84
99
|
await nitro.hooks.callHook("sitemap:output", ctx);
|
|
100
|
+
return ctx.sitemap;
|
|
101
|
+
}
|
|
102
|
+
const buildSitemapXmlCached = defineCachedFunction(
|
|
103
|
+
buildSitemapXml,
|
|
104
|
+
{
|
|
105
|
+
name: "sitemap:xml",
|
|
106
|
+
group: "sitemap",
|
|
107
|
+
maxAge: 60 * 10,
|
|
108
|
+
// Default 10 minutes
|
|
109
|
+
base: "sitemap",
|
|
110
|
+
// Use the sitemap storage
|
|
111
|
+
getKey: (event, definition) => {
|
|
112
|
+
const host = getHeader(event, "host") || getHeader(event, "x-forwarded-host") || "";
|
|
113
|
+
const proto = getHeader(event, "x-forwarded-proto") || "https";
|
|
114
|
+
const sitemapName = definition.sitemapName || "default";
|
|
115
|
+
return `${sitemapName}-${proto}-${host}`;
|
|
116
|
+
},
|
|
117
|
+
swr: true
|
|
118
|
+
// Enable stale-while-revalidate
|
|
119
|
+
}
|
|
120
|
+
);
|
|
121
|
+
export async function createSitemap(event, definition, runtimeConfig) {
|
|
122
|
+
const resolvers = useNitroUrlResolvers(event);
|
|
123
|
+
const shouldCache = !import.meta.dev && runtimeConfig.cacheMaxAgeSeconds > 0;
|
|
124
|
+
const xml = shouldCache ? await buildSitemapXmlCached(event, definition, resolvers, runtimeConfig) : await buildSitemapXml(event, definition, resolvers, runtimeConfig);
|
|
85
125
|
setHeader(event, "Content-Type", "text/xml; charset=UTF-8");
|
|
86
|
-
if (runtimeConfig.cacheMaxAgeSeconds)
|
|
87
|
-
setHeader(event, "Cache-Control", `public, max-age=${runtimeConfig.cacheMaxAgeSeconds},
|
|
88
|
-
|
|
126
|
+
if (runtimeConfig.cacheMaxAgeSeconds) {
|
|
127
|
+
setHeader(event, "Cache-Control", `public, max-age=${runtimeConfig.cacheMaxAgeSeconds}, s-maxage=${runtimeConfig.cacheMaxAgeSeconds}, stale-while-revalidate=3600`);
|
|
128
|
+
const now = /* @__PURE__ */ new Date();
|
|
129
|
+
setHeader(event, "X-Sitemap-Generated", now.toISOString());
|
|
130
|
+
setHeader(event, "X-Sitemap-Cache-Duration", `${runtimeConfig.cacheMaxAgeSeconds}s`);
|
|
131
|
+
const expiryTime = new Date(now.getTime() + runtimeConfig.cacheMaxAgeSeconds * 1e3);
|
|
132
|
+
setHeader(event, "X-Sitemap-Cache-Expires", expiryTime.toISOString());
|
|
133
|
+
const remainingSeconds = Math.floor((expiryTime.getTime() - now.getTime()) / 1e3);
|
|
134
|
+
setHeader(event, "X-Sitemap-Cache-Remaining", `${remainingSeconds}s`);
|
|
135
|
+
} else {
|
|
89
136
|
setHeader(event, "Cache-Control", `no-cache, no-store`);
|
|
137
|
+
}
|
|
90
138
|
event.context._isSitemap = true;
|
|
91
|
-
return
|
|
139
|
+
return xml;
|
|
92
140
|
}
|
|
@@ -75,29 +75,31 @@ export function normaliseEntry(_e, defaults, resolvers) {
|
|
|
75
75
|
delete e.lastmod;
|
|
76
76
|
e.loc = resolve(e.loc, resolvers);
|
|
77
77
|
if (e.alternatives) {
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
78
|
+
const alternatives = e.alternatives;
|
|
79
|
+
for (let i = 0; i < alternatives.length; i++) {
|
|
80
|
+
const alt = alternatives[i];
|
|
81
|
+
if (typeof alt.href === "string") {
|
|
82
|
+
alt.href = resolve(alt.href, resolvers);
|
|
83
|
+
} else if (typeof alt.href === "object" && alt.href) {
|
|
84
|
+
alt.href = resolve(alt.href.href, resolvers);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
e.alternatives = mergeOnKey(alternatives, "hreflang");
|
|
86
88
|
}
|
|
87
89
|
if (e.images) {
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
i.loc = resolve(i.loc, resolvers);
|
|
91
|
-
|
|
92
|
-
|
|
90
|
+
const images = e.images;
|
|
91
|
+
for (let i = 0; i < images.length; i++) {
|
|
92
|
+
images[i].loc = resolve(images[i].loc, resolvers);
|
|
93
|
+
}
|
|
94
|
+
e.images = mergeOnKey(images, "loc");
|
|
93
95
|
}
|
|
94
96
|
if (e.videos) {
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
if (
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
}
|
|
97
|
+
const videos = e.videos;
|
|
98
|
+
for (let i = 0; i < videos.length; i++) {
|
|
99
|
+
if (videos[i].content_loc) {
|
|
100
|
+
videos[i].content_loc = resolve(videos[i].content_loc, resolvers);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
101
103
|
}
|
|
102
104
|
return e;
|
|
103
105
|
}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
import type { ResolvedSitemapUrl, SitemapUrlInput } from '../../../types.js';
|
|
2
|
-
export declare function
|
|
2
|
+
export declare function sortInPlace<T extends SitemapUrlInput[] | ResolvedSitemapUrl[]>(urls: T): T;
|
|
@@ -1,19 +1,13 @@
|
|
|
1
|
-
export function
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
const bLoc = typeof b === "string" ? b : b.loc;
|
|
6
|
-
return aLoc.localeCompare(bLoc, void 0, { numeric: true });
|
|
7
|
-
}
|
|
8
|
-
).sort((a, b) => {
|
|
9
|
-
const aLoc = (typeof a === "string" ? a : a.loc) || "";
|
|
10
|
-
const bLoc = (typeof b === "string" ? b : b.loc) || "";
|
|
1
|
+
export function sortInPlace(urls) {
|
|
2
|
+
urls.sort((a, b) => {
|
|
3
|
+
const aLoc = typeof a === "string" ? a : a.loc;
|
|
4
|
+
const bLoc = typeof b === "string" ? b : b.loc;
|
|
11
5
|
const aSegments = aLoc.split("/").length;
|
|
12
6
|
const bSegments = bLoc.split("/").length;
|
|
13
|
-
if (aSegments
|
|
14
|
-
return
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
return 0;
|
|
7
|
+
if (aSegments !== bSegments) {
|
|
8
|
+
return aSegments - bSegments;
|
|
9
|
+
}
|
|
10
|
+
return aLoc.localeCompare(bLoc, void 0, { numeric: true });
|
|
18
11
|
});
|
|
12
|
+
return urls;
|
|
19
13
|
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { ModuleRuntimeConfig, SitemapDefinition } from '../../../types.js';
|
|
2
|
+
export interface ChunkInfo {
|
|
3
|
+
isChunked: boolean;
|
|
4
|
+
baseSitemapName: string;
|
|
5
|
+
chunkIndex?: number;
|
|
6
|
+
chunkSize: number;
|
|
7
|
+
}
|
|
8
|
+
export declare function parseChunkInfo(sitemapName: string, sitemaps: ModuleRuntimeConfig['sitemaps'], defaultChunkSize?: number): ChunkInfo;
|
|
9
|
+
export declare function getSitemapConfig(sitemapName: string, sitemaps: ModuleRuntimeConfig['sitemaps'], defaultChunkSize?: number): SitemapDefinition;
|
|
10
|
+
export declare function sliceUrlsForChunk<T>(urls: T[], sitemapName: string, sitemaps: ModuleRuntimeConfig['sitemaps'], defaultChunkSize?: number): T[];
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
export function parseChunkInfo(sitemapName, sitemaps, defaultChunkSize = 1e3) {
|
|
2
|
+
if (typeof sitemaps.chunks !== "undefined" && !Number.isNaN(Number(sitemapName))) {
|
|
3
|
+
return {
|
|
4
|
+
isChunked: true,
|
|
5
|
+
baseSitemapName: "sitemap",
|
|
6
|
+
chunkIndex: Number(sitemapName),
|
|
7
|
+
chunkSize: defaultChunkSize
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
if (sitemapName.includes("-")) {
|
|
11
|
+
const parts = sitemapName.split("-");
|
|
12
|
+
const lastPart = parts.pop();
|
|
13
|
+
if (!Number.isNaN(Number(lastPart))) {
|
|
14
|
+
const baseSitemapName = parts.join("-");
|
|
15
|
+
const baseSitemap = sitemaps[baseSitemapName];
|
|
16
|
+
if (baseSitemap && (baseSitemap.chunks || baseSitemap._isChunking)) {
|
|
17
|
+
const chunkSize = typeof baseSitemap.chunks === "number" ? baseSitemap.chunks : baseSitemap.chunkSize || defaultChunkSize;
|
|
18
|
+
return {
|
|
19
|
+
isChunked: true,
|
|
20
|
+
baseSitemapName,
|
|
21
|
+
chunkIndex: Number(lastPart),
|
|
22
|
+
chunkSize
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return {
|
|
28
|
+
isChunked: false,
|
|
29
|
+
baseSitemapName: sitemapName,
|
|
30
|
+
chunkIndex: void 0,
|
|
31
|
+
chunkSize: defaultChunkSize
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
export function getSitemapConfig(sitemapName, sitemaps, defaultChunkSize = 1e3) {
|
|
35
|
+
const chunkInfo = parseChunkInfo(sitemapName, sitemaps, defaultChunkSize);
|
|
36
|
+
if (chunkInfo.isChunked) {
|
|
37
|
+
if (chunkInfo.baseSitemapName === "sitemap" && typeof sitemaps.chunks !== "undefined") {
|
|
38
|
+
return {
|
|
39
|
+
...sitemaps.chunks,
|
|
40
|
+
sitemapName,
|
|
41
|
+
_isChunking: true,
|
|
42
|
+
_chunkSize: chunkInfo.chunkSize
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
const baseSitemap = sitemaps[chunkInfo.baseSitemapName];
|
|
46
|
+
if (baseSitemap) {
|
|
47
|
+
return {
|
|
48
|
+
...baseSitemap,
|
|
49
|
+
sitemapName,
|
|
50
|
+
// Use the full name with chunk index
|
|
51
|
+
_isChunking: true,
|
|
52
|
+
_chunkSize: chunkInfo.chunkSize
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return sitemaps[sitemapName];
|
|
57
|
+
}
|
|
58
|
+
export function sliceUrlsForChunk(urls, sitemapName, sitemaps, defaultChunkSize = 1e3) {
|
|
59
|
+
const chunkInfo = parseChunkInfo(sitemapName, sitemaps, defaultChunkSize);
|
|
60
|
+
if (chunkInfo.isChunked && chunkInfo.chunkIndex !== void 0) {
|
|
61
|
+
const startIndex = chunkInfo.chunkIndex * chunkInfo.chunkSize;
|
|
62
|
+
const endIndex = (chunkInfo.chunkIndex + 1) * chunkInfo.chunkSize;
|
|
63
|
+
return urls.slice(startIndex, endIndex);
|
|
64
|
+
}
|
|
65
|
+
return urls;
|
|
66
|
+
}
|
package/dist/runtime/types.d.ts
CHANGED
|
@@ -211,6 +211,7 @@ export interface AutoI18nConfig {
|
|
|
211
211
|
})[];
|
|
212
212
|
defaultLocale: string;
|
|
213
213
|
strategy: 'prefix' | 'prefix_except_default' | 'prefix_and_default' | 'no_prefix';
|
|
214
|
+
pages?: Record<string, Record<string, string | false>>;
|
|
214
215
|
}
|
|
215
216
|
export interface ModuleRuntimeConfig extends Pick<ModuleOptions, 'sitemapsPathPrefix' | 'cacheMaxAgeSeconds' | 'sitemapName' | 'excludeAppSources' | 'sortEntries' | 'defaultSitemapsChunkSize' | 'xslColumns' | 'xslTips' | 'debug' | 'discoverImages' | 'discoverVideos' | 'autoLastmod' | 'xsl' | 'credits' | 'minify'> {
|
|
216
217
|
version: string;
|
|
@@ -291,10 +292,49 @@ export interface SitemapDefinition {
|
|
|
291
292
|
* Additional sources of URLs to include in the sitemap.
|
|
292
293
|
*/
|
|
293
294
|
sources?: SitemapSourceInput[];
|
|
295
|
+
/**
|
|
296
|
+
* Whether to enable chunking for this sitemap.
|
|
297
|
+
*
|
|
298
|
+
* - `true`: Enable with default chunk size from `defaultSitemapsChunkSize`
|
|
299
|
+
* - `number`: Enable with specific chunk size (must be > 0)
|
|
300
|
+
* - `false` or `undefined`: Disable chunking
|
|
301
|
+
*
|
|
302
|
+
* Note: Chunking only applies to sitemaps with sources. URLs provided directly
|
|
303
|
+
* are not chunked.
|
|
304
|
+
*
|
|
305
|
+
* @default false
|
|
306
|
+
* @example true
|
|
307
|
+
* @example 5000
|
|
308
|
+
*/
|
|
309
|
+
chunks?: boolean | number;
|
|
310
|
+
/**
|
|
311
|
+
* The maximum number of URLs per chunk when chunking is enabled.
|
|
312
|
+
* Takes precedence over the `chunks` property when both are specified.
|
|
313
|
+
* Also overrides the global `defaultSitemapsChunkSize`.
|
|
314
|
+
*
|
|
315
|
+
* Must be a positive integer.
|
|
316
|
+
*
|
|
317
|
+
* @default 1000
|
|
318
|
+
* @example 500
|
|
319
|
+
* @example 10000
|
|
320
|
+
*/
|
|
321
|
+
chunkSize?: number;
|
|
294
322
|
/**
|
|
295
323
|
* @internal
|
|
296
324
|
*/
|
|
297
325
|
_route?: string;
|
|
326
|
+
/**
|
|
327
|
+
* @internal
|
|
328
|
+
*/
|
|
329
|
+
_isChunking?: boolean;
|
|
330
|
+
/**
|
|
331
|
+
* @internal
|
|
332
|
+
*/
|
|
333
|
+
_chunkSize?: number;
|
|
334
|
+
/**
|
|
335
|
+
* @internal
|
|
336
|
+
*/
|
|
337
|
+
_chunkCount?: number;
|
|
298
338
|
}
|
|
299
339
|
interface NitroBaseHook {
|
|
300
340
|
event: H3Event;
|
|
@@ -314,6 +354,10 @@ export interface SitemapOutputHookCtx extends NitroBaseHook {
|
|
|
314
354
|
sitemapName: string;
|
|
315
355
|
sitemap: string;
|
|
316
356
|
}
|
|
357
|
+
export interface SitemapSourcesHookCtx extends NitroBaseHook {
|
|
358
|
+
sitemapName: string;
|
|
359
|
+
sources: (SitemapSourceBase | SitemapSourceResolved)[];
|
|
360
|
+
}
|
|
317
361
|
export type Changefreq = 'always' | 'hourly' | 'daily' | 'weekly' | 'monthly' | 'yearly' | 'never';
|
|
318
362
|
export interface SitemapUrl {
|
|
319
363
|
loc: string;
|