From f67e8f5f559ecb37db71fbea1b60b570bc6bfd47 Mon Sep 17 00:00:00 2001 From: "Fred K. Schott" Date: Wed, 21 Jul 2021 07:11:57 -0700 Subject: [PATCH] New Collections API (#703) * updated createCollection API * Update examples/portfolio/src/pages/projects.astro Co-authored-by: Caleb Jasik * Update docs/reference/api-reference.md Co-authored-by: Caleb Jasik * fix(docs): collection doc typos (#758) * keep cleaning up docs and adding tests Co-authored-by: Caleb Jasik Co-authored-by: Mark Pinero --- .changeset/famous-bees-fry.md | 9 + docs/src/pages/core-concepts/collections.md | 342 +++++++++++------- docs/src/pages/reference/api-reference.md | 170 ++++----- .../src/pages/$author.astro | 55 ++- .../src/pages/$posts.astro | 34 +- .../src/pages/index.astro | 4 +- .../pages/{$projects.astro => projects.astro} | 15 +- packages/astro/package.json | 1 + packages/astro/src/@types/astro.ts | 27 +- packages/astro/src/build.ts | 10 +- packages/astro/src/build/page.ts | 109 +++--- packages/astro/src/compiler/index.ts | 4 +- packages/astro/src/internal/fetch-content.ts | 1 - packages/astro/src/runtime.ts | 217 ++++++----- packages/astro/src/search.ts | 71 ++-- packages/astro/src/util.ts | 30 ++ packages/astro/test/astro-collection.test.js | 86 +---- packages/astro/test/astro-global.test.js | 1 - .../astro-collection/src/pages/$grouped.astro | 30 +- .../src/pages/$individual.astro | 29 -- .../src/pages/$individuals.astro | 24 -- .../astro-collection/src/pages/$nested.astro | 27 -- .../src/pages/$paginated.astro | 22 +- .../src/pages/$params-and-paginated.astro | 33 ++ .../astro-collection/src/pages/$remote.astro | 28 +- .../astro-collection/src/pages/$shallow.astro | 28 -- .../astro-global/src/pages/$posts.astro | 14 +- .../astro-rss/src/pages/$episodes.astro | 19 +- yarn.lock | 5 + 29 files changed, 713 insertions(+), 732 deletions(-) create mode 100644 .changeset/famous-bees-fry.md rename examples/portfolio/src/pages/{$projects.astro => projects.astro} (64%) create mode 100644 packages/astro/src/util.ts delete mode 100644 packages/astro/test/fixtures/astro-collection/src/pages/$individual.astro delete mode 100644 packages/astro/test/fixtures/astro-collection/src/pages/$individuals.astro delete mode 100644 packages/astro/test/fixtures/astro-collection/src/pages/$nested.astro create mode 100644 packages/astro/test/fixtures/astro-collection/src/pages/$params-and-paginated.astro delete mode 100644 packages/astro/test/fixtures/astro-collection/src/pages/$shallow.astro diff --git a/.changeset/famous-bees-fry.md b/.changeset/famous-bees-fry.md new file mode 100644 index 000000000..cdb2cac82 --- /dev/null +++ b/.changeset/famous-bees-fry.md @@ -0,0 +1,9 @@ +--- +'astro': minor +--- + +New Collections API (createCollection) + +[BREAKING CHANGE:] The expected return format from createCollection() has been changed. Visit https://docs.astro.build/core-concepts/collections to learn the new API. + +This feature was implemented with backwards-compatible deprecation warnings, to help you find and update pages that are using the legacy API. diff --git a/docs/src/pages/core-concepts/collections.md b/docs/src/pages/core-concepts/collections.md index 96da0b6a5..f2e9a2aab 100644 --- a/docs/src/pages/core-concepts/collections.md +++ b/docs/src/pages/core-concepts/collections.md @@ -3,60 +3,191 @@ layout: ~/layouts/Main.astro title: Collections --- -**Collections** are a special type of [Page](/core-concepts/astro-pages) that help you generate multiple pages from a larger set of data. Example use-cases include: +**Collections** are a special type of [page](/core-concepts/astro-pages) in Astro that can generate multiple pages at different URLs for a larger set of data. If you've seen an Astro file that starts with a dollar sign (ex: `$posts.astro`), that's a collection. -- Pagination: `/posts/1`, `/posts/2`, etc. -- Grouping content by author: `/author/fred`, `/author/matthew`, etc. -- Grouping content by some tag: `/tags/red`, `/tags/blue`, etc. -- Working with remote data -- Mixing remote and local data +Example use-cases include: -**Use a Collection when you need to generate multiple pages from a single template.** If you just want to generate a single page (ex: a long list of every post on your site) then you can just fetch that data on a normal Astro page without using the Collection API. +- Generating multiple pages from remote data +- Generating multiple pages from local data (ex: list all markdown posts) +- pagination: `/posts/1`, `/posts/2`, etc. +- Grouping items into multiple pages: `/author/fred`, `/author/matthew`, etc. +- Generating one page per item: `/pokemon/pikachu`, `/pokemon/charmander`, etc. + +**Use a Collection when you need to generate multiple pages from a single template.** If you just want to generate a single page -- like a long list linking to every post on your blog -- then you can just use a normal [page](/core-concepts/astro-pages). ## Using Collections -To create a new Astro Collection, you must do three things: +To create a new Astro Collection, you need to do two things: -1. Create a new file in the `src/pages` directory that starts with the `$` symbol. This is required to enable the Collections API. +### 1. Create the File -- Example: `src/pages/$posts.astro` -> `/posts/1`, `/posts/2`, etc. -- Example: `src/pages/$tags.astro` -> `/tags/:tag` (or `/tags/:tag/1`) +Create a new file in the `src/pages` directory that starts with the dollar sign (`$`) symbol. This symbol is required to enable the Collections API. -2. Define and export the `collection` prop: `collection.data` is how you'll access the data for every page in the collection. Astro populates this prop for you automatically. It MUST be named `collection` and it must be exported. +Astro uses file-based routing, which means that the file must match the URL that you expect to generate. You are able to define a custom route structure in the next step, but the collection file name must always match the start of the URL. -- Example: `const { collection } = Astro.props;` +- **Example**: `src/pages/$tags.astro` -> `/tags/:tag` +- **Example**: `src/pages/$posts.astro` -> `/posts/1`, `/posts/2`, etc. -3. Define and export `createCollection` function: this tells Astro how to load and structure your collection data. Check out the examples below for documentation on how it should be implemented. It MUST be named `createCollection` and it must be exported. +### 2. Export createCollection -- Example: `export async function createCollection() { /* ... */ }` -- API Reference: [createCollection](/reference/api-reference#collections-api) +Every collection must define and export a `createCollection` function inside the component script. This exported function is where you fetch your data for the entire collection and tell Astro the exact URLs that you'd like to generate. It **MUST** be named `createCollection` and it must be exported. Check out the examples below for examples of how this should be implemented. -## Example: Simple Pagination +```astro +--- +export async function createCollection() { + /* fetch collection data here */ + return { /* see examples below */ }; +} +--- + +``` + +API Reference: [createCollection](/reference/api-reference#collections-api) + +## Example: Individual Pages + +One of the most common reasons to use a collection is to generate a page for every item fetched from a larger dataset. In this example, we'll query a remote API and use the result to generate 150 different pages: one for each pokemon returned by the API call. + +Run this example in development, and then visit [http://localhost:3000/pokemon/pikachu](http://localhost:3000/pokemon/pikachu) to see one of the generated pages. ```jsx --- -// Define the `collection` prop. -const { collection } = Astro.props; - +// Example: src/pages/$pokemon.astro // Define a `createCollection` function. +// In this example, we'll create a new page for every single pokemon. export async function createCollection() { - const allPosts = Astro.fetchContent('../posts/*.md'); // fetch local posts. - allPosts.sort((a, b) => a.title.localeCompare(b.title)); // sort by title. + // Do your data fetching here. + const allPokemonResponse = await fetch(`https://pokeapi.co/api/v2/pokemon?limit=150`); + const allPokemonResult = await allPokemonResponse.json(); + const allPokemon = allPokemonResult.results; return { - // Because you are not doing anything more than simple pagination, - // its fine to just return the full set of posts for the collection data. - async data() { return allPosts; }, - // number of posts loaded per page (default: 25) - pageSize: 10, + // `route` defines the URL structure for your collection. + // You can use any URL path pattern here, as long as it + // matches the filename prefix (`$pokemon.astro` -> `/pokemon/*`). + route: `/pokemon/:name`, + // `paths` tells Astro which pages to generate in your collection. + // Provide an array of `params` objects that match the `route` pattern. + paths() { + return allPokemon.map((pokemon, i) => ({params: {name: pokemon.name}})); + }, + // For each individual page, return the data needed on each page. + // If you needed to fetch more data for each page, you can do that here as well. + // Luckily, we loaded all of the data that we need at the top of the function. + async props({ params }) { + return {item: allPokemon.find((pokemon) => pokemon.name === params.name)}; + }, }; } +// The rest of your component script now runs on each individual page. +// "item" is one of the props returned in the `props()` function. +const {item} = Astro.props; --- - Pagination Example: Page Number {collection.page.current} + Pokemon: {item.name}</head> + <body> + Who's that pokemon? It's {item.name}! + </body> +</html> +``` + +## Example: Grouping Content by Page + +You can also group items by page. In this example, we'll fetch data from the same Pokemon API. But instead of generating 150 pages, we'll just generate one page for every letter of the alphabet, creating an alphabetical index of Pokemon. + +*Note: Looking for pagination? Collections have built-in support to make pagination easy. Be sure to check out the next example.* + +```jsx +--- +// Define a `createCollection` function. +export async function createCollection() { + // Do your data fetching here. + const allPokemonResponse = await fetch(`https://pokeapi.co/api/v2/pokemon?limit=150`); + const allPokemonResult = await allPokemonResponse.json(); + const allPokemon = allPokemonResult.results; + const allLetters = ['a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z']; + return { + // `route` defines the URL structure for your collection. + // You can use any URL path pattern here, as long as it + // matches the filename prefix (`$pokemon.astro` -> `/pokemon/*`). + route: `/pokemon/:letter`, + // `paths` tells Astro which pages to generate in your collection. + // Provide an array of `params` objects that match the `route` pattern. + // Here, we create a route for each letter (ex: "a" -> {letter: "a"}). + paths() { + return allLetters.map(letter => ({params: {letter}})); + }, + // `props` returns the data needed on each page. + // For each individual page, return the data needed on each page. + // If you needed to fetch more data for each page, you can do that here as well. + // Luckily, we loaded all of the data that we need at the top of the function. + async props({ params }) { + return { + letter: params.letter, + items: allPokemon.filter((pokemon) => pokemon.name[0] === params.letter)}; + }, + }; +} +// The rest of your component script now runs on each individual page. +// "item" is one of the props returned in the `props()` function. +const {letter, items} = Astro.props; +--- +<html lang="en"> + <head> + <title>Page: {letter}</head> + <body> + {items.map((pokemon) => (<h1>{pokemon.name}</h1>))} + </body> +</html> +``` + +## Example: Pagination + +Pagination is a common use-case for static websites. Astro has built-in pagination support that was designed to make pagination effortless. Just pass `paginate: true` in the `createCollection` return object to enable automatic pagination. + +This example provides a basic implementation of pagination. In the previous examples, we had fetched from a remote API. In this example, we'll fetch our local markdown files to create a paginated list of all posts for a blog. + +```jsx +--- +// Define a `createCollection` function. +export async function createCollection() { + const allPosts = Astro.fetchContent('../posts/*.md') // fetch local posts... + .sort((a, b) => a.title.localeCompare(b.title)); // ... and sort by title. + + return { + // Set "paginate" to true to enable pagination. + paginate: true, + // Remember to add the ":page?" param for pagination. + // The "?" indicates an optional param, since the first page does not use it. + // Example: `/posts`, `/posts/2`, `/posts/3`, etc. + route: '/posts/:page?', + // `paths()` - not needed if `:page?` is your only route param. + // If you define have other params in your route, then you will still + // need a paths() function similar to the examples above. + // + // `props()` - notice the new `{paginate}` argument! This is passed to + // the props() function when `paginate` is set to true. We can now use + // it to enable pagination on a certain prop. In this example, we paginate + // "posts" so that multiple pages will be generated based on the given page size. + async props({paginate}) { + return { + posts: paginate(allPosts, {pageSize: 10}), + }; + }, + }; +} +// Now, you can get the paginated posts from your props. +// Note that a paginated prop is a custom object format, where the data +// for the page is available at `posts.data`. See the next example to +// learn how to use the other properties of this object. +const {posts} = Astro.props; +--- +<html lang="en"> + <head> + <title>Pagination Example - {collection.data.map((post) => ( + {posts.data.map((post) => (

{post.title}

Read Post @@ -67,144 +198,83 @@ export async function createCollection() { ## Example: Pagination Metadata +Building on the example above: when you use the `paginate` API you get access to several other properties in the paginated data prop. Your paginated prop includes important metadata +for the collection, such as: `.page` for keeping track of your page number and `.url` for linking to other pages in the collection. + +In this example, we'll use these values to add pagination UI controls to your HTML template. + ```jsx --- -// In addition to `collection.data` usage illustrated above, the `collection` -// prop also provides some important metadata for you to use, like: `collection.page`, -// `collection.url`, `collection.start`, `collection.end`, and `collection.total`. -// In this example, we'll use these values to do pagination in the template. -const { collection } = Astro.props; export async function createCollection() { /* See Previous Example */ } +// Remember that a paginated prop uses a custom object format to help with pagination. +const {posts} = Astro.props; --- - Pagination Example: Page Number {collection.page.current} - - - + Pagination Example: Page Number {posts.page.current} + + +
-
Results {collection.start + 1}–{collection.end + 1} of {collection.total}
- {collection.data.map((post) => ( -

{post.title}

- - Read Post - ))} +
Results {posts.start + 1}–{posts.end + 1} of {posts.total}
-

Page {collection.page.current} / {collection.page.last}

+

Page {posts.page.current} / {posts.page.last}

``` -## Example: Grouping Content by Tag, Author, etc. +## RSS Feeds + +You can generate an RSS 2.0 feed from the `createCollection()` result by adding the `rss` option. Here are all the options: ```jsx ---- -// Define the `collection` prop. -const { collection } = Astro.props; - -// Define a `createCollection` function. -// In this example, we'll customize the URLs that we generate to -// create a new page to group every pokemon by first letter of their name. export async function createCollection() { - const allPokemonResponse = await fetch(`https://pokeapi.co/api/v2/pokemon?limit=150`); - const allPokemonResult = await allPokemonResponse.json(); - const allPokemon = allPokemonResult.results; - const allLetters = ['a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z']; return { - // `routes` defines the total collection of routes as `params` data objects. - // In this example, we format each letter (ex: "a") to params (ex: {letter: "a"}). - routes: allLetters.map(letter => { - const params = {letter}; - return params; - }), - // `permalink` defines the final URL for each route object defined in `routes`. - // It should always match the file location (ex: `src/pages/$pokemon.astro`). - permalink: ({ params }) => `/pokemon/${params.letter}`, - // `data` is now responsible for return the data for each page. - // Luckily we had already loaded all of the data at the top of the function, - // so we just filter the data here to group pages by first letter. - // If you needed to fetch more data for each page, you can do that here as well. - async data({ params }) { - return allPokemon.filter((pokemon) => pokemon.name[0] === params.letter); + paginate: true, + route: '/posts/:page?', + async props({paginate}) { /* Not shown: see examples above */ }, + rss: { + title: 'My RSS Feed', + description: 'Description of the feed', + // (optional) add xmlns:* properties to root element + xmlns: { + itunes: 'http://www.itunes.com/dtds/podcast-1.0.dtd', + content: 'http://purl.org/rss/1.0/modules/content/', + }, + // (optional) add arbitrary XML to + customData: `en-us +The Sunset Explorers`, + // Format each paginated item in the collection + item: (item) => ({ + title: item.title, + description: item.description, + // enforce GMT timezone (otherwise it’ll be different based on where it’s built) + pubDate: item.pubDate + 'Z', + // custom data is supported here as well + }), }, - // Finally, `pageSize` and `pagination` is still on by default. Because - // we don't want to paginate the already-grouped pages a second time, we'll - // disable pagination. - pageSize: Infinity, }; } ---- - - - Pokemon: {collection.params.letter}</head> - <body> - {collection.data.map((pokemon) => (<h1>{pokemon.name}</h1>))} - </body> -</html> ``` -## Example: Individual Pages from a Collection +Astro will generate your RSS feed at the URL `/feed/[collection].xml`. For example, `/src/pages/$podcast.astro` would generate URL `/feed/podcast.xml`. -**Note**: collection.data and .params are being fetched async, use optional chaining or some other way of handling this in template. Otherwise you will get build errors. +Even though Astro will create the RSS feed for you, you’ll still need to add `<link>` tags manually in your `<head>` HTML for feed readers and browsers to pick up: -```jsx ---- -// Define the `collection` prop. -const { collection } = Astro.props; - -// Define a `createCollection` function. -// In this example, we'll create a new page for every single pokemon. -export async function createCollection() { - const allPokemonResponse = await fetch(`https://pokeapi.co/api/v2/pokemon?limit=150`); - const allPokemonResult = await allPokemonResponse.json(); - const allPokemon = allPokemonResult.results; - return { - // `routes` defines the total collection of routes as data objects. - routes: allPokemon.map((pokemon, i) => { - const params = {name: pokemon.name, index: i}; - return params; - }), - // `permalink` defines the final URL for each route object defined in `routes`. - permalink: ({ params }) => `/pokemon/${params.name}`, - // `data` is now responsible for return the data for each page. - // Luckily we had already loaded all of the data at the top of the function, - // so we just filter the data here to group pages by first letter. - // If you needed to fetch more data for each page, you can do that here as well. - // Note: data() is expected to return an array! - async data({ params }) { - return [allPokemon[params.index]]; - }, - // Note: The default pageSize is fine because technically only one data object - // is ever returned per route. We set it to Infinity in this example for completeness. - pageSize: Infinity, - }; -} ---- -<html lang="en"> - <head> - <title>Pokemon: {collection.params?.name} - - - Who's that pokemon? It's {collection.data[0]?.name}! - - +```html + ``` - -## Tips - -- If you find yourself duplicating markup across many pages and collections, you should probably be using more reusable components. - ### 📚 Further Reading - [Fetching data in Astro](/guides/data-fetching) -- API Reference: [collection](/reference/api-reference#collections-api) - API Reference: [createCollection()](/reference/api-reference#createcollection) -- API Reference: [Creating an RSS feed](/reference/api-reference#rss-feed) +- API Reference: [createCollection() > Pagination](/reference/api-reference#pagination) +- API Reference: [createCollection() > RSS](/reference/api-reference#rss) diff --git a/docs/src/pages/reference/api-reference.md b/docs/src/pages/reference/api-reference.md index 9667af2ec..1da33ae9b 100644 --- a/docs/src/pages/reference/api-reference.md +++ b/docs/src/pages/reference/api-reference.md @@ -66,106 +66,112 @@ const data = Astro.fetchContent('../pages/post/*.md'); // returns an array of po ## Collections API -### `collection` prop - -```jsx -const { collection } = Astro.props; -``` - -When using the [Collections API](/core-concepts/collections), `collection` is a prop exposed to the page with the following shape: - -| Name | Type | Description | -| :------------------------ | :-------------------: | :-------------------------------------------------------------------------------------------------------------------------------- | -| `collection.data` | `Array` | Array of data returned from `data()` for the current page. | -| `collection.start` | `number` | Index of first item on current page, starting at `0` (e.g. if `pageSize: 25`, this would be `0` on page 1, `25` on page 2, etc.). | -| `collection.end` | `number` | Index of last item on current page. | -| `collection.total` | `number` | The total number of items across all pages. | -| `collection.page.current` | `number` | The current page number, starting with `1`. | -| `collection.page.size` | `number` | How many items per-page. | -| `collection.page.last` | `number` | The total number of pages. | -| `collection.url.current` | `string` | Get the URL of the current page (useful for canonical URLs) | -| `collection.url.prev` | `string \| undefined` | Get the URL of the previous page (will be `undefined` if on page 1). | -| `collection.url.next` | `string \| undefined` | Get the URL of the next page (will be `undefined` if no more pages). | -| `collection.params` | `object` | If page params were used, this returns a `{ key: value }` object of all values. | +A collection is any file in the `src/pages` directory that starts with a dollar sign (`$`) and includes an exported `createCollection` function in the component script. +Check out our [Astro Collections](/core-concepts/collections) guide for more information and examples. ### `createCollection()` ```jsx +--- export async function createCollection() { - return { - async data({ params }) { - // load data - }, - pageSize: 25, - routes: [{ tag: 'movie' }, { tag: 'television' }], - permalink: ({ params }) => `/tag/${params.tag}`, + return { /* ... */ }; +} +--- + +``` + +⚠️ The `createCollection()` function executes in its own isolated scope before page loads. Therefore you can’t reference anything from its parent scope, other than file imports. The compiler will warn if you break this requirement. + +The `createCollection()` function should returns an object of the following shape: + +| Name | Type | Description | +| :--------- | :--------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `route` | `string` | **Required.** A route pattern for matching URL requests. This is used to generate the page URL in your final build. It must begin with the file name: for example, `pages/$tags.astro` must return a `route` that starts with `/tags/`. | +| `paths` | `{params: Params}[]` | Return an array of all URL to be generated. | +| `props` | `async ({ params, paginate }) => object` | **Required.** Load data for the page that will get passed to the page component as props. | +| `paginate` | `boolean` | Optional: Enable automatic pagination. See next section. | +| `rss` | [RSS](/reference/api-reference#rss-feed) | Optional: generate an RSS 2.0 feed from this collection ([docs](/reference/api-reference#rss-feed)) | + +### Pagination + +Enable pagination for a collection by returning `paginate: true` from `createCollection()`. This passes a `paginate()` argument to `props()` that you can use to return paginated data in your HTML template via props. + +The `paginate()` function that you use inside of `props()` has the following interface: + +```ts +/* the "paginate()" passed to props({paginate}) */ +type PaginateFunction = (data: any[], args?: { + /* pageSize: set the number of items to be shown on every page. Defaults to 10. */ + pageSize?: number +}) => PaginatedCollectionResult; + +/* the paginated return value, aka the prop passed to every page in the collection. */ +interface PaginatedCollectionResult { + /** result */ + data: any[]; + + /** metadata */ + /** the count of the first item on the page, starting from 0 */ + start: number; + /** the count of the last item on the page, starting from 0 */ + end: number; + /** total number of results */ + total: number; + page: { + /** the current page number, starting from 1 */ + current: number; + /** number of items per page (default: 25) */ + size: number; + /** number of last page */ + last: number; + }; + url: { + /** url of the current page */ + current: string; + /** url of the previous page (if there is one) */ + prev: string | undefined; + /** url of the next page (if there is one) */ + next: string | undefined; }; } ``` -When using the [Collections API](/core-concepts/collections), `createCollection()` is an async function that returns an object of the following shape: +📚 Learn more about pagination (and see an example) in our [Astro Collections guide.](/core-concepts/collections). -| Name | Type | Description | -| :---------- | :--------------------------------------: | :--------------------------------------------------------------------------------------------------------- | -| `data` | `async ({ params }) => any[]` | **Required.** Load an array of data with this function to be returned. | -| `pageSize` | `number` | Specify number of items per page (default: `25`). | -| `routes` | `params[]` | **Required for URL Params.** Return an array of all possible URL `param` values in `{ name: value }` form. | -| `permalink` | `({ params }) => string` | **Required for URL Params.** Given a `param` object of `{ name: value }`, generate the final URL.\* | -| `rss` | [RSS](/reference/api-reference#rss-feed) | Optional: generate an RSS 2.0 feed from this collection ([docs](/reference/api-reference#rss-feed)) | +### RSS -_\* Note: don’t create confusing URLs with `permalink`, e.g. rearranging params conditionally based on their values._ +Create an RSS 2.0 feed for a collection by returning `paginate: true` & an `rss` object from `createCollection()`. The `rss` object will be used to generate the contents of the RSS XML file. -⚠️ `createCollection()` executes in its own isolated scope before page loads. Therefore you can’t reference anything from its parent scope. If you need to load data you may fetch or use async `import()`s within the function body for anything you need (that’s why it’s `async`—to give you this ability). If it wasn’t isolated, then `collection` would be undefined! Therefore, duplicating imports between `createCollection()` and your Astro component is OK. +The `rss` object follows the `CollectionRSS`data type: -#### RSS Feed - -You can optionally generate an RSS 2.0 feed from `createCollection()` by adding an `rss` option. Here are all the options: - -```jsx -export async function createCollection() { - return { - async data({ params }) { - // load data - }, - pageSize: 25, - rss: { - title: 'My RSS Feed', - description: 'Description of the feed', - /** (optional) add xmlns:* properties to root element */ - xmlns: { - itunes: 'http://www.itunes.com/dtds/podcast-1.0.dtd', - content: 'http://purl.org/rss/1.0/modules/content/', - }, - /** (optional) add arbitrary XML to */ - customData: `en-us -The Sunset Explorers`, - /** Format each item from things returned in data() */ - item: (item) => ({ - title: item.title, - description: item.description, - pubDate: item.pubDate + 'Z', // enforce GMT timezone (otherwise it’ll be different based on where it’s built) - /** (optional) add arbitrary XML to each */ - customData: `${item.type} -${item.duration} -${item.explicit || false}`, - }), - }, +```ts +export interface CollectionRSS { + /** (required) Title of the RSS Feed */ + title: string; + /** (required) Description of the RSS Feed */ + description: string; + /** Specify arbitrary metadata on opening tag */ + xmlns?: Record; + /** Specify custom data in opening of file */ + customData?: string; + /** Return data about each item */ + item: (item: T) => { + /** (required) Title of item */ + title: string; + /** (required) Link to item */ + link: string; + /** Publication date of item */ + pubDate?: Date; + /** Item description */ + description?: string; + /** Append some other XML-valid data to this item */ + customData?: string; }; } ``` -Astro will generate an RSS 2.0 feed at `/feed/[collection].xml` (for example, `/src/pages/$podcast.xml` would generate `/feed/podcast.xml`). +📚 Learn more about RSS feed generation (and see an example) in our [Astro Collections guide.](/core-concepts/collections). -⚠️ Even though Astro will create the RSS feed for you, you’ll still need to add `` tags manually in your `` HTML: - -```html - -``` ## `import.meta` diff --git a/examples/blog-multiple-authors/src/pages/$author.astro b/examples/blog-multiple-authors/src/pages/$author.astro index e0a6a1919..76b372897 100644 --- a/examples/blog-multiple-authors/src/pages/$author.astro +++ b/examples/blog-multiple-authors/src/pages/$author.astro @@ -12,37 +12,32 @@ let canonicalURL = Astro.request.canonicalURL; // collection import authorData from '../data/authors.json'; -let { collection } = Astro.props; -export async function createCollection() { +export function createCollection() { /** Load posts */ let allPosts = Astro.fetchContent('./post/*.md'); - let allAuthors = new Set(); - - /** Loop through all posts, gather all authors */ - let routes = []; - for (const post of allPosts) { - if (!allAuthors.has(post.author)) { - allAuthors.add(post.author); - routes.push({ author: post.author }); - } - } - return { - /** Sort posts newest -> oldest, filter by params.author */ - async data({ params }) { - allPosts.sort((a, b) => new Date(b.date) - new Date(a.date)); - return allPosts.filter((post) => post.author === params.author); + paginate: true, + route: `/author/:author/:page?`, + paths() { + let allAuthorsUnique = [...new Set(allPosts.map(p => p.author))]; + return allAuthorsUnique.map(author => ({params: {author}})) + }, + async props({ params, paginate }) { + /** filter posts by author, sort by date */ + const filteredPosts = allPosts + .filter((post) => post.author === params.author) + .sort((a, b) => new Date(b.date) - new Date(a.date)); + return { + posts: paginate(filteredPosts, {pageSize: 1}), + } }, - /** Set page size */ - pageSize: 3, - /** Set permalink URL */ - permalink: ({ params }) => `/author/${params.author}`, - /** Pass back all routes so Astro can generate the static build */ - routes, }; } -const author = authorData[collection.params.author]; + +const { posts } = Astro.props; +const { params } = Astro.request; +const author = authorData[posts.data[0].author]; --- @@ -51,10 +46,10 @@ const author = authorData[collection.params.author];