gray-matter
解析 Markdown 文件顶部 Front Matter 元数据的 Node.js 库,把结构化元数据和正文分离,是内容工程管线的基础层。
[!info] related notes
- 操作指南: gray-matter 使用指南
- 生态位置: gray-matter 在内容管线中的位置
- 上位概念: Markdown
- 生态组合: Dataview, 创建个人数字花园的方案
- 相关实践: 知识库批量更新指南
gray-matter
一句话定义
gray-matter 是一个 Node.js 库,专门从 Markdown / 文本文件中解析顶部的 YAML(或 JSON / TOML)Front Matter,把”给机器用的结构化元数据”和”给人读的正文”拆开。
Front Matter 是什么
Front Matter 是 Markdown 文件最顶部的一段结构化配置,用 --- 分隔符包起来:
---
title: SSE 数据传输与 Markdown 渲染
slug: sse-markdown-rendering
tags:
- ai-agent
- frontend
status: draft
created: "2026-06-28"
---
# 正文内容
这里是给人读的 Markdown 正文……
可以把它理解为两个区域:
┌──────────────────────────────┐
│ Front Matter 元数据 │ 给程序读:标题、日期、标签、状态、封面、slug
├──────────────────────────────┤
│ Markdown 正文 │ 给人读:文章内容、笔记内容、教程内容
└──────────────────────────────┘
Front Matter 常见于:Astro、VitePress、Docusaurus、Gatsby、Next.js MDX、Obsidian 知识库、静态博客、文档站、内容管理系统。
它在解决什么
没有 gray-matter 时,你可能会自己写正则:
const match = raw.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/)
但实际项目里会遇到很多边界情况:
- 正文里有代码块,代码块里包含
---分隔符 - 正文里有 YAML 示例,看起来像 front matter
- 空 front matter(
---\n---) - 非标准分隔符(
~~~而不是---) - JSON / TOML 格式的 front matter
gray-matter 的核心价值就是:正确处理这些边界情况,不需要你自己维护一套脆弱的正则。它不依赖正则做解析,能区分正文里的 --- 和真正的 front matter 边界。
核心心智模型
gray-matter 做且只做一件事:拆。
输入:一段完整的 Markdown 字符串
↓
gray-matter
↓
输出:{ data: {...}, content: '...' }
data— 解析后的 front matter 对象(JS 对象)content— 去掉 front matter 之后的正文(字符串)
它不负责:
- 校验 front matter 结构是否正确(交给 Zod / Valibot)
- 渲染 Markdown 正文(交给 remark / rehype / MDX)
- 建立搜索索引(交给 lunr / flexsearch)
- 生成 slug(交给 slugify)
它负责:
- 识别 front matter 的起止边界
- 解析 YAML / JSON / TOML 为 JS 对象
- 提取纯正文部分
- 处理各种边界情况(空 front matter、代码块里的假分隔符等)
返回对象详解
matter(raw) 返回的不是单纯的 { data, content },而是一个 file object,包含多个字段:
枚举字段(enumerable)
const file = matter(raw)
file.data // 解析后的 front matter 对象
file.content // 去掉 front matter 之后的正文
file.excerpt // 摘要,只有配置 excerpt 选项时才有
file.empty // front matter 为空时的原始内容
file.isEmpty // front matter 是否为空(boolean)
调试字段(non-enumerable)
file.orig // 原始输入字符串
file.language // 检测到的 front matter 语言(yaml / json / toml)
file.matter // 原始 front matter 字符串(未经解析)
file.stringify // 序列化函数,可自定义输出格式
其中 file.matter 和 file.data 的区别很重要:
file.matter是原始 YAML 字符串:"title: AI Agent\ntags:\n - langgraph"file.data是解析后的 JS 对象:{ title: 'AI Agent', tags: ['langgraph'] }
支持的格式
gray-matter 默认解析 YAML,也支持其他格式:
YAML(默认)
---
title: Hello
tags:
- ai
- frontend
draft: false
---
JSON
---json
{
"title": "Hello",
"tags": ["ai", "frontend"],
"draft": false
}
---
TOML
---toml
title = "Hello"
draft = false
---
自定义分隔符
const file = matter(raw, {
delimiters: '~~~',
})
也支持 open / close 分隔符数组:
const file = matter(raw, {
delimiters: ['~~~', '~~~'],
})
语言自动检测
分隔符后面可以加语言标识来指定解析器:
---json
{ "title": "Hello" }
---
---toml
title = "Hello"
---
不加标识时默认走 YAML 解析器。
工程建议:实际项目里统一用 YAML。原因是 Markdown 生态最通用,Astro / VitePress / Obsidian 都更熟悉 YAML front matter,人工维护成本最低。
边界与常见误解
它不是 Markdown 渲染器
错误理解:“gray-matter 可以把 Markdown 渲染成 HTML”
不对。它只做 Markdown 原文 → frontmatter data + content。Markdown 渲染要交给 remark / rehype / markdown-it / MDX / Astro content pipeline。
它不是 schema 校验器
file.data 是 YAML 解析器的原始输出,不做结构校验。tags: frontend(字符串)和 tags: [frontend](数组)都会被原样返回。校验要交给 Zod / Valibot。
它不是文件读取器
matter() 接收的是字符串,不负责读文件。matter.read(filepath) 存在但是同步的,现代项目建议自己用 fs/promises 读文件再交给 matter()。
常见坑
YAML 类型自动转换
YAML 解析器会自动转换某些值的类型:
draft: false # → boolean
count: 123 # → number
date: 2026-06-28 # → 可能变成 Date 或 string,取决于解析器行为
工程建议:
- 日期统一写字符串:
created: "2026-06-28" - 状态统一用 enum:
status: draft - 标签统一用数组:
tags: [ai, frontend] - 不要在 front matter 里塞复杂业务对象
正文开头空行
解析后 content 可能包含开头换行:
const file = matter(`---
title: Test
---
# Hello`)
console.log(JSON.stringify(file.content))
// "\n# Hello"
如果要做摘要、首段提取、embedding,最好 content.trimStart()。但如果要保持原文格式,就不要随便 trim。
stringify 会改变 YAML 格式
matter.stringify() 很方便,但它可能会重排 YAML 格式、改变引号、改变数组风格:
# 原来
tags: [ai, frontend]
# stringify 后可能变成
tags:
- ai
- frontend
批量 fix 前建议:先 git diff,小批量处理,保留人工写作习惯,只对规范化字段做自动修复。
空 front matter
当文件没有 front matter 时:
const file = matter('# Just content\nNo front matter here.')
file.isEmpty // true
file.data // {}
file.content // '# Just content\nNo front matter here.'
当 front matter 为空时:
const file = matter('---\n---\nContent')
file.isEmpty // true
file.data // {}
两种情况都需要在后续处理中考虑。