@nuxtjs/sitemap 7.2.9 → 7.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/dist/client/200.html +9 -9
  2. package/dist/client/404.html +9 -9
  3. package/dist/client/_nuxt/Cp-IABpG.js +1 -0
  4. package/dist/client/_nuxt/DJVkgDQ2.js +1 -0
  5. package/dist/client/_nuxt/SmY-NWqO.js +172 -0
  6. package/dist/client/_nuxt/builds/latest.json +1 -1
  7. package/dist/client/_nuxt/builds/meta/e48bfd5b-6605-4bbc-a466-32a664787616.json +1 -0
  8. package/dist/client/_nuxt/{entry.CJ2Eg9q-.css → entry.CgW0_noo.css} +1 -1
  9. package/dist/client/_nuxt/error-404.CtcyoHAN.css +1 -0
  10. package/dist/client/_nuxt/error-500.BIlfyoPk.css +1 -0
  11. package/dist/client/_nuxt/lL_X76lO.js +1 -0
  12. package/dist/client/index.html +9 -9
  13. package/dist/content.d.cts +2 -1
  14. package/dist/content.d.mts +2 -1
  15. package/dist/content.d.ts +2 -1
  16. package/dist/module.cjs +83 -42
  17. package/dist/module.d.cts +3 -1
  18. package/dist/module.d.mts +3 -1
  19. package/dist/module.d.ts +3 -1
  20. package/dist/module.json +3 -3
  21. package/dist/module.mjs +84 -43
  22. package/dist/runtime/server/plugins/warm-up.js +20 -4
  23. package/dist/runtime/server/routes/__sitemap__/debug.d.ts +4 -4
  24. package/dist/runtime/server/routes/__sitemap__/debug.js +2 -2
  25. package/dist/runtime/server/routes/__sitemap__/nuxt-content-urls-v3.js +7 -2
  26. package/dist/runtime/server/routes/sitemap/[sitemap].xml.js +37 -7
  27. package/dist/runtime/server/routes/sitemap_index.xml.js +11 -3
  28. package/dist/runtime/server/sitemap/builder/sitemap-index.d.ts +1 -1
  29. package/dist/runtime/server/sitemap/builder/sitemap-index.js +110 -16
  30. package/dist/runtime/server/sitemap/builder/sitemap.d.ts +1 -1
  31. package/dist/runtime/server/sitemap/builder/sitemap.js +62 -36
  32. package/dist/runtime/server/sitemap/builder/xml.d.ts +2 -3
  33. package/dist/runtime/server/sitemap/builder/xml.js +182 -80
  34. package/dist/runtime/server/sitemap/nitro.js +68 -20
  35. package/dist/runtime/server/sitemap/urlset/normalise.js +21 -19
  36. package/dist/runtime/server/sitemap/urlset/sort.d.ts +1 -1
  37. package/dist/runtime/server/sitemap/urlset/sort.js +9 -15
  38. package/dist/runtime/server/sitemap/utils/chunk.d.ts +10 -0
  39. package/dist/runtime/server/sitemap/utils/chunk.js +66 -0
  40. package/dist/runtime/types.d.ts +44 -0
  41. package/dist/runtime/utils-pure.js +13 -5
  42. package/dist/types.d.mts +5 -1
  43. package/package.json +32 -38
  44. package/content.d.ts +0 -1
  45. package/dist/client/_nuxt/BQoSv7ci.js +0 -1
  46. package/dist/client/_nuxt/BuImKM08.js +0 -1
  47. package/dist/client/_nuxt/DGiw9jHL.js +0 -172
  48. package/dist/client/_nuxt/LwtYuSjN.js +0 -1
  49. package/dist/client/_nuxt/builds/meta/491f5d1a-004d-407a-b464-c3363f77bc23.json +0 -1
  50. package/dist/client/_nuxt/error-404.CmL6pbzl.css +0 -1
  51. package/dist/client/_nuxt/error-500.CnmYQu0q.css +0 -1
  52. package/dist/types.d.ts +0 -1
@@ -2,9 +2,9 @@ import { resolveSitePath } from "nuxt-site-config/urls";
2
2
  import { joinURL, withHttps } from "ufo";
3
3
  import { preNormalizeEntry } from "../urlset/normalise.js";
4
4
  import { childSitemapSources, globalSitemapSources, resolveSitemapSources } from "../urlset/sources.js";
5
- import { sortSitemapUrls } from "../urlset/sort.js";
5
+ import { sortInPlace } from "../urlset/sort.js";
6
6
  import { createPathFilter, logger, splitForLocales } from "../../../utils-pure.js";
