Markdown方言比較ツールの紹介と公開

なぜこのツールを作ったのか

軽量マークアップ言語であるMarkdownは、記法について覚えることが少なく気軽に書けるため、ちょっとしたメモから、手順書等のドキュメント、スクリプトのREADME.md等を書くのに重宝している。しかし、以下記事で取り上げたように、実はMarkdownには各種方言があり、実行環境によっては特定の記法が使えないなどの落とし穴がある。

そこで上記記事では、代表的な方言である、CommonMarkGitHub Flavored Markdownを取り上げ、以下図のように比較した。この比較画像を作成する際、HTML+JavaScriptにてちょっとした比較ツールを作成したのだが、1回使ってお蔵入りするのも勿体無いと感じたので、ソースコードを公開することにする。

CommonMark vs GitHub Flavored Markdown

使い方

この比較スクリプトは、Markdownを異なるレンダリングエンジンで表示した結果を並べて比較できる、ローカル実行型のHTMLツールである。

主な表示内容は以下の3つである:

  • Raw Markdown:入力したMarkdownソース
  • Whitespace Annotated:空白・改行を記号で可視化したもの(スペース=␣、改行=↵)
  • CommonMark / GFM Rendering:2つのエンジン(CommonMarkとGitHub Flavored Markdown)による表示結果

1. ファイルの準備

記事内に掲載されたHTMLコードをコピーし、任意のファイル名(例:compare.html)で保存する。

2. 実行方法

保存したHTMLファイルをWebブラウザで開くと、自動的にツールが起動する。 ※このツールはJavaScriptを使用しており、ライブラリをCDN経由で読み込むため、インターネット接続が必要である。

3. 使用手順

左側のテキストエリアに比較したいMarkdown文書を入力する。 入力に応じて、以下の3カラムがリアルタイムに更新される:

  • Whitespace Annotated:スペースと改行を可視化(整形トラブルの特定に有用)
  • CommonMark Rendering:CommonMark仕様による出力
  • GFM Rendering:GitHub Flavored Markdownによる出力

4. 例

以下のようなMarkdownを入力すると:

# 見出し
- リスト
  - ネスト

これは~~打ち消し線~~である

- [x] 完了
- [ ] 未完了

GFMとCommonMarkの挙動の違い(チェックボックスの描画、打ち消し線の有無など)が視覚的に確認できる。

5. ライセンス

  • MITライセンス

6. 補足事項

※本ツールでは、JavaScriptライブラリをCDN(jsDelivr)経由で読み込んでいる。信頼性の高いCDNを使用しており、通常の使用においてはセキュリティ上の問題はほぼないが、気になる場合はローカルにライブラリをダウンロードして使用することもできる。

ソースコード

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>Markdown方言比較エディタ+スペース可視化</title>
  <script src="https://cdn.jsdelivr.net/npm/commonmark/dist/commonmark.min.js"></script>
  <script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
  <style>
    body {
      display: flex;
      flex-direction: row;
      font-family: sans-serif;
      margin: 0;
      padding: 0;
      height: 100vh;
    }
    .input-wrapper, .preview, .visual {
      width: 25%;
      height: 100vh;
      padding: 10px;
      box-sizing: border-box;
      overflow: auto;
    }
    textarea {
      width: 100%;
      height: calc(100% - 1.5em);
      font-family: monospace;
      box-sizing: border-box;
    }
    .preview {
      border-left: 1px solid #ccc;
      background-color: #fafafa;
    }
    .visual {
      border-left: 1px dashed #aaa;
      font-family: monospace;
      white-space: pre-wrap;
      background-color: #f4f4f4;
      color: #555;
    }
    h3 {
      margin-top: 0;
    }
  </style>
</head>
<body>
  <div class="input-wrapper">
    <h3>Raw Markdown</h3>
    <textarea id="input"></textarea>
  </div>
  <div class="visual" id="visual">
    <h3>Whitespace Annotated</h3>
  </div>
  <div class="preview" id="commonmark"></div>
  <div class="preview" id="gfm"></div>

  <script>
    const input = document.getElementById('input');
    const cmDiv = document.getElementById('commonmark');
    const gfmDiv = document.getElementById('gfm');
    const visualDiv = document.getElementById('visual');

    const reader = new commonmark.Parser();
    const writer = new commonmark.HtmlRenderer();

    function render() {
      const src = input.value;
      const parsed = reader.parse(src);
      cmDiv.innerHTML = '<h3>CommonMark Rendering</h3>' + writer.render(parsed);
      gfmDiv.innerHTML = '<h3>GFM Rendering</h3>' + marked.parse(src);

      const visual = src
        .replace(/ /g, '␣')
        .replace(/\n/g, '↵\n');
      visualDiv.innerHTML = '<h3>Whitespace Annotated</h3><pre>' + visual + '</pre>';
    }

    input.addEventListener('input', render);
    render();
  </script>
</body>
</html>