feat: add support for styled RSS feeds (#2371)

This commit is contained in:
Nate Moore 2022-01-19 14:34:52 -06:00 committed by GitHub
parent 04c2e2e4cd
commit 85ad1aab67
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 170 additions and 12 deletions

View file

@ -0,0 +1,5 @@
---
'astro': patch
---
Add support for styled RSS feeds using the new `stylesheet` option

View file

@ -4,7 +4,7 @@ title: RSS
description: An intro to RSS in Astro
---
Astro supports fast, automatic RSS feed generation for blogs and other content websites.
Astro supports fast, automatic RSS feed generation for blogs and other content websites. For more information about RSS feeds in general, see [aboutfeeds.com](https://aboutfeeds.com/).
You can create an RSS feed from any Astro page that uses a `getStaticPaths()` function for routing. Only dynamic routes can use `getStaticPaths()` today (see [Routing](/en/core-concepts/routing)).
@ -22,6 +22,8 @@ export async function getStaticPaths({rss}) {
rss({
// The RSS Feed title, description, and custom metadata.
title: 'Don\'s Blog',
// See "Styling" section below
stylesheet: true,
description: 'An example blog on Astro',
customData: `<language>en-us</language>`,
// The list of items for your RSS feed, sorted.
@ -41,3 +43,11 @@ export async function getStaticPaths({rss}) {
```
Note: RSS feeds will **not** be built during development. Currently, RSS feeds are only generated during your final build.
### Styling
RSS Feeds can be styled with an XSL stylesheet for a more pleasant user experience when they are opened directly in a browser. By default, Astro does not set a stylesheet for RSS feeds, but it can be enabled by setting the `stylesheet` option.
Astro can automatically use [Pretty Feed](https://github.com/genmon/aboutfeeds/blob/main/tools/pretty-feed-v3.xsl), a popular open-source XSL stylesheet. To enable this behavior, pass `stylesheet: true`.
If you'd like to use a custom XSL stylesheet, you can pass a string value like `stylesheet: '/my-custom-stylesheet.xsl'`. This file should be in your `public/` directory (in this case, `public/my-custom-stylesheet.xsl`).

View file

@ -339,6 +339,12 @@ export interface RSS {
description: string;
/** Specify arbitrary metadata on opening <xml> tag */
xmlns?: Record<string, string>;
/**
* If false (default), does not include XSL stylesheet.
* If true, automatically includes 'pretty-feed-v3'.
* If a string value, specifies a local custom XSL stylesheet, for example '/custom-feed.xsl'.
*/
stylesheet?: string | boolean;
/** Specify custom data in opening of file */
customData?: string;
/**
@ -364,7 +370,8 @@ export interface RSS {
export type RSSFunction = (args: RSS) => void;
export type RSSResult = { url: string; xml?: string };
export type FeedResult = { url: string; content?: string; };
export type RSSResult = { xml: FeedResult; xsl?: FeedResult };
export type SSRError = Error & vite.ErrorPayload['err'];

View file

@ -79,11 +79,22 @@ export async function collectPagesData(opts: CollectPagesDataOptions): Promise<C
throw err;
});
if (result.rss?.xml) {
const rssFile = new URL(result.rss.url.replace(/^\/?/, './'), astroConfig.dist);
if (assets[fileURLToPath(rssFile)]) {
throw new Error(`[getStaticPaths] RSS feed ${result.rss.url} already exists.\nUse \`rss(data, {url: '...'})\` to choose a unique, custom URL. (${route.component})`);
const { url, content } = result.rss.xml;
if (content) {
const rssFile = new URL(url.replace(/^\/?/, './'), astroConfig.dist);
if (assets[fileURLToPath(rssFile)]) {
throw new Error(`[getStaticPaths] RSS feed ${url} already exists.\nUse \`rss(data, {url: '...'})\` to choose a unique, custom URL. (${route.component})`);
}
assets[fileURLToPath(rssFile)] = content;
}
assets[fileURLToPath(rssFile)] = result.rss.xml;
}
if (result.rss?.xsl?.content) {
const { url, content } = result.rss.xsl;
const stylesheetFile = new URL(url.replace(/^\/?/, './'), astroConfig.dist);
if (assets[fileURLToPath(stylesheetFile)]) {
throw new Error(`[getStaticPaths] RSS feed stylesheet ${url} already exists.\nUse \`rss(data, {stylesheet: '...'})\` to choose a unique, custom URL. (${route.component})`);
}
assets[fileURLToPath(stylesheetFile)] = content;
}
allPages[route.component] = {
route,

View file

@ -1,7 +1,7 @@
import type { RSSFunction, RSS, RSSResult, RouteData } from '../../@types/astro';
import type { RSSFunction, RSS, RSSResult, FeedResult, RouteData } from '../../@types/astro';
import { XMLValidator } from 'fast-xml-parser';
import { canonicalURL } from '../util.js';
import { canonicalURL, PRETTY_FEED_V3 } from '../util.js';
/** Validates getStaticPaths.rss */
export function validateRSS(args: GenerateRSSArgs): void {
@ -20,7 +20,11 @@ export function generateRSS(args: GenerateRSSArgs): string {
const { srcFile, feedURL, rssData, site } = args;
if ((rssData as any).item) throw new Error(`[${srcFile}] rss() \`item()\` function was deprecated, and is now \`items: object[]\`.`);
let xml = `<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"`;
let xml = `<?xml version="1.0" encoding="UTF-8"?>`;
if (typeof rssData.stylesheet === 'string') {
xml += `<?xml-stylesheet href="${rssData.stylesheet}" type="text/xsl"?>`;
}
xml += `<rss version="2.0"`;
// xmlns
if (rssData.xmlns) {
@ -72,18 +76,35 @@ export function generateRSS(args: GenerateRSSArgs): string {
return xml;
}
export function generateRSSStylesheet() {
return PRETTY_FEED_V3;
}
/** Generated function to be run */
export function generateRssFunction(site: string | undefined, route: RouteData): { generator: RSSFunction; rss?: RSSResult } {
let result: RSSResult = {} as any;
return {
generator: function rssUtility(args: any) {
generator: function rssUtility(args: RSS) {
if (!site) {
throw new Error(`[${route.component}] rss() tried to generate RSS but "buildOptions.site" missing in astro.config.mjs`);
}
const { dest, ...rssData } = args;
const feedURL = dest || '/rss.xml';
result.url = feedURL;
result.xml = generateRSS({ rssData, site, srcFile: route.component, feedURL });
if (rssData.stylesheet === true) {
rssData.stylesheet = feedURL.replace(/\.xml$/, '.xsl');
result.xsl = {
url: rssData.stylesheet,
content: generateRSSStylesheet(),
}
} else if (typeof rssData.stylesheet === 'string') {
result.xsl = {
url: rssData.stylesheet,
}
}
result.xml = {
url: feedURL,
content: generateRSS({ rssData, site, srcFile: route.component, feedURL })
};
},
rss: result,
};

File diff suppressed because one or more lines are too long