@nuxtjs/sitemap 8.2.3 → 8.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.
Files changed (46) hide show
  1. package/dist/content.mjs +11 -8
  2. package/dist/devtools/lib/sitemap/state.ts +4 -1
  3. package/dist/devtools/pages/sitemap/index.vue +2 -2
  4. package/dist/module.json +2 -2
  5. package/dist/module.mjs +29 -45
  6. package/dist/runtime/server/plugins/compression.js +57 -13
  7. package/dist/runtime/server/plugins/stream-transport.d.ts +2 -0
  8. package/dist/runtime/server/plugins/stream-transport.js +16 -0
  9. package/dist/runtime/server/routes/__sitemap__/debug-production.d.ts +3 -3
  10. package/dist/runtime/server/routes/__sitemap__/debug-production.js +43 -27
  11. package/dist/runtime/server/routes/__sitemap__/debug.d.ts +1 -0
  12. package/dist/runtime/server/routes/__zero-runtime/sitemap/[sitemap].xml.d.ts +1 -1
  13. package/dist/runtime/server/routes/__zero-runtime/sitemap.xml.d.ts +1 -1
  14. package/dist/runtime/server/routes/__zero-runtime/sitemap_index.xml.d.ts +1 -1
  15. package/dist/runtime/server/routes/sitemap/[sitemap].xml.d.ts +1 -1
  16. package/dist/runtime/server/routes/sitemap.xml.d.ts +1 -1
  17. package/dist/runtime/server/routes/sitemap_index.xml.d.ts +1 -1
  18. package/dist/runtime/server/sitemap/builder/index-xml.d.ts +13 -0
  19. package/dist/runtime/server/sitemap/builder/index-xml.js +67 -0
  20. package/dist/runtime/server/sitemap/builder/sitemap-index.d.ts +1 -4
  21. package/dist/runtime/server/sitemap/builder/sitemap-index.js +15 -60
  22. package/dist/runtime/server/sitemap/builder/sitemap.d.ts +1 -1
  23. package/dist/runtime/server/sitemap/builder/sitemap.js +36 -25
  24. package/dist/runtime/server/sitemap/builder/xml.d.ts +9 -1
  25. package/dist/runtime/server/sitemap/builder/xml.js +37 -11
  26. package/dist/runtime/server/sitemap/event-handlers.d.ts +3 -3
  27. package/dist/runtime/server/sitemap/event-handlers.js +18 -23
  28. package/dist/runtime/server/sitemap/nitro.d.ts +4 -1
  29. package/dist/runtime/server/sitemap/nitro.js +116 -26
  30. package/dist/runtime/server/sitemap/stream.d.ts +30 -0
  31. package/dist/runtime/server/sitemap/stream.js +144 -0
  32. package/dist/runtime/server/sitemap/urlset/normalise.d.ts +6 -1
  33. package/dist/runtime/server/sitemap/urlset/normalise.js +26 -6
  34. package/dist/runtime/server/sitemap/urlset/sort.js +12 -3
  35. package/dist/runtime/server/sitemap/urlset/sources.js +50 -9
  36. package/dist/runtime/server/utils.d.ts +0 -1
  37. package/dist/runtime/server/utils.js +12 -12
  38. package/dist/runtime/types.d.ts +14 -3
  39. package/dist/runtime/utils-pure.d.ts +5 -4
  40. package/dist/runtime/utils-pure.js +43 -26
  41. package/dist/utils.d.mts +5 -29
  42. package/dist/utils.d.ts +5 -29
  43. package/dist/utils.mjs +3 -418
  44. package/package.json +28 -30
  45. package/dist/runtime/server/kit.d.ts +0 -2
  46. package/dist/runtime/server/kit.js +0 -23
package/dist/content.mjs CHANGED
@@ -7,6 +7,16 @@ if (!globalThis.__sitemapCollectionOnUrlFns)
7
7
  globalThis.__sitemapCollectionOnUrlFns = /* @__PURE__ */ new Map();
8
8
  const collectionFilters = globalThis.__sitemapCollectionFilters;
9
9
  const collectionOnUrlFns = globalThis.__sitemapCollectionOnUrlFns;
