見出し画像

数式機能実装の裏側

noteのカイゼンチームでエンジニアをしているKota Nonakaです。
note社には今年の8月にJoinしました。もう4ヶ月経つことに驚きが隠せません。

普段は趣味の写真のことばかり書いているnoteですが、今日は今年カイゼンチームで実装した数式機能について技術の面からご紹介します。

(この記事は「noteのみんなAdvent Calendar 2021」10日目の記事です)

数式機能について

数式機能は、noteの本文内で特定の記法を利用することで、以下のように整形された数式を表示する機能です。

$$
x = \frac{-b \pm \sqrt{b^2-4ac}}{2a}
$$

この場合は以下のように書くことで自動的に数式表示になります。

$$
x = \frac{-b \pm \sqrt{b^2-4ac}}{2a}
$$

数式自体は一般的に広く用いられているTeXの記法を利用することができます。

詳しい使い方などについては以下のページをご覧ください。

https://www.help-note.com/hc/ja/articles/4410665086873-数式記法の使い方

この記事では、この機能をnoteへ実装するにあたっての課題や、その課題をどう解決していったかについて紹介していきます。

実装するにあたっての課題

パフォーマンス

本機能に限ったことではないのですが、noteのフロントエンドアプリケーションにおいて、記事詳細画面(今皆さんにご覧いただいているこのページ)は特にアクセスが多いページのうちの一つであり、パフォーマンスの低下やスタイル崩れなどに対しては細心の注意を払う必要があります。

詳しくは後述しますが、数式機能はKaTeXというライブラリを用いて実装しています。
このライブラリはTeXで書かれた数式をHTMLへ変換し、それを正しく表示するためのフォントやスタイルシートを当てることで数式表示を実現しています。

これらのフォントやスタイルシートを読み込むことによって、既存の記事の表示が遅くなったり、スタイルが崩れたりするという影響を避ける必要がありました。

エディタとnote本体の話

noteでは現在エディタ画面(記事を編集する画面)のリニューアルを進めています。

この新エディターを使って数式を書くと、編集中に書いている数式を以下のようにプレビューできる機能がついています。

数式部分にキャレットを持っていくとこのように表示されます

記事詳細画面で表示される数式と、エディターで表示される数式は同じ見た目であることが期待されます。
しかし、現在note本体はVue.jsを用いて実装されているのに対し、新しいエディターはReactを用いて実装されているため、KaTeXを用いて数式をレンダリングするコンポーネントをそれぞれのフレームワークで実装してあげる必要があるという課題がありました。

使用技術

前述の課題を踏まえて、KaTeXというライブラリとWeb Componentsを使用して実装することとしました。

KaTeX

KaTeXはTeX方式で書かれた数式をブラウザ上で表示するためのライブラリです。
前述の通り、このライブラリはTeX方式で書かれた数式をHTMLに変換し、専用のスタイルシートとフォントを適用することでブラウザ上での数式表示を実現しています。

ブラウザ上で数式を表現する方法としては他にもMathMLと呼ばれる技術が存在しますが、対応ブラウザが限られていることやTeXと比べて一般的ではないことなどからKaTeXを採用することにしました。

Web Components

Web Componentsは再利用可能なコンポーネントを作るためのWeb標準の技術です。

Web Componentsのカスタム要素を使うと、独自のタグを定義することができます。
また、Shadow DOMと呼ばれる機能を用いることで、DOMツリー上に、独立したDOMツリーを構築することができます。これにより、コンポーネント内部で当てたスタイルが外部のDOMに影響して意図しないスタイルが適用されるといった事象を防ぐことができます。

何より、Web標準の技術で特定のフレームワークやライブラリに依存しないためnote本体だけでなく、別で開発されているエディター側でも同じ実装を使い回すことができます。
これは単に実装工数の削減といったメリットがあるだけではなく、エディター側で確認したプレビューが確実に記事画面でも表示されるという点においてもメリットであると考えWeb Componentsを採用しました。

実装

Web Componentsの実装は極めてシンプルなもので、外から受け取ったTeX方式で書かれた数式をKaTeXのrenderToStringという関数に渡すとHTMLの文字列が得られるので、それをそのままレンダリングしています。

それに加えて、KaTeXを動作させるのに必要なスタイルシートも同様にShadow DOMの中に展開するようにしています。これによって、使用する側のフレームワークに関係なく、このコンポーネントを読み込むだけでTeX方式の数式を表示するための処理が完了します。

Web ComponentsはWeb標準の技術であるため、これを使用するために特に追加のライブラリ等は必要ありませんが、リアクティブな処理やライフサイクルに沿った処理を書きやすくするという目的で、実際にはLitというライブラリを使用して実装しました。

Lit自体の詳しい紹介は割愛しますが、Reactのクラスコンポーネントなどに近い書き心地でWeb Componentsを扱うことができるため、非常に見通し良く書けたと感じています。

作成したWeb Componentsはnoteの本体とは別のパッケージとしてGitHub Packagesを用いて配信し、それをnote本体やエディタのアプリケーションでインストールして使うようにしています。

実際に数式を表示する際は、作成したパッケージを読み込み、通常のタグと同じようにカスタムタグの名前を使ってDOMを構築するだけで使用することができます。(Reactの場合はそのまま使えますが、Vue.jsではv-preディレクティブを付けるかignoredElementsオプションでカスタム要素名をVue.jsのコンポーネントとして認識しないようにしてあげる必要があります。)

記事詳細画面では、記事内で数式が使われている場合にだけdynamic importsを用いてパッケージを読み込むことで、従来の数式を使用していない記事のパフォーマンスに悪影響が出ないようにしています。

まとめ

今回は数式機能の実装の詳細をご紹介しました。

数式機能は以前からご要望をいただいていた機能でしたが、諸々の懸念とリソースのバランスから実装を進めづらい状況にありました。

今回それらの課題を既存の技術の組み合わせで解決し、実際にnoteをお使いいただいているクリエイターの方に使っていただけるようにできたという点はよかったと感じています。

リリース前から先行でご利用いただき、フィードバックをいただいたクリエイターの方や、告知記事へTwitterで反応いただいたクリエイターの皆様の声が、個人的にはとても嬉しかったです。

来年もクリエイターの皆様が創作をしやすくできるようなカイゼンをできるように精進していきたいと思います。

以上、noteのみんなAdvent Calendar 2021 10日目の記事でした。

もしもサポートをいただいたら全額趣味に使います!🚲🧑‍💻📷