summaryrefslogtreecommitdiff
path: root/packages/integrations/sitemap/src/index.ts
blob: 70441cb8eb910be8476d65bd58e8eb539f4d2723 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
import fs from 'node:fs';
import type { AstroConfig, AstroIntegration } from 'astro';
const STATUS_CODE_PAGE_REGEXP = /\/[0-9]{3}\/?$/;

type SitemapOptions =
	| {
			/**
			 * All pages are included in your sitemap by default.
			 * With this config option, you can filter included pages by URL.
			 *
			 * The `page` function parameter is the full URL of your rendered page, including your `site` domain.
			 * Return `true` to include a page in your sitemap, and `false` to remove it.
			 *
			 * ```js
			 * filter: (page) => page !== 'http://example.com/secret-page'
			 * ```
			 */
			filter?(page: string): string;

			/**
			 * If present, we use the `site` config option as the base for all sitemap URLs
			 * Use `canonicalURL` to override this
			 */
			canonicalURL?: string;
	  }
	| undefined;

/** Construct sitemap.xml given a set of URLs */
function generateSitemap(pages: string[]) {
	// TODO: find way to respect <link rel="canonical"> URLs here
	const urls = [...pages].filter((url) => !STATUS_CODE_PAGE_REGEXP.test(url));
	urls.sort((a, b) => a.localeCompare(b, 'en', { numeric: true })); // sort alphabetically so sitemap is same each time
	let sitemap = `<?xml version="1.0" encoding="UTF-8"?><urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">`;
	for (const url of urls) {
		sitemap += `<url><loc>${url}</loc></url>`;
	}
	sitemap += `</urlset>\n`;
	return sitemap;
}

export default function createPlugin({
	filter,
	canonicalURL,
}: SitemapOptions = {}): AstroIntegration {
	let config: AstroConfig;
	return {
		name: '@astrojs/sitemap',
		hooks: {
			'astro:config:done': async ({ config: _config }) => {
				config = _config;
			},
			'astro:build:done': async ({ pages, dir }) => {
				const finalSiteUrl = canonicalURL || config.site;
				if (!finalSiteUrl) {
					console.warn(
						'The Sitemap integration requires either the `site` astro.config option or `canonicalURL` integration option. Skipping.'
					);
					return;
				}
				let pageUrls = pages.map((p) => new URL(p.pathname, finalSiteUrl).href);
				if (filter) {
					pageUrls = pageUrls.filter((page: string) => filter(page));
				}
				const sitemapContent = generateSitemap(pageUrls);
				fs.writeFileSync(new URL('sitemap.xml', dir), sitemapContent);
			},
		},
	};
}