@nuxtjs/sitemap 2.4.0 → 5.0.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/README.md +60 -593
- package/dist/client/200.html +11 -0
- package/dist/client/404.html +11 -0
- package/dist/client/_nuxt/Icon.I3NdYkjC.css +1 -0
- package/dist/client/_nuxt/Icon.wdYGFqbO.js +1 -0
- package/dist/client/_nuxt/IconCSS.6oz918NR.css +1 -0
- package/dist/client/_nuxt/IconCSS.L-uj6Ouj.js +1 -0
- package/dist/client/_nuxt/builds/latest.json +1 -0
- package/dist/client/_nuxt/builds/meta/c3bdccb2-d4db-4a94-9e61-39b740a57bf8.json +1 -0
- package/dist/client/_nuxt/entry.Soe9IWze.js +15 -0
- package/dist/client/_nuxt/entry.UqhvG0ao.css +1 -0
- package/dist/client/_nuxt/error-404.DkXpI38i.css +1 -0
- package/dist/client/_nuxt/error-404.uz-DxZsp.js +1 -0
- package/dist/client/_nuxt/error-500.BsNDeZpL.js +1 -0
- package/dist/client/_nuxt/error-500.SLhS9LVu.css +1 -0
- package/dist/client/_nuxt/index.lSDm5iYo.js +1 -0
- package/dist/client/index.html +11 -0
- package/dist/module.cjs +5 -0
- package/dist/module.d.mts +350 -0
- package/dist/module.d.ts +350 -0
- package/dist/module.json +9 -0
- package/dist/module.mjs +994 -0
- package/dist/runtime/nitro/composables/asSitemapUrl.d.ts +2 -0
- package/dist/runtime/nitro/composables/asSitemapUrl.mjs +3 -0
- package/dist/runtime/nitro/composables/defineSitemapEventHandler.d.ts +3 -0
- package/dist/runtime/nitro/composables/defineSitemapEventHandler.mjs +2 -0
- package/dist/runtime/nitro/composables/getPathRobotConfigPolyfill.d.ts +4 -0
- package/dist/runtime/nitro/composables/getPathRobotConfigPolyfill.mjs +3 -0
- package/dist/runtime/nitro/kit.d.ts +3 -0
- package/dist/runtime/nitro/kit.mjs +23 -0
- package/dist/runtime/nitro/middleware/[sitemap]-sitemap.xml.d.ts +2 -0
- package/dist/runtime/nitro/middleware/[sitemap]-sitemap.xml.mjs +23 -0
- package/dist/runtime/nitro/plugins/compression.d.ts +2 -0
- package/dist/runtime/nitro/plugins/compression.mjs +8 -0
- package/dist/runtime/nitro/plugins/nuxt-content.d.ts +2 -0
- package/dist/runtime/nitro/plugins/nuxt-content.mjs +38 -0
- package/dist/runtime/nitro/plugins/warm-up.d.ts +2 -0
- package/dist/runtime/nitro/plugins/warm-up.mjs +23 -0
- package/dist/runtime/nitro/routes/__sitemap__/debug.d.ts +37 -0
- package/dist/runtime/nitro/routes/__sitemap__/debug.mjs +29 -0
- package/dist/runtime/nitro/routes/__sitemap__/nuxt-content-urls.d.ts +2 -0
- package/dist/runtime/nitro/routes/__sitemap__/nuxt-content-urls.mjs +6 -0
- package/dist/runtime/nitro/routes/sitemap.xml.d.ts +2 -0
- package/dist/runtime/nitro/routes/sitemap.xml.mjs +13 -0
- package/dist/runtime/nitro/routes/sitemap.xsl.d.ts +2 -0
- package/dist/runtime/nitro/routes/sitemap.xsl.mjs +230 -0
- package/dist/runtime/nitro/routes/sitemap_index.xml.d.ts +2 -0
- package/dist/runtime/nitro/routes/sitemap_index.xml.mjs +27 -0
- package/dist/runtime/nitro/sitemap/builder/sitemap-index.d.ts +2 -0
- package/dist/runtime/nitro/sitemap/builder/sitemap-index.mjs +86 -0
- package/dist/runtime/nitro/sitemap/builder/sitemap.d.ts +2 -0
- package/dist/runtime/nitro/sitemap/builder/sitemap.mjs +107 -0
- package/dist/runtime/nitro/sitemap/builder/xml.d.ts +4 -0
- package/dist/runtime/nitro/sitemap/builder/xml.mjs +83 -0
- package/dist/runtime/nitro/sitemap/nitro.d.ts +4 -0
- package/dist/runtime/nitro/sitemap/nitro.mjs +36 -0
- package/dist/runtime/nitro/sitemap/urlset/filter.d.ts +5 -0
- package/dist/runtime/nitro/sitemap/urlset/filter.mjs +50 -0
- package/dist/runtime/nitro/sitemap/urlset/i18n.d.ts +8 -0
- package/dist/runtime/nitro/sitemap/urlset/i18n.mjs +128 -0
- package/dist/runtime/nitro/sitemap/urlset/normalise.d.ts +3 -0
- package/dist/runtime/nitro/sitemap/urlset/normalise.mjs +77 -0
- package/dist/runtime/nitro/sitemap/urlset/sort.d.ts +2 -0
- package/dist/runtime/nitro/sitemap/urlset/sort.mjs +19 -0
- package/dist/runtime/nitro/sitemap/urlset/sources.d.ts +5 -0
- package/dist/runtime/nitro/sitemap/urlset/sources.mjs +82 -0
- package/dist/runtime/nitro/tsconfig.json +3 -0
- package/dist/runtime/nitro/utils.d.ts +4 -0
- package/dist/runtime/nitro/utils.mjs +13 -0
- package/dist/runtime/types.d.ts +355 -0
- package/dist/runtime/types.mjs +0 -0
- package/dist/runtime/utils-pure.d.ts +7 -0
- package/dist/runtime/utils-pure.mjs +32 -0
- package/dist/types.d.mts +18 -0
- package/dist/types.d.ts +18 -0
- package/package.json +79 -75
- package/CHANGELOG.md +0 -268
- package/LICENSE +0 -21
- package/lib/builder.js +0 -172
- package/lib/cache.js +0 -95
- package/lib/generator.js +0 -113
- package/lib/logger.js +0 -19
- package/lib/middleware.js +0 -195
- package/lib/module.js +0 -72
- package/lib/options.js +0 -135
- package/lib/routes.js +0 -55
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { type EventHandlerRequest, type EventHandlerResponse, defineEventHandler } from 'h3';
|
|
2
|
+
import type { SitemapUrlInput } from '../../types';
|
|
3
|
+
export declare const defineSitemapEventHandler: typeof defineEventHandler<EventHandlerRequest, EventHandlerResponse<SitemapUrlInput[]>>;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { createRouter as createRadixRouter, toRouteMatcher } from "radix3";
|
|
2
|
+
import { defu } from "defu";
|
|
3
|
+
import { withoutBase, withoutTrailingSlash } from "ufo";
|
|
4
|
+
import { useRuntimeConfig } from "#imports";
|
|
5
|
+
export function withoutQuery(path) {
|
|
6
|
+
return path.split("?")[0];
|
|
7
|
+
}
|
|
8
|
+
export function createNitroRouteRuleMatcher() {
|
|
9
|
+
const { nitro, app } = useRuntimeConfig();
|
|
10
|
+
const _routeRulesMatcher = toRouteMatcher(
|
|
11
|
+
createRadixRouter({
|
|
12
|
+
routes: Object.fromEntries(
|
|
13
|
+
Object.entries(nitro?.routeRules || {}).map(([path, rules]) => [withoutTrailingSlash(path), rules])
|
|
14
|
+
)
|
|
15
|
+
})
|
|
16
|
+
);
|
|
17
|
+
return (path) => {
|
|
18
|
+
return defu({}, ..._routeRulesMatcher.matchAll(
|
|
19
|
+
// radix3 does not support trailing slashes
|
|
20
|
+
withoutBase(withoutTrailingSlash(withoutQuery(path)), app.baseURL)
|
|
21
|
+
).reverse());
|
|
22
|
+
};
|
|
23
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { createError, defineEventHandler } from "h3";
|
|
2
|
+
import { parseURL } from "ufo";
|
|
3
|
+
import { useSimpleSitemapRuntimeConfig } from "../utils.mjs";
|
|
4
|
+
import { createSitemap } from "../sitemap/nitro.mjs";
|
|
5
|
+
export default defineEventHandler(async (e) => {
|
|
6
|
+
const path = parseURL(e.path).pathname;
|
|
7
|
+
if (!path.endsWith("-sitemap.xml"))
|
|
8
|
+
return;
|
|
9
|
+
const runtimeConfig = useSimpleSitemapRuntimeConfig();
|
|
10
|
+
const { sitemaps } = runtimeConfig;
|
|
11
|
+
const sitemapName = path.replace("-sitemap.xml", "").replace("/", "");
|
|
12
|
+
const isChunking = typeof sitemaps.chunks !== "undefined" && !Number.isNaN(Number(sitemapName));
|
|
13
|
+
if (!(sitemapName in sitemaps) && !isChunking) {
|
|
14
|
+
return createError({
|
|
15
|
+
statusCode: 404,
|
|
16
|
+
message: `Sitemap "${sitemapName}" not found.`
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
return createSitemap(e, isChunking ? {
|
|
20
|
+
...sitemaps.chunks,
|
|
21
|
+
sitemapName
|
|
22
|
+
} : sitemaps[sitemapName], runtimeConfig);
|
|
23
|
+
});
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { useCompressionStream } from "h3-compression";
|
|
2
|
+
import { defineNitroPlugin } from "#imports";
|
|
3
|
+
export default defineNitroPlugin((nitro) => {
|
|
4
|
+
nitro.hooks.hook("beforeResponse", async (event, response) => {
|
|
5
|
+
if (event.context._isSitemap)
|
|
6
|
+
await useCompressionStream(event, response);
|
|
7
|
+
});
|
|
8
|
+
});
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { defu } from "defu";
|
|
2
|
+
import { defineNitroPlugin } from "nitropack/dist/runtime/plugin";
|
|
3
|
+
import { useSimpleSitemapRuntimeConfig } from "../utils.mjs";
|
|
4
|
+
export default defineNitroPlugin((nitroApp) => {
|
|
5
|
+
const { discoverImages, isNuxtContentDocumentDriven } = useSimpleSitemapRuntimeConfig();
|
|
6
|
+
nitroApp.hooks.hook("content:file:afterParse", async (content) => {
|
|
7
|
+
if (content.sitemap === false || content._draft || content._extension !== "md" || content._partial || content.indexable === false || content.index === false)
|
|
8
|
+
return;
|
|
9
|
+
let images = [];
|
|
10
|
+
if (discoverImages) {
|
|
11
|
+
images = content.body?.children?.filter(
|
|
12
|
+
(c) => c.tag && c.props?.src && ["image", "img", "nuxtimg", "nuxt-img"].includes(c.tag.toLowerCase())
|
|
13
|
+
).map((i) => ({ loc: i.props.src })) || [];
|
|
14
|
+
}
|
|
15
|
+
const sitemapConfig = typeof content.sitemap === "object" ? content.sitemap : {};
|
|
16
|
+
const lastmod = content.modifiedAt || content.updatedAt;
|
|
17
|
+
const defaults = {};
|
|
18
|
+
if (isNuxtContentDocumentDriven)
|
|
19
|
+
defaults.loc = content._path;
|
|
20
|
+
if (content.path)
|
|
21
|
+
defaults.loc = content.path;
|
|
22
|
+
if (images.length > 0)
|
|
23
|
+
defaults.images = images;
|
|
24
|
+
if (lastmod)
|
|
25
|
+
defaults.lastmod = lastmod;
|
|
26
|
+
const definition = defu(sitemapConfig, defaults);
|
|
27
|
+
if (!definition.loc) {
|
|
28
|
+
if (content.path && content.path && content.path.startsWith("/"))
|
|
29
|
+
definition.loc = content.path;
|
|
30
|
+
if (Object.keys(sitemapConfig).length > 0 && import.meta.dev)
|
|
31
|
+
console.warn(`[@nuxtjs/content] The @nuxt/content file \`${content._path}\` is missing a sitemap \`loc\`.`);
|
|
32
|
+
}
|
|
33
|
+
content.sitemap = definition;
|
|
34
|
+
if (!definition.loc)
|
|
35
|
+
delete content.sitemap;
|
|
36
|
+
return content;
|
|
37
|
+
});
|
|
38
|
+
});
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { defineNitroPlugin } from "nitropack/dist/runtime/plugin";
|
|
2
|
+
import { withLeadingSlash } from "ufo";
|
|
3
|
+
import { useSimpleSitemapRuntimeConfig } from "../utils.mjs";
|
|
4
|
+
export default defineNitroPlugin((nitroApp) => {
|
|
5
|
+
const { sitemaps } = useSimpleSitemapRuntimeConfig();
|
|
6
|
+
const queue = [];
|
|
7
|
+
const sitemapsWithRoutes = Object.entries(sitemaps).filter(([, sitemap]) => sitemap._route);
|
|
8
|
+
for (const [, sitemap] of sitemapsWithRoutes)
|
|
9
|
+
queue.push(() => nitroApp.localFetch(withLeadingSlash(sitemap._route), {}));
|
|
10
|
+
setTimeout(
|
|
11
|
+
() => {
|
|
12
|
+
const next = async () => {
|
|
13
|
+
if (queue.length === 0)
|
|
14
|
+
return;
|
|
15
|
+
await queue.shift()();
|
|
16
|
+
setTimeout(next, 1e3);
|
|
17
|
+
};
|
|
18
|
+
next();
|
|
19
|
+
},
|
|
20
|
+
2500
|
|
21
|
+
/* https://github.com/unjs/nitro/pull/1906 */
|
|
22
|
+
);
|
|
23
|
+
});
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { SitemapDefinition } from '../../../types';
|
|
2
|
+
declare const _default: import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<{
|
|
3
|
+
nitroOrigin: any;
|
|
4
|
+
sitemaps: Record<string, SitemapDefinition>;
|
|
5
|
+
runtimeConfig: {
|
|
6
|
+
version: string;
|
|
7
|
+
isNuxtContentDocumentDriven: boolean;
|
|
8
|
+
sitemaps: {
|
|
9
|
+
index?: (Pick<SitemapDefinition, "sitemapName" | "_route"> & {
|
|
10
|
+
sitemaps: import("../../../types").SitemapIndexEntry[];
|
|
11
|
+
}) | undefined;
|
|
12
|
+
} & Record<string, Omit<SitemapDefinition, "urls" | "sources"> & {
|
|
13
|
+
_hasSourceChunk?: boolean | undefined;
|
|
14
|
+
}>;
|
|
15
|
+
autoI18n?: import("../../../types").AutoI18nConfig | undefined;
|
|
16
|
+
isMultiSitemap: boolean;
|
|
17
|
+
isI18nMapped: boolean;
|
|
18
|
+
cacheMaxAgeSeconds: number | false;
|
|
19
|
+
sitemapName: string;
|
|
20
|
+
excludeAppSources: true | import("../../../types").AppSourceContext[];
|
|
21
|
+
sortEntries: boolean;
|
|
22
|
+
defaultSitemapsChunkSize: number | false;
|
|
23
|
+
xslColumns?: {
|
|
24
|
+
label: string;
|
|
25
|
+
width: `${string}%`;
|
|
26
|
+
select?: string | undefined;
|
|
27
|
+
}[] | undefined;
|
|
28
|
+
xslTips: boolean;
|
|
29
|
+
debug: boolean;
|
|
30
|
+
discoverImages: boolean;
|
|
31
|
+
autoLastmod: boolean;
|
|
32
|
+
xsl: string | false;
|
|
33
|
+
credits: boolean;
|
|
34
|
+
};
|
|
35
|
+
globalSources: import("../../../types").SitemapSourceResolved[];
|
|
36
|
+
}>>;
|
|
37
|
+
export default _default;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { defineEventHandler } from "h3";
|
|
2
|
+
import { useSimpleSitemapRuntimeConfig } from "../../utils.mjs";
|
|
3
|
+
import {
|
|
4
|
+
childSitemapSources,
|
|
5
|
+
globalSitemapSources,
|
|
6
|
+
resolveSitemapSources
|
|
7
|
+
} from "../../sitemap/urlset/sources.mjs";
|
|
8
|
+
import { useNitroOrigin } from "#imports";
|
|
9
|
+
export default defineEventHandler(async (e) => {
|
|
10
|
+
const _runtimeConfig = useSimpleSitemapRuntimeConfig();
|
|
11
|
+
const { sitemaps: _sitemaps } = _runtimeConfig;
|
|
12
|
+
const runtimeConfig = { ..._runtimeConfig };
|
|
13
|
+
delete runtimeConfig.sitemaps;
|
|
14
|
+
const globalSources = await globalSitemapSources();
|
|
15
|
+
const nitroOrigin = useNitroOrigin(e);
|
|
16
|
+
const sitemaps = {};
|
|
17
|
+
for (const s of Object.keys(_sitemaps)) {
|
|
18
|
+
sitemaps[s] = {
|
|
19
|
+
..._sitemaps[s],
|
|
20
|
+
sources: await resolveSitemapSources(await childSitemapSources(_sitemaps[s]))
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
return {
|
|
24
|
+
nitroOrigin,
|
|
25
|
+
sitemaps,
|
|
26
|
+
runtimeConfig,
|
|
27
|
+
globalSources: await resolveSitemapSources(globalSources)
|
|
28
|
+
};
|
|
29
|
+
});
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { defineEventHandler } from "h3";
|
|
2
|
+
import { serverQueryContent } from "#content/server";
|
|
3
|
+
export default defineEventHandler(async (e) => {
|
|
4
|
+
const contentList = await serverQueryContent(e).find();
|
|
5
|
+
return contentList.map((c) => c.sitemap).filter(Boolean);
|
|
6
|
+
});
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { defineEventHandler, sendRedirect } from "h3";
|
|
2
|
+
import { withBase } from "ufo";
|
|
3
|
+
import { useSimpleSitemapRuntimeConfig } from "../utils.mjs";
|
|
4
|
+
import { createSitemap } from "../sitemap/nitro.mjs";
|
|
5
|
+
import { useRuntimeConfig } from "#imports";
|
|
6
|
+
export default defineEventHandler(async (e) => {
|
|
7
|
+
const runtimeConfig = useSimpleSitemapRuntimeConfig();
|
|
8
|
+
const { sitemaps } = runtimeConfig;
|
|
9
|
+
if ("index" in sitemaps) {
|
|
10
|
+
return sendRedirect(e, withBase("/sitemap_index.xml", useRuntimeConfig().app.baseURL), process.dev ? 302 : 301);
|
|
11
|
+
}
|
|
12
|
+
return createSitemap(e, Object.values(sitemaps)[0], runtimeConfig);
|
|
13
|
+
});
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
import { defineEventHandler, getHeader, setHeader } from "h3";
|
|
2
|
+
import { getQuery, parseURL, withQuery } from "ufo";
|
|
3
|
+
import { useSimpleSitemapRuntimeConfig } from "../utils.mjs";
|
|
4
|
+
import { createSitePathResolver, useSiteConfig } from "#imports";
|
|
5
|
+
export default defineEventHandler(async (e) => {
|
|
6
|
+
const fixPath = createSitePathResolver(e, { absolute: false, withBase: true });
|
|
7
|
+
const { sitemapName: fallbackSitemapName, cacheMaxAgeSeconds, version, xslColumns, xslTips } = useSimpleSitemapRuntimeConfig();
|
|
8
|
+
setHeader(e, "Content-Type", "application/xslt+xml");
|
|
9
|
+
if (cacheMaxAgeSeconds)
|
|
10
|
+
setHeader(e, "Cache-Control", `public, max-age=${cacheMaxAgeSeconds}, must-revalidate`);
|
|
11
|
+
else
|
|
12
|
+
setHeader(e, "Cache-Control", `no-cache, no-store`);
|
|
13
|
+
const svgIcon = `<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="icon" style="margin-right: 4px; font-size: 25px;" width="1em" height="1em" viewBox="0 0 32 32"><path fill="#93c5fd" d="M4 26h4v4H4zm10 0h4v4h-4zm10 0h4v4h-4zm1-10h-8v-2h-2v2H7a2.002 2.002 0 0 0-2 2v6h2v-6h8v6h2v-6h8v6h2v-6a2.002 2.002 0 0 0-2-2zM9 2v10h14V2zm2 2h2v6h-2zm10 6h-6V4h6z"></path></svg>`;
|
|
14
|
+
const creditName = `<a href="https://github.com/nuxt-modules/sitemap" style="color: black; display: flex; align-items: center; font-weight: 500;" target="_blank" rel="noopener">${svgIcon} Nuxt
|
|
15
|
+
Simple Sitemap v${version}</a>`;
|
|
16
|
+
const { name: siteName, url: siteUrl } = useSiteConfig(e);
|
|
17
|
+
const referrer = getHeader(e, "Referer") || "/";
|
|
18
|
+
const isNotIndexButHasIndex = referrer !== fixPath("/sitemap.xml") && parseURL(referrer).pathname.endsWith("-sitemap.xml");
|
|
19
|
+
const sitemapName = parseURL(referrer).pathname.split("/").pop()?.split("-sitemap")[0] || fallbackSitemapName;
|
|
20
|
+
const title = `${siteName}${sitemapName !== "sitemap.xml" ? ` - ${sitemapName === "sitemap_index.xml" ? "index" : sitemapName}` : ""}`.replace(/&/g, "&");
|
|
21
|
+
const canonicalQuery = getQuery(referrer).canonical;
|
|
22
|
+
const isShowingCanonical = typeof canonicalQuery !== "undefined" && canonicalQuery !== "false";
|
|
23
|
+
const conditionalTips = [
|
|
24
|
+
'You are looking at a <a href="https://developer.mozilla.org/en-US/docs/Web/XSLT/Transforming_XML_with_XSLT/An_Overview" style="color: #398465" target="_blank">XML stylesheet</a>. Read the <a href="https://nuxtseo.com/sitemap/guides/customising-ui" style="color: #398465" target="_blank">docs</a> to learn how to customize it. View the page source to see the raw XML.',
|
|
25
|
+
`URLs missing? Check Nuxt Devtools Sitemap tab (or the <a href="${withQuery("/__sitemap__/debug.json", { sitemap: sitemapName })}" style="color: #398465" target="_blank">debug endpoint</a>).`
|
|
26
|
+
];
|
|
27
|
+
if (!isShowingCanonical) {
|
|
28
|
+
const canonicalPreviewUrl = withQuery(referrer, { canonical: "" });
|
|
29
|
+
conditionalTips.push(`Your canonical site URL is <strong>${siteUrl}</strong>.`);
|
|
30
|
+
conditionalTips.push(`You can preview your canonical sitemap by visiting <a href="${canonicalPreviewUrl}" style="color: #398465; white-space: nowrap;">${fixPath(canonicalPreviewUrl)}?canonical</a>`);
|
|
31
|
+
} else {
|
|
32
|
+
conditionalTips.push(`You are viewing the canonical sitemap. You can switch to using the request origin: <a href="${fixPath(referrer)}" style="color: #398465; white-space: nowrap ">${fixPath(referrer)}</a>`);
|
|
33
|
+
}
|
|
34
|
+
const tips = conditionalTips.map((t) => `<li><p>${t}</p></li>`).join("\n");
|
|
35
|
+
const showTips = process.dev && xslTips !== false;
|
|
36
|
+
let columns = [...xslColumns];
|
|
37
|
+
if (!columns.length) {
|
|
38
|
+
columns = [
|
|
39
|
+
{ label: "URL", width: "50%" },
|
|
40
|
+
{ label: "Images", width: "25%", select: "count(image:image)" },
|
|
41
|
+
{ label: "Last Updated", width: "25%", select: "concat(substring(sitemap:lastmod,0,11),concat(' ', substring(sitemap:lastmod,12,5)),concat(' ', substring(sitemap:lastmod,20,6)))" }
|
|
42
|
+
];
|
|
43
|
+
}
|
|
44
|
+
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
45
|
+
<xsl:stylesheet version="2.0"
|
|
46
|
+
xmlns:html="http://www.w3.org/TR/REC-html40"
|
|
47
|
+
xmlns:image="http://www.google.com/schemas/sitemap-image/1.1"
|
|
48
|
+
xmlns:sitemap="http://www.sitemaps.org/schemas/sitemap/0.9"
|
|
49
|
+
xmlns:xhtml="http://www.w3.org/1999/xhtml"
|
|
50
|
+
xmlns:news="http://www.google.com/schemas/sitemap-news/0.9"
|
|
51
|
+
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
|
|
52
|
+
<xsl:output method="html" version="1.0" encoding="UTF-8" indent="yes"/>
|
|
53
|
+
<xsl:template match="/">
|
|
54
|
+
<html xmlns="http://www.w3.org/1999/xhtml">
|
|
55
|
+
<head>
|
|
56
|
+
<title>XML Sitemap</title>
|
|
57
|
+
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
|
|
58
|
+
<style type="text/css">
|
|
59
|
+
body {
|
|
60
|
+
font-family: Inter, Helvetica, Arial, sans-serif;
|
|
61
|
+
font-size: 14px;
|
|
62
|
+
color: #333;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
table {
|
|
66
|
+
border: none;
|
|
67
|
+
border-collapse: collapse;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
.bg-yellow-200 {
|
|
71
|
+
background-color: #fef9c3;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
.p-5 {
|
|
75
|
+
padding: 1.25rem;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
.rounded {
|
|
79
|
+
border-radius: 4px;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
.shadow {
|
|
83
|
+
box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
#sitemap tr:nth-child(odd) td {
|
|
87
|
+
background-color: #f8f8f8 !important;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
#sitemap tbody tr:hover td {
|
|
91
|
+
background-color: #fff;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
#sitemap tbody tr:hover td, #sitemap tbody tr:hover td a {
|
|
95
|
+
color: #000;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
.expl a {
|
|
99
|
+
color: #398465
|
|
100
|
+
font-weight: 600;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
.expl a:visited {
|
|
104
|
+
color: #398465
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
a {
|
|
108
|
+
color: #000;
|
|
109
|
+
text-decoration: none;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
a:visited {
|
|
113
|
+
color: #777;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
a:hover {
|
|
117
|
+
text-decoration: underline;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
td {
|
|
121
|
+
font-size: 12px;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
.text-2xl {
|
|
125
|
+
font-size: 2rem;
|
|
126
|
+
font-weight: 600;
|
|
127
|
+
line-height: 1.25;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
th {
|
|
131
|
+
text-align: left;
|
|
132
|
+
padding-right: 30px;
|
|
133
|
+
font-size: 12px;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
thead th {
|
|
137
|
+
border-bottom: 1px solid #000;
|
|
138
|
+
}
|
|
139
|
+
.fixed { position: fixed; }
|
|
140
|
+
.right-2 { right: 2rem; }
|
|
141
|
+
.top-2 { top: 2rem; }
|
|
142
|
+
.w-30 { width: 30rem; }
|
|
143
|
+
p { margin: 0; }
|
|
144
|
+
li { padding-bottom: 0.5rem; line-height: 1.5; }
|
|
145
|
+
h1 { margin: 0; }
|
|
146
|
+
.mb-5 { margin-bottom: 1.25rem; }
|
|
147
|
+
.mb-3 { margin-bottom: 0.75rem; }
|
|
148
|
+
</style>
|
|
149
|
+
</head>
|
|
150
|
+
<body>
|
|
151
|
+
<div style="grid-template-columns: 1fr 1fr; display: grid; margin: 3rem;">
|
|
152
|
+
<div>
|
|
153
|
+
<div id="content">
|
|
154
|
+
<h1 class="text-2xl mb-3">XML Sitemap</h1>
|
|
155
|
+
<h2>${title}</h2>
|
|
156
|
+
${isNotIndexButHasIndex ? `<p style="font-size: 12px; margin-bottom: 1rem;"><a href="${fixPath("/sitemap_index.xml")}">${fixPath("/sitemap_index.xml")}</a></p>` : ""}
|
|
157
|
+
<xsl:if test="count(sitemap:sitemapindex/sitemap:sitemap) > 0">
|
|
158
|
+
<p class="expl" style="margin-bottom: 1rem;">
|
|
159
|
+
This XML Sitemap Index file contains
|
|
160
|
+
<xsl:value-of select="count(sitemap:sitemapindex/sitemap:sitemap)"/> sitemaps.
|
|
161
|
+
</p>
|
|
162
|
+
<table id="sitemap" cellpadding="3">
|
|
163
|
+
<thead>
|
|
164
|
+
<tr>
|
|
165
|
+
<th width="75%">Sitemap</th>
|
|
166
|
+
<th width="25%">Last Modified</th>
|
|
167
|
+
</tr>
|
|
168
|
+
</thead>
|
|
169
|
+
<tbody>
|
|
170
|
+
<xsl:for-each select="sitemap:sitemapindex/sitemap:sitemap">
|
|
171
|
+
<xsl:variable name="sitemapURL">
|
|
172
|
+
<xsl:value-of select="sitemap:loc"/>
|
|
173
|
+
</xsl:variable>
|
|
174
|
+
<tr>
|
|
175
|
+
<td>
|
|
176
|
+
<a href="{$sitemapURL}">
|
|
177
|
+
<xsl:value-of select="sitemap:loc"/>
|
|
178
|
+
</a>
|
|
179
|
+
</td>
|
|
180
|
+
<td>
|
|
181
|
+
<xsl:value-of
|
|
182
|
+
select="concat(substring(sitemap:lastmod,0,11),concat(' ', substring(sitemap:lastmod,12,5)),concat(' ', substring(sitemap:lastmod,20,6)))"/>
|
|
183
|
+
</td>
|
|
184
|
+
</tr>
|
|
185
|
+
</xsl:for-each>
|
|
186
|
+
</tbody>
|
|
187
|
+
</table>
|
|
188
|
+
</xsl:if>
|
|
189
|
+
<xsl:if test="count(sitemap:sitemapindex/sitemap:sitemap) < 1">
|
|
190
|
+
<p class="expl" style="margin-bottom: 1rem;">
|
|
191
|
+
This XML Sitemap contains
|
|
192
|
+
<xsl:value-of select="count(sitemap:urlset/sitemap:url)"/> URLs.
|
|
193
|
+
</p>
|
|
194
|
+
<table id="sitemap" cellpadding="3">
|
|
195
|
+
<thead>
|
|
196
|
+
<tr>
|
|
197
|
+
${columns.map((c) => `<th width="${c.width}">${c.label}</th>`).join("\n")}
|
|
198
|
+
</tr>
|
|
199
|
+
</thead>
|
|
200
|
+
<tbody>
|
|
201
|
+
<xsl:variable name="lower" select="'abcdefghijklmnopqrstuvwxyz'"/>
|
|
202
|
+
<xsl:variable name="upper" select="'ABCDEFGHIJKLMNOPQRSTUVWXYZ'"/>
|
|
203
|
+
<xsl:for-each select="sitemap:urlset/sitemap:url">
|
|
204
|
+
<tr>
|
|
205
|
+
<td>
|
|
206
|
+
<xsl:variable name="itemURL">
|
|
207
|
+
<xsl:value-of select="sitemap:loc"/>
|
|
208
|
+
</xsl:variable>
|
|
209
|
+
<a href="{$itemURL}">
|
|
210
|
+
<xsl:value-of select="sitemap:loc"/>
|
|
211
|
+
</a>
|
|
212
|
+
</td>
|
|
213
|
+
${columns.filter((c) => c.label !== "URL").map((c) => `<td>
|
|
214
|
+
<xsl:value-of select="${c.select}"/>
|
|
215
|
+
</td>`).join("\n")}
|
|
216
|
+
</tr>
|
|
217
|
+
</xsl:for-each>
|
|
218
|
+
</tbody>
|
|
219
|
+
</table>
|
|
220
|
+
</xsl:if>
|
|
221
|
+
</div>
|
|
222
|
+
</div>
|
|
223
|
+
${showTips ? `<div class="w-30 top-2 shadow rounded p-5 right-2" style="margin: 0 auto;"><p><strong>Sitemap Tips (development only)</strong></p><ul style="margin: 1rem; padding: 0;">${tips}</ul><p style="margin-top: 1rem;">${creditName}</p></div>` : ""}
|
|
224
|
+
</div>
|
|
225
|
+
</body>
|
|
226
|
+
</html>
|
|
227
|
+
</xsl:template>
|
|
228
|
+
</xsl:stylesheet>
|
|
229
|
+
`;
|
|
230
|
+
});
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { defineEventHandler, getQuery, setHeader } from "h3";
|
|
2
|
+
import { fixSlashes } from "site-config-stack/urls";
|
|
3
|
+
import { useSimpleSitemapRuntimeConfig } from "../utils.mjs";
|
|
4
|
+
import { buildSitemapIndex } from "../sitemap/builder/sitemap-index.mjs";
|
|
5
|
+
import { createSitePathResolver, useNitroApp, useSiteConfig } from "#imports";
|
|
6
|
+
export default defineEventHandler(async (e) => {
|
|
7
|
+
const canonicalQuery = getQuery(e).canonical;
|
|
8
|
+
const isShowingCanonical = typeof canonicalQuery !== "undefined" && canonicalQuery !== "false";
|
|
9
|
+
const runtimeConfig = useSimpleSitemapRuntimeConfig();
|
|
10
|
+
const siteConfig = useSiteConfig(e);
|
|
11
|
+
let sitemap = await buildSitemapIndex({
|
|
12
|
+
event: e,
|
|
13
|
+
canonicalUrlResolver: createSitePathResolver(e, { canonical: isShowingCanonical || !import.meta.dev, absolute: true, withBase: true }),
|
|
14
|
+
relativeBaseUrlResolver: createSitePathResolver(e, { absolute: false, withBase: true }),
|
|
15
|
+
fixSlashes: (path) => fixSlashes(siteConfig.trailingSlash, path)
|
|
16
|
+
}, runtimeConfig);
|
|
17
|
+
const nitro = useNitroApp();
|
|
18
|
+
const ctx = { sitemap, sitemapName: "sitemap" };
|
|
19
|
+
await nitro.hooks.callHook("sitemap:output", ctx);
|
|
20
|
+
sitemap = ctx.sitemap;
|
|
21
|
+
setHeader(e, "Content-Type", "text/xml; charset=UTF-8");
|
|
22
|
+
if (runtimeConfig.cacheMaxAgeSeconds)
|
|
23
|
+
setHeader(e, "Cache-Control", `public, max-age=${runtimeConfig.cacheMaxAgeSeconds}, must-revalidate`);
|
|
24
|
+
else
|
|
25
|
+
setHeader(e, "Cache-Control", `no-cache, no-store`);
|
|
26
|
+
return sitemap;
|
|
27
|
+
});
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { defu } from "defu";
|
|
2
|
+
import { appendHeader } from "h3";
|
|
3
|
+
import { normaliseDate, normaliseSitemapUrls } from "../urlset/normalise.mjs";
|
|
4
|
+
import { globalSitemapSources, resolveSitemapSources } from "../urlset/sources.mjs";
|
|
5
|
+
import { applyI18nEnhancements } from "../urlset/i18n.mjs";
|
|
6
|
+
import { filterSitemapUrls } from "../urlset/filter.mjs";
|
|
7
|
+
import { sortSitemapUrls } from "../urlset/sort.mjs";
|
|
8
|
+
import { escapeValueForXml, wrapSitemapXml } from "./xml.mjs";
|
|
9
|
+
export async function buildSitemapIndex(resolvers, runtimeConfig) {
|
|
10
|
+
const {
|
|
11
|
+
sitemaps,
|
|
12
|
+
// enhancing
|
|
13
|
+
autoLastmod,
|
|
14
|
+
// chunking
|
|
15
|
+
defaultSitemapsChunkSize,
|
|
16
|
+
autoI18n,
|
|
17
|
+
isI18nMapped,
|
|
18
|
+
sortEntries,
|
|
19
|
+
// xls
|
|
20
|
+
version,
|
|
21
|
+
xsl,
|
|
22
|
+
credits
|
|
23
|
+
} = runtimeConfig;
|
|
24
|
+
if (!sitemaps)
|
|
25
|
+
throw new Error("Attempting to build a sitemap index without required `sitemaps` configuration.");
|
|
26
|
+
function maybeSort(urls) {
|
|
27
|
+
return sortEntries ? sortSitemapUrls(urls) : urls;
|
|
28
|
+
}
|
|
29
|
+
const isChunking = typeof sitemaps.chunks !== "undefined";
|
|
30
|
+
const chunks = {};
|
|
31
|
+
if (isChunking) {
|
|
32
|
+
const sitemap = sitemaps.chunks;
|
|
33
|
+
const sources = await resolveSitemapSources(await globalSitemapSources());
|
|
34
|
+
const normalisedUrls = normaliseSitemapUrls(sources.map((e) => e.urls).flat(), resolvers);
|
|
35
|
+
let enhancedUrls = normalisedUrls.map((e) => defu(e, sitemap.defaults));
|
|
36
|
+
if (autoI18n?.locales)
|
|
37
|
+
enhancedUrls = applyI18nEnhancements(enhancedUrls, { isI18nMapped, autoI18n, sitemapName: sitemap.sitemapName });
|
|
38
|
+
const filteredUrls = filterSitemapUrls(enhancedUrls, { ...sitemap, autoI18n, isMultiSitemap: true });
|
|
39
|
+
const sortedUrls = maybeSort(filteredUrls);
|
|
40
|
+
sortedUrls.forEach((url, i) => {
|
|
41
|
+
const chunkIndex = Math.floor(i / defaultSitemapsChunkSize);
|
|
42
|
+
chunks[chunkIndex] = chunks[chunkIndex] || { urls: [] };
|
|
43
|
+
chunks[chunkIndex].urls.push(url);
|
|
44
|
+
});
|
|
45
|
+
} else {
|
|
46
|
+
for (const sitemap in sitemaps) {
|
|
47
|
+
if (sitemap !== "index") {
|
|
48
|
+
chunks[sitemap] = chunks[sitemap] || { urls: [] };
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
if (import.meta.prerender) {
|
|
53
|
+
appendHeader(
|
|
54
|
+
resolvers.event,
|
|
55
|
+
"x-nitro-prerender",
|
|
56
|
+
Object.keys(chunks).map((name) => encodeURIComponent(`/${name}-sitemap.xml`)).join(", ")
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
const entries = [];
|
|
60
|
+
for (const name in chunks) {
|
|
61
|
+
const sitemap = chunks[name];
|
|
62
|
+
const entry = {
|
|
63
|
+
sitemap: resolvers.canonicalUrlResolver(`${name}-sitemap.xml`)
|
|
64
|
+
};
|
|
65
|
+
let lastmod = sitemap.urls.filter((a) => !!a?.lastmod).map((a) => typeof a.lastmod === "string" ? new Date(a.lastmod) : a.lastmod).sort((a, b) => (b?.getTime() || 0) - (a?.getTime() || 0))?.[0];
|
|
66
|
+
if (!lastmod && autoLastmod)
|
|
67
|
+
lastmod = /* @__PURE__ */ new Date();
|
|
68
|
+
if (lastmod)
|
|
69
|
+
entry.lastmod = normaliseDate(lastmod);
|
|
70
|
+
entries.push(entry);
|
|
71
|
+
}
|
|
72
|
+
if (sitemaps.index)
|
|
73
|
+
entries.push(...sitemaps.index.sitemaps);
|
|
74
|
+
const sitemapXml = entries.map((e) => [
|
|
75
|
+
" <sitemap>",
|
|
76
|
+
` <loc>${escapeValueForXml(e.sitemap)}</loc>`,
|
|
77
|
+
// lastmod is optional
|
|
78
|
+
e.lastmod ? ` <lastmod>${escapeValueForXml(e.lastmod)}</lastmod>` : false,
|
|
79
|
+
" </sitemap>"
|
|
80
|
+
].filter(Boolean).join("\n")).join("\n");
|
|
81
|
+
return wrapSitemapXml([
|
|
82
|
+
'<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">',
|
|
83
|
+
sitemapXml,
|
|
84
|
+
"</sitemapindex>"
|
|
85
|
+
], resolvers, { version, xsl, credits });
|
|
86
|
+
}
|