7
- import { handleEntry, wrapSitemapXml } from "./xml.js";
7
+ import { parseChunkInfo, sliceUrlsForChunk } from "../utils/chunk.js";
8
8
  export function resolveSitemapEntries(sitemap, urls, runtimeConfig, resolvers) {
9
9
  const {
10
10
  autoI18n,
@@ -85,9 +85,23 @@ export function resolveSitemapEntries(sitemap, urls, runtimeConfig, resolvers) {
85
85
  });
86
86
  } else {
87
87
  for (const l of autoI18n.locales) {
88
- let loc = joinURL(`/${l.code}`, e._pathWithoutPrefix);
89
- if (autoI18n.differentDomains || ["prefix_and_default", "prefix_except_default"].includes(autoI18n.strategy) && l.code === autoI18n.defaultLocale)
90
- loc = e._pathWithoutPrefix;
88
+ let loc = e._pathWithoutPrefix;
89
+ if (autoI18n.pages) {
90
+ const pageKey = e._pathWithoutPrefix.replace(/^\//, "").replace(/\/index$/, "") || "index";
91
+ const pageMappings = autoI18n.pages[pageKey];
92
+ if (pageMappings && pageMappings[l.code] !== void 0) {
93
+ const customPath = pageMappings[l.code];
94
+ if (customPath === false)
95
+ continue;
96
+ if (typeof customPath === "string")
97
+ loc = customPath.startsWith("/") ? customPath : `/${customPath}`;
98
+ } else if (!autoI18n.differentDomains && !(["prefix_and_default", "prefix_except_default"].includes(autoI18n.strategy) && l.code === autoI18n.defaultLocale)) {
99
+ loc = joinURL(`/${l.code}`, e._pathWithoutPrefix);
100
+ }
101
+ } else {
102
+ if (!autoI18n.differentDomains && !(["prefix_and_default", "prefix_except_default"].includes(autoI18n.strategy) && l.code === autoI18n.defaultLocale))
103
+ loc = joinURL(`/${l.code}`, e._pathWithoutPrefix);
104
+ }
91
105
  const _sitemap = isI18nMapped ? l._sitemap : void 0;
92
106
  const newEntry = preNormalizeEntry({
93
107
  _sitemap,
@@ -99,14 +113,30 @@ export function resolveSitemapEntries(sitemap, urls, runtimeConfig, resolvers) {
99
113
  alternatives: [{ code: "x-default", _hreflang: "x-default" }, ...autoI18n.locales].map((locale) => {
100
114
  const code = locale.code === "x-default" ? autoI18n.defaultLocale : locale.code;
101
115
  const isDefault = locale.code === "x-default" || locale.code === autoI18n.defaultLocale;
102
- let href = "";
103
- if (autoI18n.strategy === "prefix") {
104
- href = joinURL("/", code, e._pathWithoutPrefix);
105
- } else if (["prefix_and_default", "prefix_except_default"].includes(autoI18n.strategy)) {
106
- if (isDefault) {
107
- href = e._pathWithoutPrefix;
108
- } else {
116
+ let href = e._pathWithoutPrefix;
117
+ if (autoI18n.pages) {
118
+ const pageKey = e._pathWithoutPrefix.replace(/^\//, "").replace(/\/index$/, "") || "index";
119
+ const pageMappings = autoI18n.pages[pageKey];
120
+ if (pageMappings && pageMappings[code] !== void 0) {
121
+ const customPath = pageMappings[code];
122
+ if (customPath === false)
123
+ return false;
124
+ if (typeof customPath === "string")
125
+ href = customPath.startsWith("/") ? customPath : `/${customPath}`;
126
+ } else if (autoI18n.strategy === "prefix") {
109
127
  href = joinURL("/", code, e._pathWithoutPrefix);
128
+ } else if (["prefix_and_default", "prefix_except_default"].includes(autoI18n.strategy)) {
129
+ if (!isDefault) {
130
+ href = joinURL("/", code, e._pathWithoutPrefix);
131
+ }
132
+ }
133
+ } else {
134
+ if (autoI18n.strategy === "prefix") {
135
+ href = joinURL("/", code, e._pathWithoutPrefix);
136
+ } else if (["prefix_and_default", "prefix_except_default"].includes(autoI18n.strategy)) {
137
+ if (!isDefault) {
138
+ href = joinURL("/", code, e._pathWithoutPrefix);
139
+ }
110
140
  }
111
141
  }
112
142
  if (!filterPath(href))
@@ -151,16 +181,12 @@ export async function buildSitemapUrls(sitemap, resolvers, runtimeConfig, nitro)
151
181
  // chunking
152
182
  defaultSitemapsChunkSize
153
183
  } = runtimeConfig;
154
- const isChunking = typeof sitemaps.chunks !== "undefined" && !Number.isNaN(Number(sitemap.sitemapName));
184
+ const chunkInfo = parseChunkInfo(sitemap.sitemapName, sitemaps, defaultSitemapsChunkSize);
155
185
  function maybeSort(urls) {
156
- return sortEntries ? sortSitemapUrls(urls) : urls;
186
+ return sortEntries ? sortInPlace(urls) : urls;
157
187
  }
158
188
  function maybeSlice(urls) {
159
- if (isChunking && defaultSitemapsChunkSize) {
160
- const chunk = Number(sitemap.sitemapName);
161
- return urls.slice(chunk * defaultSitemapsChunkSize, (chunk + 1) * defaultSitemapsChunkSize);
162
- }
163
- return urls;
189
+ return sliceUrlsForChunk(urls, sitemap.sitemapName, sitemaps, defaultSitemapsChunkSize);
164
190
  }
165
191
  if (autoI18n?.differentDomains) {
166
192
  const domain = autoI18n.locales.find((e) => [e.language, e.code].includes(sitemap.sitemapName))?.domain;
@@ -175,8 +201,22 @@ export async function buildSitemapUrls(sitemap, resolvers, runtimeConfig, nitro)
175
201
  });
176
202
  }
177
203
  }
178
- const sourcesInput = sitemap.includeAppSources ? await globalSitemapSources() : [];
179
- sourcesInput.push(...await childSitemapSources(sitemap));
204
+ let effectiveSitemap = sitemap;
205
+ const baseSitemapName = chunkInfo.baseSitemapName;
206
+ if (chunkInfo.isChunked && baseSitemapName !== sitemap.sitemapName && sitemaps[baseSitemapName]) {
207
+ effectiveSitemap = sitemaps[baseSitemapName];
208
+ }
209
+ let sourcesInput = effectiveSitemap.includeAppSources ? await globalSitemapSources() : [];
210
+ sourcesInput.push(...await childSitemapSources(effectiveSitemap));
211
+ if (nitro && resolvers.event) {
212
+ const ctx = {
213
+ event: resolvers.event,
214
+ sitemapName: baseSitemapName,
215
+ sources: sourcesInput
216
+ };
217
+ await nitro.hooks.callHook("sitemap:sources", ctx);
218
+ sourcesInput = ctx.sources;
219
+ }
180
220
  const sources = await resolveSitemapSources(sourcesInput, resolvers.event);
181
221
  const resolvedCtx = {
182
222
  urls: sources.flatMap((s) => s.urls),
@@ -193,18 +233,4 @@ export async function buildSitemapUrls(sitemap, resolvers, runtimeConfig, nitro)
193
233
  const sortedUrls = maybeSort(filteredUrls);
194
234
  return maybeSlice(sortedUrls);
195
235
  }
196
- export function urlsToXml(urls, resolvers, { version, xsl, credits, minify }) {
197
- const urlset = urls.map((e) => {
198
- const keys = Object.keys(e).filter((k) => !k.startsWith("_"));
199
- return [
200
- " <url>",
201
- keys.map((k) => handleEntry(k, e)).filter(Boolean).join("\n"),
202
- " </url>"
203
- ].join("\n");
204
- });
205
- return wrapSitemapXml([
206
- '<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">',
207
- urlset.join("\n"),
208
- "</urlset>"
209
- ], resolvers, { version, xsl, credits, minify });
210
- }
236
+ export { urlsToXml } from "./xml.js";
@@ -1,4 +1,3 @@
1
- import type { ModuleRuntimeConfig, NitroUrlResolvers } from '../../../types.js';
2
- export declare function handleEntry(k: string, e: Record<string, any> | (string | Record<string, any>)[]): string | false;
3
- export declare function wrapSitemapXml(input: string[], resolvers: NitroUrlResolvers, options: Pick<ModuleRuntimeConfig, 'version' | 'xsl' | 'credits' | 'minify'>): string;
1
+ import type { ModuleRuntimeConfig, NitroUrlResolvers, ResolvedSitemapUrl } from '../../../types.js';
4
2
  export declare function escapeValueForXml(value: boolean | string | number): string;
3
+ export declare function urlsToXml(urls: ResolvedSitemapUrl[], resolvers: NitroUrlResolvers, { version, xsl, credits, minify }: Pick<ModuleRuntimeConfig, 'version' | 'xsl' | 'credits' | 'minify'>): string;
@@ -1,86 +1,188 @@
1
- function resolveKey(k) {
2
- switch (k) {
3
- case "images":
4
- return "image";
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, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&apos;");
13
5
  }
14
- function handleObject(key, obj) {
15
- return [
16
- ` <${key}:${key}>`,
17
- ...Object.entries(obj).map(([sk, sv]) => {
18
- if (key === "video" && Array.isArray(sv)) {
19
- return sv.map((v) => {
20
- if (typeof v === "string") {
21
- return [
22
- ` `,
23
- `<${key}:${sk}>`,
24
- escapeValueForXml(v),
25
- `</${key}:${sk}>`
26
- ].join("");
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
- return [
48
- ` <${key}:${sk}>`,
49
- ...Object.entries(sv).map(([ssk, ssv]) => ` <${key}:${ssk}>${escapeValueForXml(ssv)}</${key}:${ssk}>`),
50
- ` </${key}:${sk}>`
51
- ].join("\n");
52
- }
53
- return ` <${key}:${sk}>${escapeValueForXml(sv)}</${key}:${sk}>`;
54
- }),
55
- ` </${key}:${key}>`
56
- ].join("\n");
57
- }
58
- function handleArray(key, arr) {
59
- if (arr.length === 0)
60
- return false;
61
- key = resolveKey(key);
62
- if (key === "alternatives") {
63
- return arr.map((obj) => [
64
- ` <xhtml:link rel="alternate" ${Object.entries(obj).map(([sk, sv]) => `${sk}="${escapeValueForXml(sv)}"`).join(" ")} />`
65
- ].join("\n")).join("\n");
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
- return arr.map((obj) => handleObject(key, obj)).join("\n");
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 wrapSitemapXml(input, resolvers, options) {
73
- const xsl = options.xsl ? resolvers.relativeBaseUrlResolver(options.xsl) : false;
74
- const credits = options.credits;
75
- input.unshift(`<?xml version="1.0" encoding="UTF-8"?>${xsl ? `<?xml-stylesheet type="text/xsl" href="${xsl}"?>` : ""}`);
76
- if (credits)
77
- input.push(`<!-- XML Sitemap generated by @nuxtjs/sitemap v${options.version} at ${(/* @__PURE__ */ new Date()).toISOString()} -->`);
78
- if (options.minify)
79
- return input.join("").replace(/(?<!<[^>]*)\s(?![^<]*>)/g, "");
80
- return input.join("\n");
81
- }
82
- export function escapeValueForXml(value) {
83
- if (value === true || value === false)
84
- return value ? "yes" : "no";
85
- return String(value).replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&apos;");
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 { sortSitemapUrls } from "./urlset/sort.js";
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
- export async function createSitemap(event, definition, runtimeConfig) {
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 resolvers = useNitroUrlResolvers(event);
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
- sitemapUrls = sitemapUrls.map((u) => {
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
- return false;
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
- return false;
61
- if (typeof routeRules.robots !== "undefined" && !routeRules.robots) {
62
- return false;
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
- return false;
67
- return routeRules.sitemap ? defu(u, routeRules.sitemap) : u;
68
- }).filter(Boolean);
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 ? sortSitemapUrls(urls2) : urls2;
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}, must-revalidate`);
88
- else
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 ctx.sitemap;
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
- e.alternatives = mergeOnKey(e.alternatives.map((e2) => {
79
- const a = { ...e2 };
80
- if (typeof a.href === "string")
81
- a.href = resolve(a.href, resolvers);
82
- else if (typeof a.href === "object" && a.href)
83
- a.href = resolve(a.href.href, resolvers);
84
- return a;
85
- }), "hreflang");
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
- e.images = mergeOnKey(e.images.map((i) => {
89
- i = { ...i };
90
- i.loc = resolve(i.loc, resolvers);
91
- return i;
92
- }), "loc");
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
- e.videos = e.videos.map((v) => {
96
- v = { ...v };
97
- if (v.content_loc)
98
- v.content_loc = resolve(v.content_loc, resolvers);
99
- return v;
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 sortSitemapUrls<T extends SitemapUrlInput[] | ResolvedSitemapUrl[]>(urls: T): T;
2
+ export declare function sortInPlace<T extends SitemapUrlInput[] | ResolvedSitemapUrl[]>(urls: T): T;