POST://markdown-content-pipeline
带点橘子味的馒头的头像
带点橘子味的馒头

FRONTEND / DESKTOP DEV

返回文章列表

用 gray-matter + remark 构建 Markdown 内容管线

2 分钟· 599 62 次阅读

Markdown 内容管线的核心就两件事:解析 frontmatter(元数据)渲染正文。这篇从零实现一个够用的管线,并分享性能与安全上的坑。

第一步:gray-matter 解析元数据

frontmatter 是文章开头的 --- 包裹的 YAML:

---
title: 我的文章
date: '2026-08-17'
tags: [前端, Markdown]
---
正文内容...

gray-matter 一行拆开:

import matter from "gray-matter";
import fs from "node:fs";

const raw = fs.readFileSync("post.md", "utf8");
const { data, content } = matter(raw);
// data: { title, date, tags }
// content: "正文内容..."

第二步:remark 渲染正文

remark 是 Markdown 的语法树工具,配合插件链做转换:

import { remark } from "remark";
import html from "remark-html";
import gfm from "remark-gfm";

const processed = await remark()
  .use(gfm)      // GitHub 风格:表格、删除线、任务列表
  .use(html)     // 转成 HTML 字符串
  .process(content);

const html = processed.toString();

remark-gfm 几乎是必装的——它带来表格、~~删除线~~- [ ] 任务列表支持。

第三步:排序与聚合

文章列表需要按日期排序,精选、热门是聚合逻辑:

export function getAllPosts() {
  return readDir()
    .map(parse)
    .sort((a, b) => new Date(b.date) - new Date(a.date));
}

注意 date 字符串格式要统一,'2026-08-17' 这种 ISO 日期才能被 new Date() 正确解析。

进阶:自定义插件

想给正文加自定义功能(比如提示块),写个 remark 插件:

import { visit } from "unist-util-visit";

function remarkAdmonition() {
  return (tree) => {
    visit(tree, "blockquote", (node) => {
      // 把 "> ⚠️ xxx" 转成自定义 HTML
      node.data = { hName: "div", hProperties: { class: "admonition" } };
    });
  };
}

性能陷阱

remark 每次解析都不便宜,两处容易踩坑:

  1. 列表页不要渲染全文——列表只需要 frontmatter,contentHtml 留空或只在详情页渲染:
// 列表:不调 remark
posts.map(p => ({ ...p, contentHtml: "" }));

// 详情:才完整渲染
const html = await renderMarkdown(fullContent);
  1. 避免重复读盘——高频调用时用简单缓存:
const cache = new Map<string, Post>();
export function getPost(slug: string) {
  if (cache.has(slug)) return cache.get(slug)!;
  // 读盘解析,写入缓存
}

安全:XSS 要小心

remark-html 默认会转义 HTML,但 allowDangerousHtml 打开后要自己负责消毒:

// ❌ 用户内容里嵌入 <script> 就中招
.use(html, { allowDangerousHtml: true })

// ✅ 用 sanitize 兜底
import sanitize from "sanitize-html";
const safe = sanitize(htmlOutput, { allowedTags: [...] });

自己博客还好,多用户系统必须消毒。

小结

一个够用的 Markdown 管线就三块:

  1. gray-matter 拆元数据;
  2. remark + gfm + html 渲染正文;
  3. 排序/聚合 + 缓存 + 消毒。

管线本身不难,难点在性能(别渲染不需要的)安全(别信任输入)

相关文章