unified.jsでMarkdownをHTMLに変換する

unified.jsはAST（抽象構文木）を使いテキストフォーマットを変換できるライブラリです。
remark（Markdown）、rehype（HTML）、retext（自然言語）といったフォーマットの実装があります。

https://github.com/unifiedjs/unified

今回はremarkとrehypeを使い、MarkdownをHTMLに変換してみます。

# インストール

必要なパッケージをあらかじめインストールします。

```
npm i unified
npm i remark-parse
npm i remark-rehype
npm i rehype-stringify
```

unifiedはES Modulesとして提供されているので、Node.jsで使うには`package.json`に次の記載を追加する必要があるようです。

```json:package.json
"type" : "module"
```

# Markdownをパースする

unifiedではある形式のテキストをASTに変換する処理を`Parser`が担います。
今回はMarkdownをパースするのに`remark-parse`を使います。

```js
import unified from 'unified';
import remarkParse from 'remark-parse';

const parser = unified().use(remarkParse);
const mdast = parser.parse('# test');

// できたASTを出力
console.log(JSON.stringify(mdast, null, 2));
```

実際の`mdast`の内容はこのようになっていました。`heading`の下に`text`がぶら下がっていますね。

```json
{
  "type": "root",
  "children": [
    {
      "type": "heading",
      "depth": 1,
      "children": [
        {
          "type": "text",
          "value": "test",
          "position": {
            "start": {
              "line": 1,
              "column": 3,
              "offset": 2
            },
            "end": {
              "line": 1,
              "column": 7,
              "offset": 6
            }
          }
        }
      ],
      "position": {
        "start": {
          "line": 1,
          "column": 1,
          "offset": 0
        },
        "end": {
          "line": 1,
          "column": 7,
          "offset": 6
        }
      }
    }
  ],
  "position": {
    "start": {
      "line": 1,
      "column": 1,
      "offset": 0
    },
    "end": {
      "line": 1,
      "column": 7,
      "offset": 6
    }
  }
}
```

# Markdown ASTからHTML ASTに変換

Markdown形式のASTからHTML形式のASTに変換をかけます。
unifiedではこれを行うのは`Transformer`です。今回は`remark2rehype`を使います。

```js
...
import remark2rehype from 'remark-rehype';
...
const transformer = unified().use(remark2rehype);
const hast = transformer.runSync(mdast);

console.log(JSON.stringify(hast, null, 2));
```

次のように`h1`タグの`element`の子供に`text`がぶら下がりました。

```json
{
  "type": "root",
  "children": [
    {
      "type": "element",
      "tagName": "h1",
      "properties": {},
      "children": [
        {
          "type": "text",
          "value": "test",
          "position": {
            "start": {
              "line": 1,
              "column": 3,
              "offset": 2
            },
            "end": {
              "line": 1,
              "column": 7,
              "offset": 6
            }
          }
        }
      ],
      "position": {
        "start": {
          "line": 1,
          "column": 1,
          "offset": 0
        },
        "end": {
          "line": 1,
          "column": 7,
          "offset": 6
        }
      }
    }
  ],
  "position": {
    "start": {
      "line": 1,
      "column": 1,
      "offset": 0
    },
    "end": {
      "line": 1,
      "column": 7,
      "offset": 6
    }
  }
}
```

# HTMLに変換

最後にHTMLのASTからテキストに変換します。
unifiedでは`Compiler`とよばれ、今回は`rehype-stringify`を使います。

```js
...
import rehypeStringify from 'rehype-stringify';
...
const compiler = unified().use(rehypeStringify);
const html = compiler.stringify(hast);

console.log(html);
```

次のようにHTMLに変換されました。

```
<h1>test</h1>
```

# まとめて実行

`parse`,`run`,`stringify`はまとめて`process`で実行できます。
3段階の処理をまとめたコードは次のようになります。

```js
import { unified } from 'unified';
import remarkParse from 'remark-parse'
import remark2rehype from 'remark-rehype'
import rehypeStringify from 'rehype-stringify';

const processor = unified()
  .use(remarkParse) // Parser
  .use(remark2rehype) // Transformer
  .use(rehypeStringify); // Compiler
const html = processor.processSync('# test');

console.log(html.value);
```