無駄と文化

実用的ブログ

dangerouslySetInnerHTMLを避けてHTML文字列をReactに埋め込む

Markdown を React でレンダリングして表示するための react-markdown というライブラリがある。Markdown には HTML を直接記述可能で、どのようにして任意の HTML 文字列を React コンポーネントに埋め込んでいるのか気になった。 というのも、真っ先に思いつくのは dangerouslySetInnerHTML という推奨されない方法 だからだ。

 

react-markdown はいかにして dangerouslySetInnerHTML を回避しているのだろうか。
結論を先に書くと、rehype というライブラリ群を使っているようだ。これについて調べ、試してみた。

 

rehype とは

github.com

rehype は HTML 文字列を AST として扱うためのエコシステムで、

  • HTML 文字列を AST にパース
  • AST を加工
    • 無害化 (Sanitize) など
  • HTML 文字列に再レンダリング

などの仕組みを提供している。

 

ちなみに、rehype は unified というコンテンツ加工のライブラリ群の一部で。rehype に含まれるのは unified プラグインの数々という位置付けになっているようだ。

 

rehype を試す

早速だがデモを作った。

https://todays-mitsui.github.io/rehype-drill/

画面左側の <textarea> に入力された HTML 文字列がリアルタイムに画面右側に表示される。ただし <script> などのは安全に除去される。
このデモは React で作ってあって、dangerouslySetInnerHTML は使われていない。

 

処理の中核を抜き出すとこのようになる。

return unified()
  .use(rehypeParse, { fragment: true })
  .use(rehypeSanitize, schema)
  .use(rehypeReact, {
    Fragment,
    jsx,
    jsxs,
  })
  .processSync(html).result;

unified のパイプラインによって、

  • rehypeParse: HTML文字列のパース
  • rehypeSanitize: 無害化
  • rehypeReact: React.JSX.Element にレンダリング

を次々に行うようになっている。

 

.use(rehypeSanitize, schema) に着目してほしい。第二引数で schema を渡している。これは、

const schema = {
  ...defaultSchema,
  tagNames: [...(defaultSchema.tagNames ?? []), "section", "div", "span"],
  attributes: {
    ...defaultSchema.attributes,
    "*": [...(defaultSchema.attributes?.["*"] ?? []), "className"],
  },
};

このような形をしている。

  • schema.tagNames: 許可される (除去されない) HTML タグを列挙
  • schema.attributes: 許可される (除去されない) プロパティを列挙

このような形式で無害化の挙動を細かく指定できる。

 

おまけ

ソースコードがここにある。

github.com

 

まとめ

一般に使われるコンテンツメディアを作ろうと思ったら、このようなライブラリを大いに活用しなければいけないんだろうなという感想。
使い方は簡単で強力で便利。

 

 

私からは以上です。