Split README into docs (#118)
This commit is contained in:
parent
3263c02d77
commit
76932822b8
4 changed files with 487 additions and 319 deletions
388
README.md
388
README.md
|
@ -55,14 +55,53 @@ export default {
|
|||
};
|
||||
```
|
||||
|
||||
## 🥾 Guides
|
||||
|
||||
### 🚀 Basic Usage
|
||||
|
||||
Even though nearly-everything [is configurable][config], we recommend starting out by creating an `astro/` folder in your project with the following structure:
|
||||
|
||||
```
|
||||
├── astro/
|
||||
│ ├── components/
|
||||
│ └── pages/
|
||||
│ └── index.astro
|
||||
├── public/
|
||||
└── package.json
|
||||
```
|
||||
|
||||
- `astro/components/*`: where your reusable components go. You can place these anywhere, but we recommend a single folder to keep them organized.
|
||||
- `astro/pages/*`: this is a special folder where your [routing][routing] lives.
|
||||
|
||||
#### 🚦 Routing
|
||||
|
||||
Routing happens in `astro/pages/*`. Every `.astro` or `.md.astro` file in this folder corresponds with a public URL. For example:
|
||||
|
||||
| Local file | Public URL |
|
||||
| :--------------------------------------- | :------------------------------ |
|
||||
| `astro/pages/index.astro` | `/index.html` |
|
||||
| `astro/pages/post/my-blog-post.md.astro` | `/post/my-blog-post/index.html` |
|
||||
|
||||
#### 🗂 Static Assets
|
||||
|
||||
Static assets should be placed in a `public/` folder in your project. You can place any images, fonts, files, or global CSS in here you need to reference.
|
||||
|
||||
#### 🪨 Generating HTML with Astro
|
||||
|
||||
TODO: Astro syntax guide
|
||||
|
||||
#### ⚡ Dynamic Components
|
||||
|
||||
TODO: Astro dynamic components guide
|
||||
|
||||
### 💧 Partial Hydration
|
||||
|
||||
By default, Astro outputs zero client-side JS. If you'd like to include an interactive component in the client output, you may use any of the following techniques.
|
||||
|
||||
- `<MyComponent />` will render an HTML-only version of `MyComponent` (default)
|
||||
- `<MyComponent:load />` will render `MyComponent` on page load
|
||||
- `<MyComponent:idle />` will use [requestIdleCallback()][request-idle-cb] to render `MyComponent` as soon as main thread is free
|
||||
- `<MyComponent:visible />` will use an [IntersectionObserver][intersection-observer] to render `MyComponent` when the element enters the viewport
|
||||
- `<MyComponent:idle />` will use [requestIdleCallback()][mdn-ric] to render `MyComponent` as soon as main thread is free
|
||||
- `<MyComponent:visible />` will use an [IntersectionObserver][mdn-io] to render `MyComponent` when the element enters the viewport
|
||||
|
||||
### ⚛️ State Management
|
||||
|
||||
|
@ -93,282 +132,40 @@ Styling in Astro is meant to be as flexible as you’d like it to be! The follow
|
|||
|
||||
¹ _`.astro` files have no runtime, therefore Scoped CSS takes the place of CSS Modules (styles are still scoped to components, but don’t need dynamic values)_
|
||||
|
||||
#### 🖍 Styling by Framework
|
||||
To learn more about writing styles in Astro, see our [Styling Guide][docs-styling].
|
||||
|
||||
##### Astro
|
||||
👉 [**Styling**][docs-styling]
|
||||
|
||||
Styling in an Astro component is done by adding a `<style>` tag anywhere. By default, all styles are **scoped**, meaning they only apply to the current component. To create global styles, add a `:global()` wrapper around a selector (the same as if you were using [CSS Modules][css-modules]).
|
||||
### 🐶 Fetching Data
|
||||
|
||||
```html
|
||||
<!-- astro/components/MyComponent.astro -->
|
||||
Fetching data is what Astro is all about! Whether your data lives remotely in an API or in your local project, Astro has got you covered.
|
||||
|
||||
<style>
|
||||
/* Scoped class selector within the component */
|
||||
.scoped {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/* Scoped element selector within the component */
|
||||
h1 {
|
||||
color: red;
|
||||
}
|
||||
|
||||
/* Global style */
|
||||
:global(h1) {
|
||||
font-size: 32px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="scoped">I’m a scoped style and only apply to this component</div>
|
||||
<h1>I have both scoped and global styles</h1>
|
||||
```
|
||||
|
||||
**Tips**
|
||||
|
||||
- `<style>` tags within `.astro` files will be extracted and optimized for you on build. So you can write CSS without worrying too much about delivery.
|
||||
- For best result, only have one `<style>` tag per-Astro component. This isn’t necessarily a limitation, but it may result in better optimization at buildtime.
|
||||
- If you want to import third-party libraries into an Astro component, you can use [Sass][sass]! In particular, [@use][sass-use] may come in handy (e.g. `@use "bootstrap/scss/bootstrap"`);
|
||||
|
||||
#### React / Preact
|
||||
|
||||
`.jsx` files support both global CSS and CSS Modules. To enable the latter, use the `.module.css` extension (or `.module.scss`/`.module.sass` if using Sass).
|
||||
For fetching from a remote API, use a native JavaScript `fetch()` ([docs][fetch-js]) as you are used to. For fetching local content, use `Astro.fetchContent()` ([docs][fetch-content]).
|
||||
|
||||
```js
|
||||
import './global.css'; // include global CSS
|
||||
import Styles from './styles.module.css'; // Use CSS Modules (must end in `.module.css`, `.module.scss`, or `.module.sass`!)
|
||||
```
|
||||
// astro/components/MyComponent.Astro
|
||||
|
||||
##### Vue
|
||||
|
||||
Vue in Astro supports the same methods as `vue-loader` does:
|
||||
|
||||
- [Scoped CSS][vue-scoped]
|
||||
- [CSS Modules][vue-css-modules]
|
||||
|
||||
##### Svelte
|
||||
|
||||
Svelte in Astro also works exactly as expected: [Svelte Styling Docs][svelte-style].
|
||||
|
||||
#### 👓 Sass
|
||||
|
||||
Astro also supports [Sass][sass] out-of-the-box. To enable for each framework:
|
||||
|
||||
- **Astro**: `<style lang="scss">` or `<style lang="sass">`
|
||||
- **React** / **Preact**: `import Styles from './styles.module.scss'`;
|
||||
- **Vue**: `<style lang="scss">` or `<style lang="sass">`
|
||||
- **Svelte**: `<style lang="scss">` or `<style lang="sass">`
|
||||
|
||||
#### 🦊 Autoprefixer
|
||||
|
||||
We also automatically add browser prefixes using [Autoprefixer][autoprefixer]. By default, Astro loads the default values, but you may also specify your own by placing a [Browserslist][browserslist] file in your project root.
|
||||
|
||||
## 🥾 Guides
|
||||
|
||||
#### 🍱 Collections (beta)
|
||||
|
||||
By default, any Astro component can fetch data from any API or local `*.md` files. But what if you had a blog you wanted to paginate? What if you wanted to generate dynamic URLs based on metadata (e.g. `/tag/[tag]/`)? Or do both together? Astro Collections are a way to do all of that. It’s perfect for generating blog-like content, or scaffolding out dynamic URLs from your data.
|
||||
|
||||
First, decide on a URL schema. For our example, perhaps you want all your paginated posts at `/posts/1`, `/posts/2`, etc. But in addition, you also wanted `/tag/[tag]` and `/year/[year]` collections where posts are filtered by tag or year.
|
||||
|
||||
Next, for each “owner” of a URL tree, create a `/astro/pages/$[collection].astro` file. So in our example, we’d need 3:
|
||||
|
||||
```
|
||||
└── astro/
|
||||
└── pages/
|
||||
├── $posts.astro -> /posts/1, /posts/2, …
|
||||
├── $tag.astro -> /tag/[tag]/1, /tag/[tag]/2, …
|
||||
└── $year.astro -> /year/[year]/1, /year/[year]/2, …
|
||||
```
|
||||
|
||||
Lastly, in each `$[collection].astro` file, add 2 things:
|
||||
|
||||
```js
|
||||
export let collection: any;
|
||||
```
|
||||
|
||||
```js
|
||||
export async function createCollection() {
|
||||
return {
|
||||
async data() {
|
||||
// return data here to load (we’ll cover how later)
|
||||
},
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
These are important so your data is exposed to the page as a prop, and also Astro has everything it needs to gather your data and generate the proper routes. How it does this is more clear if we walk through a practical example.
|
||||
|
||||
#### Example 1: Simple pagination
|
||||
|
||||
Assume we have Markdown files that have `title`, `tag`, and `date` in their frontmatter, like so:
|
||||
|
||||
```md
|
||||
---
|
||||
title: My Blog Post
|
||||
tag: javascript
|
||||
date: 2021-03-01 09:34:00
|
||||
// Example 1: fetch remote data from your own API
|
||||
const remoteData = await fetch('https://api.mysite.com/v1/people').then((res) => res.json());
|
||||
|
||||
// Example 2: load local markdown files
|
||||
const localData = Astro.fetchContent('../post/*.md');
|
||||
---
|
||||
|
||||
# My Blog post
|
||||
|
||||
…
|
||||
```
|
||||
|
||||
It’s important to know that these could be anything! There’s no restrictions around what can go in your frontmatter, but these will explain values we see later. Assume nothing is “special“ or reserved; we named everything.
|
||||
##### Examples
|
||||
|
||||
Also, assume we want the following final routes:
|
||||
- [Blog Example][example-blog]
|
||||
- TODO: Headless CMS Example
|
||||
|
||||
- Individual blog posts live at `/post/[slug]`.
|
||||
- The paginated blog posts live at `/posts/1` for page 1, `/posts/2` for page 2, etc.
|
||||
- We also want to add `/tag/[tag]/1` for tagged posts, page 1, or `/year/[year]/1` for posts by year. We’ll add these at the end.
|
||||
### 🍱 Collections (beta)
|
||||
|
||||
Let’s start with paginated posts. Since we want `/posts/` to be the root, we’ll create a file at `/astro/pages/$posts.astro` (the `$` indicates that this is a multi-route page):
|
||||
[Fetching data is easy in Astro](#-fetching-data). But what if you wanted to make a paginated blog? What if you wanted an easy way to sort data, or filter, say, by a given tag? When you need something a little more powerful than simple data fetching, Astro’s Collections API may be what you need.
|
||||
|
||||
```html
|
||||
// /astro/pages/$posts.astro
|
||||
---
|
||||
import Pagination from '../components/Pagination.astro';
|
||||
import PostPreview from '../components/PostPreview.astro';
|
||||
👉 [**Collections API**][docs-collections]
|
||||
|
||||
export let collection: any;
|
||||
|
||||
export async function createCollection() {
|
||||
const allPosts = Astro.fetchContent('./post/*.md'); // load data that already lives at `/post/[slug]`
|
||||
allPosts.sort((a, b) => new Date(b.date) - new Date(a.date)); // sort newest -> oldest (we got "date" from frontmatter!)
|
||||
|
||||
// (load more data here, if needed)
|
||||
|
||||
return {
|
||||
async data() {
|
||||
return allPosts;
|
||||
},
|
||||
pageSize: 10, // how many we want to show per-page (default: 25)
|
||||
};
|
||||
}
|
||||
---
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>Blog Posts: page {collection.page.current}</title>
|
||||
<link rel="canonical" href={collection.url.current} />
|
||||
<link rel="prev" href={collection.url.prev} />
|
||||
<link rel="next" href={collection.url.next} />
|
||||
</head>
|
||||
<body>
|
||||
<main>
|
||||
<h5>Results {collection.start + 1}–{collection.start + 1 + collection.page.size} of {collection.total}</h6>
|
||||
{collection.data.map((post) => (
|
||||
<PostPreview post={post} />
|
||||
)}
|
||||
</main>
|
||||
<footer>
|
||||
<Pagination
|
||||
currentPage={collection.page.current}
|
||||
totalPages={collection.page.last}
|
||||
prevURL={collection.url.prev}
|
||||
nextURL={collection.url.next}
|
||||
/>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
Let’s walk through some of the key parts:
|
||||
|
||||
- `export let collection`: this is important because it exposes a prop to the page for Astro to return with all your data loaded. ⚠️ **It must be named `collection`**.
|
||||
- `export async function createCollection()`: this is also required, **and must be named this exactly.** This is an async function that lets you load data from anywhere (even a remote API!). At the end, you must return an object with `{ data: yourData }`. There are other options such as `pageSize` we’ll cover later.
|
||||
- `{collection.data.map((post) => (…`: this lets us iterate over all the markdown posts. This will take the shape of whatever you loaded in `createCollection()`. It will always be an array.
|
||||
- `{collection.page.current}`: this, and other properties, simply return more info such as what page a user is on, what the URL is, etc. etc.
|
||||
|
||||
It should be noted that the above example shows `<PostPreview />` and `<Pagination />` components. Pretend those are custom components that you made to display the post data, and the pagination navigation. There’s nothing special about them; only consider those examples of how you’d use collection data to display everything the way you’d like.
|
||||
|
||||
#### Example 2: Advanced filtering & pagination
|
||||
|
||||
In our earlier example, we covered simple pagination for `/posts/1`, but we’d still like to make `/tag/[tag]/1` and `/year/[year]/1`. To do that, we’ll create 2 more collections: `/astro/pages/$tag.astro` and `astro/pages/$year.astro`. Assume that the markup is the same, but we’ve expanded the `createCollection()` function with more data.
|
||||
|
||||
```diff
|
||||
// /astro/pages/$tag.astro
|
||||
---
|
||||
import Pagination from '../components/Pagination.astro';
|
||||
import PostPreview from '../components/PostPreview.astro';
|
||||
|
||||
export let collection: any;
|
||||
|
||||
export async function createCollection() {
|
||||
const allPosts = Astro.fetchContent('./post/*.md');
|
||||
allPosts.sort((a, b) => new Date(b.date) - new Date(a.date));
|
||||
+ const allTags = [...new Set(allPosts.map((post) => post.tags).flat())]; // gather all unique tags (we got "tag" from frontmatter!)
|
||||
+ allTags.sort((a, b) => a.localeCompare(b)); // sort tags A -> Z
|
||||
+ const routes = allTags.map((tag) => ({ tag })); // this is where we set { params: { tag } }
|
||||
|
||||
return {
|
||||
- async data() {
|
||||
- return allPosts;
|
||||
+ async data({ params }) {
|
||||
+ return allPosts.filter((post) => post.tag === params.tag); // filter post by "date" frontmatter, from params (we get `{ params }` from the routes array above)
|
||||
},
|
||||
pageSize: 10,
|
||||
+ routes,
|
||||
+ permalink: ({ params }) => `/tag/${params.tag}/` // important! the root must match (/tag/[tag] -> $tag.astro)
|
||||
};
|
||||
}
|
||||
---
|
||||
```
|
||||
|
||||
Some important concepts here:
|
||||
|
||||
- `routes = allTags.map((tag) => ({ tag }))`: Astro handles pagination for you automatically. But when it needs to generate multiple routes, this is where you tell Astro about all the possible routes. This way, when you run `astro build`, your static build isn’t missing any pages.
|
||||
- `permalink: ({ params }) => `/tag/${params.tag}/`: this is where you tell Astro what the generated URL should be. Note that while you have control over this, the root of this must match the filename (it’s best **NOT** to use `/pages/$tag.astro`to generate`/year/$year.astro`; that should live at `/pages/$year.astro` as a separate file).
|
||||
- `allPosts.filter((post) => post.tag === params.tag)`: we aren’t returning all posts here; we’re only returning posts with a matching tag. _What tag,_ you ask? The `routes` array has `[{ tag: 'javascript' }, { tag: '…`, and all the routes we need to gather. So we first need to query everything, but only return the `.filter()`ed posts at the very end.
|
||||
|
||||
Other things of note is that we are sorting like before, but we filter by the frontmatter `tag` property, and return those at URLs.
|
||||
|
||||
These are still paginated, too! But since there are other conditions applied, they live at a different URL.
|
||||
|
||||
Lastly, what about `/year/*`? Well hopefully you can figure that out from here. It follows the exact same pattern, except using `post.date` frontmatter. You’ll grab the year from that date string, and sort probably newest to oldest rather than alphabetical. You’ll also change `params.tag` to `params.year` (or whatever you name it), but otherwise most everything else should be the same.
|
||||
|
||||
#### Tips
|
||||
|
||||
- Having to load different collections in different `$[collection].astro` files might seem like a pain at first, until you remember **you can create reusable components!** Treat `/pages/*.astro` files as your one-off routing & data fetching logic, and treat `/components/*.astro` as your reusable markup. If you find yourself duplicating things too much, you can probably use a component instead!
|
||||
- Stay true to `/pages/$[collection].astro` naming. If you have an `/all-posts/*` route, then use `/pages/$all-posts.astro` to manage that. Don’t try and trick `permalink` to generate too many URL trees; it’ll only result in pages being missed when it comes time to build.
|
||||
- Need to load local markdown? Try [`Astro.fetchContent()`][fetch-content]
|
||||
- Need to load remote data from an API? Simply `fetch()` to make it happen!
|
||||
|
||||
#### 🍃 Tailwind
|
||||
|
||||
Astro can be configured to use [Tailwind][tailwind] easily! Install the dependencies:
|
||||
|
||||
```
|
||||
npm install --save-dev tailwindcss
|
||||
```
|
||||
|
||||
And also create a `tailwind.config.js` in your project root:
|
||||
|
||||
```js
|
||||
// tailwind.config.js
|
||||
|
||||
module.exports = {
|
||||
mode: 'jit',
|
||||
purge: ['./public/**/*.html', './astro/**/*.{astro,js,jsx,ts,tsx,vue}'],
|
||||
// more options here
|
||||
};
|
||||
```
|
||||
|
||||
Then add [Tailwind utilities][tailwind-utilities] to any Astro component that needs it:
|
||||
|
||||
```html
|
||||
<style>
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
</style>
|
||||
```
|
||||
|
||||
You should see Tailwind styles compile successfully in Astro.
|
||||
|
||||
💁 **Tip**: to reduce duplication, try loading `@tailwind base` from a parent page (`./pages/*.astro`) instead of the component itself.
|
||||
|
||||
## 🚀 Build & Deployment
|
||||
### 🚀 Build & Deployment
|
||||
|
||||
Add a `build` npm script to your `/package.json` file:
|
||||
|
||||
|
@ -391,62 +188,15 @@ Now upload the contents of `/_site_` to your favorite static site host.
|
|||
|
||||
## 📚 API
|
||||
|
||||
### `Astro` global
|
||||
👉 [**Full API Reference**][docs-api]
|
||||
|
||||
The `Astro` global is available in all contexts in `.astro` files. It has the following functions:
|
||||
|
||||
#### `config`
|
||||
|
||||
`Astro.config` returns an object with the following properties:
|
||||
|
||||
| Name | Type | Description |
|
||||
| :----- | :------- | :--------------------------------------------------------------------------------------------------------- |
|
||||
| `site` | `string` | Your website’s public root domain. Set it with `site: "https://mysite.com"` in your [Astro config][config] |
|
||||
|
||||
#### `fetchContent()`
|
||||
|
||||
`Astro.fetchContent()` is a way to load local `*.md` files into your static site setup. You can either use this on its own, or within [Astro Collections][collections].
|
||||
|
||||
```
|
||||
// ./astro/components/my-component.astro
|
||||
---
|
||||
const data = Astro.fetchContent('../pages/post/*.md'); // returns an array of posts that live at ./astro/pages/post/*.md
|
||||
---
|
||||
|
||||
<div>
|
||||
{data.slice(0, 3).map((post) => (
|
||||
<article>
|
||||
<h1>{post.title}</h1>
|
||||
<p>{post.description}</p>
|
||||
<a href={post.url}>Read more</a>
|
||||
</article>
|
||||
))}
|
||||
</div>
|
||||
```
|
||||
|
||||
`.fetchContent()` only takes one parameter: a relative URL glob of which local files you’d like to import. Currently only `*.md` files are supported. It’s synchronous, and returns an array of items of type:
|
||||
|
||||
```
|
||||
{
|
||||
url: string; // the URL of this item (if it’s in pages/)
|
||||
content: string; // the HTML of this item
|
||||
// frontmatter data expanded here
|
||||
}[];
|
||||
```
|
||||
|
||||
[autoprefixer]: https://github.com/postcss/autoprefixer
|
||||
[browserslist]: https://github.com/browserslist/browserslist
|
||||
[collections]: #-collections-beta
|
||||
[css-modules]: https://github.com/css-modules/css-modules
|
||||
[config]: #%EF%B8%8F-configuration
|
||||
[fetch-content]: #fetchContent--
|
||||
[intersection-observer]: https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API
|
||||
[request-idle-cb]: https://developer.mozilla.org/en-US/docs/Web/API/Window/requestIdleCallback
|
||||
[sass]: https://sass-lang.com/
|
||||
[sass-use]: https://sass-lang.com/documentation/at-rules/use
|
||||
[svelte]: https://svelte.dev
|
||||
[svelte-style]: https://svelte.dev/docs#style
|
||||
[tailwind]: https://tailwindcss.com
|
||||
[tailwind-utilities]: https://tailwindcss.com/docs/adding-new-utilities#using-css
|
||||
[vue-css-modules]: https://vue-loader.vuejs.org/guide/css-modules.html
|
||||
[vue-scoped]: https://vue-loader.vuejs.org/guide/scoped-css.html
|
||||
[docs-api]: ./docs/api.md
|
||||
[docs-collections]: ./docs/collections.md
|
||||
[docs-styling]: ./docs/styling.md
|
||||
[example-blog]: ./examples/blog
|
||||
[fetch-content]: ./docs/api.md#fetchcontent
|
||||
[fetch-js]: https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API
|
||||
[mdn-io]: https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API
|
||||
[mdn-ric]: https://developer.mozilla.org/en-US/docs/Web/API/Window/requestIdleCallback
|
||||
[routing]: #-routing
|
||||
|
|
97
docs/api.md
Normal file
97
docs/api.md
Normal file
|
@ -0,0 +1,97 @@
|
|||
## 📚 API
|
||||
|
||||
### `Astro` global
|
||||
|
||||
The `Astro` global is available in all contexts in `.astro` files. It has the following functions:
|
||||
|
||||
#### `config`
|
||||
|
||||
`Astro.config` returns an object with the following properties:
|
||||
|
||||
| Name | Type | Description |
|
||||
| :----- | :------- | :--------------------------------------------------------------------------------------------------------- |
|
||||
| `site` | `string` | Your website’s public root domain. Set it with `site: "https://mysite.com"` in your [Astro config][config] |
|
||||
|
||||
#### `fetchContent()`
|
||||
|
||||
`Astro.fetchContent()` is a way to load local `*.md` files into your static site setup. You can either use this on its own, or within [Astro Collections][docs-collections].
|
||||
|
||||
```jsx
|
||||
// ./astro/components/my-component.astro
|
||||
---
|
||||
const data = Astro.fetchContent('../pages/post/*.md'); // returns an array of posts that live at ./astro/pages/post/*.md
|
||||
---
|
||||
|
||||
<div>
|
||||
{data.slice(0, 3).map((post) => (
|
||||
<article>
|
||||
<h1>{post.title}</h1>
|
||||
<p>{post.description}</p>
|
||||
<a href={post.url}>Read more</a>
|
||||
</article>
|
||||
))}
|
||||
</div>
|
||||
```
|
||||
|
||||
`.fetchContent()` only takes one parameter: a relative URL glob of which local files you’d like to import. Currently only `*.md` files are supported. It’s synchronous, and returns an array of items of type:
|
||||
|
||||
```
|
||||
{
|
||||
url: string; // the URL of this item (if it’s in pages/)
|
||||
content: string; // the HTML of this item
|
||||
// frontmatter data expanded here
|
||||
}[];
|
||||
```
|
||||
|
||||
### `collection`
|
||||
|
||||
```jsx
|
||||
export let collection;
|
||||
```
|
||||
|
||||
When using the [Collections API][docs-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. |
|
||||
|
||||
### `createCollection()`
|
||||
|
||||
```jsx
|
||||
export async function createCollection() {
|
||||
return {
|
||||
async data({ params }) {
|
||||
// load data
|
||||
},
|
||||
pageSize: 25,
|
||||
routes: [{ tag: 'movie' }, { tag: 'television' }],
|
||||
permalink: ({ params }) => `/tag/${params.tag}`,
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
When using the [Collections API][docs-collections], `createCollection()` is an async function that returns an object of the following shape:
|
||||
|
||||
| Name | Type | Description |
|
||||
| :---------- | :---------------------------: | :--------------------------------------------------------------------------------------------------------- |
|
||||
| `data` | `async ({ params }) => any[]` | **Required.** Load 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.\* |
|
||||
|
||||
_\* Note: don’t create confusing URLs with `permalink`, e.g. rearranging params conditionally based on their values._
|
||||
|
||||
⚠️ `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.
|
||||
|
||||
[config]: ../README.md#%EF%B8%8F-configuration
|
||||
[docs-collections]: ./collections.md
|
195
docs/collections.md
Normal file
195
docs/collections.md
Normal file
|
@ -0,0 +1,195 @@
|
|||
# 🍱 Collections
|
||||
|
||||
## ❓ What are Collections?
|
||||
|
||||
[Fetching data is easy in Astro][docs-data]. But what if you wanted to make a paginated blog? What if you wanted an easy way to sort data, or filter, say, by a given tag? When you need something a little more powerful than simple data fetching, Astro’s Collections API may be what you need.
|
||||
|
||||
An Astro Collection is similar to the general concept of Collections in static site generators like Jekyll, Hugo, Eleventy, etc. It’s a general way to load an entire data set. But one big difference between Astro Collections and traditional static site generators is: **Astro lets you seamlessly blend remote API data and local files in a JAMstack-friendly way.** To see how, this guide will walk through a few examples. If you’d like, you can reference the [blog example project][example-blog] to see the finished code in context.
|
||||
|
||||
## 🧑🎨 How to Use
|
||||
|
||||
By default, any Astro component can fetch data from any API or local `*.md` files. But what if you had a blog you wanted to paginate? What if you wanted to generate dynamic URLs based on metadata (e.g. `/tag/:tag/`)? Or do both together? Astro Collections are a way to do all of that. It’s perfect for generating blog-like content, or scaffolding out dynamic URLs from your data.
|
||||
|
||||
Let’s pretend we have some blog posts written already. This is our starting project structure:
|
||||
|
||||
```
|
||||
└── astro/
|
||||
└── pages/
|
||||
└── post/
|
||||
└── (blog content)
|
||||
```
|
||||
|
||||
The first step in adding some dynamic collections is deciding on a URL schema. For our example website, we’re aiming for the following URLs:
|
||||
|
||||
- `/post/:post`: A single blog post page
|
||||
- `/posts/:page`: A list page of all blog posts, paginated, and sorted most recent first
|
||||
- `/tag/:tag`: All blog posts, filtered by a specific tag
|
||||
|
||||
Because `/post/:post` references the static files we have already, that doesn’t need to be a collection. But we will need collections for `/posts/:page` and `/tag/:tag` because those will be dynamically generated. For both collections we’ll create a `/astro/pages/$[collection].astro` file. This is our new structure:
|
||||
|
||||
```diff
|
||||
└── astro/
|
||||
└── pages/
|
||||
├── post/
|
||||
│ └── (blog content)
|
||||
+ ├── $posts.astro -> /posts/1, /posts/2, …
|
||||
+ └── $tag.astro -> /tag/:tag/1, /tag/:tag/2, …
|
||||
```
|
||||
|
||||
💁 **Tip**: Any `.astro` filename beginning with a `$` is how it’s marked as a collection.
|
||||
|
||||
In each `$[collection].astro` file, we’ll need 2 things:
|
||||
|
||||
```js
|
||||
// 1. We need to mark “collection” as a prop (this is a special reserved name)
|
||||
export let collection: any;
|
||||
|
||||
// 2. We need to export an async createCollection() function that will retrieve our data.
|
||||
export async function createCollection() {
|
||||
return {
|
||||
async data() {
|
||||
// return data here to load (we’ll cover how later)
|
||||
},
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
These are important so your data is exposed to the page as a prop, and also Astro has everything it needs to gather your data and generate the proper routes. How it does this is more clear if we walk through a practical example.
|
||||
|
||||
#### Example 1: Simple pagination
|
||||
|
||||
Our blog posts all contain `title`, `tags`, and `published_at` in their frontmatter:
|
||||
|
||||
```md
|
||||
---
|
||||
title: My Blog Post
|
||||
tags:
|
||||
- javascript
|
||||
published_at: 2021-03-01 09:34:00
|
||||
---
|
||||
|
||||
# My Blog post
|
||||
|
||||
…
|
||||
```
|
||||
|
||||
There’s nothing special or reserved about any of these names; you’re free to name everything whatever you’d like, or have as much or little frontmatter as you need.
|
||||
|
||||
```jsx
|
||||
// /astro/pages/$posts.astro
|
||||
---
|
||||
export let collection: any;
|
||||
|
||||
export async function createCollection() {
|
||||
const allPosts = Astro.fetchContent('./post/*.md'); // load data that already lives at `/post/:slug`
|
||||
allPosts.sort((a, b) => new Date(b.published_at) - new Date(a.published_at)); // sort newest -> oldest (we got "published_at" from frontmatter!)
|
||||
|
||||
// (load more data here, if needed)
|
||||
|
||||
return {
|
||||
async data() {
|
||||
return allPosts;
|
||||
},
|
||||
pageSize: 10, // how many we want to show per-page (default: 25)
|
||||
};
|
||||
}
|
||||
|
||||
function formatDate(date) {
|
||||
return new Date(date).toUTCString();
|
||||
}
|
||||
---
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>Blog Posts: page {collection.page.current}</title>
|
||||
<link rel="canonical" href={collection.url.current} />
|
||||
<link rel="prev" href={collection.url.prev} />
|
||||
<link rel="next" href={collection.url.next} />
|
||||
</head>
|
||||
<body>
|
||||
<main>
|
||||
<h5>Results {collection.start + 1}–{collection.end + 1} of {collection.total}</h6>
|
||||
{collection.data.map((post) => (
|
||||
<h1>{post.title}</h1>
|
||||
<time>{formatDate(post.published_at)}</time>
|
||||
<a href={post.url}>Read</a>
|
||||
)}
|
||||
</main>
|
||||
<footer>
|
||||
<h4>Page {collection.page.current} / {collection.page.last}</h4>
|
||||
<nav class="nav">
|
||||
<a class="prev" href={collection.url.prev || '#'}>Prev</a>
|
||||
<a class="next" href={collection.url.next || '#'}>Next</a>
|
||||
</nav>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
Let’s walk through some of the key parts:
|
||||
|
||||
- `export let collection`: this is important because it exposes a prop to the page for Astro to return with all your data loaded. ⚠️ **It must be named `collection`**.
|
||||
- `export async function createCollection()`: this is also required, **and must be named this exactly.** This is an async function that lets you load data from anywhere (even a remote API!). At the end, you must return an object with `{ data: yourData }`. There are other options such as `pageSize` we’ll cover later.
|
||||
- `{collection.data.map((post) => (…`: this lets us iterate over all the markdown posts. This will take the shape of whatever you loaded in `createCollection()`. It will always be an array.
|
||||
- `{collection.page.current}`: this, and other properties, simply return more info such as what page a user is on, what the URL is, etc. etc.
|
||||
- Curious about everything on `collection`? See the [reference][collection-api].
|
||||
|
||||
#### Example 2: Advanced filtering & pagination
|
||||
|
||||
In our earlier example, we covered simple pagination for `/posts/1`, but we’d still like to make `/tag/:tag/1` and `/year/:year/1`. To do that, we’ll create 2 more collections: `/astro/pages/$tag.astro` and `astro/pages/$year.astro`. Assume that the markup is the same, but we’ve expanded the `createCollection()` function with more data.
|
||||
|
||||
```diff
|
||||
// /astro/pages/$tag.astro
|
||||
---
|
||||
import Pagination from '../components/Pagination.astro';
|
||||
import PostPreview from '../components/PostPreview.astro';
|
||||
|
||||
export let collection: any;
|
||||
|
||||
export async function createCollection() {
|
||||
const allPosts = Astro.fetchContent('./post/*.md');
|
||||
allPosts.sort((a, b) => new Date(b.published_at) - new Date(a.published_at));
|
||||
+ const allTags = [...new Set(allPosts.map((post) => post.tags).flat())]; // gather all unique tags (we got "tags" from frontmatter!)
|
||||
+ allTags.sort((a, b) => a.localeCompare(b)); // sort tags A -> Z
|
||||
+ const routes = allTags.map((tag) => ({ tag })); // this is where we set { params: { tag } }
|
||||
|
||||
return {
|
||||
- async data() {
|
||||
- return allPosts;
|
||||
+ async data({ params }) {
|
||||
+ return allPosts.filter((post) => post.tags.includes(params.tag)); // filter posts that match the :tag from the URL ("params")
|
||||
},
|
||||
pageSize: 10,
|
||||
+ routes,
|
||||
+ permalink: ({ params }) => `/tag/${params.tag}/` // this is where we generate our URL structure
|
||||
};
|
||||
}
|
||||
---
|
||||
```
|
||||
|
||||
Some important concepts here:
|
||||
|
||||
- `routes = allTags.map((tag) => ({ tag }))`: Astro handles pagination for you automatically. But when it needs to generate multiple routes, this is where you tell Astro about all the possible routes. This way, when you run `astro build`, your static build isn’t missing any pages.
|
||||
- `permalink: ({ params }) => `/tag/${params.tag}/`: this is where you tell Astro what the generated URL should be. Note that while you have control over this, the root of this must match the filename (it’s best **NOT** to use `/pages/$tag.astro`to generate`/year/$year.astro`; that should live at `/pages/$year.astro` as a separate file).
|
||||
- `allPosts.filter((post) => post.tag === params.tag)`: we aren’t returning all posts here; we’re only returning posts with a matching tag. _What tag,_ you ask? The `routes` array has `[{ tag: 'javascript' }, { tag: '…`, and all the routes we need to gather. So we first need to query everything, but only return the `.filter()`ed posts at the very end.
|
||||
|
||||
Other things of note is that we are sorting like before, but we filter by the frontmatter `tag` property, and return those at URLs.
|
||||
|
||||
These are still paginated, too! But since there are other conditions applied, they live at a different URL.
|
||||
|
||||
#### Tips
|
||||
|
||||
- Having to load different collections in different `$[collection].astro` files might seem like a pain at first, until you remember **you can create reusable components!** Treat `/pages/*.astro` files as your one-off routing & data fetching logic, and treat `/components/*.astro` as your reusable markup. If you find yourself duplicating things too much, you can probably use a component instead!
|
||||
- Stay true to `/pages/$[collection].astro` naming. If you have an `/all-posts/*` route, then use `/pages/$all-posts.astro` to manage that. Don’t try and trick `permalink` to generate too many URL trees; it’ll only result in pages being missed when it comes time to build.
|
||||
|
||||
### 📚 Further Reading
|
||||
|
||||
- [Fetching data in Astro][docs-data]
|
||||
- API Reference: [collection][collection-api]
|
||||
- API Reference: [createCollection()][create-collection-api]
|
||||
|
||||
[docs-data]: ../README.md#-fetching-data
|
||||
[collection-api]: ./api.md#collection
|
||||
[create-collection-api]: ./api.md#createcollection
|
||||
[example-blog]: ../examples/blog
|
||||
[fetch-content]: ./api.md#fetchcontent
|
126
docs/styling.md
Normal file
126
docs/styling.md
Normal file
|
@ -0,0 +1,126 @@
|
|||
# 💅 Styling
|
||||
|
||||
Styling in Astro is meant to be as flexible as you’d like it to be! The following options are all supported:
|
||||
|
||||
| Framework | Global CSS | Scoped CSS | CSS Modules |
|
||||
| :--------------- | :--------: | :--------: | :---------: |
|
||||
| Astro (`.astro`) | ✅ | ✅ | N/A¹ |
|
||||
| React / Preact | ✅ | ❌ | ✅ |
|
||||
| Vue | ✅ | ✅ | ✅ |
|
||||
| Svelte | ✅ | ✅ | ❌ |
|
||||
|
||||
¹ _`.astro` files have no runtime, therefore Scoped CSS takes the place of CSS Modules (styles are still scoped to components, but don’t need dynamic values)_
|
||||
|
||||
#### 🖍 Styling by Framework
|
||||
|
||||
##### Astro
|
||||
|
||||
Styling in an Astro component is done by adding a `<style>` tag anywhere. By default, all styles are **scoped**, meaning they only apply to the current component. To create global styles, add a `:global()` wrapper around a selector (the same as if you were using [CSS Modules][css-modules]).
|
||||
|
||||
```html
|
||||
<!-- astro/components/MyComponent.astro -->
|
||||
|
||||
<style>
|
||||
/* Scoped class selector within the component */
|
||||
.scoped {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/* Scoped element selector within the component */
|
||||
h1 {
|
||||
color: red;
|
||||
}
|
||||
|
||||
/* Global style */
|
||||
:global(h1) {
|
||||
font-size: 32px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="scoped">I’m a scoped style and only apply to this component</div>
|
||||
<h1>I have both scoped and global styles</h1>
|
||||
```
|
||||
|
||||
**Tips**
|
||||
|
||||
- `<style>` tags within `.astro` files will be extracted and optimized for you on build. So you can write CSS without worrying too much about delivery.
|
||||
- For best result, only have one `<style>` tag per-Astro component. This isn’t necessarily a limitation, but it may result in better optimization at buildtime.
|
||||
- If you want to import third-party libraries into an Astro component, you can use [Sass][sass]! In particular, [@use][sass-use] may come in handy (e.g. `@use "bootstrap/scss/bootstrap"`);
|
||||
|
||||
#### React / Preact
|
||||
|
||||
`.jsx` files support both global CSS and CSS Modules. To enable the latter, use the `.module.css` extension (or `.module.scss`/`.module.sass` if using Sass).
|
||||
|
||||
```js
|
||||
import './global.css'; // include global CSS
|
||||
import Styles from './styles.module.css'; // Use CSS Modules (must end in `.module.css`, `.module.scss`, or `.module.sass`!)
|
||||
```
|
||||
|
||||
##### Vue
|
||||
|
||||
Vue in Astro supports the same methods as `vue-loader` does:
|
||||
|
||||
- [Scoped CSS][vue-scoped]
|
||||
- [CSS Modules][vue-css-modules]
|
||||
|
||||
##### Svelte
|
||||
|
||||
Svelte in Astro also works exactly as expected: [Svelte Styling Docs][svelte-style].
|
||||
|
||||
#### 👓 Sass
|
||||
|
||||
Astro also supports [Sass][sass] out-of-the-box. To enable for each framework:
|
||||
|
||||
- **Astro**: `<style lang="scss">` or `<style lang="sass">`
|
||||
- **React** / **Preact**: `import Styles from './styles.module.scss'`;
|
||||
- **Vue**: `<style lang="scss">` or `<style lang="sass">`
|
||||
- **Svelte**: `<style lang="scss">` or `<style lang="sass">`
|
||||
|
||||
#### 🦊 Autoprefixer
|
||||
|
||||
We also automatically add browser prefixes using [Autoprefixer][autoprefixer]. By default, Astro loads the default values, but you may also specify your own by placing a [Browserslist][browserslist] file in your project root.
|
||||
|
||||
#### 🍃 Tailwind
|
||||
|
||||
Astro can be configured to use [Tailwind][tailwind] easily! Install the dependencies:
|
||||
|
||||
```
|
||||
npm install --save-dev tailwindcss
|
||||
```
|
||||
|
||||
And also create a `tailwind.config.js` in your project root:
|
||||
|
||||
```js
|
||||
// tailwind.config.js
|
||||
|
||||
module.exports = {
|
||||
mode: 'jit',
|
||||
purge: ['./public/**/*.html', './astro/**/*.{astro,js,jsx,ts,tsx,vue}'],
|
||||
// more options here
|
||||
};
|
||||
```
|
||||
|
||||
Then add [Tailwind utilities][tailwind-utilities] to any Astro component that needs it:
|
||||
|
||||
```html
|
||||
<style>
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
</style>
|
||||
```
|
||||
|
||||
You should see Tailwind styles compile successfully in Astro.
|
||||
|
||||
💁 **Tip**: to reduce duplication, try loading `@tailwind base` from a parent page (`./pages/*.astro`) instead of the component itself.
|
||||
|
||||
[autoprefixer]: https://github.com/postcss/autoprefixer
|
||||
[browserslist]: https://github.com/browserslist/browserslist
|
||||
[css-modules]: https://github.com/css-modules/css-modules
|
||||
[vue-css-modules]: https://vue-loader.vuejs.org/guide/css-modules.html
|
||||
[vue-scoped]: https://vue-loader.vuejs.org/guide/scoped-css.html
|
||||
[sass]: https://sass-lang.com/
|
||||
[sass-use]: https://sass-lang.com/documentation/at-rules/use
|
||||
[svelte-style]: https://svelte.dev/docs#style
|
||||
[tailwind]: https://tailwindcss.com
|
||||
[tailwind-utilities]: https://tailwindcss.com/docs/adding-new-utilities#using-css
|
Loading…
Reference in a new issue