@nuxtjs/sitemap 8.2.2 → 8.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 (41) hide show
  1. package/README.md +1 -1
  2. package/dist/module.json +1 -1
  3. package/dist/module.mjs +8 -3
  4. package/dist/runtime/server/kit.js +9 -2
  5. package/dist/runtime/server/plugins/compression.js +57 -13
  6. package/dist/runtime/server/plugins/stream-transport.d.ts +2 -0
  7. package/dist/runtime/server/plugins/stream-transport.js +16 -0
  8. package/dist/runtime/server/routes/__sitemap__/debug.d.ts +1 -0
  9. package/dist/runtime/server/routes/__zero-runtime/sitemap/[sitemap].xml.d.ts +1 -1
  10. package/dist/runtime/server/routes/__zero-runtime/sitemap.xml.d.ts +1 -1
  11. package/dist/runtime/server/routes/__zero-runtime/sitemap_index.xml.d.ts +1 -1
  12. package/dist/runtime/server/routes/sitemap/[sitemap].xml.d.ts +1 -1
  13. package/dist/runtime/server/routes/sitemap.xml.d.ts +1 -1
  14. package/dist/runtime/server/routes/sitemap_index.xml.d.ts +1 -1
  15. package/dist/runtime/server/sitemap/builder/index-xml.d.ts +13 -0
  16. package/dist/runtime/server/sitemap/builder/index-xml.js +70 -0
  17. package/dist/runtime/server/sitemap/builder/sitemap-index.d.ts +1 -4
  18. package/dist/runtime/server/sitemap/builder/sitemap-index.js +2 -34
  19. package/dist/runtime/server/sitemap/builder/sitemap.d.ts +1 -1
  20. package/dist/runtime/server/sitemap/builder/sitemap.js +36 -25
  21. package/dist/runtime/server/sitemap/builder/xml.d.ts +8 -0
  22. package/dist/runtime/server/sitemap/builder/xml.js +35 -6
  23. package/dist/runtime/server/sitemap/event-handlers.d.ts +3 -3
  24. package/dist/runtime/server/sitemap/event-handlers.js +18 -10
  25. package/dist/runtime/server/sitemap/nitro.d.ts +3 -1
  26. package/dist/runtime/server/sitemap/nitro.js +107 -20
  27. package/dist/runtime/server/sitemap/stream.d.ts +30 -0
  28. package/dist/runtime/server/sitemap/stream.js +144 -0
  29. package/dist/runtime/server/sitemap/urlset/normalise.d.ts +6 -1
  30. package/dist/runtime/server/sitemap/urlset/normalise.js +15 -4
  31. package/dist/runtime/server/sitemap/urlset/sort.js +12 -3
  32. package/dist/runtime/server/sitemap/urlset/sources.js +3 -3
  33. package/dist/runtime/server/utils.d.ts +0 -1
  34. package/dist/runtime/server/utils.js +12 -12
  35. package/dist/runtime/types.d.ts +14 -3
  36. package/dist/runtime/utils-pure.d.ts +5 -4
  37. package/dist/runtime/utils-pure.js +41 -19
  38. package/dist/utils.d.mts +30 -2
  39. package/dist/utils.d.ts +30 -2
  40. package/dist/utils.mjs +646 -43
  41. package/package.json +22 -22
package/README.md CHANGED
@@ -7,7 +7,7 @@
7
7
 
8
8
  Nuxt Sitemap is a module for generating best-practice XML sitemaps that are consumed by the robots crawling your site.
9
9
 