10
+ function registerCollectionHooks(options, callerName) {
11
+ if (!options?.filter && !options?.onUrl)
12
+ return;
13
+ if (!options.name)
14
+ throw new Error(`[sitemap] \`name\` is required when using \`filter\` or \`onUrl\` in ${callerName}()`);
15
+ if (options.filter)
16
+ collectionFilters.set(options.name, options.filter);
17
+ if (options.onUrl)
18
+ collectionOnUrlFns.set(options.name, options.onUrl);
19
+ }
10
20
  const { defineSchema, asCollection, schema } = createContentSchemaFactory({
11
21
  fieldName: "sitemap",
12
22
  label: "sitemap",
@@ -16,14 +26,7 @@ const { defineSchema, asCollection, schema } = createContentSchemaFactory({
16
26
  if ("type" in options || "source" in options)
17
27
  throw new Error("[sitemap] `defineSitemapSchema()` returns a schema field, not a collection wrapper. Use it inside your schema: `schema: z.object({ sitemap: defineSitemapSchema() })`. See https://nuxtseo.com/sitemap/guides/content");
18
28
  warnIfZodMismatch(options?.z);
19
- if (options?.filter || options?.onUrl) {
20
- if (!options.name)
21
- throw new Error("[sitemap] `name` is required when using `filter` or `onUrl` in defineSitemapSchema()");
22
- if (options.filter)
23
- collectionFilters.set(options.name, options.filter);
24
- if (options.onUrl)
25
- collectionOnUrlFns.set(options.name, options.onUrl);
26
- }
29
+ registerCollectionHooks(options, "defineSitemapSchema");
27
30
  }
28
31
  }, z);
29
32
  function asSitemapCollection(collection, options) {
@@ -35,7 +35,10 @@ export async function refreshProductionData() {
35
35
  // Try fetching the full debug endpoint from production first (proxied through local server)
36
36
  const remoteDebug = await appFetch.value('/__sitemap__/debug-production.json', {
37
37
  query: { url: productionUrl.value, mode: 'debug' },
38
- }).catch(() => null) as (typeof data.value & { error?: string }) | null
38
+ }).catch(() => {
39
+ // Production debug is optional; use the public sitemap XML fallback below.
40
+ return null
41
+ }) as (typeof data.value & { error?: string }) | null
39
42
  if (remoteDebug && !remoteDebug.error && remoteDebug.sitemaps && !Array.isArray(remoteDebug.sitemaps)) {
40
43
  // Response has object sitemaps (debug.json format) rather than array (XML fallback format)
41
44
  productionRemoteDebugData.value = remoteDebug
@@ -137,7 +137,7 @@ const totalProductionWarnings = computed(() =>
137
137
  target="_blank"
138
138
  class="link-external"
139
139
  >
140
- Learn more
140
+ Learn more about large sitemaps
141
141
  </a>
142
142
  </DevtoolsAlert>
143
143
  </template>
@@ -320,7 +320,7 @@ const totalProductionWarnings = computed(() =>
320
320
  target="_blank"
321
321
  class="link-external"
322
322
  >
323
- Learn more
323
+ Learn more about large sitemaps
324
324
  </a>
325
325
  </DevtoolsAlert>
326
326
  </template>
package/dist/module.json CHANGED
@@ -4,9 +4,9 @@
4
4
  "nuxt": ">=3.9.0"
5
5
  },
6
6
  "configKey": "sitemap",
7
- "version": "8.2.3",
7
+ "version": "8.3.1",
8
8
  "builder": {
9
- "@nuxt/module-builder": "1.0.2",
9
+ "@nuxt/module-builder": "1.0.3",
10
10
  "unbuild": "3.6.1"
11
11
  }
12
12
  }
package/dist/module.mjs CHANGED
@@ -1,7 +1,9 @@
1
- import { useNuxt, hasNuxtModule, addTypeTemplate, addTemplate, defineNuxtModule, createResolver, useLogger, getNuxtModuleVersion, hasNuxtModuleCompatibility, addServerImports, addPrerenderRoutes, addServerPlugin, resolveModule, addServerHandler } from '@nuxt/kit';
1
+ import { useNuxt, hasNuxtModule, addTypeTemplate, addTemplate, defineNuxtModule, createResolver, getNuxtModuleVersion, hasNuxtModuleCompatibility, addServerImports, addPrerenderRoutes, addServerPlugin, resolveModule, addServerHandler } from '@nuxt/kit';
2
2
  import { defu } from 'defu';
3
3
  import { withSiteUrl, installNuxtSiteConfig } from 'nuxt-site-config/kit';
4
4
  import { isPathFile } from 'nuxt-site-config/urls';
5
+ import { isNuxtGenerate, useModuleLogger, getNuxtModuleOptions, resolveNitroPreset, resolveNuxtContentVersion, createPagesPromise, createNitroPromise } from 'nuxtseo-shared/kit';
6
+ import { serializeFilters } from 'nuxtseo-shared/utils';
5
7
  import { dirname, extname } from 'pathe';
6
8
  import { readPackageJSON } from 'pkg-types';
7
9
  import { withBase, withHttps, withTrailingSlash, withoutLeadingSlash, joinURL, withLeadingSlash, withoutTrailingSlash } from 'ufo';
@@ -11,7 +13,6 @@ import { mkdir, writeFile } from 'node:fs/promises';
11
13
  import { join } from 'node:path';
12
14
  import { colors } from 'consola/utils';
13
15
  import { splitForLocales, createPathFilter } from '../dist/runtime/utils-pure.js';
14
- import { isNuxtGenerate, getNuxtModuleOptions, resolveNitroPreset, resolveNuxtContentVersion, createPagesPromise, createNitroPromise } from 'nuxtseo-shared/kit';
15
16
  import { p as parseHtmlExtractSitemapMeta } from './shared/sitemap.BoMnWHOt.mjs';
16
17
  import { normaliseDate } from '../dist/runtime/server/sitemap/urlset/normalise.js';
17
18
  import { splitPathForI18nLocales as splitPathForI18nLocales$1, expandCompactLocaleRoute, normalizeLocales, generatePathForI18nPages } from 'nuxtseo-shared/i18n';
@@ -53,6 +54,7 @@ function setupPrerenderHandler(_options, nuxt = useNuxt()) {
53
54
  }
54
55
  nuxt.options.nitro.prerender.routes = nuxt.options.nitro.prerender.routes.filter((r) => r && !includesSitemapRoot(options.sitemapName, [r]));
55
56
  const runtimeAssetsPath = join(nuxt.options.rootDir, "node_modules/.cache/nuxt/sitemap");
57
+ const localeCodes = options.autoI18n ? new Set(options.autoI18n.locales.map((l) => l.code)) : void 0;
56
58
  nuxt.hooks.hook("nitro:config", (nitroConfig) => {
57
59
  nitroConfig.virtual = nitroConfig.virtual || {};
58
60
  nitroConfig.virtual["#sitemap-virtual/read-sources.mjs"] = `
@@ -103,7 +105,7 @@ export async function readSourcesFromFilesystem(filename) {
103
105
  });
104
106
  if (options.autoI18n && Object.keys(options.sitemaps).length > 1) {
105
107
  const path = route.route;
106
- const match = splitForLocales(path, options.autoI18n.locales.map((l) => l.code));
108
+ const match = splitForLocales(path, localeCodes);
107
109
  const locale = match[0] || options.autoI18n.defaultLocale;
108
110
  if (options.isI18nMapped) {
109
111
  const { _sitemap } = options.autoI18n.locales.find((l) => l.code === locale) || { _sitemap: locale };
@@ -241,6 +243,10 @@ declare module 'nuxt/app' {
241
243
  export {}
242
244
  `;
243
245
  }
246
+ }, {
247
+ nitro: true,
248
+ node: true,
249
+ nuxt: true
244
250
  });
245
251
  addTemplate({
246
252
  filename: "types/nuxt-sitemap-virtual.d.ts",
@@ -271,25 +277,6 @@ declare module '#sitemap/content-on-url' {
271
277
  });
272
278
  }
273
279
 
274
- function isValidFilter(filter) {
275
- if (typeof filter === "string")
276
- return true;
277
- if (filter instanceof RegExp)
278
- return true;
279
- if (typeof filter === "object" && typeof filter.regex === "string")
280
- return true;
281
- return false;
282
- }
283
- function normalizeFilters(filters) {
284
- return (filters || []).map((filter) => {
285
- if (!isValidFilter(filter)) {
286
- console.warn(`[@nuxtjs/sitemap] You have provided an invalid filter: ${filter}, ignoring.`);
287
- return false;
288
- }
289
- return filter instanceof RegExp ? { regex: filter.toString() } : filter;
290
- }).filter(Boolean);
291
- }
292
-
293
280
  function splitPathForI18nLocales(path, autoI18n) {
294
281
  if (typeof path !== "string")
295
282
  return path;
@@ -461,8 +448,7 @@ function generateExtraRoutesFromNuxtConfig(nuxt = useNuxt()) {
461
448
  return false;
462
449
  return !v.redirect;
463
450
  }).map(([k]) => k).filter(filterForValidPage);
464
- const prerenderUrls = (nuxt.options.nitro.prerender?.routes || []).filter(filterForValidPage);
465
- return { routeRules, prerenderUrls };
451
+ return { routeRules };
466
452
  }
