Remark プラグインを使って Markdown から Front Matter を抽出する

Markdown をパースして HTML に変換したりできる Remark。今回は Markdown ファイル中に書かれた Front Matter を抽出して利用できるようにする、

というプラグインを紹介する。

目次

Front Matter とは

まず Front Matter とはなんぞや、というと、元々は Jekyll という静的サイトジェネレータが広めたらしい、文書のメタデータを YAML で書く手法。

Markdown ファイルに限らず、そのファイルの冒頭で、以下のようにハイフン3つで囲んだ行の中が YAML 形式になっていて、その部分をメタデータ的に活用できるというモノ。

---
title: こんにちはブログ
date : 2020-11-01
categories:
  - HTML
  - CSS
  - JavaScript
---

以下、Markdown で本文を書く。

上のように titledatecategories といったデータを用意しておき、静的サイトを生成する時なんかに、title のデータを HTML の title 要素に埋め込んだり、日付データを基に URL を構築したりするのに活用できる、というワケだ。

厳密な規格というワケではないが、ハイフン3つ --- で囲むことと、囲んだ中は YAML 形式で書くのが主流。

Remark プラグインで Front Matter を抽出する

上のように、Front Matter セクションを持つ Markdown ファイルをいきなり Remark-Rehype なんかに食わせると、Front Matter 部分も素の Markdown としてパースされてしまい、おかしな文章になってしまう。当然、メタデータを抽出して利用したりも出来ない。

そこで、今回紹介する Remark プラグイン2つを使うことで、Front Matter 部分を抽出してやろう。

必要なパッケージとサンプルコード

インストールする npm パッケージは以下のとおり。

# Markdown を HTML へと変換するために必要な最低限の Unified・Remark・Rehype 関連パッケージ
$ npm install --save-dev unified remark-parse remark-rehype rehype-stringify

# Markdown 内の Front Matter のパースに必要なパッケージ
$ npm install --save-dev remark-frontmatter remark-extract-frontmatter yaml

そして以下のようにパースしていく。

const fs = require('fs').promises;

const unified = require('unified');
const remarkParse = require('remark-parse');
const remarkFrontmatter = require('remark-frontmatter');
const remarkExtractFrontmatter = require('remark-extract-frontmatter');
const yaml = require('yaml');
const remarkRehype = require('remark-rehype');
const rehypeStringify = require('rehype-stringify');

(async () => {
  const inputMarkdown = await fs.readFile('./example.md', 'utf-8');
  
  const processor = unified()
    .use(remarkParse)
    .use(remarkFrontmatter, [{
      type: 'yaml',
      marker: '-',
      anywhere: false  // ファイルの冒頭に Front Matter がある前提で探索する
    }])
    .use(remarkExtractFrontmatter, {
      yaml: yaml.parse,
      name: 'frontMatter'  // result.data 配下のキー名を決める
    })
    .use(remarkRehype)
    .use(rehypeStringify);
  const result = await processor.process(inputMarkdown);
  
  // Front Matter 部分が以下のように抽出できる (上で指定した name が 'frontMatter' なので)
  const frontMatter = result.data.frontMatter;
  // 先程の例のような Front Matter セクションであれば、次のようにデータが取れる
  const title      = result.data.frontMatter.title;       // 'こんにちはブログ'
  const date       = result.data.frontMatter.date;        // '2020-11-01'
  const categories = result.data.frontMatter.categories;  // ['HTML', 'CSS', 'JavaScript']
  
  // パースした HTML には Front Matter 部分が含まれていない
  const outputHtml = result.contents;
  await fs.writeFile('./example.html', outputHtml, 'utf-8');
})();

上のコードでは抽出した const frontMatter の情報を何にも使用していないが、パース後の HTML に埋め込んだり、ファイル名に利用したり、といった用途が考えられるだろう。