GitHubのようにMarkdownが書けるようなサイトが増えています。僕の働いているソニックガーデンが使っているオフィスツールRemottyでもMarkdownのような記法を一部サポートしています。

WebでMarkdownを扱うにはMarkdown形式の文字列をサーバーサイドなりクライアントサイドでHTML化して表示するだけなのでとても簡単です。また、 ただのHTMLなのでCSSにより見た目もかなり柔軟に変えることができます。

ではReact Nativeアプリではどうしたらいいだろうか、というのが最近の課題です。実はRemottyのモバイルアプリでちょっとしたマークダウン的なものをどのように表示すべきなのかというのは悩んでいて、現状では開発した当時一番マシかなと思ったものを使っています。

Markdownを表示するためにライブラリ(または標準コンポーネント)を使います。使い方には大きく2通りあって、Markdown文字列のまま処理してくれるものと一度HTMLに変換してHTMLをレンダリングするものとがあります。さらに内部実装でHTMLを表示できるWebViewを使っているのかMarkdownからHTMLにして、そのHTMLの構文解析をしてコンポーネント化しているのか、という分類もできそうです。

ライブラリはこのあたりで検索します。また、Googleでも色々と検索してヒットしたものを使ってみました。

jondot/awesome-react-native
Awesome React Native components, news, tools, and learning material! - jondot/awesome-react-native

今日は以下の6種類を比較してみます。と言ってもメンテされていなくて最新のRNでは動かないものもあります。一部手元で手直しすれば動いたものもあるので補足しつつ書いてみます。

前提

Markdownのサンプルとして、いくつかの要素を含んだ次のようなものを考えます。

const markdown = `
# 見出し

- 箇条書き
  - 入れ子
  - 入れ子
- 箇条書き
- 箇条書き

[Google](https://www.google.co.jp/)

> 引用

**太字太字**

![画像](https://blog.piyo.tech/images/prof.png)
`;

せめてこのぐらいはちゃんと表示してほしいなという気持ちで例文を作りました。

また、HTMLを受け取るコンポーネントがあるため、このMarkdown文字列をmarkedライブラリに食わせてHTML化します。

const html = marked(markdown);

ライブラリの使い方は全部シンプルなのでドキュメントをみてくれれば大丈夫です。特に紹介しません。

また、各ライブラリはそれぞれスタイルの調整はある程度できそうですが、一旦は何も指定しない状態で試しています。

Markdownで渡す系

react-native-simple-markdown

CharlesMangwa/react-native-simple-markdown
📜 React Native Markdown component (iOS & Android). Contribute to CharlesMangwa/react-native-simple-markdown development by creating an account on GitHub.

コードが古いせいか使えませんでした。残念。

react-native-showdown

jerolimov/react-native-showdown
React-native component which renders markdown into a webview! - jerolimov/react-native-showdown

こちらは実はそのままでは警告により正しく描画されません。内部的にReact NativeのWebViewが使われています。最近のWebViewはHTML文字列を使う際にはoriginWhitelist={\\\['\\\*'\\\]}というプロパティが必要になったため、警告が出ていました。

その箇所を(行儀が悪いですが)ローカルで修正し実行したのがこちらです。

react-native-markdown-renderer

mientjan/react-native-markdown-renderer
React Native 100% compatible CommonMark renderer. Contribute to mientjan/react-native-markdown-renderer development by creating an account on GitHub.

こちらはMarkdownを一度HTMLにパースした後、HTMLのツリー構造を解釈して適切なReact Nativeコンポーネントに置き換えるようなことを(どうやら)やってくれているように見えます。

例えばこのようなコードがあり、linkはスタイル付きのテキストのようなルール付がされています。

  // a
  link: (node, children, parent, styles) => {
    return (
      <Text key={node.key} style={styles.link} onPress={() => openUrl(node.attributes.href)}>
        {children}
      </Text>
    );
  },

HTMLで渡す系

react-native-htmlview

jsdf/react-native-htmlview
A React Native component which renders HTML content as native views - jsdf/react-native-htmlview

こちらが現状のRemottyで使っているライブラリです。MarkdownをHTMLにしたあとでReactNativeコンポーネントにするというのは変わらないですが、liの入れ子などが考慮されていないため残念な見た目になります。

react-native-autoheight-webview

iou90/react-native-autoheight-webview
An auto height webview for React Native. Contribute to iou90/react-native-autoheight-webview development by creating an account on GitHub.

こちらは内部でWebViewを使っており、react-native-showdownと同様originWhitelist={\\\['\\\*'\\\]}が必要でした。画像がはみ出るものの比較的いい感じで表示できました。高さを開発者が指定しなくていいのが良いです。

WebView

WebView · React Native
> **Warning** Please use the [react-native-community/react-native-webview](https://github.com/react-native-community/react-native-webview) fork of this component instead. To reduce the surface area of React Native, `<WebView/>` is going to be removed from the React Native core. For more information, please read [The Slimmening proposal](https://github.com/react-native-community/discussions-and-proposals/issues/6).

標準のコンポーネントでも試しました。高さ指定が必要で無指定だと何もヒョ時されません。コンテンツが色々動的に変わるんだとしたら実際に使うのはなかなかに大変そうです。

結論

ここまで比較してみて、今使っているreact-native-htmlviewは相当イケてないなということがわかりましたので、早急に対応すべきな気がしてきましたw

React Nativeのコンポーネントに直してくれるreact-native-markdown-rendererが良さそうだなという感覚がありますね。うまくコンポーネントにしてくれるならレイアウトが楽になりそうです。

それで難しいのであれば、次点はreact-native-autoheight-webviewでしょうか。MarkdownがHTMLを書くための記法だと思えばWebViewに表示を任せるのは悪くない選択肢かと思います。こちらは速度に課題があるかもしれません。

今回試すにあたり作ったサンプルアプリをGitHubに上げておきました。

pi-chan/ReactNativeMarkdownSample
Contribute to pi-chan/ReactNativeMarkdownSample development by creating an account on GitHub.