gray-matter 在内容管线中的位置
gray-matter 在 Markdown 内容工程管线中的定位,各层职责分工,以及和 Zod、remark、rehype、搜索索引等工具的协作关系。
[!info] related notes
- 概念定义: gray-matter
- 操作指南: gray-matter 使用指南
- 上位概念: Markdown
- 生态组合: Dataview, 创建个人数字花园的方案
- 相关实践: 知识库批量更新指南, 知识库扁平化管理指南
gray-matter 在内容管线中的位置
范围
这篇笔记解释 gray-matter 在 Markdown 内容工程管线中处于什么位置、它和哪些工具协作、各层分别负责什么。
为什么需要理解它的位置
gray-matter 几乎不单独使用。它的价值在于作为管线中的”拆包层”,把原始 Markdown 文件拆成两路:
- 元数据路 → 校验、索引、列表页、搜索、排序、筛选
- 正文路 → 渲染、摘要、embedding、RAG、展示
理解这个分层,才能正确设计内容处理系统,避免把所有逻辑堆在一个函数里。
管线全景
读取 Markdown 文件(fs / fast-glob)
↓
gray-matter 拆出 data 和 content
↓
├── data 路
│ ↓
│ Zod / Valibot 校验 schema
│ ↓
│ normalize 补默认值
│ ↓
│ 索引 / 列表页 / 搜索 / 排序 / 筛选 / Dataview
│
└── content 路
↓
remark(Markdown → MDAST)
↓
rehype(MDAST → HAST)
↓
HTML / React 组件 / 向量 embedding / RAG
各层职责
第一层:文件发现
工具:fast-glob、glob、fs.readdir
职责:扫描目录,找出所有 .md 文件。
import fg from 'fast-glob'
const files = await fg('content/**/*.md', { absolute: true })
这一层不关心文件内容,只负责找到文件路径。
第二层:拆包(gray-matter)
工具:gray-matter
职责:把原始 Markdown 字符串拆成结构化元数据和纯正文。
import matter from 'gray-matter'
const file = matter(raw)
// file.data → 元数据对象
// file.content → 正文字符串
这一层只拆不校验。file.data 是 YAML 解析器的原始输出,不做结构检查。tags: frontend(字符串)和 tags: [frontend](数组)都会被原样返回。
第三层:元数据校验(Zod / Valibot)
工具:zod、valibot、superstruct
职责:校验 front matter 结构是否符合预期,补默认值,报错不合法字段。
import { z } from 'zod'
const NoteSchema = z.object({
title: z.string().min(1),
tags: z.array(z.string()).default([]),
status: z.enum(['draft', 'evergreen', 'archived']).default('draft'),
})
const result = NoteSchema.safeParse(file.data)
if (!result.success) {
console.warn(result.error.flatten())
}
为什么需要这一层:
- 用户手写的 front matter 不可信
tags: frontend需要转成['frontend']status: xxx需要报错- 缺失字段需要补默认值
第四层:规范化(normalize)
职责:对校验后的元数据做业务层面的规范化。
function normalize(meta: z.infer<typeof NoteSchema>) {
return {
...meta,
slug: meta.slug ?? slugify(meta.title),
created: meta.created ?? new Date().toISOString().slice(0, 10),
updated: meta.updated ?? new Date().toISOString().slice(0, 10),
}
}
这一层和 Zod 校验可以合并,也可以分开。分开的好处是:校验层只管”对不对”,规范化层管”补什么”。
第五层:正文渲染(remark / rehype)
工具:remark、rehype、markdown-it、MDX、Astro content pipeline
职责:把 Markdown 正文转换成 HTML、React 组件或其他格式。
import { unified } from 'unified'
import remarkParse from 'remark-parse'
import remarkRehype from 'remark-rehype'
import rehypeStringify from 'rehype-stringify'
const html = await unified()
.use(remarkParse)
.use(remarkRehype)
.use(rehypeStringify)
.process(file.content)
gray-matter 和这一层完全解耦——它只负责把 content 提取出来,不关心后面怎么渲染。
第六层:索引与搜索
工具:lunr、minisearch、flexsearch、自定义索引
职责:基于元数据和正文建立搜索索引。
const index = notes.map(note => ({
id: note.filepath,
title: note.meta.title,
tags: note.meta.tags,
body: note.content.slice(0, 500), // 摘要
}))
第七层:语义检索 / RAG
工具:OpenAI Embedding、Cohere、本地 embedding 模型
职责:把正文转换成向量,存入向量数据库,支持语义检索。
const embedding = await openai.embeddings.create({
model: 'text-embedding-3-small',
input: note.content,
})
这一层消费的是 gray-matter 提取出来的 content,不碰 front matter。
各层职责对比
| 层 | 工具 | 输入 | 输出 | 负责什么 | 不负责什么 |
|---|---|---|---|---|---|
| 文件发现 | fast-glob | 目录路径 | 文件路径列表 | 找文件 | 不读内容 |
| 拆包 | gray-matter | 原始字符串 | { data, content } | 分离元数据和正文 | 不校验、不渲染 |
| 校验 | Zod / Valibot | data 对象 | 类型安全对象 | 检查结构、补默认值 | 不修改文件 |
| 规范化 | normalize | 校验后对象 | 最终元数据 | 补 slug、补日期 | 不读写文件 |
| 渲染 | remark / rehype | content 字符串 | HTML / AST | Markdown → 可视化 | 不碰元数据 |
| 索引 | lunr / flexsearch | 元数据 + 正文 | 搜索索引 | 全文搜索 | 不渲染 |
| Embedding | embedding model | 正文 | 向量 | 语义检索 | 不渲染、不索引 |
和其他工具的关系图
gray-matter ──→ zod / valibot ──→ normalize ──→ 索引 / Dataview / 列表页
│
└──→ remark ──→ rehype ──→ HTML / React
│
└──→ embedding model ──→ 向量数据库 ──→ RAG
fast-glob ──→ gray-matter(文件路径 → 原始字符串)
slugify ←── normalize(生成 slug)
lunr / flexsearch ←── 索引层(全文搜索)
在不同场景中的位置
静态站生成(Astro / VitePress / Docusaurus)
Markdown 文件
→ gray-matter 提取 data
→ Astro content collection schema 校验
→ 生成页面路由、列表页、RSS
→ gray-matter 提取 content
→ remark / rehype 渲染成 HTML
Astro 内置了 astro:content API,底层就是 gray-matter + Zod 校验。
知识库管理(Obsidian / thought-forest)
Markdown 文件
→ gray-matter 提取 data
→ 标签校验、front matter 审计
→ Dataview 查询、MOC 聚合
→ gray-matter 提取 content
→ wikilink 解析、backlink 建立
dataview 插件的 file.tags、file.frontmatter 等字段,底层也是从 front matter 提取的。
RAG 内容预处理
Markdown 文件
→ gray-matter 提取 content
→ 分块(chunking)
→ embedding model 转向量
→ 存入向量数据库(Pinecone / Chroma / Weaviate)
→ query → embedding → 相似度搜索 → 返回相关片段
这一路完全不需要 data,只需要 content。
知识库批量工具
Markdown 文件
→ gray-matter 提取 data
→ 缺失 front matter 审计
→ 缺失 tags 审计
→ 孤岛笔记检测
→ gray-matter.stringify 写回修正后的文件
推荐工程结构
src/content/
read-note.ts 读取单篇 Markdown(fs/promises + gray-matter)
parse-frontmatter.ts gray-matter + Zod schema 校验
normalize-note.ts 补 slug、补日期、补默认标签
build-index.ts 批量扫描 → 生成 note index JSON
render-note.ts remark / rehype 渲染正文
sync-assets.ts 处理图片、wikilink、favicon 等
核心原则:
gray-matter 只负责"拆"
Zod / Valibot 负责"校验"
normalize 层负责"补默认值"
renderer 层负责"渲染正文"
index 层负责"建立查询数据"
每一层只做一件事,层与层之间通过数据对象传递,不共享状态。