Markdownを強力に変換するNodeJSライブラリーremarkを使ってみた1

これは　yuriacats アドベントカレンダー３日目の記事です.

---

## remarkって何？
https://github.com/remarkjs/remark

remarkとは高機能なMarkdownパーサーライブラリー群です。
特徴として[mdast](https://github.com/syntax-tree/mdast)というAST[^1]（抽象構文木）を介していることが挙げられます。ASTを仲介させることでMarkdownとTexを混在させた形態などにも対応できる柔軟さを生み出し、それもあってか[Gatsby](https://www.gatsbyjs.com/starters/)のスターターの一つである「[gatsby-starter-blog](https://www.gatsbyjs.com/starters/gatsbyjs/gatsby-starter-blog/)」でも採用されています。ESにのみ対応しておりますが、TypeScript対応やDeno対応などモダンな開発にも適応できるサポートがあります。いかにプラグインを活用することでできることを箇条書きにします。
- Githubの記法のMarkdown対応
- 目次の作成 
- Markdownの文法チェック
- Katex（TexのJS移植版）
- [その他のプラグイン](https://github.com/remarkjs/remark/blob/main/doc/plugins.md#list-of-plugins)

しかしながらかなり、使いこなすのが難しいライブラリーでもあると思います。公式で言っているように単にMarkdownをHTMLに変換する用途であれば[micromark](https://github.com/micromark/micromark)などの他のライブラリーを積極的に使っても良いかもしれません。

## remarkの主要ライブラリー

ざっくりと入力と出力のからプラグインを割り出そうとすると以下のようになるかと思います。

|出力＼入力|HTML|Markdown|MDAST|
|:---:|:---:|:---:|:---:|
|HTML|＼|[micromark](https://github.com/micromark/micromark)[^2]|[rehype-stringify](https://github.com/rehypejs/rehype/tree/main/packages/rehype-stringify)|
|Markdown|[micromark](https://github.com/micromark/micromark)[^2]|＼|[remarkStringify](https://github.com/remarkjs/remark/tree/main/packages/remark-stringify)|
|MDAST|[rehype-parse](https://github.com/rehypejs/rehype/tree/main/packages/)|[remak-parse](https://github.com/remarkjs/remark/tree/main/packages/remark-parse)|＼|

[^2]: 他にもライブラリーの選択肢はあるかと思います。
- HTMLのサポートをするため（rehype系プラグインを使うため）に[remark-rehype](https://www.npmjs.com/package/remark-rehype)がある
- ASTを扱う基幹ライブラリーとして[unified](https://github.com/unifiedjs/unified)がある。

## 実装編１


(参考リポジトリー：https://github.com/yuriacats/remark-howto　）

- 目標　：テキストエリアに書かれた文字をそのままHTML要素に変換してHTMLを横に生成する。

```
yarn install remark unified　remark-parse　remark-rehype　rehype-stringify
```

 1. Reactのチュートリアル等を参考にしながら、文字をそのまま返すような形にしてみよう。そうすると以下のコードになる。以下のコードをいじって今回はMarkdownをそのまま表示することにする。
 
```js
import React, { useEffect, useState } from "react";

function App() {
    const [testText, setTestText] = useState("")
    const [resultText, setResultText] = useState("")
    return (
        <div>
            <div className="App">
                <input
                    value={testText}
                    onChange={(event) => setTestText(event.target.)}
                />
                <p>{testText}</p>
            </div>
        </div>
    );
}

export default App;
```
　
2. 今回は、Markdown→mdast→Stringに変換していきたいと思いますので使用するライブラリーである以下をimportします。大体途中まで書くとVSCodeが補完してくれるので、それでうまく書いていきましょう。
- unified
- remark-parse
- remark-rehype
- rehype-stringify

```js
import React, { useEffect, useState, Fragment } from "react";
import { unified } from "unified";
import remarkParse from "remark-parse/lib";
import remarkRehype from "remark-rehype";
import rehypeStringify from "rehype-stringify/lib";


function App() {
    const [testText, setTestText] = useState("")
    const [resultText, setResultText] = useState(Fragment)
    return (
        <div>
            <p>HTMLを入力すると下にMarkdown変換したものを表示するよ！</p>
            <textarea
                rows={10}
                onChange={(event) => setTestText(event.target.value)}
            />
           <p>{testText}</p>
        </div>
    );
}

export default App;

```

3. 今回は書いたものをリアルタイムでHTMLの例として出力したい
- testTextが変化することをトリガーに関数を走らせましょう。
- Dom動作を皮切りに変更させる場合`useEffect(関数,[監視する変数])`を利用すると良い。
- Markdown　→　mdast →　HTML　という順番なので、`remark-parse→remark-rehype→rehype-stringify`で変換するとよさそう。
- 実際にremark-parseのチュートリアルには、`remark-parse→remark-rehype→rehype-stringify`でチェーンが作られている。
- 帰ってくるオブジェクトはPromiseなので.then内で変数を変更させて結果をDomに反映させてみた。

結果的には以下のようになるかと思います。([github](https://github.com/yuriacats/remark-howto/tree/1202WhatRemark))
```js
import React, { useEffect, useState, Fragment } from "react";
import { unified } from "unified";
import remarkParse from "remark-parse/lib";
import remarkRehype from "remark-rehype";
import rehypeStringify from "rehype-stringify/lib";


function App() {
    const [testText, setTestText] = useState("")
    const [resultText, setResultText] = useState(Fragment)
    useEffect(() => {
        const result = unified()
            .use(remarkParse)
            .use(remarkRehype)
            .use(rehypeStringify)
            .process(testText)
            .then((res) => setResultText(res.value))

    }, [testText])
    return (
        <div>
            <p>HTMLを入力すると下にMarkdown変換したものを表示するよ！</p>
            <textarea
                rows={10}
                onChange={(event) => setTestText(event.target.value)}
            />
            <p>
                {resultText}
            </p>
        </div>
    );
}

export default App;

```



[^1]: AST（抽象構文木）とは文章を解析し、プログラムにとって不要なものを削除した結果として得られる木構造（グラフ構造の一種）の木。