467
453
 
468
454
  const module$1 = defineNuxtModule({
@@ -501,6 +487,7 @@ const module$1 = defineNuxtModule({
501
487
  // cache for 10 minutes
502
488
  minify: false,
503
489
  debug: false,
490
+ experimentalStreaming: false,
504
491
  defaultSitemapsChunkSize: 1e3,
505
492
  autoLastmod: false,
506
493
  discoverImages: true,
@@ -526,8 +513,7 @@ const module$1 = defineNuxtModule({
526
513
  async setup(config, nuxt) {
527
514
  const { resolve } = createResolver(import.meta.url);
528
515
  const { name, version } = await readPackageJSON(resolve("../package.json"));
529
- const logger = useLogger(name);
530
- logger.level = config.debug || nuxt.options.debug ? 4 : 3;
516
+ const logger = useModuleLogger(name, config, nuxt);
531
517
  if (config.enabled === false) {
532
518
  logger.debug("The module is disabled, skipping setup.");
533
519
  return;
@@ -767,13 +753,15 @@ const module$1 = defineNuxtModule({
767
753
  } else if (hasRouteRuleContent) {
768
754
  nuxt.options.nitro.routeRules[`/${config.sitemapName}`] = routeRules;
769
755
  }
770
- if (config.zeroRuntime && (config.experimentalWarmUp || config.experimentalCompression))
771
- logger.warn("`experimentalWarmUp` and `experimentalCompression` are ignored in zeroRuntime mode.");
756
+ if (config.zeroRuntime && (config.experimentalWarmUp || config.experimentalCompression || config.experimentalStreaming))
757
+ logger.warn("`experimentalWarmUp`, `experimentalCompression`, and `experimentalStreaming` are ignored in zeroRuntime mode.");
772
758
  if (!config.zeroRuntime) {
773
759
  if (config.experimentalWarmUp)
774
760
  addServerPlugin(resolve("./runtime/server/plugins/warm-up"));
775
761
  if (config.experimentalCompression)
776
762
  addServerPlugin(resolve("./runtime/server/plugins/compression"));
763
+ if (config.experimentalStreaming || config.experimentalCompression)
764
+ addServerPlugin(resolve("./runtime/server/plugins/stream-transport"));
777
765
  }
778
766
  const isNuxtContentDocumentDriven = !!nuxt.options.content?.documentDriven || config.strictNuxtContentPaths;
779
767
  const contentVersion = await resolveNuxtContentVersion();
@@ -1042,8 +1030,8 @@ ${onUrlEntries.join("\n")}`;
1042
1030
  }
1043
1031
  for (const sitemapName in sitemaps) {
1044
1032
  const sitemap = sitemaps[sitemapName];
1045
- sitemap.include = normalizeFilters(sitemap.include);
1046
- sitemap.exclude = normalizeFilters(sitemap.exclude);
1033
+ sitemap.include = serializeFilters(sitemap.include || [], "@nuxtjs/sitemap");
1034
+ sitemap.exclude = serializeFilters(sitemap.exclude || [], "@nuxtjs/sitemap");
1047
1035
  }
1048
1036
  const runtimeConfig = {
1049
1037
  isI18nMapped,
@@ -1051,6 +1039,7 @@ ${onUrlEntries.join("\n")}`;
1051
1039
  isMultiSitemap: usingMultiSitemaps,
1052
1040
  excludeAppSources: config.excludeAppSources,
1053
1041
  cacheMaxAgeSeconds: nuxt.options.dev ? 0 : config.cacheMaxAgeSeconds,
1042
+ experimentalStreaming: config.experimentalStreaming,
1054
1043
  autoLastmod: config.autoLastmod,
1055
1044
  defaultSitemapsChunkSize: config.defaultSitemapsChunkSize,
1056
1045
  minify: config.minify,
@@ -1148,8 +1137,8 @@ ${onUrlEntries.join("\n")}`;
1148
1137
  routesNameSeparator: nuxtI18nConfig.routesNameSeparator,
1149
1138
  normalisedLocales,
1150
1139
  filter: {
1151
- include: normalizeFilters(config.include),
1152
- exclude: normalizeFilters(config.exclude)
1140
+ include: serializeFilters(config.include || [], "@nuxtjs/sitemap"),
1141
+ exclude: serializeFilters(config.exclude || [], "@nuxtjs/sitemap")
1153
1142
  },
1154
1143
  autoI18n: !!resolvedAutoI18n
1155
1144
  });
@@ -1281,19 +1270,14 @@ export async function readSourcesFromFilesystem() {
1281
1270
  }
1282
1271
  `;
1283
1272
  }
1284
- if (prerenderSitemap && !nuxt.options.dev) {
1285
- nitroConfig.virtual["#sitemap-virtual/global-sources.mjs"] = `export const sources = []`;
1286
- nitroConfig.virtual[`#sitemap-virtual/child-sources.mjs`] = `export const sources = {}`;
1287
- } else {
1288
- nitroConfig.virtual["#sitemap-virtual/global-sources.mjs"] = async () => {
1289
- const globalSources = await generateGlobalSources();
1290
- return `export const sources = ${JSON.stringify(globalSources, null, 4)}`;
1291
- };
1292
- nitroConfig.virtual[`#sitemap-virtual/child-sources.mjs`] = async () => {
1293
- const childSources = await generateChildSources();
1294
- return `export const sources = ${JSON.stringify(childSources, null, 4)}`;
1295
- };
1296
- }
1273
+ nitroConfig.virtual["#sitemap-virtual/global-sources.mjs"] = async () => {
1274
+ const globalSources = await generateGlobalSources();
1275
+ return `export const sources = ${JSON.stringify(globalSources, null, 4)}`;
1276
+ };
1277
+ nitroConfig.virtual[`#sitemap-virtual/child-sources.mjs`] = async () => {
1278
+ const childSources = await generateChildSources();
1279
+ return `export const sources = ${JSON.stringify(childSources, null, 4)}`;
1280
+ };
1297
1281
  });
1298
1282
  if (config.xsl === "/__sitemap__/style.xsl") {
1299
1283
  addServerHandler({
@@ -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
+ });
@@ -1,8 +1,8 @@
1
- import type { SitemapWarning } from '@nuxtjs/sitemap/utils';
1
+ import type { SitemapIssue } from 'sitemapd/parse';
2
2
  export interface ProductionSitemapEntry {
3
3
  loc: string;
4
4
  urlCount: number;
5
- warnings: SitemapWarning[];
5
+ warnings: SitemapIssue[];
6
6
  error?: string;
7
7
  lastmod?: string;
8
8
  }
@@ -10,7 +10,7 @@ export interface ProductionDebugResponse {
10
10
  url: string;
11
11
  isIndex: boolean;
12
12
  sitemaps: ProductionSitemapEntry[];
13
- warnings: SitemapWarning[];
13
+ warnings: SitemapIssue[];
14
14
  error?: string;
15
15
  }
16
16
  declare const _default: import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<Record<string, any> | ProductionDebugResponse>>;
@@ -1,13 +1,13 @@
1
- import { isSitemapIndex, parseSitemapIndex, parseSitemapXml } from "@nuxtjs/sitemap/utils";
2
1
  import { defineEventHandler, getQuery } from "h3";
3
- async function fetchXml(url) {
2
+ import { collectSitemap } from "sitemapd/parse";
3
+ async function fetchSitemapBody(url) {
4
4
  const response = await fetch(url, {
5
- headers: { Accept: "application/xml, text/xml" },
5
+ headers: { Accept: "application/xml, text/xml, application/gzip" },
6
6
  signal: AbortSignal.timeout(15e3)
7
7
  });
8
8
  if (!response.ok)
9
9
  throw new Error(`HTTP ${response.status}: ${response.statusText}`);
10
- return response.text();
10
+ return new Uint8Array(await response.arrayBuffer());
11
11
  }
12
12
  export default defineEventHandler(async (e) => {
13
13
  const { url, mode } = getQuery(e);
@@ -18,58 +18,74 @@ export default defineEventHandler(async (e) => {
18
18
  const response = await fetch(debugUrl, {
19
19
  headers: { Accept: "application/json" },
20
20
  signal: AbortSignal.timeout(1e4)
21
- }).catch(() => null);
21
+ }).catch(() => {
22
+ return null;
23
+ });
22
24
  if (response?.ok) {
23
- const json = await response.json().catch(() => null);
25
+ const json = await response.json().catch(() => {
26
+ return null;
27
+ });
24
28
  if (json?.sitemaps)
25
29
  return json;
26
30
  }
27
31
  }
28
32
  const sitemapUrl = url.endsWith("/") ? `${url}sitemap.xml` : url;
29
- const xml = await fetchXml(sitemapUrl).catch((err) => {
33
+ const body = await fetchSitemapBody(sitemapUrl).catch((err) => {
30
34
  return err;
31
35
  });
32
- if (xml instanceof Error)
33
- return { url: sitemapUrl, isIndex: false, sitemaps: [], warnings: [], error: `Failed to fetch sitemap: ${xml.message}` };
34
- if (isSitemapIndex(xml)) {
35
- const { entries, warnings } = await parseSitemapIndex(xml);
36
+ if (body instanceof Error)
37
+ return { url: sitemapUrl, isIndex: false, sitemaps: [], warnings: [], error: `Failed to fetch sitemap: ${body.message}` };
38
+ const parsed = await collectSitemap(body);
39
+ if (parsed._tag !== "document") {
40
+ return {
41
+ url: sitemapUrl,
42
+ isIndex: false,
43
+ sitemaps: [],
44
+ warnings: parsed.issues,
45
+ error: parsed.issues.map((issue) => issue.message).join("; ") || "Invalid sitemap document"
46
+ };
47
+ }
48
+ if (parsed.document._tag === "index") {
49
+ const { entries } = parsed.document;
36
50
  const sitemaps = await Promise.all(
37
51
  entries.map(async (entry) => {
38
- const childXml = await fetchXml(entry.loc).catch((err) => err);
39
- if (childXml instanceof Error) {
52
+ const childBody = await fetchSitemapBody(entry.loc).catch((err) => err);
53
+ if (childBody instanceof Error) {
40
54
  return {
41
55
  loc: entry.loc,
42
56
  urlCount: 0,
43
57
  warnings: [],
44
- error: childXml.message,
58
+ error: childBody.message,
59
+ lastmod: entry.lastmod
60
+ };
61
+ }
62
+ const result = await collectSitemap(childBody);
63
+ if (result._tag !== "document" || result.document._tag !== "urlset") {
64
+ return {
65
+ loc: entry.loc,
66
+ urlCount: 0,
67
+ warnings: result.issues,
68
+ error: result.issues.map((issue) => issue.message).join("; ") || "Child is not a URL set",
45
69
  lastmod: entry.lastmod
46
70
  };
47
71
  }
48
- const result2 = await parseSitemapXml(childXml).catch((err) => ({
49
- urls: [],
50
- warnings: [{ type: "validation", message: err.message }]
51
- }));
52
72
  return {
53
73
  loc: entry.loc,
54
- urlCount: result2.urls.length,
55
- warnings: result2.warnings,
74
+ urlCount: result.document.entries.length,
75
+ warnings: result.issues,
56
76
  lastmod: entry.lastmod
57
77
  };
58
78
  })
59
79
  );
60
- return { url: sitemapUrl, isIndex: true, sitemaps, warnings };
80
+ return { url: sitemapUrl, isIndex: true, sitemaps, warnings: parsed.issues };
61
81
  }
62
- const result = await parseSitemapXml(xml).catch((err) => ({
63
- urls: [],
64
- warnings: [{ type: "validation", message: err.message }]
65
- }));
66
82
  return {
67
83
  url: sitemapUrl,
68
84
  isIndex: false,
69
85
  sitemaps: [{
70
86
  loc: sitemapUrl,
71
- urlCount: result.urls.length,
72
- warnings: result.warnings
87
+ urlCount: parsed.document.entries.length,
88
+ warnings: parsed.issues
73
89
  }],
74
90
  warnings: []
75
91
  };
@@ -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,67 @@
1
+ import { withQuery } from "ufo";
2
+ import { createChunkedXmlStream } from "../stream.js";
3
+ import { escapeValueForXml } from "./xml.js";
4
+ function resolveIndexXslHref(resolvers, xsl, errorInfo) {
5
+ let relativeBaseUrl = resolvers.relativeBaseUrlResolver?.(xsl) ?? xsl;
6
+ if (errorInfo && errorInfo.messages.length > 0) {
7
+ relativeBaseUrl = withQuery(relativeBaseUrl, {
8
+ errors: "true",
9
+ error_messages: errorInfo.messages,
10
+ error_urls: errorInfo.urls
11
+ });
12
+ }
13
+ return relativeBaseUrl;
14
+ }
15
+ export function* renderSitemapIndexXmlChunks(sitemaps, resolvers, { version, xsl, credits, minify }, errorInfo) {
16
+ const NL = minify ? "" : "\n";
17
+ const I1 = minify ? "" : " ";
18
+ const I2 = minify ? "" : " ";
19
+ yield '<?xml version="1.0" encoding="UTF-8"?>';
20
+ if (xsl) {
21
+ const relativeBaseUrl = resolveIndexXslHref(resolvers, xsl, errorInfo);
22
+ yield `${NL}<?xml-stylesheet type="text/xsl" href="${escapeValueForXml(relativeBaseUrl)}"?>`;
23
+ }
24
+ yield `${NL}<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">${NL}`;
25
+ for (let i = 0; i < sitemaps.length; i++) {
26
+ const entry = sitemaps[i];
27
+ if (i > 0)
28
+ yield NL;
29
+ yield `${I1}<sitemap>${NL}${I2}<loc>${escapeValueForXml(entry.sitemap)}</loc>${NL}`;
30
+ if (entry.lastmod)
31
+ yield `${I2}<lastmod>${escapeValueForXml(entry.lastmod)}</lastmod>${NL}`;
32
+ yield `${I1}</sitemap>`;
33
+ }
34
+ yield `${NL}</sitemapindex>`;
35
+ if (credits)
36
+ yield `${NL}<!-- XML Sitemap Index generated by @nuxtjs/sitemap v${version} at ${(/* @__PURE__ */ new Date()).toISOString()} -->`;
37
+ }
38
+ export function urlsToIndexXml(sitemaps, resolvers, { version, xsl, credits, minify }, errorInfo) {
39
+ const NL = minify ? "" : "\n";
40
+ const I1 = minify ? "" : " ";
41
+ const I2 = minify ? "" : " ";
42
+ let sitemapXml = "";
43
+ for (const entry of sitemaps) {
44
+ if (sitemapXml)
45
+ sitemapXml += NL;
46
+ sitemapXml += `${I1}<sitemap>${NL}${I2}<loc>${escapeValueForXml(entry.sitemap)}</loc>${NL}`;
47
+ if (entry.lastmod)
48
+ sitemapXml += `${I2}<lastmod>${escapeValueForXml(entry.lastmod)}</lastmod>${NL}`;
49
+ sitemapXml += `${I1}</sitemap>`;
50
+ }
51
+ const xmlParts = ['<?xml version="1.0" encoding="UTF-8"?>'];
52
+ if (xsl) {
53
+ const relativeBaseUrl = resolveIndexXslHref(resolvers, xsl, errorInfo);
54
+ xmlParts.push(`<?xml-stylesheet type="text/xsl" href="${escapeValueForXml(relativeBaseUrl)}"?>`);
55
+ }
56
+ xmlParts.push(
57
+ '<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">',
58
+ sitemapXml,
59
+ "</sitemapindex>"
60
+ );
61
+ if (credits)
62
+ xmlParts.push(`<!-- XML Sitemap Index generated by @nuxtjs/sitemap v${version} at ${(/* @__PURE__ */ new Date()).toISOString()} -->`);
63
+ return xmlParts.join(NL);
64
+ }
65
+ export function urlsToIndexXmlStream(sitemaps, resolvers, config, errorInfo) {
66
+ return createChunkedXmlStream(renderSitemapIndexXmlChunks(sitemaps, resolvers, config, errorInfo));
67
+ }
@@ -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';