0
Fork 0
mirror of https://github.com/withastro/astro.git synced 2024-12-30 22:03:56 -05:00

fix(sitemap): Trailing slashes on root url (#10772)

* add tests that reveal issue

* fix trailing slash root page issue

* add changeset
This commit is contained in:
Robin Gisler 2024-04-18 09:09:25 +02:00 committed by GitHub
parent 914daad18b
commit 0e22462d15
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 117 additions and 13 deletions

View file

@ -0,0 +1,5 @@
---
"@astrojs/sitemap": patch
---
Fixes an issue where the root url does not follow the `trailingSlash` config option

View file

@ -34,6 +34,7 @@
},
"dependencies": {
"sitemap": "^7.1.1",
"stream-replace-string": "^2.0.0",
"zod": "^3.22.4"
},
"devDependencies": {

View file

@ -2,11 +2,11 @@ import path from 'node:path';
import { fileURLToPath } from 'node:url';
import type { AstroConfig, AstroIntegration } from 'astro';
import type { EnumChangefreq, LinkItem as LinkItemBase, SitemapItemLoose } from 'sitemap';
import { simpleSitemapAndIndex } from 'sitemap';
import { ZodError } from 'zod';
import { generateSitemap } from './generate-sitemap.js';
import { validateOptions } from './validate-options.js';
import { generateSitemap } from './generate-sitemap.js';
import { writeSitemap } from './write-sitemap.js';
export { EnumChangefreq as ChangeFreqEnum } from 'sitemap';
export type ChangeFreq = `${EnumChangefreq}`;
@ -167,14 +167,13 @@ const createPlugin = (options?: SitemapOptions): AstroIntegration => {
}
}
const destDir = fileURLToPath(dir);
await simpleSitemapAndIndex({
await writeSitemap({
hostname: finalSiteUrl.href,
destinationDir: destDir,
publicBasePath: config.base,
sourceData: urlData,
limit: entryLimit,
gzip: false,
});
limit: entryLimit
}, config)
logger.info(`\`${OUTFILE}\` created at \`${path.relative(process.cwd(), destDir)}\``);
} catch (err) {
if (err instanceof ZodError) {

View file

@ -0,0 +1,69 @@
import { normalize, resolve } from 'path';
import { createWriteStream, type WriteStream } from 'fs'
import { mkdir } from 'fs/promises';
import { promisify } from 'util';
import { Readable, pipeline } from 'stream';
import replace from 'stream-replace-string'
import { SitemapAndIndexStream, SitemapStream } from 'sitemap';
import type { AstroConfig } from 'astro';
import type { SitemapItem } from "./index.js";
type WriteSitemapConfig = {
hostname: string;
sitemapHostname?: string;
sourceData: SitemapItem[];
destinationDir: string;
publicBasePath?: string;
limit?: number;
}
// adapted from sitemap.js/sitemap-simple
export async function writeSitemap({ hostname, sitemapHostname = hostname,
sourceData, destinationDir, limit = 50000, publicBasePath = './', }: WriteSitemapConfig, astroConfig: AstroConfig) {
await mkdir(destinationDir, { recursive: true })
const sitemapAndIndexStream = new SitemapAndIndexStream({
limit,
getSitemapStream: (i) => {
const sitemapStream = new SitemapStream({
hostname,
});
const path = `./sitemap-${i}.xml`;
const writePath = resolve(destinationDir, path);
if (!publicBasePath.endsWith('/')) {
publicBasePath += '/';
}
const publicPath = normalize(publicBasePath + path);
let stream: WriteStream
if (astroConfig.trailingSlash === 'never' || astroConfig.build.format === 'file') {
// workaround for trailing slash issue in sitemap.js: https://github.com/ekalinin/sitemap.js/issues/403
const host = hostname.endsWith('/') ? hostname.slice(0, -1) : hostname
const searchStr = `<loc>${host}/</loc>`
const replaceStr = `<loc>${host}</loc>`
stream = sitemapStream.pipe(replace(searchStr, replaceStr)).pipe(createWriteStream(writePath))
} else {
stream = sitemapStream.pipe(createWriteStream(writePath))
}
return [
new URL(
publicPath,
sitemapHostname
).toString(),
sitemapStream,
stream,
];
},
});
let src = Readable.from(sourceData)
const indexPath = resolve(
destinationDir,
`./sitemap-index.xml`
);
return promisify(pipeline)(src, sitemapAndIndexStream, createWriteStream(indexPath));
}

View file

@ -0,0 +1,8 @@
<html>
<head>
<title>Index</title>
</head>
<body>
<h1>Index</h1>
</body>
</html>

View file

@ -22,7 +22,10 @@ describe('Trailing slash', () => {
it('URLs end with trailing slash', async () => {
const data = await readXML(fixture.readFile('/sitemap-0.xml'));
const urls = data.urlset.url;
assert.equal(urls[0].loc[0], 'http://example.com/one/');
assert.equal(urls[0].loc[0], 'http://example.com/');
assert.equal(urls[1].loc[0], 'http://example.com/one/');
assert.equal(urls[2].loc[0], 'http://example.com/two/');
});
});
@ -41,7 +44,10 @@ describe('Trailing slash', () => {
it('URLs do not end with trailing slash', async () => {
const data = await readXML(fixture.readFile('/sitemap-0.xml'));
const urls = data.urlset.url;
assert.equal(urls[0].loc[0], 'http://example.com/one');
assert.equal(urls[0].loc[0], 'http://example.com');
assert.equal(urls[1].loc[0], 'http://example.com/one');
assert.equal(urls[2].loc[0], 'http://example.com/two');
});
});
});
@ -55,10 +61,13 @@ describe('Trailing slash', () => {
await fixture.build();
});
it('URLs do no end with trailing slash', async () => {
it('URLs do not end with trailing slash', async () => {
const data = await readXML(fixture.readFile('/sitemap-0.xml'));
const urls = data.urlset.url;
assert.equal(urls[0].loc[0], 'http://example.com/one');
assert.equal(urls[0].loc[0], 'http://example.com');
assert.equal(urls[1].loc[0], 'http://example.com/one');
assert.equal(urls[2].loc[0], 'http://example.com/two');
});
describe('with base path', () => {
before(async () => {
@ -73,7 +82,9 @@ describe('Trailing slash', () => {
it('URLs do not end with trailing slash', async () => {
const data = await readXML(fixture.readFile('/sitemap-0.xml'));
const urls = data.urlset.url;
assert.equal(urls[0].loc[0], 'http://example.com/base/one');
assert.equal(urls[0].loc[0], 'http://example.com/base');
assert.equal(urls[1].loc[0], 'http://example.com/base/one');
assert.equal(urls[2].loc[0], 'http://example.com/base/two');
});
});
});
@ -90,7 +101,9 @@ describe('Trailing slash', () => {
it('URLs end with trailing slash', async () => {
const data = await readXML(fixture.readFile('/sitemap-0.xml'));
const urls = data.urlset.url;
assert.equal(urls[0].loc[0], 'http://example.com/one/');
assert.equal(urls[0].loc[0], 'http://example.com/');
assert.equal(urls[1].loc[0], 'http://example.com/one/');
assert.equal(urls[2].loc[0], 'http://example.com/two/');
});
describe('with base path', () => {
before(async () => {
@ -105,7 +118,9 @@ describe('Trailing slash', () => {
it('URLs end with trailing slash', async () => {
const data = await readXML(fixture.readFile('/sitemap-0.xml'));
const urls = data.urlset.url;
assert.equal(urls[0].loc[0], 'http://example.com/base/one/');
assert.equal(urls[0].loc[0], 'http://example.com/base/');
assert.equal(urls[1].loc[0], 'http://example.com/base/one/');
assert.equal(urls[2].loc[0], 'http://example.com/base/two/');
});
});
});

View file

@ -4888,6 +4888,9 @@ importers:
sitemap:
specifier: ^7.1.1
version: 7.1.1
stream-replace-string:
specifier: ^2.0.0
version: 2.0.0
zod:
specifier: ^3.22.4
version: 3.22.4
@ -15642,6 +15645,10 @@ packages:
bl: 5.1.0
dev: false
/stream-replace-string@2.0.0:
resolution: {integrity: sha512-TlnjJ1C0QrmxRNrON00JvaFFlNh5TTG00APw23j74ET7gkQpTASi6/L2fuiav8pzK715HXtUeClpBTw2NPSn6w==}
dev: false
/stream-transform@2.1.3:
resolution: {integrity: sha512-9GHUiM5hMiCi6Y03jD2ARC1ettBXkQBoQAe7nJsPknnI0ow10aXjTnew8QtYQmLjzn974BnmWEAJgCY6ZP1DeQ==}
dependencies: