blocks 字段存储一个有序的页面组合。每一项都会记录区块类型、保留的 schema 版本、稳定的 key,以及该版本所声明的字段。编辑者在内容编辑器中添加区块并调整顺序。Astro 路由会把每种区块类型映射到一个组件。
定义区块类型
请在使用区块类型的集合字段之前先定义区块类型。种子会保留每一个编号版本以及当前生效的 currentVersion 指针。
下面的种子定义了 Hero 和 Feature grid 区块,然后让它们可用于 Pages 的 layout 字段:
{
"$schema": "https://emdashcms.com/seed.schema.json",
"version": "1",
"blockTypes": [
{
"slug": "hero",
"label": "Hero",
"category": "Layout",
"currentVersion": 1,
"versions": [
{
"version": 1,
"fields": [
{ "slug": "heading", "label": "Heading", "type": "string", "required": true },
{ "slug": "body", "label": "Body", "type": "portableText" },
{ "slug": "image", "label": "Image", "type": "image" },
{ "slug": "link_label", "label": "Link label", "type": "string" },
{ "slug": "link_url", "label": "Link URL", "type": "url" }
]
}
]
},
{
"slug": "feature_grid",
"label": "Feature grid",
"category": "Layout",
"currentVersion": 1,
"versions": [
{
"version": 1,
"fields": [
{ "slug": "heading", "label": "Heading", "type": "string" },
{
"slug": "items",
"label": "Items",
"type": "repeater",
"validation": {
"subFields": [
{ "slug": "title", "label": "Title", "type": "string", "required": true },
{ "slug": "description", "label": "Description", "type": "text" }
]
}
}
]
}
]
}
],
"collections": [
{
"slug": "pages",
"label": "Pages",
"fields": [
{ "slug": "title", "label": "Title", "type": "string", "required": true },
{
"slug": "layout",
"label": "Layout",
"type": "blocks",
"validation": {
"allowedTypes": ["hero", "feature_grid"],
"maxItems": 20
}
}
]
}
]
}
allowedTypes 的顺序决定区块选择器中的顺序。如果你之后移除了某个允许的类型,EmDash 会把它移到由服务器管理的 retiredTypes 列表中。现有区块仍然可以编辑,但编辑者无法添加或复制该类型。
创建 Astro 组件
每个组件都会接收 value、index 和 blockKey。该值包含 _type、_version 和 _key,因此当区块 schema 演进时,组件可以收窄保留的版本。
Hero 组件从已存储的区块中读取所有要显示的值:
---
import { sanitizeHref } from "emdash";
import { Image, PortableText, type BlockComponentProps } from "emdash/ui";
import type { PageLayoutBlock } from "../../../emdash-env";
type HeroBlock = Extract<PageLayoutBlock, { _type: "hero" }>;
type Props = BlockComponentProps<HeroBlock>;
const { value } = Astro.props;
---
<section class="hero">
<div>
<h1>{value.heading}</h1>
{value.body && <PortableText value={value.body} />}
{value.link_url && <a href={sanitizeHref(value.link_url)}>{value.link_label}</a>}
</div>
{value.image && <Image image={value.image} />}
</section>
请为每个允许的类型创建一个组件。组件控制标记和样式;区块的值提供内容和媒体。
渲染页面组合
使用 defineBlockComponents,要求为生成的字段联合类型中的每个 _type 都提供一个组件。然后在页面路由中把该映射传给 <Blocks>。
---
import { decodeSlug, getEmDashEntry } from "emdash";
import { Blocks, defineBlockComponents } from "emdash/ui";
import type { PageLayoutBlock } from "../../../emdash-env";
import FeatureGrid from "../../components/blocks/FeatureGrid.astro";
import Hero from "../../components/blocks/Hero.astro";
const slug = decodeSlug(Astro.params.slug);
if (!slug) return Astro.rewrite("/404");
const { entry: page, cacheHint } = await getEmDashEntry("pages", slug);
if (!page) return Astro.rewrite("/404");
Astro.cache.set(cacheHint);
const components = defineBlockComponents<PageLayoutBlock>({
hero: Hero,
feature_grid: FeatureGrid,
});
---
<Blocks value={page.data.layout} components={components} />
<Blocks> 不会执行任何内容、schema、媒体或网络查询。它按存储的顺序渲染所提供的数组。当某个区块组件需要其他数据时,它可以执行显式的应用程序查询。
处理缺失的组件
在开发期间,未映射的类型会生成一个可见的占位符和一条控制台警告。占位符会显示 _type 的名称,但不会打印已存储的区块值。
在生产环境中,未映射的类型会在提供了 fallback 组件时渲染该组件,否则不渲染任何输出:
---
import MissingBlock from "../../components/blocks/MissingBlock.astro";
---
<Blocks value={page.data.layout} components={components} fallback={MissingBlock} />
在启用或激活生产内容所使用的区块类型之前,请先发布渲染器的支持。
修改区块 schema
兼容的更改会修订当前生效的版本。添加可选字段、添加默认值或放宽校验,都会保持相同的版本号。已存储的区块会在下次写入时获得默认值。
破坏性更改会创建一个未激活的版本。移除字段、更改字段类型、添加必填字段或收紧校验,都属于破坏性更改。
-
通过 schema API 或 MCP 创建破坏性版本。让它保持未激活状态。
-
更新渲染器,使其同时处理保留的版本和新版本。部署渲染器。
-
激活新版本。激活之后,新区块会使用它。
-
使用
migrateBlocks: true显式迁移已存储的区块。在更改_version及其特定于版本的字段时,保留每个区块的_key。
旧版本仍然可用于修订、草稿、媒体追踪和已存储的内容。区块类型和保留的版本没有硬删除操作。
支持的嵌套字段
区块定义支持 string、text、url、number、integer、boolean、datetime、select、multiSelect、portableText、image、file 和 repeater。
区块定义内部不支持引用、JSON、slug、嵌套区块、自定义小部件、物理索引、唯一性以及按子字段的本地化。blocks 字段本身不能设为必填、唯一、可搜索、可索引,也不能指定自定义字段小部件。
有关确切的字段校验和已存储值的规则,请参阅 blocks 字段参考。有关种子冲突和导出行为,请参阅 Seed 文件。