Day16 - Astro Series: Content Collection

Astro 系列文第十六日:内容集合

一個漂亮的漸層背景上面有一句標題:「內容集合」

前言

除了在组件中定义数据、在 src 中 import 进来或者 fetch 远端数据之外,还有其他撰写内容的方式吗?有的!

什么是内容集合 Content Collection?

内容集合在 2.0 版本推出,用于为网站的本地内容提供易于使用的管理、自动化类型验证功能。如果你有大量文件需要注入网页中便可以使用该功能。

怎么使用内容集合?

在介绍资料夹结构的章节中提到 src/content 是被保留的资料夹,其用途是用于存放内容集合相关的文件与設定,此外不能用做其他用途。首先可以创造一个条目资料夹,并且在里面存放与该条目相关的文件,文件可以是 Markdown 或 MDX 甚至是 YAML 或 JSON:

src/content/
├── newsletter/
│ ├── week-1.md
│ └── week-2.md
├── blog/
│ ├── post-1.md
│ └── post-2.md
└── authors/
├── grace-hopper.json
└── alan-turing.json

当创建好条目与其内容文件之后就可以透过 Astro 提供的 API 查询。

什么是 .astro 资料夹?

在使用内容集合时会发现 Astro 会为内容集合的设定自动生成相关的档案,这些档案会被放置在 .astro 资料夹中,不需要做任何的设定或修改,只要执行 astro dev或 astro build 就会自动生成,也可以手动执行 astro sync 来生成。如果你有使用 Git 管理专案,建议将该资料夹给写入 .gitignore 设定当中,避免被记录下来。

定义集合

内容集合的设定是可选的,增加额外的集合设定将会更好的帮助 Astro 验证资料的型别。要定义集合就需要创建 src/content/config.ts 文件(也可以是 .js 或 .mjs),基本内容如下:

// 1. 导入 `astro:content` 提供的工具函式
import { defineCollection } from 'astro:content';
// 2. 定义集合
const blogCollection = defineCollection({
/* ... */
});
// 3. 输出一个 `collections` 物件用于注册集合,名称应与条目资料夹相同
export const collections = {
blog: blogCollection,
};

透过定义集合型别可以强制集合内资料的准确与一致性,当有违背规则的情况 Astro 将会提供错误与建议,以下是一个集合物件的范例:

import { z, defineCollection } from 'astro:content';
defineCollection({
type: 'content', // v2.5.0 版本新增,注明资料种类是 Markdown 还是像 JSON 或 YAML 的格式
(content / data)
schema: z.object({
title: z.string(),
tags: z.array(z.string()),
image: z.string().optional(),
}),
});

当集合内的资料是 Markdown 时 type 为 content,当是 JSON 或 YAML 时则是 data。 schema 的设置可以参考 Zod 文件🔗,Astro 使用 Zod 来为资料做检核。

定义多个集合

可以创造多个集合之后再放入 collections 物件中。

const blogCollection = defineCollection({
type: 'content',
schema: z.object({
/* ... */
}),
});
const newsletter = defineCollection({
type: 'content',
schema: z.object({
/* ... */
}),
});
const authors = defineCollection({
type: 'data',
schema: z.object({
/* ... */
}),
});
export const collections = {
blog: blogCollection,
newsletter: newsletter,
authors: authors,
};

查询集合

Astro 提供两个函式用于查询一个或多个内容集合,分别是:getCollection🔗 与 getEntry🔗。

import { getCollection, getEntry } from 'astro:content';
// 获取所有内容集合
// 需要集合的名称作为参数
// 举例来说: `src/content/blog/**`
const allBlogPosts = await getCollection('blog');
// 获取单个条目从集合之中
// 需要集合的名称以及条目 `slug` (内容集合)或 `id`(资料集合)
const graceHopperProfile = await getEntry('authors', 'grace-hopper');

筛选集合

getCollection 接受一个「过滤用」的回呼函式,用于过滤搜寻内容,像以下的案例:「当在 Production 环境时,如果该笔项目 draft 非为 true才显示,在非 Production 则显示一切资料」

import { getCollection } from 'astro:content';
const blogEntries = await getCollection('blog', ({ data }) => {
return import.meta.env.PROD ? data.draft !== true : true;
});

显示集合内容

透过 map 来遍历并回传每个项目的 Markup,我们能将捞取到的资料制作成一个清单并呈现到页面当中。

---
import { getCollection } from 'astro:content';
const blogEntries = await getCollection('blog');
---
<ul>
{blogEntries.map(blogPostEntry => (
<li>
<a href={`/my-blog-url/${blogPostEntry.slug}`}>{blogPostEntry.data.title}</a>
<time datetime={blogPostEntry.data.publishedDate.toISOString()}>
{blogPostEntry.data.publishedDate.toDateString()}
</time>
</li>
))}
</ul>

运用集合内容产生 SSG Route

一样透过 getStaticPaths 这个方法,不过这次使用的资料不像之前一样是写死在元件内的,而是透过集合内容的资料来建立。

src/pages/posts/[...slug].astro
---
import { getCollection } from 'astro:content';
export async function getStaticPaths() {
// 1. 抓取 blog 集合
const blogEntries = await getCollection('blog');
// 2. 回传个别的 Route 名称与 Props 用于产生 Route
return blogEntries.map(entry => ({
params:{ slug: entry.slug }, props: { entry },
}));
}
// 3. 再透过解构 Props 得到个别集合项目中的内容并显示出来
const { entry } = Astro.props;
const { Content } = await entry.render();
---
<h1>{entry.data.title}</h1>
<Content />

总结

最后会建议实际动手练习,如果过程中有问题可以参考看看我的范例🔗:

  • 使用 Astro 提供的 defineCollection 来创建自己理想的集合并定义其资料型态
  • 在 content 资料夹中撰写建立相关档案
  • 使用 getCollection 抓取并显示在页面当中
  • 使用 getCollection 生成 Route

延伸阅读

Content Collections🔗 - Astro DOCS