Skip to content

Commit 81dce94

Browse files
[RSS] Get ready for content collections (#5851)
* chore: strictNullChecks for zod * feat: expose `rssSchema` helper * refactor: align types with schema types * feat: break glob handler to globToRssItems util * refactor: RSS options validation to Zod * refactor: avoid intermediate type * fix: allow numbers and dates in pubDate * test: update glob and error tests * feat: add rss to with-content starter * fix: move globToRssItems back to internal behavior * chore: JSON.stringify * Revert "fix: move globToRssItems back to internal behavior" This reverts commit 8530507. * test: missing url * docs: `import.meta.env.SITE` -> `context.site` * docs: update README to content collections example * fix: url -> link * docs: add `rssSchema` to README * chore: consistent formatting * docs: add `pagesGlobToRssItems()` reference * chore: globToRssItems -> pagesGlobToRssItems * chore: changeset * fix: bad docs line highlighting * fix: add collections export to example * nit: remove "our" * fix: are -> all * fix: more README edits * deps: kleur * chore: add back import.meta.glob handling as deprecated * docs: bump down to `minor`, update headline to be less content collections-y * typo: suggest adding * chore: support URL object on `site` * docs: add await to pagesGlob ex * docs: tighten `rssSchema` explainer * docs: tighten pagesGlobToRssItems section * docs: add content to README * docs: replace examples with docs link * docs: re-we Co-authored-by: Sarah Rainsberger <[email protected]> Co-authored-by: Sarah Rainsberger <[email protected]>
1 parent 97267e3 commit 81dce94

12 files changed

Lines changed: 456 additions & 381 deletions

File tree

.changeset/fluffy-cups-travel.md

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
---
2+
'@astrojs/rss': minor
3+
---
4+
5+
Update RSS config for readability and consistency with Astro 2.0.
6+
7+
#### Migration - `import.meta.glob()` handling
8+
9+
We have deprecated `items: import.meta.glob(...)` handling in favor of a separate `pagesGlobToRssItems()` helper. This simplifies our `items` configuration option to accept a single type, without losing existing functionality.
10+
11+
If you rely on our `import.meta.glob()` handling, we suggest adding the `pagesGlobToRssItems()` wrapper to your RSS config:
12+
13+
```diff
14+
// src/pages/rss.xml.js
15+
import rss, {
16+
+ pagesGlobToRssItems
17+
} from '@astrojs/rss';
18+
19+
export function get(context) {
20+
return rss({
21+
+ items: pagesGlobToRssItems(
22+
import.meta.glob('./blog/*.{md,mdx}'),
23+
+ ),
24+
});
25+
}
26+
```
27+
28+
#### New `rssSchema` for content collections
29+
30+
`@astrojs/rss` now exposes an `rssSchema` for use with content collections. This ensures all RSS feed properties are present in your frontmatter:
31+
32+
```ts
33+
import { defineCollection } from 'astro:content';
34+
import { rssSchema } from '@astrojs/rss';
35+
36+
const blog = defineCollection({
37+
schema: rssSchema,
38+
});
39+
40+
export const collections = { blog };
41+
```

examples/blog/src/pages/rss.xml.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ export async function get(context) {
77
return rss({
88
title: SITE_TITLE,
99
description: SITE_DESCRIPTION,
10-
site: context.site.href,
10+
site: context.site,
1111
items: posts.map((post) => ({
1212
...post.data,
1313
link: `/blog/${post.slug}/`,

packages/astro-rss/README.md

Lines changed: 108 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -19,46 +19,62 @@ pnpm i @astrojs/rss
1919

2020
The `@astrojs/rss` package provides helpers for generating RSS feeds within [Astro endpoints][astro-endpoints]. This unlocks both static builds _and_ on-demand generation when using an [SSR adapter](https://docs.astro.build/en/guides/server-side-rendering/#enabling-ssr-in-your-project).
2121

22-
For instance, say you need to generate an RSS feed for all posts under `src/pages/blog/`. Start by [adding a `site` to your project's `astro.config` for link generation](https://docs.astro.build/en/reference/configuration-reference/#site). Then, create an `rss.xml.js` file under your project's `src/pages/` directory, and use [Vite's `import.meta.glob` helper](https://vitejs.dev/guide/features.html#glob-import) like so:
22+
For instance, say you need to generate an RSS feed for all posts under `src/content/blog/` using content collections.
23+
24+
Start by [adding a `site` to your project's `astro.config` for link generation](https://docs.astro.build/en/reference/configuration-reference/#site). Then, create an `rss.xml.js` file under your project's `src/pages/` directory, and [use `getCollection()`](https://docs.astro.build/en/guides/content-collections/#getcollection) to generate a feed from all documents in the `blog` collection:
2325

2426
```js
2527
// src/pages/rss.xml.js
2628
import rss from '@astrojs/rss';
29+
import { getCollection } from 'astro:content';
2730

28-
export const get = () => rss({
31+
export async function get(context) {
32+
const posts = await getCollection('blog');
33+
return rss({
2934
title: 'Buzz’s Blog',
3035
description: 'A humble Astronaut’s guide to the stars',
31-
// pull in the "site" from your project's astro.config
32-
site: import.meta.env.SITE,
33-
items: import.meta.glob('./blog/**/*.md'),
36+
// Pull in your project "site" from the endpoint context
37+
// https://docs.astro.build/en/reference/api-reference/#contextsite
38+
site: context.site,
39+
items: posts.map(post => ({
40+
// Assumes all RSS feed item properties are in post frontmatter
41+
...post.data,
42+
// Generate a `url` from each post `slug`
43+
// This assumes all blog posts are rendered as `/blog/[slug]` routes
44+
// https://docs.astro.build/en/guides/content-collections/#generating-pages-from-content-collections
45+
link: `/blog/${post.slug}/`,
46+
}))
3447
});
48+
}
3549
```
3650

37-
Read **[Astro's RSS docs][astro-rss]** for full usage examples.
51+
Read **[Astro's RSS docs][astro-rss]** for more on using content collections, and instructions for globbing entries in `/src/pages/`.
3852

3953
## `rss()` configuration options
4054

4155
The `rss` default export offers a number of configuration options. Here's a quick reference:
4256

4357
```js
44-
rss({
45-
// `<title>` field in output xml
46-
title: 'Buzz’s Blog',
47-
// `<description>` field in output xml
48-
description: 'A humble Astronaut’s guide to the stars',
49-
// provide a base URL for RSS <item> links
50-
site: import.meta.env.SITE,
51-
// list of `<item>`s in output xml
52-
items: import.meta.glob('./**/*.md'),
53-
// include draft posts in the feed (default: false)
54-
drafts: true,
55-
// (optional) absolute path to XSL stylesheet in your project
56-
stylesheet: '/rss-styles.xsl',
57-
// (optional) inject custom xml
58-
customData: '<language>en-us</language>',
59-
// (optional) add arbitrary metadata to opening <rss> tag
60-
xmlns: { h: 'http://www.w3.org/TR/html4/' },
61-
});
58+
export function get(context) {
59+
return rss({
60+
// `<title>` field in output xml
61+
title: 'Buzz’s Blog',
62+
// `<description>` field in output xml
63+
description: 'A humble Astronaut’s guide to the stars',
64+
// provide a base URL for RSS <item> links
65+
site: context.site,
66+
// list of `<item>`s in output xml
67+
items: [...],
68+
// include draft posts in the feed (default: false)
69+
drafts: true,
70+
// (optional) absolute path to XSL stylesheet in your project
71+
stylesheet: '/rss-styles.xsl',
72+
// (optional) inject custom xml
73+
customData: '<language>en-us</language>',
74+
// (optional) add arbitrary metadata to opening <rss> tag
75+
xmlns: { h: 'http://www.w3.org/TR/html4/' },
76+
});
77+
}
6278
```
6379

6480
### title
@@ -77,13 +93,22 @@ The `<description>` attribute of your RSS feed's output xml.
7793

7894
Type: `string (required)`
7995

80-
The base URL to use when generating RSS item links. We recommend using `import.meta.env.SITE` to pull in the "site" from your project's astro.config. Still, feel free to use a custom base URL if necessary.
96+
The base URL to use when generating RSS item links. We recommend using the [endpoint context object](https://docs.astro.build/en/reference/api-reference/#contextsite), which includes the `site` configured in your project's `astro.config.*`:
97+
98+
```ts
99+
import rss from '@astrojs/rss';
100+
101+
export const get = (context) => rss({
102+
site: context.site,
103+
...
104+
});
105+
```
81106

82107
### items
83108

84-
Type: `RSSFeedItem[] | GlobResult (required)`
109+
Type: `RSSFeedItem[] (required)`
85110

86-
Either a list of formatted RSS feed items or the result of [Vite's `import.meta.glob` helper](https://vitejs.dev/guide/features.html#glob-import). See [Astro's RSS items documentation](https://docs.astro.build/en/guides/rss/#generating-items) for usage examples to choose the best option for you.
111+
A list of formatted RSS feed items. See [Astro's RSS items documentation](https://docs.astro.build/en/guides/rss/#generating-items) for usage examples to choose the best option for you.
87112

88113
When providing a formatted RSS item list, see the `RSSFeedItem` type reference below:
89114

@@ -152,6 +177,62 @@ Will inject the following XML:
152177
<rss xmlns:h="http://www.w3.org/TR/html4/"...
153178
```
154179

180+
### content
181+
182+
The `content` key contains the full content of the post as HTML. This allows you to make your entire post content available to RSS feed readers.
183+
184+
**Note:** Whenever you're using HTML content in XML, we suggest using a package like [`sanitize-html`](https://www.npmjs.com/package/sanitize-html) in order to make sure that your content is properly sanitized, escaped, and encoded.
185+
186+
[See our RSS documentation](https://docs.astro.build/en/guides/rss/#including-full-post-content) for examples using content collections and glob imports.
187+
188+
## `rssSchema`
189+
190+
When using content collections, you can configure your collection schema to enforce expected [`RSSFeedItem`](#items) properties. Import and apply `rssSchema` to ensure that each collection entry produces a valid RSS feed item:
191+
192+
```ts "schema: rssSchema,"
193+
import { defineCollection } from 'astro:content';
194+
import { rssSchema } from '@astrojs/rss';
195+
196+
const blog = defineCollection({
197+
schema: rssSchema,
198+
});
199+
200+
export const collections = { blog };
201+
```
202+
203+
If you have an existing schema, you can merge extra properties using `extends()`:
204+
205+
```ts ".extends({ extraProperty: z.string() }),"
206+
import { defineCollection } from 'astro:content';
207+
import { rssSchema } from '@astrojs/rss';
208+
209+
const blog = defineCollection({
210+
schema: rssSchema.extends({ extraProperty: z.string() }),
211+
});
212+
```
213+
214+
## `pagesGlobToRssItems()`
215+
216+
To create an RSS feed from documents in `src/pages/`, use the `pagesGlobToRssItems()` helper. This accepts an `import.meta.glob` result ([see Vite documentation](https://vitejs.dev/guide/features.html#glob-import)) and outputs an array of valid [`RSSFeedItem`s](#items).
217+
218+
This function assumes, but does not verify, you are globbing for items inside `src/pages/`, and all necessary feed properties are present in each document's frontmatter. If you encounter errors, verify each page frontmatter manually.
219+
220+
```ts "pagesGlobToRssItems"
221+
// src/pages/rss.xml.js
222+
import rss, { pagesGlobToRssItems } from '@astrojs/rss';
223+
224+
export async function get(context) {
225+
return rss({
226+
title: 'Buzz’s Blog',
227+
description: 'A humble Astronaut’s guide to the stars',
228+
site: context.site,
229+
items: await pagesGlobToRssItems(
230+
import.meta.glob('./blog/*.{md,mdx}'),
231+
),
232+
});
233+
}
234+
```
235+
155236
---
156237

157238
For more on building with Astro, [visit the Astro docs][astro-rss].

packages/astro-rss/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
"mocha": "^9.2.2"
3636
},
3737
"dependencies": {
38-
"fast-xml-parser": "^4.0.8"
38+
"fast-xml-parser": "^4.0.8",
39+
"kleur": "^4.1.5"
3940
}
4041
}

0 commit comments

Comments
 (0)