マークダウンエディタをハイブリッドにするために必要なこと マークダウン、皆さん使っているでしょうか。使っていますよね、GitHub(GFM)やSlack(一部記法)、個人的なメモアプリ等など、マークダウンを使えるサービスは多いので、使ったことがある人は多いんじゃないかと思います。 そして弊社の[DocBase](https://docbase.io/)というサービスは、マークダウンを使って情報共有をするサービスです。これも皆さん使っていますね? この度は、そんなDocBaseのメモ編集画面で、**ハイブリッドエディタ**なるものをリリースしました。これはDocBaseに存在したいくつもの不満、問題を解消してくれるもので、これが一体どういったもので、どうやって作られたのかを書いていきます。 ## ハイブリッドエディタとは?? 「ハイブリッド」何やらガソリンと電気を使って動きそうな印象を受ける言葉ですが、もちろんそういった意味はありません。 次の動画を見てください。  左側がマークダウン、右側がHTMLのプレビューとなっています。よくあるマークダウンエディタに見えるかもしれません。 「うーん、ハイブリッドと言っても、普通じゃね?」と思ったそこのあなた!もう一度、よく見てみてください、右のプレビュー側をクリックして、**直接クリックした位置の文章を編集しています**よね。そしてその変更は左側のマークダウンに反映されています。 実は右側の表示はただのプレビューではなく、WYSIWYGエディターになっているんです。左側のテキストエディタがいつでも編集できる。さらに右側のWYSIWYGエディターも何時でも編集できる。これが「ハイブリッド」と名付けた所以です。 細かいことは置いておいて「ハイブリッド」って感じがしますよね。声に出したくなると思います。「ハイブリッド」「ハイブリッドエディタ」どうでしょうか?声に出しましたか?「俺は今日からハイブリッドエディタを使うぜ!」「俺、またなんかやっちゃいました?ただのハイブリッドエディタですけど」など、声に出して言ってみてください。かっこいいですよね。 ## DocBaseの従来の問題点 DocBaseの従来のエディタにはいくつかの問題点があり、それがUXの低下やサービスの質を低下させる原因になっていました。大まかには次のような問題点が上げられます。 ### CommonMarkに準規していない マークダウンと言っても、それには統一的な仕様書が無かったです。そのために細かい動作は実装次第となっている部分があります。実際にDocBaseでも、GithubFlavoredMarkdown(GFM)というマークダウンの方言のパーサを使っており、しかもパーサ自体の開発、更新が終わっているバージョンの古すぎるものを使っていました。 そして、そんな「マークダウンの仕様が無い状況を解決しよう」として策定されたCommonMarkという仕様があります。これが広く使われ出しており、これに従ってサービスを作ることによりCommonMarkに準規しているアプリやサービス間での情報のポータビリティが上がります。これはユーザにとっては非常に大きなメリットですよね。 けれども、DocBaseはCommonMarkには移行していませんでした。GFMに追加して差し込みメモ(`#{}`を書くと他のメモを埋め込める機能)を使えるようにしたり、画像サイズ指定を可能にするなどの独自の拡張をしてしまっているために、単純にマークダウンパーサをCommonMarkパーサに変更すれば良いというものでは無かったのです。 ちょっとマークダウンの種類に関して整理しておきます。 * 従来のDocBase * CommonMarkに**準規する前**のGFMに、差し込みメモや画像サイズ指定などの改造を加えた実装を使っていた * 今後のDocBase * CommonMarkに**準規した後**のGFMに、差し込みメモや画像サイズ指定などの改造を加えた実装を使うようにしたい ### プレビューが遅い 従来の方法では、マークダウンをhttpでサーバに送り、サーバでマークダウンをパースしてからブラウザに返しているため通信の時間がかかりました。また、ブラウザからサーバにマークダウンを送信するタイミングは、サーバの負荷を考慮してユーザの入力が終わってから500ミリ秒経過後となっていました。そのため、ユーザが入力したマークダウンがHTML化され、それがブラウザ上で表示されるまでに数秒かかってしまっているのです。ユーザは、入力したマークダウンが想定した結果通りになるかどうかを確認するためには数秒待つ必要があるので、UXの低下を引き起こします。 ### マークダウンを学習する必要がある そもそも、マークダウンを知らないと、サービスを使うことを躊躇してしまいます。マークダウンを知らないと使えないサービスというのは、サービスを使える人が限られてしまいます。これは情報共有サービスとしては致命的な問題です。情報共有サービスとしてはチームの誰もが、簡単に、情報を共有できるサービスである必要があります。 ## ハイブリッドエディタにむけて これまでに上げたような問題を解決するためには何が必要なのかを考えました。 「CommonMarkに準規していない」この問題を解決する方法は単純で、パーサをCommonMarkにすることです。しかしそのパーサは、DocBaseの差し込みメモなどの独自実装に対応する必要があるため、パース部分がブラックボックスになっているようなパーサは使えません。AbstractSyntaxTree(AST)を組み立て、それに変更を加えることにより出力を柔軟に変更できる、そんなCommonMarkパーサのライブラリを探すことにしました。 次に、「プレビューが遅い」問題の解決方法を考えます。理想的にはユーザが入力した途端にプレビューが更新されて欲しいです。それにはサーバ側にパーサがあるのでは絶対にダメです。クライアント側にCommonMarkパーサがある必要があります。 最後に「マークダウンを学習する必要がある」の件。これを解決するには究極的には「マークダウンを書かなくても良いこと」です。つまりWYSIWYGエディタです。WYSIWYGエディタであれば、マークダウンの文法を知らなくても、UIによって見出しやリスト、装飾などを付けることもできれば完璧です。しかし、現在マークダウンエディタを気に入って使っているユーザはWYSIWYGは使いたくないかもしれません。それを両立する必要があります。 マークダウンとWYSIWYGを扱えるライブラリは、[ProseMirror](https://prosemirror.net/)や[TOAST UI Editor](https://ui.toast.com/tui-editor)などが存在し、これらは、マークダウンとWYSIWYGを切り替えて使えます。 ただ、どうせなら切り替えるのではなく、同時に使いたいですよね!そう、ハイブリッドエディターとして最初に見せたように、プレビュー側を直接編集したいのです!それにはマークダウンのテキストエディタとWYSIWYGエディタを同時に扱える「ハイブリッドエディタ」を開発する必要があるのです!というわけで、WYSIWYGエディタをフルスクラッチで開発することにしました。 無謀だと思いましたか? はい、そう思います。ちょっとチャレンジングで面白そうだったので開発を始めてみました。正直、開発初めて最初の1年ほどは、これを本当にリリースできる品質まで持って行けるのか不安でした。 ### パーサの選定 WYSIWYGエディタはフルスクラッチと言っても、CommonMarkパーサはさすがにライブラリを使います。独立した機能で、仕様もはっきりしていますからね。 パーサへの要件を整理しましょう。必要な要件はこのようになります。 * ASTを扱えるCommonMark+gfmパーサであること * それはクライアント側で動作すること クライアント側で動作するという要件があるので、JavaScriptを使うことが考えられます。そしてJavaScriptのCommonMarkパーサといえば、[commonmark.js](https://github.com/commonmark/commonmark.js/)ですね。これはASTも取得でき、完全にCommonMark準規と、要件を満たしているように思えます。 ですが、DocBaseで投稿できるマークダウンは10万文字までという制約があります。はたしてcommonmark.jsは10万文字のパースという試練に耐えることができるのか・・・。10万文字規模のマークダウンをcommonmark.jsでパースしてみたところ、パースの度にブラウザがフリーズします。うーん、これでは文字入力の度にブラウザがフリーズしてしまいます。パーサの要件に「10万文字の文書を入力中にブラウザがフリーズしないこと」を追加しなければいけません。 このフリーズを解決するのにWASMを使うという案が上がりました。WASMで実行すれば非同期で処理されるので、パースに時間がかかったとしてもユーザの操作がフリーズすることは無くなります。WASMとして使えるマークダウンパーサを調査したところ、要件全てを満たしているであろう[comrak](https://github.com/kivikakk/comrak)というRustのCommonMarkパーサが見つかったため、これをWASMとしてビルドして使用することにしました。 ### WYSIWYGどうする? ここまでの話では、**マークダウンパーサをCommonMark準規のものにした**というだけです。WYSIWYGエディタの話にはまだ入っていないですね。では、WYSIWYGエディタはどのように実装したら良いでしょうか。 ハイブリッドにするために、右側のプレビューのどこかを選択したらエディタのその位置を選択してほしい、逆に、左側のエディタのどこかを選択したら、プレビュー側のその位置を選択してほしいです。なので、マークダウン位置とHTML位置の双方向の対応付けが必要です。今回使用することにしたパーサのcomrakでは、`--sourcepos`オプションを使うことにより、それぞれのHTMLタグに`data-sourcepos`という属性を埋め込んでくれる機能があります。 たとえば`# heading`という文字列をパースした出力は`