astro/packages/integrations/sitemap/src/index.ts

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

179 lines
4.6 KiB
TypeScript
Raw Normal View History

import type { AstroConfig, AstroIntegration } from 'astro';
2022-06-20 19:31:39 +00:00
import {
EnumChangefreq,
simpleSitemapAndIndex,
type LinkItem as LinkItemBase,
type SitemapItemLoose,
2022-06-20 19:31:39 +00:00
} from 'sitemap';
2022-06-16 19:08:44 +00:00
import { fileURLToPath } from 'url';
import { ZodError } from 'zod';
import { generateSitemap } from './generate-sitemap.js';
import { Logger } from './utils/logger.js';
import { validateOptions } from './validate-options.js';
export type ChangeFreq = `${EnumChangefreq}`;
export type SitemapItem = Pick<
SitemapItemLoose,
'url' | 'lastmod' | 'changefreq' | 'priority' | 'links'
>;
export type LinkItem = LinkItemBase;
export type SitemapOptions =
Migrate to new config (#2962) * wip: config migration * fix: formatting * refactor: projectRoot -> root * refactor: pageUrlFormat -> format * refactor: buildOptions.site -> site * refactor: public -> publicDir * refactor: dist -> outDir * refactor: styleOptions -> style * fix: some dist tests -> outDir * refactor: remove legacyBuild (with TODOs) * refactor: more legacyBuild cleanup * refactor: server host and port * fix: remove experimentalStaticBuild CLI flag * refactor: src -> srcDir * refactor: devOptions.trailing -> trailing * refactor: remove sitemap + related flags * refactor: experimentalSSR -> experimental.ssr * fix: last devOptions * refactor: drafts -> markdown.drafts * fix: TS error on port as const * refactor: remove pages * refactor: more --project-root updates * refactor: markdownOptions -> markdown * fix: remaining type errors * feat: update AstroUserConfig * refactor: update CLI flag mapper + server mapper * fix: loadFixture projectRoot * fix: merge CLI flags before validating / transforming * wip: attempt to fix bad createRouteManifest config * refactor: combine config.base and config.site * fix: skip route manifest test for now * fix: site and base handling * refactor: update failing config testes * fix: build failure * feat: update config types with migration help * chore: update types * fix(deno): update deno fixture * chore: remove config migration logic * chore: remove logLevel * chore: clean-up config types * chore: update config warning * chore: add changeset * Sitemap Integration (#2965) * feat: add sitemap filter config option * feat: add canonicalURL sitemap config option * docs: update sitemap README * fix: update for new config * fix: filter not being applied * chore: changeset Co-authored-by: bholmesdev <hey@bholmes.dev> * fred pass * fix: Astro.resolve typo * fix: public => publicDir Co-authored-by: bholmesdev <hey@bholmes.dev> Co-authored-by: Fred K. Schott <fkschott@gmail.com>
2022-04-02 18:29:59 +00:00
| {
filter?(page: string): boolean;
customPages?: string[];
i18n?: {
defaultLocale: string;
locales: Record<string, string>;
};
// number of entries per sitemap file
entryLimit?: number;
// sitemap specific
changefreq?: ChangeFreq;
lastmod?: Date;
priority?: number;
// called for each sitemap item just before to save them on disk, sync or async
serialize?(item: SitemapItem): SitemapItem | Promise<SitemapItem | undefined> | undefined;
Migrate to new config (#2962) * wip: config migration * fix: formatting * refactor: projectRoot -> root * refactor: pageUrlFormat -> format * refactor: buildOptions.site -> site * refactor: public -> publicDir * refactor: dist -> outDir * refactor: styleOptions -> style * fix: some dist tests -> outDir * refactor: remove legacyBuild (with TODOs) * refactor: more legacyBuild cleanup * refactor: server host and port * fix: remove experimentalStaticBuild CLI flag * refactor: src -> srcDir * refactor: devOptions.trailing -> trailing * refactor: remove sitemap + related flags * refactor: experimentalSSR -> experimental.ssr * fix: last devOptions * refactor: drafts -> markdown.drafts * fix: TS error on port as const * refactor: remove pages * refactor: more --project-root updates * refactor: markdownOptions -> markdown * fix: remaining type errors * feat: update AstroUserConfig * refactor: update CLI flag mapper + server mapper * fix: loadFixture projectRoot * fix: merge CLI flags before validating / transforming * wip: attempt to fix bad createRouteManifest config * refactor: combine config.base and config.site * fix: skip route manifest test for now * fix: site and base handling * refactor: update failing config testes * fix: build failure * feat: update config types with migration help * chore: update types * fix(deno): update deno fixture * chore: remove config migration logic * chore: remove logLevel * chore: clean-up config types * chore: update config warning * chore: add changeset * Sitemap Integration (#2965) * feat: add sitemap filter config option * feat: add canonicalURL sitemap config option * docs: update sitemap README * fix: update for new config * fix: filter not being applied * chore: changeset Co-authored-by: bholmesdev <hey@bholmes.dev> * fred pass * fix: Astro.resolve typo * fix: public => publicDir Co-authored-by: bholmesdev <hey@bholmes.dev> Co-authored-by: Fred K. Schott <fkschott@gmail.com>
2022-04-02 18:29:59 +00:00
}
| undefined;
function formatConfigErrorMessage(err: ZodError) {
const errorList = err.issues.map((issue) => ` ${issue.path.join('.')} ${issue.message + '.'}`);
return errorList.join('\n');
}
const PKG_NAME = '@astrojs/sitemap';
const OUTFILE = 'sitemap-index.xml';
const createPlugin = (options?: SitemapOptions): AstroIntegration => {
let config: AstroConfig;
const logger = new Logger(PKG_NAME);
return {
name: PKG_NAME,
hooks: {
'astro:config:done': async ({ config: cfg }) => {
config = cfg;
},
'astro:build:done': async ({ dir, routes, pages }) => {
try {
if (!config.site) {
logger.warn(
'The Sitemap integration requires the `site` astro.config option. Skipping.'
);
return;
}
const opts = validateOptions(config.site, options);
const { filter, customPages, serialize, entryLimit } = opts;
let finalSiteUrl: URL;
if (config.site) {
finalSiteUrl = new URL(config.base, config.site);
} else {
// eslint-disable-next-line no-console
console.warn(
'The Sitemap integration requires the `site` astro.config option. Skipping.'
);
return;
}
let pageUrls = pages.map((p) => {
if (p.pathname !== '' && !finalSiteUrl.pathname.endsWith('/'))
finalSiteUrl.pathname += '/';
const path = finalSiteUrl.pathname + p.pathname;
return new URL(path, finalSiteUrl).href;
});
let routeUrls = routes.reduce<string[]>((urls, r) => {
/**
* Dynamic URLs have entries with `undefined` pathnames
*/
if (r.pathname) {
/**
* remove the initial slash from relative pathname
* because `finalSiteUrl` always has trailing slash
*/
const path = finalSiteUrl.pathname + r.generate(r.pathname).substring(1);
let newUrl = new URL(path, finalSiteUrl).href;
if (config.trailingSlash === 'never') {
urls.push(newUrl);
} else if (config.build.format === 'directory' && !newUrl.endsWith('/')) {
urls.push(newUrl + '/');
} else {
urls.push(newUrl);
}
}
return urls;
}, []);
try {
if (filter) {
pageUrls = pageUrls.filter(filter);
}
} catch (err) {
logger.error(`Error filtering pages\n${(err as any).toString()}`);
return;
}
pageUrls = Array.from(new Set([...pageUrls, ...routeUrls, ...(customPages ?? [])]));
if (pageUrls.length === 0) {
logger.warn(`No pages found!\n\`${OUTFILE}\` not created.`);
return;
}
let urlData = generateSitemap(pageUrls, finalSiteUrl.href, opts);
if (serialize) {
try {
2022-06-20 19:29:53 +00:00
const serializedUrls: SitemapItem[] = [];
for (const item of urlData) {
const serialized = await Promise.resolve(serialize(item));
2022-06-27 18:12:43 +00:00
if (serialized) {
serializedUrls.push(serialized);
}
}
if (serializedUrls.length === 0) {
logger.warn('No pages found!');
return;
}
urlData = serializedUrls;
} catch (err) {
logger.error(`Error serializing pages\n${(err as any).toString()}`);
return;
}
}
await simpleSitemapAndIndex({
hostname: finalSiteUrl.href,
destinationDir: fileURLToPath(dir),
sourceData: urlData,
limit: entryLimit,
gzip: false,
});
logger.success(`\`${OUTFILE}\` is created.`);
} catch (err) {
if (err instanceof ZodError) {
logger.warn(formatConfigErrorMessage(err));
} else {
throw err;
}
}
},
},
};
};
export default createPlugin;