やってみる

アウトプットすべく己を導くためのブログ。その試行錯誤すらたれ流す。

ブラウザのみでMarkdownをハイライトしてみた

動的にパースとハイライトをしたい。

成果物

github.com

概要

Markdownで書かれたプレーンテキストを読み込んで、HTMLにパースし、ブラウザで表示する。

構成

ライブラリ 役割
require JSをモジュール化する
marked MarkdownをHTMLにパースする
highlight <pre><code>タグをハイライトする

問題

ローカルで外部テキストファイルを読込めない

クロスドメイン制約のせい。ローカルファイルを操作するのはセキュリティ的に問題があるから触らせてくれない。サーバにUPしてWebから参照すれば解決する。

text.js:325 XMLHttpRequest cannot load file:///tmp/work/JS.require.20180418173809/docs/src/2/txt/default.md. Cross origin requests are only supported for protocol schemes: http, data, chrome, chrome-extension, https, chrome-extension-resource.

ローカルサーバを立てる方法もある。だが、ブラウザだけで動作するコードにならない。と思う。

ハイライト用JSの動的読込とパースが同期できない

highlight.jsではシンタックス・ハイライトをするために、各言語用のJSファイルをロードする必要がある。だが、176件もある。あまりに膨大で全部ロードすると遅延は必至。

そこで、必要なものだけ動的ロードしたい。Markdownのコード用タグに書かれた言語を取得し、それに対応するハイライト用JSを動的ロードしようと考えた。だが、いい感じのロジックが思いつかない。

動的ロードのロジック

  • <code>に該当するMarkdownのパースをするときに動的ロードする
    • https://ytyaru.github.io/JS.require.20180418173809/src/1/index.html
      • 同期できない
        • ロード前にパース結果を返してしまいハイライトできない
          • (URLの例でいえば一番下のMarkdownコードがハイライトされない)
          • Marked.Render.codeでオーバーライドするメソッドは同期のreturnにしか対応していない
            • 非同期でJSロード完了したときにはすでにreturnでパース結果を返す構造になる
            • returnしないと<code>に該当する部分がundefinedになってしまう

上記のことから、markedでパースする前にハイライト用JSをロードする必要がある。だが、ロードしたいファイルはパースしないと抽出できない。さて、どうしたものか。

解決案? 1

  • パースを2回に分ける
    • 必要な言語JSを取得するためのパース(<code>に該当するMarkdownから言語名を取得)
    • MarkdownをHTMLに変換するパース

ただ、どうすれば「全部のJSを動的ロードし終わった」と判定できるのか。それまで待機できるか? そんな仕組みはあるのか?

仮にできたとしても非同期のネスト地獄になる予感。

解決案? 2

  • 動的ロード完了したとき、改めてハイライト用コードを挿入する

もはやmarkedの枠組みを逸脱していないか? そんな実装が可能なのか? 強引にDOM操作すれば不可能ではないと思うが、肝心の対象DOM要素がどれなのか判断できない。

妥協案

ハイライトする言語を決めてしまい、固定で静的ロードする。

これが一番楽。ただし、パフォーマンスを考えると対応言語数が少なくなる。