10
- New to XML sitemaps or SEO? Check out the [Controlling Web Crawlers](https://nuxtseo.com/learn/controlling-crawlers) guide to learn more about why you might
10
+ New to XML sitemaps or SEO? Check out the [Controlling Web Crawlers](https://nuxtseo.com/learn-seo/nuxt/controlling-crawlers) guide to learn more about why you might
11
11
  need these.
12
12
 
13
13
  <p align="center">
package/dist/module.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "nuxt": ">=3.9.0"
5
5
  },
6
6
  "configKey": "sitemap",
7
- "version": "8.2.2",
7
+ "version": "8.3.0",
8
8
  "builder": {
9
9
  "@nuxt/module-builder": "1.0.2",
10
10
  "unbuild": "3.6.1"
package/dist/module.mjs CHANGED
@@ -53,6 +53,7 @@ function setupPrerenderHandler(_options, nuxt = useNuxt()) {
53
53
  }
54
54
  nuxt.options.nitro.prerender.routes = nuxt.options.nitro.prerender.routes.filter((r) => r && !includesSitemapRoot(options.sitemapName, [r]));
55
55
  const runtimeAssetsPath = join(nuxt.options.rootDir, "node_modules/.cache/nuxt/sitemap");
56
+ const localeCodes = options.autoI18n ? new Set(options.autoI18n.locales.map((l) => l.code)) : void 0;
56
57
  nuxt.hooks.hook("nitro:config", (nitroConfig) => {
57
58
  nitroConfig.virtual = nitroConfig.virtual || {};
58
59
  nitroConfig.virtual["#sitemap-virtual/read-sources.mjs"] = `
@@ -103,7 +104,7 @@ export async function readSourcesFromFilesystem(filename) {
103
104
  });
104
105
  if (options.autoI18n && Object.keys(options.sitemaps).length > 1) {
105
106
  const path = route.route;
106
- const match = splitForLocales(path, options.autoI18n.locales.map((l) => l.code));
107
+ const match = splitForLocales(path, localeCodes);
107
108
  const locale = match[0] || options.autoI18n.defaultLocale;
108
109
  if (options.isI18nMapped) {
109
110
  const { _sitemap } = options.autoI18n.locales.find((l) => l.code === locale) || { _sitemap: locale };
@@ -501,6 +502,7 @@ const module$1 = defineNuxtModule({
501
502
  // cache for 10 minutes
502
503
  minify: false,
503
504
  debug: false,
505
+ experimentalStreaming: false,
504
506
  defaultSitemapsChunkSize: 1e3,
505
507
  autoLastmod: false,
506
508
  discoverImages: true,
@@ -767,13 +769,15 @@ const module$1 = defineNuxtModule({
767
769
  } else if (hasRouteRuleContent) {
768
770
  nuxt.options.nitro.routeRules[`/${config.sitemapName}`] = routeRules;
769
771
  }
770
- if (config.zeroRuntime && (config.experimentalWarmUp || config.experimentalCompression))
771
- logger.warn("`experimentalWarmUp` and `experimentalCompression` are ignored in zeroRuntime mode.");
772
+ if (config.zeroRuntime && (config.experimentalWarmUp || config.experimentalCompression || config.experimentalStreaming))
773
+ logger.warn("`experimentalWarmUp`, `experimentalCompression`, and `experimentalStreaming` are ignored in zeroRuntime mode.");
772
774
  if (!config.zeroRuntime) {
773
775
  if (config.experimentalWarmUp)
774
776
  addServerPlugin(resolve("./runtime/server/plugins/warm-up"));
775
777
  if (config.experimentalCompression)
776
778
  addServerPlugin(resolve("./runtime/server/plugins/compression"));
779
+ if (config.experimentalStreaming || config.experimentalCompression)
780
+ addServerPlugin(resolve("./runtime/server/plugins/stream-transport"));
777
781
  }
778
782
  const isNuxtContentDocumentDriven = !!nuxt.options.content?.documentDriven || config.strictNuxtContentPaths;
779
783
  const contentVersion = await resolveNuxtContentVersion();
@@ -1051,6 +1055,7 @@ ${onUrlEntries.join("\n")}`;
1051
1055
  isMultiSitemap: usingMultiSitemaps,
1052
1056
  excludeAppSources: config.excludeAppSources,
1053
1057
  cacheMaxAgeSeconds: nuxt.options.dev ? 0 : config.cacheMaxAgeSeconds,
1058
+ experimentalStreaming: config.experimentalStreaming,
1054
1059
  autoLastmod: config.autoLastmod,
1055
1060
  defaultSitemapsChunkSize: config.defaultSitemapsChunkSize,
1056
1061
  minify: config.minify,
@@ -3,9 +3,13 @@ import { useRuntimeConfig } from "nitropack/runtime";
3
3
  import { createRouter as createRadixRouter, toRouteMatcher } from "radix3";
4
4
  import { parseURL, withoutBase, withoutTrailingSlash } from "ufo";
5
5
  function withoutQuery(path) {
6
- return path.split("?")[0];
6
+ const queryIndex = path.indexOf("?");
7
+ return queryIndex === -1 ? path : path.slice(0, queryIndex);
7
8
  }
9
+ let cachedRouteRuleMatcher;
8
10
  export function createNitroRouteRuleMatcher() {
11
+ if (!import.meta.dev && cachedRouteRuleMatcher)
12
+ return cachedRouteRuleMatcher;
9
13
  const { nitro, app } = useRuntimeConfig();
10
14
  const _routeRulesMatcher = toRouteMatcher(
11
15
  createRadixRouter({
@@ -14,10 +18,13 @@ export function createNitroRouteRuleMatcher() {
14
18
  )
15
19
  })
16
20
  );
17
- return (pathOrUrl) => {
21
+ const matcher = (pathOrUrl) => {
18
22
  const path = pathOrUrl[0] === "/" ? pathOrUrl : parseURL(pathOrUrl, app.baseURL).pathname;
19
23
  return defu({}, ..._routeRulesMatcher.matchAll(
20
24
  withoutBase(withoutTrailingSlash(withoutQuery(path)), app.baseURL)
21
25
  ).reverse());
22
26
  };
27
+ if (!import.meta.dev)
28
+ cachedRouteRuleMatcher = matcher;
29
+ return matcher;
23
30
  }
@@ -1,23 +1,67 @@
1
- import { getRequestHeader, setResponseHeader } from "h3";
1
+ import { appendResponseHeader, getRequestHeader, getResponseHeader, removeResponseHeader, setResponseHeader } from "h3";
2
2
  import { defineNitroPlugin } from "nitropack/runtime";
3
- function getPreferredEncoding(event) {
4
- const acceptEncoding = getRequestHeader(event, "accept-encoding") || "";
5
- if (acceptEncoding.includes("gzip"))
6
- return "gzip";
7
- if (acceptEncoding.includes("deflate"))
8
- return "deflate";
9
- return null;
3
+ import { logger } from "../../utils-pure.js";
4
+ import { hasNonIdentityEncoding, isReadableStream, negotiateCompressionEncoding } from "../sitemap/stream.js";
5
+ let warnedAboutCompressionStream = false;
6
+ const NODE_COMPRESSION_INPUT_BATCH_BYTES = 512 * 1024;
7
+ function toByteStream(body) {
8
+ if (isReadableStream(body))
9
+ return body;
10
+ if (body instanceof Blob)
11
+ return body.stream();
12
+ const value = typeof body === "string" || body instanceof ArrayBuffer || ArrayBuffer.isView(body) ? body : JSON.stringify(body);
13
+ return new Blob([value]).stream();
14
+ }
15
+ function createNodeCompressionInputStream(source) {
16
+ const reader = source.getReader();
17
+ let bytesRead = 0;
18
+ return new ReadableStream({
19
+ async pull(controller) {
20
+ if (bytesRead >= NODE_COMPRESSION_INPUT_BATCH_BYTES) {
21
+ bytesRead = 0;
22
+ await new Promise((resolve) => setTimeout(resolve, 0));
23
+ }
24
+ const result = await reader.read();
25
+ if (result.done) {
26
+ controller.close();
27
+ return;
28
+ }
29
+ bytesRead += result.value.byteLength;
30
+ controller.enqueue(result.value);
31
+ },
32
+ cancel(reason) {
33
+ return reader.cancel(reason);
34
+ }
35
+ });
36
+ }
37
+ function addVaryAcceptEncoding(event) {
38
+ const vary = getResponseHeader(event, "Vary");
39
+ const values = (Array.isArray(vary) ? vary : [vary]).flatMap((value) => String(value || "").split(",")).map((value) => value.trim().toLowerCase());
40
+ if (!values.includes("*") && !values.includes("accept-encoding"))
41
+ appendResponseHeader(event, "Vary", "Accept-Encoding");
10
42
  }
11
43
  export default defineNitroPlugin((nitro) => {
12
- nitro.hooks.hook("beforeResponse", async (event, response) => {
44
+ nitro.hooks.hook("beforeResponse", (event, response) => {
13
45
  if (!event.context._isSitemap || !response.body)
14
46
  return;
15
- const encoding = getPreferredEncoding(event);
47
+ addVaryAcceptEncoding(event);
48
+ if (hasNonIdentityEncoding(getResponseHeader(event, "Content-Encoding")))
49
+ return;
50
+ const encoding = negotiateCompressionEncoding(getRequestHeader(event, "accept-encoding") || "");
16
51
  if (!encoding)
17
52
  return;
18
- const body = typeof response.body === "string" ? response.body : JSON.stringify(response.body);
19
- const stream = new Blob([body]).stream().pipeThrough(new CompressionStream(encoding));
20
- response.body = Buffer.from(await new Response(stream).arrayBuffer());
53
+ if (typeof CompressionStream === "undefined") {
54
+ if (!warnedAboutCompressionStream) {
55
+ warnedAboutCompressionStream = true;
56
+ logger.warn("Sitemap compression was requested, but CompressionStream is unavailable in this runtime. Sending the uncompressed response.");
57
+ }
58
+ return;
59
+ }
60
+ const compression = new CompressionStream(encoding);
61
+ const source = toByteStream(response.body);
62
+ const compressionInput = event.node.res?.socket ? createNodeCompressionInputStream(source) : source;
63
+ response.body = compressionInput.pipeThrough(compression);
64
+ removeResponseHeader(event, "Content-Length");
21
65
  setResponseHeader(event, "Content-Encoding", encoding);
22
66
  });
23
67
  });
@@ -0,0 +1,2 @@
1
+ declare const _default: import("nitropack/types").NitroAppPlugin;
2
+ export default _default;
@@ -0,0 +1,16 @@
1
+ import { removeResponseHeader } from "h3";
2
+ import { defineNitroPlugin } from "nitropack/runtime";
3
+ import { logger } from "../../utils-pure.js";
4
+ import { createNodeResponseStream, isReadableStream } from "../sitemap/stream.js";
5
+ export default defineNitroPlugin((nitro) => {
6
+ nitro.hooks.hook("beforeResponse", (event, response) => {
7
+ const nodeResponse = event.node.res;
8
+ if (!event.context._isSitemap || !isReadableStream(response.body) || !nodeResponse?.socket || typeof nodeResponse?.write !== "function" || typeof nodeResponse?.once !== "function") {
9
+ return;
10
+ }
11
+ response.body = createNodeResponseStream(response.body, (error) => {
12
+ logger.error("Failed to cancel sitemap response stream after the client disconnected.", error);
13
+ });
14
+ removeResponseHeader(event, "Content-Length");
15
+ });
16
+ });
@@ -35,6 +35,7 @@ declare const _default: import("h3").EventHandler<import("h3").EventHandlerReque
35
35
  xsl: string | false;
36
36
  credits: boolean;
37
37
  minify: boolean;
38
+ experimentalStreaming?: boolean | undefined;
38
39
  };
39
40
  globalSources: SitemapSourceResolved[];
40
41
  siteConfig: {
@@ -1,2 +1,2 @@
1
- declare const _default: import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<string | undefined>>;
1
+ declare const _default: import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<string | ReadableStream<Uint8Array<ArrayBufferLike>> | undefined>>;
2
2
  export default _default;
@@ -1,2 +1,2 @@
1
- declare const _default: import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<string | void>>;
1
+ declare const _default: import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<string | void | ReadableStream<Uint8Array<ArrayBufferLike>>>>;
2
2
  export default _default;
@@ -1,2 +1,2 @@
1
- declare const _default: import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<string>>;
1
+ declare const _default: import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<string | ReadableStream<Uint8Array<ArrayBufferLike>>>>;
2
2
  export default _default;
@@ -1,2 +1,2 @@
1
- declare const _default: import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<string | undefined>>;
1
+ declare const _default: import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<string | ReadableStream<Uint8Array<ArrayBufferLike>> | undefined>>;
2
2
  export default _default;
@@ -1,2 +1,2 @@
1
- declare const _default: import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<string | void>>;
1
+ declare const _default: import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<string | void | ReadableStream<Uint8Array<ArrayBufferLike>>>>;
2
2
  export default _default;
@@ -1,2 +1,2 @@
1
- declare const _default: import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<string>>;
1
+ declare const _default: import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<string | ReadableStream<Uint8Array<ArrayBufferLike>>>>;
2
2
  export default _default;
@@ -0,0 +1,13 @@
1
+ import type { ModuleRuntimeConfig, NitroUrlResolvers, SitemapIndexEntry } from '../../../types.js';
2
+ export declare function renderSitemapIndexXmlChunks(sitemaps: SitemapIndexEntry[], resolvers: NitroUrlResolvers, { version, xsl, credits, minify }: Pick<ModuleRuntimeConfig, 'version' | 'xsl' | 'credits' | 'minify'>, errorInfo?: {
3
+ messages: string[];
4
+ urls: string[];
5
+ }): Generator<string>;
6
+ export declare function urlsToIndexXml(sitemaps: SitemapIndexEntry[], resolvers: NitroUrlResolvers, { version, xsl, credits, minify }: Pick<ModuleRuntimeConfig, 'version' | 'xsl' | 'credits' | 'minify'>, errorInfo?: {
7
+ messages: string[];
8
+ urls: string[];
9
+ }): string;
10
+ export declare function urlsToIndexXmlStream(sitemaps: SitemapIndexEntry[], resolvers: NitroUrlResolvers, config: Pick<ModuleRuntimeConfig, 'version' | 'xsl' | 'credits' | 'minify'>, errorInfo?: {
11
+ messages: string[];
12
+ urls: string[];
13
+ }): ReadableStream<Uint8Array>;
@@ -0,0 +1,70 @@
1
+ import { withQuery } from "ufo";
2
+ import { createChunkedXmlStream } from "../stream.js";
3
+ import { escapeValueForXml } from "./xml.js";
4
+ export function* renderSitemapIndexXmlChunks(sitemaps, resolvers, { version, xsl, credits, minify }, errorInfo) {
5
+ const NL = minify ? "" : "\n";
6
+ const I1 = minify ? "" : " ";
7
+ const I2 = minify ? "" : " ";
8
+ yield '<?xml version="1.0" encoding="UTF-8"?>';
9
+ if (xsl) {
10
+ let relativeBaseUrl = resolvers.relativeBaseUrlResolver?.(xsl) ?? xsl;
11
+ if (errorInfo && errorInfo.messages.length > 0) {
12
+ relativeBaseUrl = withQuery(relativeBaseUrl, {
13
+ errors: "true",
14
+ error_messages: errorInfo.messages,
15
+ error_urls: errorInfo.urls
16
+ });
17
+ }
18
+ yield `${NL}<?xml-stylesheet type="text/xsl" href="${escapeValueForXml(relativeBaseUrl)}"?>`;
19
+ }
20
+ yield `${NL}<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">${NL}`;
21
+ for (let i = 0; i < sitemaps.length; i++) {
22
+ const entry = sitemaps[i];
23
+ if (i > 0)
24
+ yield NL;
25
+ yield `${I1}<sitemap>${NL}${I2}<loc>${escapeValueForXml(entry.sitemap)}</loc>${NL}`;
26
+ if (entry.lastmod)
27
+ yield `${I2}<lastmod>${escapeValueForXml(entry.lastmod)}</lastmod>${NL}`;
28
+ yield `${I1}</sitemap>`;
29
+ }
30
+ yield `${NL}</sitemapindex>`;
31
+ if (credits)
32
+ yield `${NL}<!-- XML Sitemap Index generated by @nuxtjs/sitemap v${version} at ${(/* @__PURE__ */ new Date()).toISOString()} -->`;
33
+ }
34
+ export function urlsToIndexXml(sitemaps, resolvers, { version, xsl, credits, minify }, errorInfo) {
35
+ const NL = minify ? "" : "\n";
36
+ const I1 = minify ? "" : " ";
37
+ const I2 = minify ? "" : " ";
38
+ let sitemapXml = "";
39
+ for (const entry of sitemaps) {
40
+ if (sitemapXml)
41
+ sitemapXml += NL;
42
+ sitemapXml += `${I1}<sitemap>${NL}${I2}<loc>${escapeValueForXml(entry.sitemap)}</loc>${NL}`;
43
+ if (entry.lastmod)
44
+ sitemapXml += `${I2}<lastmod>${escapeValueForXml(entry.lastmod)}</lastmod>${NL}`;
45
+ sitemapXml += `${I1}</sitemap>`;
46
+ }
47
+ const xmlParts = ['<?xml version="1.0" encoding="UTF-8"?>'];
48
+ if (xsl) {
49
+ let relativeBaseUrl = resolvers.relativeBaseUrlResolver?.(xsl) ?? xsl;
50
+ if (errorInfo && errorInfo.messages.length > 0) {
51
+ relativeBaseUrl = withQuery(relativeBaseUrl, {
52
+ errors: "true",
53
+ error_messages: errorInfo.messages,
54
+ error_urls: errorInfo.urls
55
+ });
56
+ }
57
+ xmlParts.push(`<?xml-stylesheet type="text/xsl" href="${escapeValueForXml(relativeBaseUrl)}"?>`);
58
+ }
59
+ xmlParts.push(
60
+ '<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">',
61
+ sitemapXml,
62
+ "</sitemapindex>"
63
+ );
64
+ if (credits)
65
+ xmlParts.push(`<!-- XML Sitemap Index generated by @nuxtjs/sitemap v${version} at ${(/* @__PURE__ */ new Date()).toISOString()} -->`);
66
+ return xmlParts.join(NL);
67
+ }
68
+ export function urlsToIndexXmlStream(sitemaps, resolvers, config, errorInfo) {
69
+ return createChunkedXmlStream(renderSitemapIndexXmlChunks(sitemaps, resolvers, config, errorInfo));
70
+ }
@@ -1,9 +1,5 @@
1
1
  import type { NitroApp } from 'nitropack/types';
2
2
  import type { ModuleRuntimeConfig, NitroUrlResolvers, SitemapIndexEntry } from '../../../types.js';
3
- export declare function urlsToIndexXml(sitemaps: SitemapIndexEntry[], resolvers: NitroUrlResolvers, { version, xsl, credits, minify }: Pick<ModuleRuntimeConfig, 'version' | 'xsl' | 'credits' | 'minify'>, errorInfo?: {
4
- messages: string[];
5
- urls: string[];
6
- }): string;
7
3
  export declare function buildSitemapIndex(resolvers: NitroUrlResolvers, runtimeConfig: ModuleRuntimeConfig, nitro?: NitroApp): Promise<{
8
4
  entries: SitemapIndexEntry[];
9
5
  failedSources: Array<{
@@ -11,3 +7,4 @@ export declare function buildSitemapIndex(resolvers: NitroUrlResolvers, runtimeC
11
7
  error: string;
12
8
  }>;
13
9
  }>;
10
+ export { urlsToIndexXml, urlsToIndexXmlStream } from './index-xml.js';
@@ -1,10 +1,9 @@
1
1
  import { getHeader } from "h3";
2
2
  import { defineCachedFunction } from "nitropack/runtime";
3
- import { joinURL, withQuery } from "ufo";
3
+ import { joinURL } from "ufo";
4
4
  import staticConfig from "#sitemap-virtual/static-config.mjs";
5
5
  import { normaliseDate } from "../urlset/normalise.js";
6
6
  import { getResolvedSitemapUrls } from "./sitemap.js";
7
- import { escapeValueForXml } from "./xml.js";
8
7
  const SERVER_CACHE_MAX_AGE = staticConfig.cacheMaxAgeSeconds || 60 * 10;
9
8
  const buildSitemapIndexCached = defineCachedFunction(
10
9
  async (event, resolvers, runtimeConfig, nitro) => {
@@ -105,41 +104,10 @@ async function buildSitemapIndexInternal(resolvers, runtimeConfig, nitro) {
105
104
  }
106
105
  return { entries, failedSources: allFailedSources };
107
106
  }
108
- export function urlsToIndexXml(sitemaps, resolvers, { version, xsl, credits, minify }, errorInfo) {
109
- const sitemapXml = sitemaps.map((e) => [
110
- " <sitemap>",
111
- ` <loc>${escapeValueForXml(e.sitemap)}</loc>`,
112
- // lastmod is optional
113
- e.lastmod ? ` <lastmod>${escapeValueForXml(e.lastmod)}</lastmod>` : false,
114
- " </sitemap>"
115
- ].filter(Boolean).join("\n")).join("\n");
116
- const xmlParts = [
117
- '<?xml version="1.0" encoding="UTF-8"?>'
118
- ];
119
- if (xsl) {
120
- let relativeBaseUrl = resolvers.relativeBaseUrlResolver?.(xsl) ?? xsl;
121
- if (errorInfo && errorInfo.messages.length > 0) {
122
- relativeBaseUrl = withQuery(relativeBaseUrl, {
123
- errors: "true",
124
- error_messages: errorInfo.messages,
125
- error_urls: errorInfo.urls
126
- });
127
- }
128
- xmlParts.push(`<?xml-stylesheet type="text/xsl" href="${escapeValueForXml(relativeBaseUrl)}"?>`);
129
- }
130
- xmlParts.push(
131
- '<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">',
132
- sitemapXml,
133
- "</sitemapindex>"
134
- );
135
- if (credits) {
136
- xmlParts.push(`<!-- XML Sitemap Index generated by @nuxtjs/sitemap v${version} at ${(/* @__PURE__ */ new Date()).toISOString()} -->`);
137
- }
138
- return minify ? xmlParts.join("").replace(/(?<!<[^>]*)\s(?![^<]*>)/g, "") : xmlParts.join("\n");
139
- }
140
107
  export async function buildSitemapIndex(resolvers, runtimeConfig, nitro) {
141
108
  if (!import.meta.dev && !import.meta.prerender && typeof runtimeConfig.cacheMaxAgeSeconds === "number" && runtimeConfig.cacheMaxAgeSeconds > 0 && resolvers.event) {
142
109
  return buildSitemapIndexCached(resolvers.event, resolvers, runtimeConfig, nitro);
143
110
  }
144
111
  return buildSitemapIndexInternal(resolvers, runtimeConfig, nitro);
145
112
  }
113
+ export { urlsToIndexXml, urlsToIndexXmlStream } from "./index-xml.js";
@@ -18,4 +18,4 @@ export declare function buildResolvedSitemapUrls(effectiveSitemap: SitemapDefini
18
18
  export declare const buildResolvedSitemapUrlsCached: (_event: H3Event<import("h3").EventHandlerRequest>, effectiveSitemap: SitemapDefinition, matchName: string, isChunked: boolean, resolvers: NitroUrlResolvers, runtimeConfig: ModuleRuntimeConfig, nitro?: NitroApp | undefined) => Promise<ResolvedSitemapUrlsResult>;
19
19
  export declare function getResolvedSitemapUrls(effectiveSitemap: SitemapDefinition, matchName: string, isChunked: boolean, resolvers: NitroUrlResolvers, runtimeConfig: ModuleRuntimeConfig, nitro?: NitroApp): Promise<ResolvedSitemapUrlsResult>;
20
20
  export declare function buildSitemapUrls(sitemap: SitemapDefinition, resolvers: NitroUrlResolvers, runtimeConfig: ModuleRuntimeConfig, nitro?: NitroApp): Promise<ResolvedSitemapUrlsResult>;
21
- export { urlsToXml } from './xml.js';
21
+ export { urlsToXml, urlsToXmlStream } from './xml.js';
@@ -14,30 +14,33 @@ export function resolveSitemapEntries(sitemap, urls, runtimeConfig, resolvers, b
14
14
  autoI18n,
15
15
  isI18nMapped
16
16
  } = runtimeConfig;
17
- const filterPath = createPathFilter({
17
+ const hasFilters = !!sitemap.include?.length || !!sitemap.exclude?.length;
18
+ const filterPath = hasFilters ? createPathFilter({
18
19
  include: sitemap.include,
19
20
  exclude: sitemap.exclude
20
- }, baseURL || "/");
21
- const _urls = urls.map((_e) => {
21
+ }, baseURL || "/") : void 0;
22
+ const _urls = [];
23
+ for (const _e of urls) {
22
24
  const e = preNormalizeEntry(_e, resolvers);
23
- if (!e.loc || !filterPath(e.loc))
24
- return false;
25
- return e;
26
- }).filter(Boolean);
27
- let validI18nUrlsForTransform = [];
25
+ if (e.loc && (!filterPath || filterPath(e.loc, e._path?.pathname)))
26
+ _urls.push(e);
27
+ }
28
28
  const withoutPrefixPaths = {};
29
29
  if (autoI18n && autoI18n.strategy !== "no_prefix") {
30
- const localeCodes = autoI18n.locales.map((l) => l.code);
30
+ const localeCodes = new Set(autoI18n.locales.map((l) => l.code));
31
31
  const localeByCode = new Map(autoI18n.locales.map((l) => [l.code, l]));
32
32
  const isPrefixStrategy = autoI18n.strategy === "prefix";
33
33
  const isPrefixExceptOrAndDefault = autoI18n.strategy === "prefix_and_default" || autoI18n.strategy === "prefix_except_default";
34
34
  const xDefaultAndLocales = [{ code: "x-default", _hreflang: "x-default" }, ...autoI18n.locales];
35
35
  const defaultLocale = autoI18n.defaultLocale;
36
36
  const hasPages = !!autoI18n.pages;
37
+ const sortedPageKeys = hasPages ? Object.keys(autoI18n.pages).sort((a, b) => b.length - a.length) : void 0;
37
38
  const hasDifferentDomains = !!autoI18n.differentDomains;
38
- validI18nUrlsForTransform = _urls.map((_e, i) => {
39
+ const validI18nUrlsForTransform = [];
40
+ for (let i = 0; i < _urls.length; i++) {
41
+ const _e = _urls[i];
39
42
  if (_e._abs)
40
- return false;
43
+ continue;
41
44
  const split = splitForLocales(_e._relativeLoc, localeCodes);
42
45
  let localeCode = split[0];
43
46
  const pathWithoutPrefix = split[1];
@@ -47,31 +50,30 @@ export function resolveSitemapEntries(sitemap, urls, runtimeConfig, resolvers, b
47
50
  e._pathWithoutPrefix = pathWithoutPrefix;
48
51
  const locale = localeByCode.get(localeCode);
49
52
  if (!locale)
50
- return false;
53
+ continue;
51
54
  e._locale = locale;
52
55
  e._index = i;
53
56
  e._key = `${e._sitemap || ""}${e._path?.pathname || "/"}${e._path?.search || ""}`;
54
57
  withoutPrefixPaths[pathWithoutPrefix] = withoutPrefixPaths[pathWithoutPrefix] || [];
55
58
  if (!withoutPrefixPaths[pathWithoutPrefix].some((e2) => e2._locale.code === locale.code))
56
59
  withoutPrefixPaths[pathWithoutPrefix].push(e);
57
- return e;
58
- }).filter(Boolean);
60
+ validI18nUrlsForTransform.push(e);
61
+ }
59
62
  for (const e of validI18nUrlsForTransform) {
60
63
  if (!e._i18nTransform && !e.alternatives?.length) {
61
- const alternatives = (withoutPrefixPaths[e._pathWithoutPrefix] || []).map((u) => {
62
- const entries = [];
64
+ const alternatives = [];
65
+ for (const u of withoutPrefixPaths[e._pathWithoutPrefix] || []) {
63
66
  if (u._locale.code === defaultLocale) {
64
- entries.push({
67
+ alternatives.push({
65
68
  href: u.loc,
66
69
  hreflang: "x-default"
67
70
  });
68
71
  }
69
- entries.push({
72
+ alternatives.push({
70
73
  href: u.loc,
71
74
  hreflang: u._locale._hreflang || defaultLocale
72
75
  });
73
- return entries;
74
- }).flat().filter(Boolean);
76
+ }
75
77
  if (alternatives.length)
76
78
  e.alternatives = alternatives;
77
79
  } else if (e._i18nTransform) {
@@ -91,7 +93,7 @@ export function resolveSitemapEntries(sitemap, urls, runtimeConfig, resolvers, b
91
93
  };
92
94
  });
93
95
  } else {
94
- const pageMatch = hasPages ? findPageMapping(e._pathWithoutPrefix, autoI18n.pages) : null;
96
+ const pageMatch = hasPages ? findPageMapping(e._pathWithoutPrefix, autoI18n.pages, sortedPageKeys) : null;
95
97
  const pathSearch = e._path?.search || "";
96
98
  const pathWithoutPrefix = e._pathWithoutPrefix;
97
99
  for (const l of autoI18n.locales) {
@@ -130,7 +132,7 @@ export function resolveSitemapEntries(sitemap, urls, runtimeConfig, resolvers, b
130
132
  } else if (isPrefixExceptOrAndDefault && !isDefault) {
131
133
  href = joinURL("/", code, pathWithoutPrefix);
132
134
  }
133
- if (!filterPath(href))
135
+ if (filterPath && !filterPath(href))
134
136
  continue;
135
137
  alternatives.push({
136
138
  hreflang: locale._hreflang,
@@ -192,9 +194,17 @@ export async function buildResolvedSitemapUrls(effectiveSitemap, matchName, isCh
192
194
  const localeSitemapKeys = isI18nMapped && autoI18n ? autoI18n.locales.map((l) => l._sitemap) : [];
193
195
  if (isMultiSitemap) {
194
196
  const sitemapNames = Object.keys(sitemaps).filter((k) => k !== "index");
197
+ const validSitemapNames = new Set(sitemapNames);
198
+ if (isI18nMapped) {
199
+ for (const name of sitemapNames) {
200
+ const localeKey = resolveI18nSitemapLocaleKey(name, localeSitemapKeys);
201
+ if (localeKey)
202
+ validSitemapNames.add(localeKey);
203
+ }
204
+ }
195
205
  const warnedSitemaps = nitro?._sitemapWarnedSitemaps || /* @__PURE__ */ new Set();
196
206
  for (const e of enhancedUrls) {
197
- const hasMatchingSitemap = typeof e._sitemap === "string" && (sitemapNames.includes(e._sitemap) || isI18nMapped && sitemapNames.some((name) => resolveI18nSitemapLocaleKey(name, localeSitemapKeys) === e._sitemap));
207
+ const hasMatchingSitemap = typeof e._sitemap === "string" && validSitemapNames.has(e._sitemap);
198
208
  if (typeof e._sitemap === "string" && !hasMatchingSitemap) {
199
209
  if (!warnedSitemaps.has(e._sitemap)) {
200
210
  warnedSitemaps.add(e._sitemap);
@@ -206,6 +216,7 @@ export async function buildResolvedSitemapUrls(effectiveSitemap, matchName, isCh
206
216
  nitro._sitemapWarnedSitemaps = warnedSitemaps;
207
217
  }
208
218
  }
219
+ const matchedLocaleSitemap = isI18nMapped ? resolveI18nSitemapLocaleKey(matchName, localeSitemapKeys) : null;
209
220
  const filteredUrls = enhancedUrls.filter((e) => {
210
221
  if (e._sitemap === false)
211
222
  return false;
@@ -215,7 +226,7 @@ export async function buildResolvedSitemapUrls(effectiveSitemap, matchName, isCh
215
226
  if (e._sitemap === matchName)
216
227
  return true;
217
228
  if (isI18nMapped)
218
- return e._sitemap === resolveI18nSitemapLocaleKey(matchName, localeSitemapKeys);
229
+ return e._sitemap === matchedLocaleSitemap;
219
230
  return false;
220
231
  }
221
232
  return true;
@@ -273,4 +284,4 @@ export async function buildSitemapUrls(sitemap, resolvers, runtimeConfig, nitro)
273
284
  const urls = sliceUrlsForChunk(resolved.urls, sitemap.sitemapName, sitemaps, chunkSize);
274
285
  return { urls, failedSources: resolved.failedSources };
275
286
  }
276
- export { urlsToXml } from "./xml.js";
287
+ export { urlsToXml, urlsToXmlStream } from "./xml.js";
@@ -1,6 +1,14 @@
1
1
  import type { ModuleRuntimeConfig, NitroUrlResolvers, ResolvedSitemapUrl } from '../../../types.js';
2
2
  export declare function escapeValueForXml(value: boolean | string | number): string;
3
+ export declare function renderSitemapXmlChunks(urls: ResolvedSitemapUrl[], resolvers: NitroUrlResolvers, { version, xsl, credits, minify }: Pick<ModuleRuntimeConfig, 'version' | 'xsl' | 'credits' | 'minify'>, errorInfo?: {
4
+ messages: string[];
5
+ urls: string[];
6
+ }): Generator<string>;
3
7
  export declare function urlsToXml(urls: ResolvedSitemapUrl[], resolvers: NitroUrlResolvers, { version, xsl, credits, minify }: Pick<ModuleRuntimeConfig, 'version' | 'xsl' | 'credits' | 'minify'>, errorInfo?: {
4
8
  messages: string[];
5
9
  urls: string[];
6
10
  }): string;
11
+ export declare function urlsToXmlStream(urls: ResolvedSitemapUrl[], resolvers: NitroUrlResolvers, config: Pick<ModuleRuntimeConfig, 'version' | 'xsl' | 'credits' | 'minify'>, errorInfo?: {
12
+ messages: string[];
13
+ urls: string[];
14
+ }): ReadableStream<Uint8Array>;
@@ -1,5 +1,6 @@
1
1
  import { withQuery } from "ufo";
2
- import { xmlEscape } from "../../utils.js";
2
+ import { xmlEscape } from "../../../utils-pure.js";
3
+ import { createChunkedXmlStream } from "../stream.js";
3
4
  export function escapeValueForXml(value) {
4
5
  if (value === true || value === false)
5
6
  return value ? "yes" : "no";
@@ -24,7 +25,10 @@ function buildUrlXml(url, NL, I1, I2, I3, I4) {
24
25
  if (url.alternatives) {
25
26
  for (const alt of url.alternatives) {
26
27
  let attrs = "";
27
- for (const [k, v] of Object.entries(alt)) attrs += ` ${k}="${xmlEscape(String(v))}"`;
28
+ for (const k in alt) {
29
+ if (Object.hasOwn(alt, k))
30
+ attrs += ` ${k}="${xmlEscape(String(alt[k]))}"`;
31
+ }
28
32
  xml += `${I2}<xhtml:link rel="alternate"${attrs} />${NL}`;
29
33
  }
30
34
  }
@@ -112,6 +116,30 @@ function buildUrlXml(url, NL, I1, I2, I3, I4) {
112
116
  xml += `${I1}</url>`;
113
117
  return xml;
114
118
  }
119
+ export function* renderSitemapXmlChunks(urls, resolvers, { version, xsl, credits, minify }, errorInfo) {
120
+ let xslHref = xsl ? resolvers.relativeBaseUrlResolver(xsl) : false;
121
+ if (xslHref && errorInfo?.messages.length) {
122
+ xslHref = withQuery(xslHref, {
123
+ errors: "true",
124
+ error_messages: errorInfo.messages,
125
+ error_urls: errorInfo.urls
126
+ });
127
+ }
128
+ const NL = minify ? "" : "\n";
129
+ const I1 = minify ? "" : " ";
130
+ const I2 = minify ? "" : " ";
131
+ const I3 = minify ? "" : " ";
132
+ const I4 = minify ? "" : " ";
133
+ yield xslHref ? `<?xml version="1.0" encoding="UTF-8"?><?xml-stylesheet type="text/xsl" href="${escapeValueForXml(xslHref)}"?>${NL}` : `<?xml version="1.0" encoding="UTF-8"?>${NL}`;
134
+ yield URLSET_OPENING_TAG + NL;
135
+ for (const url of urls) {
136
+ yield buildUrlXml(url, NL, I1, I2, I3, I4) + NL;
137
+ }
138
+ yield "</urlset>";
139
+ if (credits) {
140
+ yield `${NL}<!-- XML Sitemap generated by @nuxtjs/sitemap v${version} at ${(/* @__PURE__ */ new Date()).toISOString()} -->`;
141
+ }
142
+ }
115
143
  export function urlsToXml(urls, resolvers, { version, xsl, credits, minify }, errorInfo) {
116
144
  let xslHref = xsl ? resolvers.relativeBaseUrlResolver(xsl) : false;
117
145
  if (xslHref && errorInfo?.messages.length) {
@@ -128,12 +156,13 @@ export function urlsToXml(urls, resolvers, { version, xsl, credits, minify }, er
128
156
  const I4 = minify ? "" : " ";
129
157
  let xml = xslHref ? `<?xml version="1.0" encoding="UTF-8"?><?xml-stylesheet type="text/xsl" href="${escapeValueForXml(xslHref)}"?>${NL}` : `<?xml version="1.0" encoding="UTF-8"?>${NL}`;
130
158
  xml += URLSET_OPENING_TAG + NL;
131
- for (const url of urls) {
159
+ for (const url of urls)
132
160
  xml += buildUrlXml(url, NL, I1, I2, I3, I4) + NL;
133
- }
134
161
  xml += "</urlset>";
135
- if (credits) {
162
+ if (credits)
136
163
  xml += `${NL}<!-- XML Sitemap generated by @nuxtjs/sitemap v${version} at ${(/* @__PURE__ */ new Date()).toISOString()} -->`;
137
- }
138
164
  return xml;
139
165
  }
166
+ export function urlsToXmlStream(urls, resolvers, config, errorInfo) {
167
+ return createChunkedXmlStream(renderSitemapXmlChunks(urls, resolvers, config, errorInfo));
168
+ }