1. 学前准备:Front Matter 是什么 #
很多 Markdown 文件最上面有一小段配置,用一对 --- 包起来,下面才是正文。这一段叫 Front Matter(前置元数据),一般用 YAML 写键值对:
---
title: 我的文章
author: 小明
---
正文从这里开始……对 Node 来说,你需要两件事:
- 拆开:上面的 YAML → JavaScript 对象;下面的字符串 → 正文。
- 拼回(可选):改完对象后,再生成带
---的完整文件。
手写正则容易漏边界情况;gray-matter 帮你完成「拆」和「拼」。
1.1 YAML现在只需会这些 #
| 写法 | 含义 |
|---|---|
name: commit-message |
字符串 |
count: 3 |
数字 |
draft: false |
布尔值 |
2. gray-matter 解决什么问题 #
若你写:
const all = fs.readFileSync('SKILL.md', 'utf8');得到的是一整段字符串,里面既有 --- 里的配置,也有正文。模型或业务逻辑往往只要其中一部分。
gray-matter 调用后返回大致结构:
| 属性 | 含义 |
|---|---|
data |
Front Matter 解析成的对象 |
content |
去掉头尾后的正文(通常已 trim) |
3. 安装与第一个可运行示例 #
在练习目录打开终端(PowerShell 或 CMD):
npm init -y
npm install gray-matter保存为 01-hello.js,执行 node 01-hello.js:
// 引入 gray-matter;默认导出是一个函数,习惯命名为 matter
const matter = require('gray-matter');
// 模拟磁盘里一整份带 Front Matter 的文本
const raw = `---
title: Hello World
author: 小明
---
这是正文段落。`;
// 解析:拆成 data(元数据)和 content(正文)
const result = matter(raw);
// 打印元数据对象
console.log('data:', result.data);
// 打印正文
console.log('content:', result.content);说明: matter 既是函数名,也是库的常见叫法;result.data / result.content 是最常用的两个字段。
4. data 与 content 分别怎么用 #
解析完成后:
data:当普通对象用,例如data.title、data.name。content:仍是字符串,可打印、拼接,或交给其它 Markdown 工具。
下面示例演示「取字段 + 判断正文是否为空」。保存为 02-use-fields.js:
// 引入库
const matter = require('gray-matter');
// 模拟 SKILL.md 风格内容
const raw = `---
name: commit-message
description: 根据 git 变更写提交说明
---
1. 先执行 git status。
2. 再写 commit message。
`;
// 解析
const { data, content } = matter(raw);
// 使用元数据(类似 skills.js 里的 meta)
console.log('技能名:', data.name);
// 使用描述
console.log('描述:', data.description);
// 正文行数(按换行粗算)
console.log('正文行数:', content.split('\n').filter(Boolean).length);
// 正文是否为空
console.log('正文为空?', content.trim() === '');5. 从文件读取并解析 #
真实项目里内容在文件里。下面脚本会先写入示例 demo-post.md,再读取并解析,保证复制即可运行。保存为 03-read-file.js:
// 文件系统
const fs = require('fs');
// 路径拼接
const path = require('path');
// gray-matter
const matter = require('gray-matter');
// 与本脚本同目录的演示文件
const filePath = path.join(__dirname, 'demo-post.md');
// 要写入的完整 Markdown(含 Front Matter)
const seed = `---
title: 第一篇演示文章
date: 2025-01-15
tags:
- node
- tutorial
---
你好,这是 **gray-matter** 演示正文。
`;
// 写入演示文件
fs.writeFileSync(filePath, seed, 'utf8');
// 读回 UTF-8 字符串
const fileContent = fs.readFileSync(filePath, 'utf8');
// 解析
const { data, content } = matter(fileContent);
// 输出元数据
console.log('标题:', data.title);
// 输出标签数组(YAML 列表会解析成数组)
console.log('标签:', data.tags);
// 输出正文前 30 个字符
console.log('正文预览:', content.slice(0, 30));
// 演示结束,删除临时文件(可注释掉以保留查看)
fs.unlinkSync(filePath);要点: gray-matter 不替你做 readFile;标准写法是 fs.readFileSync + matter(字符串)。
6. matter.stringify:拼回带 Front Matter 的文本 #
改完 data 或 content 后,可用 matter.stringify(正文, 元数据对象) 生成完整字符串,再 writeFileSync 写盘。
保存为 04-stringify.js:
// 引入库
const matter = require('gray-matter');
// 引入 fs
const fs = require('fs');
// 引入 path
const path = require('path');
// 正文(不含 Front Matter)
const body = '更新后的正文内容。';
// 要写入头部的元数据
const meta = {
title: '新标题',
draft: false,
};
// 拼成 --- 包裹 YAML + 正文的完整字符串
const full = matter.stringify(body, meta);
// 目标路径
const outPath = path.join(__dirname, 'demo-out.md');
// 写入文件
fs.writeFileSync(outPath, full, 'utf8');
// 打印生成结果
console.log('已写入:', outPath);
// 打印文件内容便于对照
console.log('--- 文件内容 ---\n' + full);
// 清理演示文件
fs.unlinkSync(outPath);7. 小练习:模拟加载 SKILL.md #
读文件 → matter → 取 data.name、data.description 和正文 body。保存为 05-skill-like.js:
// path
const path = require('path');
// fs/promises 异步读写
const fsp = require('fs/promises');
// gray-matter
const matter = require('gray-matter');
// 模拟 .claude/skills/commit-message/SKILL.md 路径
const skillDir = path.join(process.cwd(), '.claude', 'skills', 'commit-message');
// SKILL.md 完整路径
const skillFile = path.join(skillDir, 'SKILL.md');
// SKILL.md 全文
const skillContent = `---
name: commit-message
description: 根据 git 变更生成 commit message
---
1. 执行 git status 与 git diff。
2. 写一行 conventional commits 风格标题。
`;
// 自执行异步函数
(async () => {
// 创建目录
await fsp.mkdir(skillDir, { recursive: true });
// 写入 SKILL.md
await fsp.writeFile(skillFile, skillContent, 'utf8');
// 读文件
const raw = await fsp.readFile(skillFile, 'utf8');
// 解析 frontmatter
const { data, content } = matter(raw);
// 与 skills.js 类似的字段命名
const name = (data.name || 'commit-message').trim();
const description = data.description || name;
const body = content.trim();
// 打印摘要
console.log('name:', name);
console.log('description:', description);
console.log('body 长度:', body.length);
// 可选:保留文件供 skills 教程继续用;这里删除以免堆积
await fsp.rm(skillDir, { recursive: true, force: true });
})().catch((err) => {
console.error(err);
process.exit(1);
});8. 常见误区 #
8.1 把整文件内容当正文 #
若不用 gray-matter,data 里的 name 会解析不到,正文里还会混进 --- 和 YAML。
8.2 期望库自己去读路径 #
matter('/path/to/file.md') 不是读文件路径;应 readFileSync 后再 matter(字符串)。
8.3 Front Matter 没有闭合的 --- #
格式错误时解析可能失败或得到意外结果。写 SKILL.md 时保持:第一行 ---,YAML 块,再一行 ---,然后正文。
9. 知识点速查 #
| 主题 | 记住这一句 |
|---|---|
| 安装 | npm install gray-matter |
| 解析 | const { data, content } = matter(字符串) |
| 读文件 | matter(fs.readFileSync(path, 'utf8')) |
| 拼回 | matter.stringify(正文, 元数据对象) |
| 典型场景 | SKILL.md、博客 md、静态站元数据 |