ebisen blog.

remark-embedderでMarkdownに外部サイトのツールを埋め込む

markdown

動機

このサイトの記事はMarkdownで書いています.Markdownで記事を書きつつも,技術記事としてJSのAPIの挙動を例示したり,コンポーネントを埋め込んだりしたくなりました.

この要望を叶えることができるようなライブラリがないかなーと思ったのが記事を書くきっかけになりました.

技術調査

そもそもMarkdownがHTMLになるまでなにが起きているのか

埋め込みの前になんで現状の環境でMarkdownで記事をかけているのか整理しました. ざっくりですがこんな感じです.

  1. Markdownファイルのパスpathを取得
  2. fs(ファイル処理をする)のreadFileSync(path)に渡してfileを受け取る
  3. gray-matter(ファイルをパースする)のmatter(file)に渡してパースしたファイルデータdataを受け取る
  4. remark(Markdownファイルを書き出したり読み込んだり)とremark-html(remarkでHTMLファイルを扱う)のremark().use(html).process(data)でHTMLファイルを受け取る

ちなみにこの処理はNext.jsのテンプレートであるBlog Starter Kitをほとんどそのまま使っています.

ではremarkとはなんなのか

remarkのソースコードを読み始めると沼にハマりそうなので,やめておきます.

remarkはunifiedのプロセッサの1つ.このunifiedまわりはパッケージが充実しており,気をつけないと非推奨になっているものも使ってしまいそうになるくらい技術記事も転がっています.

なので,unifiedやremark周りでパッケージを見つけて更新状況を確認すればよいって感じっぽくなりました.

候補に上がったプラグイン

mdx

mdxは,MarkdownファイルにJSXを埋め込めるプラグイン.しかも,Next.jsであれば,pages配下にmdxファイルを置いておけばそのままルーティングしてくれるらしい.こんなものまであるのかと驚愕でした...

今回のJSXを埋め込みたいという要件は満たしますが,技術記事としてソースと挙動の例示をしたいということであれば,既存のサービスを埋め込めればいい.スクラッチから作るのは大変.また,pages配下にファイルを移すなど,ディレクトリ構成も変わる.ということで今回はスルー.いつか使いたくなる気がしています.

@remark-embedder/core

今回採用することにした@remark-embedder/coreです.コード量は増えますが,勉強目的で書いているので問題なし.README.mdにあるように,埋め込みたいサービスごとにperceする関数を定義しておく.外部サービスを埋め込むためのプラグインなので,要件を過不足なく満たしています.

できたのがこんな感じ

内容はCodePenのチュートリアルのままです.

つまったところ

remark-htmlはXSS(クロスサイトスクリプティング)等のセキュリティ上の懸念から,Markdownファイルに直接HTMLを書くことができないようになっています(もしHTMLの要素があったら無視されて消えます).なので,せっかくremark-embedderでiframe要素を挿入しても,消されてしまいます.

この事実はremark-htmlのREADME.mdに書いてあります.

よって,remark-htmlとremark-embedderを併用するには,このHTMLを消す機能をOFFにしてやる必要があります.これはremark-htmlに下記のようにoptionを指定してやることで可能です.

修正前

export default async function markdownToHtml(markdown: string) {
  const result = await remark()
    .use(remarkEmbedder, {
      transformers: [CodePenTransformer],
    })
    .use(html) // ここを
    .process(markdown)
  return result.toString()
}

修正後

export default async function markdownToHtml(markdown: string) {
  const result = await remark()
    .use(remarkEmbedder, {
      transformers: [CodePenTransformer],
    })
    .use(html, { sanitize: false }) // こうする
    .process(markdown)
  return result.toString()
}

最後に

以上でMarkdownにCodepenを埋め込むことができました.セキュリティ上の懸念はあるので,よりよい方法があればアップデートしたいと思います.