@nuxtjs/sitemap 8.3.3 → 8.4.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.
- package/dist/devtools/nuxt.config.ts +4 -1
- package/dist/module.json +1 -1
- package/dist/module.mjs +125 -58
- package/dist/runtime/server/routes/__sitemap__/content-urls.d.ts +9 -0
- package/dist/runtime/server/routes/__sitemap__/content-urls.js +34 -0
- package/dist/runtime/server/sitemap/builder/entries.d.ts +7 -0
- package/dist/runtime/server/sitemap/builder/entries.js +100 -0
- package/dist/runtime/server/sitemap/builder/sitemap.d.ts +1 -7
- package/dist/runtime/server/sitemap/builder/sitemap.js +3 -161
- package/dist/runtime/types.d.ts +1 -1
- package/dist/runtime/utils-pure.d.ts +7 -7
- package/dist/runtime/utils-pure.js +44 -22
- package/package.json +20 -19
- package/dist/runtime/server/routes/__sitemap__/nuxt-content-urls-v3.d.ts +0 -2
- package/dist/runtime/server/routes/__sitemap__/nuxt-content-urls-v3.js +0 -40
|
@@ -1,7 +1,10 @@
|
|
|
1
|
+
import { fileURLToPath } from 'node:url'
|
|
1
2
|
import { resolve } from 'pathe'
|
|
2
3
|
|
|
4
|
+
const currentDir = fileURLToPath(new URL('.', import.meta.url))
|
|
5
|
+
|
|
3
6
|
// Nuxt SEO devtools panel, shipped as a layer (Model C). Components flat-registered
|
|
4
7
|
// so intra-panel references resolve by name.
|
|
5
8
|
export default defineNuxtConfig({
|
|
6
|
-
components: [{ path: resolve(
|
|
9
|
+
components: [{ path: resolve(currentDir, './components'), pathPrefix: false }],
|
|
7
10
|
})
|
package/dist/module.json
CHANGED
package/dist/module.mjs
CHANGED
|
@@ -2,22 +2,25 @@ import { useNuxt, hasNuxtModule, addTypeTemplate, addTemplate, defineNuxtModule,
|
|
|
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, renderNitroTypeAugmentations, useModuleLogger, setupNitroRuntimeCompatibility, getNuxtModuleOptions, resolveNitroPreset,
|
|
5
|
+
import { isNuxtGenerate, renderNitroTypeAugmentations, useModuleLogger, setupNitroRuntimeCompatibility, getNuxtModuleOptions, resolveNitroPreset, resolveContentProvider, setupContentRuntime, createPagesPromise, createNitroPromise } from 'nuxtseo-shared/kit';
|
|
6
6
|
import { serializeFilters } from 'nuxtseo-shared/utils';
|
|
7
7
|
import { dirname, extname } from 'pathe';
|
|
8
8
|
import { readPackageJSON } from 'pkg-types';
|
|
9
|
-
import { withBase, withHttps, withTrailingSlash, withoutLeadingSlash, joinURL, withLeadingSlash
|
|
9
|
+
import { withBase, withHttps, withTrailingSlash, withoutLeadingSlash, joinURL, withLeadingSlash } from 'ufo';
|
|
10
10
|
import { setupDevToolsUI as setupDevToolsUI$1 } from 'nuxtseo-shared/devtools';
|
|
11
11
|
import { readFileSync, statSync } from 'node:fs';
|
|
12
|
-
import { mkdir, writeFile } from 'node:fs/promises';
|
|
12
|
+
import { rm, mkdir, writeFile } from 'node:fs/promises';
|
|
13
13
|
import { join } from 'node:path';
|
|
14
14
|
import { colors } from 'consola/utils';
|
|
15
15
|
import { splitForLocales, createPathFilter } from '../dist/runtime/utils-pure.js';
|
|
16
16
|
import { p as parseHtmlExtractSitemapMeta } from './shared/sitemap.BoMnWHOt.mjs';
|
|
17
17
|
import { normaliseDate } from '../dist/runtime/server/sitemap/urlset/normalise.js';
|
|
18
|
-
import { splitPathForI18nLocales as splitPathForI18nLocales$1, expandCompactLocaleRoute, normalizeLocales, generatePathForI18nPages } from 'nuxtseo-shared/i18n';
|
|
18
|
+
import { mapPathForI18nPages, splitPathForI18nLocales as splitPathForI18nLocales$1, expandCompactLocaleRoute, normalizeLocales, generatePathForI18nPages } from 'nuxtseo-shared/i18n';
|
|
19
19
|
import 'ultrahtml';
|
|
20
20
|
|
|
21
|
+
const COMARK_CONTENT_SOURCE = "@harlan-zw/comark-content:urls";
|
|
22
|
+
const COMARK_CONTENT_SITEMAP_ROUTE = "/__sitemap__/comark-content-urls.json";
|
|
23
|
+
|
|
21
24
|
function setupDevToolsUI(_options, resolve, nuxt = useNuxt()) {
|
|
22
25
|
setupDevToolsUI$1(
|
|
23
26
|
{ route: "/__nuxt-sitemap", name: "sitemap", title: "Sitemap", icon: "carbon:load-balancer-application" },
|
|
@@ -72,6 +75,10 @@ export async function readSourcesFromFilesystem(filename) {
|
|
|
72
75
|
`;
|
|
73
76
|
});
|
|
74
77
|
nuxt.hooks.hook("nitro:init", async (nitro) => {
|
|
78
|
+
await Promise.all([
|
|
79
|
+
rm(join(runtimeAssetsPath, "global-sources.json"), { force: true }),
|
|
80
|
+
rm(join(runtimeAssetsPath, "child-sources.json"), { force: true })
|
|
81
|
+
]);
|
|
75
82
|
nitro.hooks.hook("prerender:generate", async (route) => {
|
|
76
83
|
const html = route.contents;
|
|
77
84
|
if (!route.fileName?.endsWith(".html") || !html || ["/200.html", "/404.html"].includes(route.route))
|
|
@@ -260,6 +267,33 @@ function splitPathForI18nLocales(path, autoI18n) {
|
|
|
260
267
|
return path;
|
|
261
268
|
return splitPathForI18nLocales$1(path, autoI18n);
|
|
262
269
|
}
|
|
270
|
+
function uniquePaths(paths) {
|
|
271
|
+
return [...new Set(paths)];
|
|
272
|
+
}
|
|
273
|
+
function resolveI18nFilterPaths(path, autoI18n) {
|
|
274
|
+
if (typeof path !== "string")
|
|
275
|
+
return [path];
|
|
276
|
+
const mappedPaths = mapPathForI18nPages(path, autoI18n);
|
|
277
|
+
if (mappedPaths === false) {
|
|
278
|
+
if (autoI18n.strategy === "no_prefix")
|
|
279
|
+
return [path];
|
|
280
|
+
const splitPaths = splitPathForI18nLocales$1(path, autoI18n);
|
|
281
|
+
return Array.isArray(splitPaths) ? splitPaths : [splitPaths];
|
|
282
|
+
}
|
|
283
|
+
if (autoI18n.strategy === "prefix" || autoI18n.strategy === "no_prefix")
|
|
284
|
+
return uniquePaths(mappedPaths);
|
|
285
|
+
const defaultLocales = autoI18n.locales.filter((locale) => locale.code === autoI18n.defaultLocale);
|
|
286
|
+
const defaultStrategy = autoI18n.strategy === "prefix_and_default" ? "prefix" : "no_prefix";
|
|
287
|
+
const defaultPaths = mapPathForI18nPages(path, {
|
|
288
|
+
...autoI18n,
|
|
289
|
+
locales: defaultLocales,
|
|
290
|
+
strategy: defaultStrategy
|
|
291
|
+
});
|
|
292
|
+
return uniquePaths([
|
|
293
|
+
...defaultPaths || [path],
|
|
294
|
+
...mappedPaths
|
|
295
|
+
]);
|
|
296
|
+
}
|
|
263
297
|
|
|
264
298
|
async function resolveUrls(urls, ctx) {
|
|
265
299
|
try {
|
|
@@ -428,7 +462,34 @@ function generateExtraRoutesFromNuxtConfig(nuxt = useNuxt()) {
|
|
|
428
462
|
}).map(([k]) => k).filter(filterForValidPage);
|
|
429
463
|
return { routeRules };
|
|
430
464
|
}
|
|
465
|
+
function resolveExcludedAppSources(resolved, authored) {
|
|
466
|
+
if (resolved === true || authored === true)
|
|
467
|
+
return true;
|
|
468
|
+
if (!Array.isArray(authored))
|
|
469
|
+
return resolved;
|
|
470
|
+
const excluded = [...resolved];
|
|
471
|
+
for (const source of authored) {
|
|
472
|
+
if (typeof source === "string" && !excluded.includes(source))
|
|
473
|
+
excluded.push(source);
|
|
474
|
+
}
|
|
475
|
+
return excluded;
|
|
476
|
+
}
|
|
431
477
|
|
|
478
|
+
const IMAGE_TAGS = /* @__PURE__ */ new Set(["image", "img", "nuxtimg", "nuxt-img"]);
|
|
479
|
+
function discoverContentImages(body) {
|
|
480
|
+
const images = [];
|
|
481
|
+
const walk = (nodes) => {
|
|
482
|
+
for (const node of nodes || []) {
|
|
483
|
+
if (!Array.isArray(node) || typeof node[0] !== "string")
|
|
484
|
+
continue;
|
|
485
|
+
if (IMAGE_TAGS.has(node[0]) && node[1]?.src)
|
|
486
|
+
images.push({ loc: node[1].src });
|
|
487
|
+
walk(node.slice(2));
|
|
488
|
+
}
|
|
489
|
+
};
|
|
490
|
+
walk(body?.value ?? body?.nodes);
|
|
491
|
+
return images;
|
|
492
|
+
}
|
|
432
493
|
const module$1 = defineNuxtModule({
|
|
433
494
|
meta: {
|
|
434
495
|
name: "@nuxtjs/sitemap",
|
|
@@ -453,6 +514,10 @@ const module$1 = defineNuxtModule({
|
|
|
453
514
|
version: ">=2",
|
|
454
515
|
optional: true
|
|
455
516
|
},
|
|
517
|
+
"@harlan-zw/comark-content": {
|
|
518
|
+
version: ">=0.1.2",
|
|
519
|
+
optional: true
|
|
520
|
+
},
|
|
456
521
|
"@nuxtjs/robots": {
|
|
457
522
|
version: ">=4",
|
|
458
523
|
optional: true
|
|
@@ -646,11 +711,11 @@ const module$1 = defineNuxtModule({
|
|
|
646
711
|
const hasCustomI18nSitemaps = i18nSitemaps.length > 0;
|
|
647
712
|
if (hasCustomI18nSitemaps) {
|
|
648
713
|
for (const { name: name2, cfg } of i18nSitemaps) {
|
|
714
|
+
const { sitemapName: _sitemapName, _route, _isChunking, _chunkSize, _chunkCount, ...inheritedConfig } = cfg;
|
|
649
715
|
for (const locale of resolvedAutoI18n.locales) {
|
|
650
716
|
newSitemaps[`${locale._sitemap}-${name2}`] = {
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
...cfg.include?.length && { include: cfg.include }
|
|
717
|
+
...inheritedConfig,
|
|
718
|
+
includeAppSources: true
|
|
654
719
|
};
|
|
655
720
|
}
|
|
656
721
|
}
|
|
@@ -743,20 +808,13 @@ const module$1 = defineNuxtModule({
|
|
|
743
808
|
addServerPlugin(resolve("./runtime/server/plugins/stream-transport"));
|
|
744
809
|
}
|
|
745
810
|
const isNuxtContentDocumentDriven = !!nuxt.options.content?.documentDriven || config.strictNuxtContentPaths;
|
|
746
|
-
const
|
|
747
|
-
const isNuxtContentV3 =
|
|
811
|
+
const contentProvider = await resolveContentProvider(nuxt);
|
|
812
|
+
const isNuxtContentV3 = contentProvider._tag === "NuxtContent" && contentProvider.version === 3;
|
|
813
|
+
const isNuxtContentV2 = contentProvider._tag === "NuxtContent" && contentProvider.version === 2;
|
|
814
|
+
const isComarkContent = contentProvider._tag === "Comark";
|
|
748
815
|
const nuxtV3Collections = /* @__PURE__ */ new Set();
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
if (nuxt.options._installedModules.some((m) => m.meta.name === "Content")) {
|
|
752
|
-
logger.warn("You have loaded `@nuxt/content` before `@nuxtjs/sitemap`, this may cause issues with the integration. Please ensure `@nuxtjs/sitemap` is loaded first.");
|
|
753
|
-
}
|
|
754
|
-
config.exclude.push("/__nuxt_content/**");
|
|
755
|
-
const needsCustomAlias = await hasNuxtModuleCompatibility("@nuxt/content", "<3.6.0");
|
|
756
|
-
if (needsCustomAlias) {
|
|
757
|
-
nuxt.options.alias["#sitemap/content-v3-nitro-path"] = resolve(dirname(resolveModule("@nuxt/content")), "runtime/nitro");
|
|
758
|
-
nuxt.options.alias["@nuxt/content/nitro"] = resolve("./runtime/server/content-compat");
|
|
759
|
-
}
|
|
816
|
+
setupContentRuntime(contentProvider, nuxt);
|
|
817
|
+
const registerContentSitemapHook = (options) => {
|
|
760
818
|
nuxt.hooks.hook("content:file:afterParse", (ctx) => {
|
|
761
819
|
try {
|
|
762
820
|
const content = ctx.content;
|
|
@@ -765,7 +823,7 @@ const module$1 = defineNuxtModule({
|
|
|
765
823
|
ctx.content.sitemap = null;
|
|
766
824
|
return;
|
|
767
825
|
}
|
|
768
|
-
if (!ctx.collection.fields || !("sitemap" in ctx.collection.fields)) {
|
|
826
|
+
if (options.requireCollectionField && (!ctx.collection.fields || !("sitemap" in ctx.collection.fields))) {
|
|
769
827
|
ctx.content.sitemap = null;
|
|
770
828
|
return;
|
|
771
829
|
}
|
|
@@ -778,13 +836,8 @@ const module$1 = defineNuxtModule({
|
|
|
778
836
|
return;
|
|
779
837
|
}
|
|
780
838
|
const images = [];
|
|
781
|
-
if (config.discoverImages)
|
|
782
|
-
images.push(
|
|
783
|
-
...content.body?.value?.filter(
|
|
784
|
-
(c) => ["image", "img", "nuxtimg", "nuxt-img"].includes(c[0])
|
|
785
|
-
).filter((c) => c[1]?.src).map((c) => ({ loc: c[1].src })) || []
|
|
786
|
-
);
|
|
787
|
-
}
|
|
839
|
+
if (config.discoverImages)
|
|
840
|
+
images.push(...discoverContentImages(content.body));
|
|
788
841
|
const lastmod = content.seo?.articleModifiedTime || content.updatedAt;
|
|
789
842
|
const defaults = {
|
|
790
843
|
loc: content.path
|
|
@@ -798,6 +851,8 @@ const module$1 = defineNuxtModule({
|
|
|
798
851
|
logger.warn(`Failed to process sitemap data for content file (collection: ${ctx.collection?.name}, path: ${ctx.content?.path}), skipping.`, e);
|
|
799
852
|
}
|
|
800
853
|
});
|
|
854
|
+
};
|
|
855
|
+
const addContentCallbackVirtuals = () => {
|
|
801
856
|
nuxt.hook("nitro:config", (nitroConfig) => {
|
|
802
857
|
const filterEntries = [];
|
|
803
858
|
if (globalThis.__sitemapCollectionFilters) {
|
|
@@ -815,9 +870,22 @@ ${filterEntries.join("\n")}`;
|
|
|
815
870
|
nitroConfig.virtual["#sitemap/content-on-url"] = `export const onUrlFns = new Map()
|
|
816
871
|
${onUrlEntries.join("\n")}`;
|
|
817
872
|
});
|
|
873
|
+
};
|
|
874
|
+
if (isNuxtContentV3) {
|
|
875
|
+
if (nuxt.options._installedModules.some((m) => m.meta.name === "Content")) {
|
|
876
|
+
logger.warn("You have loaded `@nuxt/content` before `@nuxtjs/sitemap`, this may cause issues with the integration. Please ensure `@nuxtjs/sitemap` is loaded first.");
|
|
877
|
+
}
|
|
878
|
+
config.exclude.push("/__nuxt_content/**");
|
|
879
|
+
const needsCustomAlias = await hasNuxtModuleCompatibility("@nuxt/content", "<3.6.0");
|
|
880
|
+
if (needsCustomAlias) {
|
|
881
|
+
nuxt.options.alias["#sitemap/content-v3-nitro-path"] = resolve(dirname(resolveModule("@nuxt/content")), "runtime/nitro");
|
|
882
|
+
nuxt.options.alias["@nuxt/content/nitro"] = resolve("./runtime/server/content-compat");
|
|
883
|
+
}
|
|
884
|
+
registerContentSitemapHook({ requireCollectionField: true });
|
|
885
|
+
addContentCallbackVirtuals();
|
|
818
886
|
addServerHandler({
|
|
819
887
|
route: "/__sitemap__/nuxt-content-urls.json",
|
|
820
|
-
handler: resolve("./runtime/server/routes/__sitemap__/
|
|
888
|
+
handler: resolve("./runtime/server/routes/__sitemap__/content-urls")
|
|
821
889
|
});
|
|
822
890
|
if (config.strictNuxtContentPaths) {
|
|
823
891
|
logger.warn("You have set `strictNuxtContentPaths: true` but are using @nuxt/content v3. This is not required, please remove it.");
|
|
@@ -830,6 +898,24 @@ ${onUrlEntries.join("\n")}`;
|
|
|
830
898
|
},
|
|
831
899
|
fetch: "/__sitemap__/nuxt-content-urls.json"
|
|
832
900
|
});
|
|
901
|
+
} else if (isComarkContent) {
|
|
902
|
+
registerContentSitemapHook({ requireCollectionField: false });
|
|
903
|
+
addContentCallbackVirtuals();
|
|
904
|
+
addServerHandler({
|
|
905
|
+
route: COMARK_CONTENT_SITEMAP_ROUTE,
|
|
906
|
+
handler: resolve("./runtime/server/routes/__sitemap__/content-urls")
|
|
907
|
+
});
|
|
908
|
+
if (config.strictNuxtContentPaths) {
|
|
909
|
+
logger.warn("You have set `strictNuxtContentPaths: true` but are using comark-content. This is not required, please remove it.");
|
|
910
|
+
}
|
|
911
|
+
appGlobalSources.push({
|
|
912
|
+
context: {
|
|
913
|
+
name: COMARK_CONTENT_SOURCE,
|
|
914
|
+
description: "Generated from your markdown files.",
|
|
915
|
+
tips: nuxtV3Collections.size ? [`Parsing the following collections: ${Array.from(nuxtV3Collections).join(", ")}`] : ["No collections found. Set `sitemap: false` on a collection to keep it out."]
|
|
916
|
+
},
|
|
917
|
+
fetch: COMARK_CONTENT_SITEMAP_ROUTE
|
|
918
|
+
});
|
|
833
919
|
} else if (isNuxtContentV2) {
|
|
834
920
|
addServerPlugin(resolve("./runtime/server/plugins/nuxt-content-v2"));
|
|
835
921
|
addServerHandler({
|
|
@@ -966,38 +1052,15 @@ ${onUrlEntries.join("\n")}`;
|
|
|
966
1052
|
};
|
|
967
1053
|
}
|
|
968
1054
|
if (resolvedAutoI18n && usingI18nPages && !hasDisabledAutoI18n) {
|
|
969
|
-
const
|
|
1055
|
+
const i18n = resolvedAutoI18n;
|
|
970
1056
|
for (const sitemapName in sitemaps) {
|
|
971
|
-
let mapToI18nPages = function(path) {
|
|
972
|
-
if (typeof path !== "string")
|
|
973
|
-
return [path];
|
|
974
|
-
const withoutSlashes = withoutTrailingSlash(withoutLeadingSlash(path)).replace("/index", "");
|
|
975
|
-
if (pages && withoutSlashes in pages) {
|
|
976
|
-
const pageLocales = pages[withoutSlashes];
|
|
977
|
-
if (pageLocales) {
|
|
978
|
-
return Object.keys(pageLocales).map((localeCode) => withLeadingSlash(generatePathForI18nPages({
|
|
979
|
-
localeCode,
|
|
980
|
-
pageLocales: pageLocales[localeCode],
|
|
981
|
-
nuxtI18nConfig,
|
|
982
|
-
normalisedLocales
|
|
983
|
-
})));
|
|
984
|
-
}
|
|
985
|
-
}
|
|
986
|
-
let match = [path];
|
|
987
|
-
Object.values(pages).forEach((pageLocales) => {
|
|
988
|
-
if (pageLocales && nuxtI18nConfig.defaultLocale in pageLocales && pageLocales[nuxtI18nConfig.defaultLocale] === path)
|
|
989
|
-
match = Object.keys(pageLocales).map((localeCode) => withLeadingSlash(generatePathForI18nPages({ localeCode, pageLocales: pageLocales[localeCode], nuxtI18nConfig, normalisedLocales })));
|
|
990
|
-
});
|
|
991
|
-
return match;
|
|
992
|
-
};
|
|
993
1057
|
if (["index", "chunks"].includes(sitemapName))
|
|
994
1058
|
continue;
|
|
995
1059
|
const sitemap = sitemaps[sitemapName];
|
|
996
|
-
sitemap.include = (sitemap.include || []).flatMap((path) =>
|
|
997
|
-
sitemap.exclude = (sitemap.exclude || []).flatMap((path) =>
|
|
1060
|
+
sitemap.include = (sitemap.include || []).flatMap((path) => resolveI18nFilterPaths(path, i18n));
|
|
1061
|
+
sitemap.exclude = (sitemap.exclude || []).flatMap((path) => resolveI18nFilterPaths(path, i18n));
|
|
998
1062
|
}
|
|
999
|
-
}
|
|
1000
|
-
if (resolvedAutoI18n && resolvedAutoI18n.locales && resolvedAutoI18n.strategy !== "no_prefix") {
|
|
1063
|
+
} else if (resolvedAutoI18n && resolvedAutoI18n.locales && resolvedAutoI18n.strategy !== "no_prefix") {
|
|
1001
1064
|
const i18n = resolvedAutoI18n;
|
|
1002
1065
|
for (const sitemapName in sitemaps) {
|
|
1003
1066
|
if (["index", "chunks"].includes(sitemapName))
|
|
@@ -1093,6 +1156,10 @@ ${onUrlEntries.join("\n")}`;
|
|
|
1093
1156
|
return r.contentType?.includes("text/html");
|
|
1094
1157
|
};
|
|
1095
1158
|
const generateGlobalSources = async () => {
|
|
1159
|
+
const excludedAppSources = resolveExcludedAppSources(
|
|
1160
|
+
config.excludeAppSources,
|
|
1161
|
+
nuxt.options.sitemap?.excludeAppSources
|
|
1162
|
+
);
|
|
1096
1163
|
const { routeRules: routeRules2 } = generateExtraRoutesFromNuxtConfig();
|
|
1097
1164
|
const nitro = await nitroPromise;
|
|
1098
1165
|
const prerenderedRoutes2 = nitro._prerenderedRoutes || [];
|
|
@@ -1169,7 +1236,7 @@ ${onUrlEntries.join("\n")}`;
|
|
|
1169
1236
|
s.sourceType = "user";
|
|
1170
1237
|
return s;
|
|
1171
1238
|
}),
|
|
1172
|
-
...(
|
|
1239
|
+
...(excludedAppSources === true ? [] : [
|
|
1173
1240
|
...appGlobalSources,
|
|
1174
1241
|
{
|
|
1175
1242
|
context: {
|
|
@@ -1201,7 +1268,7 @@ ${onUrlEntries.join("\n")}`;
|
|
|
1201
1268
|
},
|
|
1202
1269
|
urls: prerenderUrlsFinal
|
|
1203
1270
|
}
|
|
1204
|
-
]).filter((s) => !
|
|
1271
|
+
]).filter((s) => !excludedAppSources.includes(s.context.name) && (!!s.urls?.length || !!s.fetch)).map((s) => {
|
|
1205
1272
|
s.sourceType = "app";
|
|
1206
1273
|
return s;
|
|
1207
1274
|
})
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* URLs from the installed content module's page collections.
|
|
3
|
+
*
|
|
4
|
+
* One route for every provider. `#nuxtseo/content` normalizes the manifest shape
|
|
5
|
+
* and the query builder; the `sitemap` field on each entry is written by the
|
|
6
|
+
* module's `content:file:afterParse` hook.
|
|
7
|
+
*/
|
|
8
|
+
declare const _default: any;
|
|
9
|
+
export default _default;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import * as contentRuntime from "#nuxtseo/content";
|
|
2
|
+
import { defineEventHandler } from "#nuxtseo/h3";
|
|
3
|
+
import { filters } from "#sitemap/content-filters";
|
|
4
|
+
import { onUrlFns } from "#sitemap/content-on-url";
|
|
5
|
+
const { listPageCollections, provider, queryPages } = contentRuntime;
|
|
6
|
+
export default defineEventHandler(async (e) => {
|
|
7
|
+
const collections = (await listPageCollections(e)).filter((collection) => collection.inSitemap && collection.hasField("sitemap")).map((collection) => collection.name);
|
|
8
|
+
const results = await Promise.all(collections.map(async (collection) => {
|
|
9
|
+
const needsAllFields = filters?.has(collection) || onUrlFns?.has(collection);
|
|
10
|
+
const query = queryPages(e, collection).where("path", "IS NOT NULL").where("sitemap", "IS NOT NULL");
|
|
11
|
+
if (!needsAllFields)
|
|
12
|
+
query.select("path", "sitemap");
|
|
13
|
+
try {
|
|
14
|
+
const entries = await query.all();
|
|
15
|
+
const filter = filters?.get(collection);
|
|
16
|
+
return { collection, entries: filter ? entries.filter(filter) : entries };
|
|
17
|
+
} catch (err) {
|
|
18
|
+
const hint = provider === "nuxt-content-v3" ? " On serverless the content DB is restored from a prerendered sql_dump.txt that isn't readable inside the function (nuxt/content#3805). Fix: prerender the sitemap so content URLs resolve at build, or configure a runtime database (D1/Turso/Postgres)." : "";
|
|
19
|
+
console.error(`[@nuxtjs/sitemap] Couldn't query content collection "${collection}" for the sitemap, so its URLs will be missing.${hint}`, err);
|
|
20
|
+
return { collection, entries: [] };
|
|
21
|
+
}
|
|
22
|
+
}));
|
|
23
|
+
return results.flatMap(({ collection, entries }) => {
|
|
24
|
+
const onUrl = onUrlFns?.get(collection);
|
|
25
|
+
return entries.filter((entry) => entry.sitemap !== false && entry.path && !entry.path.endsWith(".navigation")).map((entry) => {
|
|
26
|
+
const url = {
|
|
27
|
+
loc: entry.path,
|
|
28
|
+
...typeof entry.sitemap === "object" && entry.sitemap ? entry.sitemap : {}
|
|
29
|
+
};
|
|
30
|
+
onUrl?.(url, entry, collection);
|
|
31
|
+
return url;
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
});
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { AutoI18nConfig, ModuleRuntimeConfig, NitroUrlResolvers, ResolvedSitemapUrl, SitemapDefinition, SitemapUrlInput } from '../../../types.js';
|
|
2
|
+
export interface NormalizedI18n extends ResolvedSitemapUrl {
|
|
3
|
+
_pathWithoutPrefix: string;
|
|
4
|
+
_locale: AutoI18nConfig['locales'][number];
|
|
5
|
+
_index?: number;
|
|
6
|
+
}
|
|
7
|
+
export declare function resolveSitemapEntries(sitemap: SitemapDefinition, urls: SitemapUrlInput[], runtimeConfig: Pick<ModuleRuntimeConfig, 'autoI18n' | 'isI18nMapped'>, resolvers?: NitroUrlResolvers, baseURL?: string): ResolvedSitemapUrl[];
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { createPathFilter, resolveI18nRouteEntries, splitForLocales } from "../../../utils-pure.js";
|
|
2
|
+
import { preNormalizeEntry } from "../urlset/normalise.js";
|
|
3
|
+
export function resolveSitemapEntries(sitemap, urls, runtimeConfig, resolvers, baseURL) {
|
|
4
|
+
const {
|
|
5
|
+
autoI18n,
|
|
6
|
+
isI18nMapped
|
|
7
|
+
} = runtimeConfig;
|
|
8
|
+
const hasFilters = !!sitemap.include?.length || !!sitemap.exclude?.length;
|
|
9
|
+
const filterPath = hasFilters ? createPathFilter({
|
|
10
|
+
include: sitemap.include,
|
|
11
|
+
exclude: sitemap.exclude
|
|
12
|
+
}, baseURL || "/") : void 0;
|
|
13
|
+
const _urls = [];
|
|
14
|
+
for (const _e of urls) {
|
|
15
|
+
const e = preNormalizeEntry(_e, resolvers);
|
|
16
|
+
if (e.loc && (!filterPath || filterPath(e.loc, e._path?.pathname)))
|
|
17
|
+
_urls.push(e);
|
|
18
|
+
}
|
|
19
|
+
const withoutPrefixPaths = {};
|
|
20
|
+
if (autoI18n && autoI18n.strategy !== "no_prefix") {
|
|
21
|
+
const localeCodes = new Set(autoI18n.locales.map((l) => l.code));
|
|
22
|
+
const localeByCode = new Map(autoI18n.locales.map((l) => [l.code, l]));
|
|
23
|
+
const defaultLocale = autoI18n.defaultLocale;
|
|
24
|
+
const hasDifferentDomains = !!autoI18n.differentDomains;
|
|
25
|
+
const validI18nUrlsForTransform = [];
|
|
26
|
+
for (let i = 0; i < _urls.length; i++) {
|
|
27
|
+
const _e = _urls[i];
|
|
28
|
+
if (_e._abs)
|
|
29
|
+
continue;
|
|
30
|
+
const split = splitForLocales(_e._relativeLoc, localeCodes);
|
|
31
|
+
let localeCode = split[0];
|
|
32
|
+
const pathWithoutPrefix = split[1];
|
|
33
|
+
if (!localeCode)
|
|
34
|
+
localeCode = defaultLocale;
|
|
35
|
+
const e = _e;
|
|
36
|
+
e._pathWithoutPrefix = pathWithoutPrefix;
|
|
37
|
+
const locale = localeByCode.get(localeCode);
|
|
38
|
+
if (!locale)
|
|
39
|
+
continue;
|
|
40
|
+
e._locale = locale;
|
|
41
|
+
e._index = i;
|
|
42
|
+
e._key = `${e._sitemap || ""}${e._path?.pathname || "/"}${e._path?.search || ""}`;
|
|
43
|
+
withoutPrefixPaths[pathWithoutPrefix] = withoutPrefixPaths[pathWithoutPrefix] || [];
|
|
44
|
+
if (!withoutPrefixPaths[pathWithoutPrefix].some((e2) => e2._locale.code === locale.code))
|
|
45
|
+
withoutPrefixPaths[pathWithoutPrefix].push(e);
|
|
46
|
+
validI18nUrlsForTransform.push(e);
|
|
47
|
+
}
|
|
48
|
+
for (const e of validI18nUrlsForTransform) {
|
|
49
|
+
if (!e._i18nTransform && !e.alternatives?.length) {
|
|
50
|
+
const alternatives = [];
|
|
51
|
+
for (const u of withoutPrefixPaths[e._pathWithoutPrefix] || []) {
|
|
52
|
+
if (u._locale.code === defaultLocale) {
|
|
53
|
+
alternatives.push({
|
|
54
|
+
href: u.loc,
|
|
55
|
+
hreflang: "x-default"
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
alternatives.push({
|
|
59
|
+
href: u.loc,
|
|
60
|
+
hreflang: u._locale._hreflang || defaultLocale
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
if (alternatives.length)
|
|
64
|
+
e.alternatives = alternatives;
|
|
65
|
+
} else if (e._i18nTransform) {
|
|
66
|
+
delete e._i18nTransform;
|
|
67
|
+
const routeEntries = resolveI18nRouteEntries(e._relativeLoc, autoI18n, (href) => !filterPath || filterPath(href));
|
|
68
|
+
if (hasDifferentDomains) {
|
|
69
|
+
e.alternatives = routeEntries[0]?.alternatives;
|
|
70
|
+
} else {
|
|
71
|
+
for (const { alternatives, locale: l, loc } of routeEntries) {
|
|
72
|
+
const _sitemap = isI18nMapped ? l._sitemap : void 0;
|
|
73
|
+
const { _index: _, ...rest } = e;
|
|
74
|
+
const newEntry = preNormalizeEntry({
|
|
75
|
+
_sitemap,
|
|
76
|
+
...rest,
|
|
77
|
+
_key: `${_sitemap || ""}${loc || "/"}`,
|
|
78
|
+
_locale: l,
|
|
79
|
+
loc,
|
|
80
|
+
alternatives
|
|
81
|
+
}, resolvers);
|
|
82
|
+
if (e._locale.code === newEntry._locale.code) {
|
|
83
|
+
_urls[e._index] = newEntry;
|
|
84
|
+
e._index = void 0;
|
|
85
|
+
} else {
|
|
86
|
+
_urls.push(newEntry);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
if (isI18nMapped) {
|
|
92
|
+
e._sitemap = e._sitemap || e._locale._sitemap;
|
|
93
|
+
e._key = `${e._sitemap || ""}${e.loc || "/"}${e._path?.search || ""}`;
|
|
94
|
+
}
|
|
95
|
+
if (e._index)
|
|
96
|
+
_urls[e._index] = e;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return _urls;
|
|
100
|
+
}
|
|
@@ -1,11 +1,5 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { ModuleRuntimeConfig, NitroUrlResolvers, ResolvedSitemapUrl, SitemapDefinition } from '../../../types.js';
|
|
2
2
|
type NitroApp = ReturnType<typeof import('#nuxtseo/nitro').useNitroApp>;
|
|
3
|
-
export interface NormalizedI18n extends ResolvedSitemapUrl {
|
|
4
|
-
_pathWithoutPrefix: string;
|
|
5
|
-
_locale: AutoI18nConfig['locales'][number];
|
|
6
|
-
_index?: number;
|
|
7
|
-
}
|
|
8
|
-
export declare function resolveSitemapEntries(sitemap: SitemapDefinition, urls: SitemapUrlInput[], runtimeConfig: Pick<ModuleRuntimeConfig, 'autoI18n' | 'isI18nMapped'>, resolvers?: NitroUrlResolvers, baseURL?: string): ResolvedSitemapUrl[];
|
|
9
3
|
export interface ResolvedSitemapUrlsResult {
|
|
10
4
|
urls: ResolvedSitemapUrl[];
|
|
11
5
|
failedSources: Array<{
|
|
@@ -1,172 +1,14 @@
|
|
|
1
1
|
import { resolveSitePath } from "nuxt-site-config/urls";
|
|
2
|
-
import {
|
|
2
|
+
import { withHttps } from "ufo";
|
|
3
3
|
import { getHeader } from "#nuxtseo/h3";
|
|
4
4
|
import { defineCachedFunction, useRuntimeConfig } from "#nuxtseo/nitro";
|
|
5
5
|
import staticConfig from "#sitemap-virtual/static-config.mjs";
|
|
6
|
-
import {
|
|
7
|
-
import { preNormalizeEntry } from "../urlset/normalise.js";
|
|
6
|
+
import { logger, resolveI18nSitemapLocaleKey } from "../../../utils-pure.js";
|
|
8
7
|
import { sortInPlace } from "../urlset/sort.js";
|
|
9
8
|
import { childSitemapSources, globalSitemapSources, resolveSitemapSources } from "../urlset/sources.js";
|
|
10
9
|
import { parseChunkInfo, sliceUrlsForChunk } from "../utils/chunk.js";
|
|
10
|
+
import { resolveSitemapEntries } from "./entries.js";
|
|
11
11
|
const SERVER_CACHE_MAX_AGE = staticConfig.cacheMaxAgeSeconds || 60 * 10;
|
|
12
|
-
export function resolveSitemapEntries(sitemap, urls, runtimeConfig, resolvers, baseURL) {
|
|
13
|
-
const {
|
|
14
|
-
autoI18n,
|
|
15
|
-
isI18nMapped
|
|
16
|
-
} = runtimeConfig;
|
|
17
|
-
const hasFilters = !!sitemap.include?.length || !!sitemap.exclude?.length;
|
|
18
|
-
const filterPath = hasFilters ? createPathFilter({
|
|
19
|
-
include: sitemap.include,
|
|
20
|
-
exclude: sitemap.exclude
|
|
21
|
-
}, baseURL || "/") : void 0;
|
|
22
|
-
const _urls = [];
|
|
23
|
-
for (const _e of urls) {
|
|
24
|
-
const e = preNormalizeEntry(_e, resolvers);
|
|
25
|
-
if (e.loc && (!filterPath || filterPath(e.loc, e._path?.pathname)))
|
|
26
|
-
_urls.push(e);
|
|
27
|
-
}
|
|
28
|
-
const withoutPrefixPaths = {};
|
|
29
|
-
if (autoI18n && autoI18n.strategy !== "no_prefix") {
|
|
30
|
-
const localeCodes = new Set(autoI18n.locales.map((l) => l.code));
|
|
31
|
-
const localeByCode = new Map(autoI18n.locales.map((l) => [l.code, l]));
|
|
32
|
-
const isPrefixStrategy = autoI18n.strategy === "prefix";
|
|
33
|
-
const isPrefixExceptOrAndDefault = autoI18n.strategy === "prefix_and_default" || autoI18n.strategy === "prefix_except_default";
|
|
34
|
-
const xDefaultAndLocales = [{ code: "x-default", _hreflang: "x-default" }, ...autoI18n.locales];
|
|
35
|
-
const defaultLocale = autoI18n.defaultLocale;
|
|
36
|
-
const hasPages = !!autoI18n.pages;
|
|
37
|
-
const sortedPageKeys = hasPages ? Object.keys(autoI18n.pages).sort((a, b) => b.length - a.length) : void 0;
|
|
38
|
-
const hasDifferentDomains = !!autoI18n.differentDomains;
|
|
39
|
-
const validI18nUrlsForTransform = [];
|
|
40
|
-
for (let i = 0; i < _urls.length; i++) {
|
|
41
|
-
const _e = _urls[i];
|
|
42
|
-
if (_e._abs)
|
|
43
|
-
continue;
|
|
44
|
-
const split = splitForLocales(_e._relativeLoc, localeCodes);
|
|
45
|
-
let localeCode = split[0];
|
|
46
|
-
const pathWithoutPrefix = split[1];
|
|
47
|
-
if (!localeCode)
|
|
48
|
-
localeCode = defaultLocale;
|
|
49
|
-
const e = _e;
|
|
50
|
-
e._pathWithoutPrefix = pathWithoutPrefix;
|
|
51
|
-
const locale = localeByCode.get(localeCode);
|
|
52
|
-
if (!locale)
|
|
53
|
-
continue;
|
|
54
|
-
e._locale = locale;
|
|
55
|
-
e._index = i;
|
|
56
|
-
e._key = `${e._sitemap || ""}${e._path?.pathname || "/"}${e._path?.search || ""}`;
|
|
57
|
-
withoutPrefixPaths[pathWithoutPrefix] = withoutPrefixPaths[pathWithoutPrefix] || [];
|
|
58
|
-
if (!withoutPrefixPaths[pathWithoutPrefix].some((e2) => e2._locale.code === locale.code))
|
|
59
|
-
withoutPrefixPaths[pathWithoutPrefix].push(e);
|
|
60
|
-
validI18nUrlsForTransform.push(e);
|
|
61
|
-
}
|
|
62
|
-
for (const e of validI18nUrlsForTransform) {
|
|
63
|
-
if (!e._i18nTransform && !e.alternatives?.length) {
|
|
64
|
-
const alternatives = [];
|
|
65
|
-
for (const u of withoutPrefixPaths[e._pathWithoutPrefix] || []) {
|
|
66
|
-
if (u._locale.code === defaultLocale) {
|
|
67
|
-
alternatives.push({
|
|
68
|
-
href: u.loc,
|
|
69
|
-
hreflang: "x-default"
|
|
70
|
-
});
|
|
71
|
-
}
|
|
72
|
-
alternatives.push({
|
|
73
|
-
href: u.loc,
|
|
74
|
-
hreflang: u._locale._hreflang || defaultLocale
|
|
75
|
-
});
|
|
76
|
-
}
|
|
77
|
-
if (alternatives.length)
|
|
78
|
-
e.alternatives = alternatives;
|
|
79
|
-
} else if (e._i18nTransform) {
|
|
80
|
-
delete e._i18nTransform;
|
|
81
|
-
if (hasDifferentDomains) {
|
|
82
|
-
const defLocale = localeByCode.get(defaultLocale);
|
|
83
|
-
e.alternatives = [
|
|
84
|
-
{
|
|
85
|
-
...defLocale,
|
|
86
|
-
code: "x-default"
|
|
87
|
-
},
|
|
88
|
-
...autoI18n.locales.filter((l) => !!l.domain)
|
|
89
|
-
].map((locale) => {
|
|
90
|
-
return {
|
|
91
|
-
hreflang: locale._hreflang,
|
|
92
|
-
href: joinURL(withHttps(locale.domain), e._pathWithoutPrefix)
|
|
93
|
-
};
|
|
94
|
-
});
|
|
95
|
-
} else {
|
|
96
|
-
const pageMatch = hasPages ? findPageMapping(e._pathWithoutPrefix, autoI18n.pages, sortedPageKeys) : null;
|
|
97
|
-
const pathSearch = e._path?.search || "";
|
|
98
|
-
const pathWithoutPrefix = e._pathWithoutPrefix;
|
|
99
|
-
for (const l of autoI18n.locales) {
|
|
100
|
-
let loc = pathWithoutPrefix;
|
|
101
|
-
if (pageMatch && pageMatch.mappings[l.code] !== void 0) {
|
|
102
|
-
const customPath = pageMatch.mappings[l.code];
|
|
103
|
-
if (customPath === false)
|
|
104
|
-
continue;
|
|
105
|
-
if (typeof customPath === "string") {
|
|
106
|
-
loc = customPath[0] === "/" ? customPath : `/${customPath}`;
|
|
107
|
-
loc = applyDynamicParams(loc, pageMatch.paramSegments);
|
|
108
|
-
if (isPrefixStrategy || isPrefixExceptOrAndDefault && l.code !== defaultLocale)
|
|
109
|
-
loc = joinURL(`/${l.code}`, loc);
|
|
110
|
-
}
|
|
111
|
-
} else if (!hasDifferentDomains && !(isPrefixExceptOrAndDefault && l.code === defaultLocale)) {
|
|
112
|
-
loc = joinURL(`/${l.code}`, pathWithoutPrefix);
|
|
113
|
-
}
|
|
114
|
-
const _sitemap = isI18nMapped ? l._sitemap : void 0;
|
|
115
|
-
const alternatives = [];
|
|
116
|
-
for (const locale of xDefaultAndLocales) {
|
|
117
|
-
const code = locale.code === "x-default" ? defaultLocale : locale.code;
|
|
118
|
-
const isDefault = locale.code === "x-default" || locale.code === defaultLocale;
|
|
119
|
-
let href = pathWithoutPrefix;
|
|
120
|
-
if (pageMatch && pageMatch.mappings[code] !== void 0) {
|
|
121
|
-
const customPath = pageMatch.mappings[code];
|
|
122
|
-
if (customPath === false)
|
|
123
|
-
continue;
|
|
124
|
-
if (typeof customPath === "string") {
|
|
125
|
-
href = customPath[0] === "/" ? customPath : `/${customPath}`;
|
|
126
|
-
href = applyDynamicParams(href, pageMatch.paramSegments);
|
|
127
|
-
if (isPrefixStrategy || isPrefixExceptOrAndDefault && !isDefault)
|
|
128
|
-
href = joinURL("/", code, href);
|
|
129
|
-
}
|
|
130
|
-
} else if (isPrefixStrategy) {
|
|
131
|
-
href = joinURL("/", code, pathWithoutPrefix);
|
|
132
|
-
} else if (isPrefixExceptOrAndDefault && !isDefault) {
|
|
133
|
-
href = joinURL("/", code, pathWithoutPrefix);
|
|
134
|
-
}
|
|
135
|
-
if (filterPath && !filterPath(href))
|
|
136
|
-
continue;
|
|
137
|
-
alternatives.push({
|
|
138
|
-
hreflang: locale._hreflang,
|
|
139
|
-
href
|
|
140
|
-
});
|
|
141
|
-
}
|
|
142
|
-
const { _index: _, ...rest } = e;
|
|
143
|
-
const newEntry = preNormalizeEntry({
|
|
144
|
-
_sitemap,
|
|
145
|
-
...rest,
|
|
146
|
-
_key: `${_sitemap || ""}${loc || "/"}${pathSearch}`,
|
|
147
|
-
_locale: l,
|
|
148
|
-
loc,
|
|
149
|
-
alternatives
|
|
150
|
-
}, resolvers);
|
|
151
|
-
if (e._locale.code === newEntry._locale.code) {
|
|
152
|
-
_urls[e._index] = newEntry;
|
|
153
|
-
e._index = void 0;
|
|
154
|
-
} else {
|
|
155
|
-
_urls.push(newEntry);
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
if (isI18nMapped) {
|
|
161
|
-
e._sitemap = e._sitemap || e._locale._sitemap;
|
|
162
|
-
e._key = `${e._sitemap || ""}${e.loc || "/"}${e._path?.search || ""}`;
|
|
163
|
-
}
|
|
164
|
-
if (e._index)
|
|
165
|
-
_urls[e._index] = e;
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
return _urls;
|
|
169
|
-
}
|
|
170
12
|
export async function buildResolvedSitemapUrls(effectiveSitemap, matchName, isChunked, resolvers, runtimeConfig, nitro) {
|
|
171
13
|
const { sitemaps, autoI18n, isI18nMapped, isMultiSitemap, sortEntries } = runtimeConfig;
|
|
172
14
|
let sourcesInput = effectiveSitemap.includeAppSources ? [...await globalSitemapSources(), ...await childSitemapSources(effectiveSitemap)] : await childSitemapSources(effectiveSitemap);
|
package/dist/runtime/types.d.ts
CHANGED
|
@@ -204,7 +204,7 @@ export interface SitemapSourceResolved extends Omit<SitemapSourceBase, 'urls'> {
|
|
|
204
204
|
message: string;
|
|
205
205
|
}[];
|
|
206
206
|
}
|
|
207
|
-
export type AppSourceContext = 'nuxt:pages' | 'nuxt:prerender' | 'nuxt:route-rules' | '@nuxtjs/i18n:pages' | 'nuxt-i18n-micro:pages' | '@nuxt/content@v2:urls' | '@nuxt/content@v3:urls';
|
|
207
|
+
export type AppSourceContext = 'nuxt:pages' | 'nuxt:prerender' | 'nuxt:route-rules' | '@nuxtjs/i18n:pages' | 'nuxt-i18n-micro:pages' | '@nuxt/content@v2:urls' | '@nuxt/content@v3:urls' | '@harlan-zw/comark-content:urls';
|
|
208
208
|
export type SitemapSourceInput = string | [string, FetchOptions] | SitemapSourceBase | SitemapSourceResolved;
|
|
209
209
|
interface LocaleObject extends Record<string, any> {
|
|
210
210
|
code: string;
|
|
@@ -1,9 +1,15 @@
|
|
|
1
|
-
import type { FilterInput } from './types.js';
|
|
1
|
+
import type { AlternativeEntry, AutoI18nConfig, FilterInput } from './types.js';
|
|
2
2
|
export { createFilter, type CreateFilterOptions } from 'nuxtseo-shared/utils';
|
|
3
3
|
export declare const logger: import("consola").ConsolaInstance;
|
|
4
4
|
export declare function xmlEscape(value: string | number | boolean | Date): string;
|
|
5
5
|
export declare function mergeOnKey<T, K extends keyof T>(arr: T[], key: K, onMerge?: (key: T[K]) => void): T[];
|
|
6
6
|
export declare function splitForLocales(path: string, locales: readonly string[] | Set<string>): [string | null, string];
|
|
7
|
+
export interface ResolvedI18nRouteEntry {
|
|
8
|
+
locale: AutoI18nConfig['locales'][number];
|
|
9
|
+
loc: string;
|
|
10
|
+
alternatives: AlternativeEntry[];
|
|
11
|
+
}
|
|
12
|
+
export declare function resolveI18nRouteEntries(route: string, i18n: AutoI18nConfig, includeHref?: (href: string) => boolean): ResolvedI18nRouteEntry[];
|
|
7
13
|
/**
|
|
8
14
|
* Resolve which locale a multi-sitemap name belongs to.
|
|
9
15
|
*
|
|
@@ -22,9 +28,3 @@ export declare function createPathFilter(options?: {
|
|
|
22
28
|
include?: (FilterInput | string | RegExp)[];
|
|
23
29
|
exclude?: (FilterInput | string | RegExp)[];
|
|
24
30
|
}, baseURL?: string): (loc: string, pathname?: string) => boolean;
|
|
25
|
-
export interface PageMatch {
|
|
26
|
-
mappings: Record<string, string | false>;
|
|
27
|
-
paramSegments: string[];
|
|
28
|
-
}
|
|
29
|
-
export declare function findPageMapping(pathWithoutPrefix: string, pages: Record<string, Record<string, string | false>>, sortedKeys?: string[]): PageMatch | null;
|
|
30
|
-
export declare function applyDynamicParams(customPath: string, paramSegments: string[]): string;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { createDefu } from "defu";
|
|
2
|
+
import { computeLocaleAlternates, resolveLocaleFromRoute } from "nuxtseo-shared/i18n-runtime";
|
|
2
3
|
import { createFilter, createModuleLogger } from "nuxtseo-shared/utils";
|
|
3
|
-
import { parseURL, withoutBase } from "ufo";
|
|
4
|
+
import { joinURL, parseURL, withHttps, withLeadingSlash, withoutBase } from "ufo";
|
|
4
5
|
export { createFilter } from "nuxtseo-shared/utils";
|
|
5
6
|
export const logger = createModuleLogger("@nuxt/sitemap");
|
|
6
7
|
const XML_ENTITIES = {
|
|
@@ -51,6 +52,47 @@ export function splitForLocales(path, locales) {
|
|
|
51
52
|
}
|
|
52
53
|
return [null, path];
|
|
53
54
|
}
|
|
55
|
+
function toRuntimeI18nConfig(i18n) {
|
|
56
|
+
return {
|
|
57
|
+
...i18n,
|
|
58
|
+
// Sitemap transforms keep the unprefixed default URL alongside Nuxt's prefixed route.
|
|
59
|
+
strategy: i18n.strategy === "prefix_and_default" ? "prefix_except_default" : i18n.strategy,
|
|
60
|
+
pages: i18n.pages && Object.fromEntries(
|
|
61
|
+
Object.entries(i18n.pages).map(([pageName, pageLocales]) => [
|
|
62
|
+
pageName,
|
|
63
|
+
Object.fromEntries(i18n.locales.map((locale) => {
|
|
64
|
+
const configuredPath = pageLocales[locale.code];
|
|
65
|
+
return [locale.code, configuredPath === void 0 ? withLeadingSlash(pageName) : configuredPath];
|
|
66
|
+
}))
|
|
67
|
+
])
|
|
68
|
+
),
|
|
69
|
+
locales: i18n.locales.map((locale) => ({
|
|
70
|
+
...locale,
|
|
71
|
+
hreflang: locale._hreflang
|
|
72
|
+
}))
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
function localeAlternateHref(alternate) {
|
|
76
|
+
return alternate.domain ? joinURL(withHttps(alternate.domain), alternate.path) : alternate.path;
|
|
77
|
+
}
|
|
78
|
+
export function resolveI18nRouteEntries(route, i18n, includeHref = () => true) {
|
|
79
|
+
const runtimeConfig = toRuntimeI18nConfig(i18n);
|
|
80
|
+
const currentLocale = resolveLocaleFromRoute(route, runtimeConfig).locale;
|
|
81
|
+
const alternates = computeLocaleAlternates(route, runtimeConfig, { locale: currentLocale });
|
|
82
|
+
const localizedAlternates = alternates.map((alternate) => ({
|
|
83
|
+
alternate,
|
|
84
|
+
href: localeAlternateHref(alternate)
|
|
85
|
+
}));
|
|
86
|
+
const defaultHref = localizedAlternates.find(({ alternate }) => alternate.code === i18n.defaultLocale)?.href;
|
|
87
|
+
const sitemapAlternatives = [
|
|
88
|
+
...defaultHref && includeHref(defaultHref) ? [{ hreflang: "x-default", href: defaultHref }] : [],
|
|
89
|
+
...localizedAlternates.filter(({ href }) => includeHref(href)).map(({ alternate, href }) => ({ hreflang: alternate.hreflang, href }))
|
|
90
|
+
];
|
|
91
|
+
return localizedAlternates.flatMap(({ alternate, href }) => {
|
|
92
|
+
const locale = i18n.locales.find((locale2) => locale2.code === alternate.code);
|
|
93
|
+
return locale ? [{ locale, loc: href, alternatives: sitemapAlternatives }] : [];
|
|
94
|
+
});
|
|
95
|
+
}
|
|
54
96
|
export function resolveI18nSitemapLocaleKey(sitemapName, localeSitemapKeys) {
|
|
55
97
|
let best = null;
|
|
56
98
|
for (const key of localeSitemapKeys) {
|
|
@@ -89,26 +131,6 @@ export function createPathFilter(options = {}, baseURL) {
|
|
|
89
131
|
}
|
|
90
132
|
if (hasBase)
|
|
91
133
|
path = withoutBase(path, baseURL);
|
|
92
|
-
return urlFilter(path);
|
|
134
|
+
return urlFilter(withLeadingSlash(path));
|
|
93
135
|
};
|
|
94
136
|
}
|
|
95
|
-
export function findPageMapping(pathWithoutPrefix, pages, sortedKeys) {
|
|
96
|
-
const stripped = pathWithoutPrefix[0] === "/" ? pathWithoutPrefix.slice(1) : pathWithoutPrefix;
|
|
97
|
-
const pageKey = stripped.endsWith("/index") ? stripped.slice(0, -6) || "index" : stripped || "index";
|
|
98
|
-
if (pages[pageKey])
|
|
99
|
-
return { mappings: pages[pageKey], paramSegments: [] };
|
|
100
|
-
const keys = sortedKeys || Object.keys(pages).sort((a, b) => b.length - a.length);
|
|
101
|
-
for (const key of keys) {
|
|
102
|
-
if (pageKey.startsWith(`${key}/`)) {
|
|
103
|
-
const paramPath = pageKey.slice(key.length + 1);
|
|
104
|
-
return { mappings: pages[key], paramSegments: paramPath.split("/") };
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
return null;
|
|
108
|
-
}
|
|
109
|
-
export function applyDynamicParams(customPath, paramSegments) {
|
|
110
|
-
if (!paramSegments.length)
|
|
111
|
-
return customPath;
|
|
112
|
-
let i = 0;
|
|
113
|
-
return customPath.replace(/\[[^\]]+\]/g, () => paramSegments[i++] || "");
|
|
114
|
-
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nuxtjs/sitemap",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "8.
|
|
4
|
+
"version": "8.4.0",
|
|
5
5
|
"description": "Powerfully flexible XML Sitemaps that integrate seamlessly, for Nuxt.",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "Harlan Wilton",
|
|
@@ -55,45 +55,46 @@
|
|
|
55
55
|
}
|
|
56
56
|
},
|
|
57
57
|
"dependencies": {
|
|
58
|
-
"@nuxt/kit": "^4.5.
|
|
58
|
+
"@nuxt/kit": "^4.5.2",
|
|
59
59
|
"consola": "^3.4.2",
|
|
60
60
|
"defu": "^6.1.7",
|
|
61
|
-
"nuxt-site-config": "^4.
|
|
62
|
-
"nuxtseo-shared": "^5.3.
|
|
61
|
+
"nuxt-site-config": "^4.2.3",
|
|
62
|
+
"nuxtseo-shared": "^5.3.14",
|
|
63
63
|
"ofetch": "^1.5.1",
|
|
64
64
|
"pathe": "^2.0.3",
|
|
65
65
|
"pkg-types": "^2.3.1",
|
|
66
66
|
"radix3": "^1.1.2",
|
|
67
67
|
"ufo": "^1.6.4",
|
|
68
68
|
"ultrahtml": "^1.7.0",
|
|
69
|
-
"sitemapd": "^0.2.
|
|
69
|
+
"sitemapd": "^0.2.2"
|
|
70
70
|
},
|
|
71
71
|
"devDependencies": {
|
|
72
|
-
"@antfu/eslint-config": "^9.
|
|
72
|
+
"@antfu/eslint-config": "^9.3.0",
|
|
73
73
|
"@arethetypeswrong/cli": "^0.18.5",
|
|
74
|
+
"@harlan-zw/comark-content": "^0.1.3",
|
|
74
75
|
"@nuxt/content": "^3.15.2",
|
|
75
|
-
"@nuxt/devtools-kit": "4.0.0-alpha.
|
|
76
|
+
"@nuxt/devtools-kit": "4.0.0-alpha.11",
|
|
76
77
|
"@nuxt/module-builder": "^1.0.3",
|
|
77
78
|
"@nuxt/test-utils": "^4.1.0",
|
|
78
79
|
"@nuxt/ui": "^4.10.0",
|
|
79
80
|
"@nuxtjs/i18n": "^10.6.0",
|
|
80
|
-
"@nuxtjs/robots": "^6.1.
|
|
81
|
+
"@nuxtjs/robots": "^6.1.5",
|
|
81
82
|
"@vue/test-utils": "^2.4.11",
|
|
82
|
-
"better-sqlite3": "^13.0.
|
|
83
|
-
"bumpp": "^12.
|
|
84
|
-
"eslint": "^10.8.
|
|
85
|
-
"eslint-plugin-harlanzw": "^0.
|
|
86
|
-
"happy-dom": "^20.11.
|
|
87
|
-
"nuxt": "^4.5.
|
|
88
|
-
"nuxt-i18n-micro": "^3.
|
|
89
|
-
"nuxtseo-layer-devtools": "^5.3.
|
|
83
|
+
"better-sqlite3": "^13.0.3",
|
|
84
|
+
"bumpp": "^12.2.1",
|
|
85
|
+
"eslint": "^10.8.1",
|
|
86
|
+
"eslint-plugin-harlanzw": "^0.20.0",
|
|
87
|
+
"happy-dom": "^20.11.2",
|
|
88
|
+
"nuxt": "^4.5.2",
|
|
89
|
+
"nuxt-i18n-micro": "^3.28.0",
|
|
90
|
+
"nuxtseo-layer-devtools": "^5.3.14",
|
|
90
91
|
"std-env": "^4.2.0",
|
|
91
92
|
"typescript": "6.0.3",
|
|
92
93
|
"unbuild": "^3.6.1",
|
|
93
|
-
"vitest": "^4.1.
|
|
94
|
-
"vue-tsc": "^3.3.
|
|
94
|
+
"vitest": "^4.1.11",
|
|
95
|
+
"vue-tsc": "^3.3.10",
|
|
95
96
|
"zod": "^4.4.3",
|
|
96
|
-
"@nuxtjs/sitemap": "8.
|
|
97
|
+
"@nuxtjs/sitemap": "8.4.0"
|
|
97
98
|
},
|
|
98
99
|
"scripts": {
|
|
99
100
|
"lint": "eslint .",
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
import { queryCollection } from "@nuxt/content/server";
|
|
2
|
-
import manifest from "#content/manifest";
|
|
3
|
-
import { defineEventHandler } from "#nuxtseo/h3";
|
|
4
|
-
import { filters } from "#sitemap/content-filters";
|
|
5
|
-
import { onUrlFns } from "#sitemap/content-on-url";
|
|
6
|
-
export default defineEventHandler(async (e) => {
|
|
7
|
-
const collections = [];
|
|
8
|
-
for (const collection in manifest) {
|
|
9
|
-
if (manifest[collection].fields.sitemap)
|
|
10
|
-
collections.push(collection);
|
|
11
|
-
}
|
|
12
|
-
const contentList = [];
|
|
13
|
-
for (const collection of collections) {
|
|
14
|
-
const needsAllFields = filters?.has(collection) || onUrlFns?.has(collection);
|
|
15
|
-
const query = queryCollection(e, collection).where("path", "IS NOT NULL").where("sitemap", "IS NOT NULL");
|
|
16
|
-
if (!needsAllFields)
|
|
17
|
-
query.select("path", "sitemap");
|
|
18
|
-
contentList.push(
|
|
19
|
-
query.all().then((results2) => {
|
|
20
|
-
const filter = filters?.get(collection);
|
|
21
|
-
return { collection, entries: filter ? results2.filter(filter) : results2 };
|
|
22
|
-
}).catch((err) => {
|
|
23
|
-
console.error(`[@nuxtjs/sitemap] Couldn't query @nuxt/content collection "${collection}" for the sitemap, so its URLs will be missing. On serverless the content DB is restored from a prerendered sql_dump.txt that isn't readable inside the function (nuxt/content#3805). Fix: prerender the sitemap so content URLs resolve at build, or configure a runtime database (D1/Turso/Postgres).`, err);
|
|
24
|
-
return { collection, entries: [] };
|
|
25
|
-
})
|
|
26
|
-
);
|
|
27
|
-
}
|
|
28
|
-
const results = await Promise.all(contentList);
|
|
29
|
-
return results.flatMap(({ collection, entries }) => {
|
|
30
|
-
const onUrl = onUrlFns?.get(collection);
|
|
31
|
-
return entries.filter((c) => c.sitemap !== false && c.path && !c.path.endsWith(".navigation")).map((c) => {
|
|
32
|
-
const url = {
|
|
33
|
-
loc: c.path,
|
|
34
|
-
...typeof c.sitemap === "object" ? c.sitemap : {}
|
|
35
|
-
};
|
|
36
|
-
onUrl?.(url, c, collection);
|
|
37
|
-
return url;
|
|
38
|
-
});
|
|
39
|
-
}).filter(Boolean);
|
|
40
|
-
});
|