Client-side rendering of KaTeX in Docusaurus
Docusaurusは静的サイトジェネレータなので、すべてのページがビルド時にレンダリングされます.
DocusaurusのマークダウンからHTMLにレンダリングする部分は remark と rehype が処理しています.数式を有効にするには、Docusaurusのドキュメント によれば、remark-math
と rehype-katex
(または rehype-mathjax
)を使います.
しかし、2,3個の数式ならともかく、それなりの数があるとビルドに時間がかかるようになり、生成されるhtmlやjsファイルが肥大化します.もちろん、数式も含めて事前にレンダリングするので、閲覧する場合はブラウザで数式の処理が入らず高速に表示できますが、個人的にはデメリットの方が大きいので、数式処理はクライアントサイドでしたいところです.ここでは、その方法を記録しておきます.
必要なスクリプトやCSSファイルを読み込む
今回は katex を使って数式処理をします.バージョンは 0.16.4
です.
必要なファイルは以下の通りです.
- //cdn.jsdelivr.net/npm/katex@0.16.4/dist/katex.min.css
- //cdn.jsdelivr.net/npm/katex@0.16.4/dist/katex.min.js
- //cdn.jsdelivr.net/npm/katex@0.16.4/dist/contrib/auto-render.min.js
Docusaurusでこれらを読み込むようにするには docusaurus.config.js
の scripts
および stylesheets
で指定します.
const config = {
scripts: [
{
src: "https://cdn.jsdelivr.net/npm/katex@0.16.4/dist/katex.min.js",
integrity: "sha384-PwRUT/YqbnEjkZO0zZxNqcxACrXe+j766U2amXcgMg5457rve2Y7I6ZJSm2A0mS4",
crossorigin: "anonymous",
defer: true,
},
{
src: "https://cdn.jsdelivr.net/npm/katex@0.16.4/dist/contrib/auto-render.min.js",
integrity: "sha384-+VBxd3r6XgURycqtZ117nYw44OOcIax56Z4dCRWbxyPt0Koah1uHoK0o4+/RRE05",
crossorigin: "anonymous",
defer: true,
}
],
stylesheets: [
{
href: 'https://cdn.jsdelivr.net/npm/katex@0.16.4/dist/katex.min.css',
type: 'text/css',
integrity: 'sha384-vKruj+a13U8yHIkAyGgK1J3ArTLzrFGBbBc0tDp4ad/EyewESeXE/Iv67Aj8gKZ0',
crossorigin: 'anonymous',
},
],
}
数式をレンダリングする
クライアントサイドでレンダリングするには、ページがロードし終わったときに renderMathInElement
を呼び出す必要があります.
そのために、プラグインを作ります.ここでは ./plugins/docusaurus-plugin-katex-client
というプラグインを作ります(以降、katex-client
プラグイン).以下のようなフォルダ・ファイル構成にします.
└─ plugins
└─ docusaurus-plugins-katex-client
├─ index.js
└─ render.js
ここから、どのような仕組みになっているか説明します.
プラグイン
Docusaurusに機能を追加するにはさまざま な方法があります. プラグイン はその1つです.Docusaurusのプラグインは公式サイトによれば、次のように説明されています.
Docusaurus' implementation of the plugins system provides us with a convenient way to hook into the website's lifecycle to modify what goes on during development/build, which involves (but is not limited to) extending the webpack config, modifying the data loaded, and creating new components to be used in a page.
要するに、開発やビルドの中でフックして処理を入れることができるよ、という自分なりの解釈です.katex-client
プラグインでは、すべてのページ(グローバル)に対してフックし、特定のページだけ処理するといったようにします.
Client Modules
追加のJavascriptをすべてのページで処理するために Client Modules を使います. 具体的には renderMathInElement
を呼び出すJavascriptファイル(render.js
)をClient Moduleとして指定します.指定する場所はプラグインファイル(index.js
)です.
const path = require("path");
module.exports = function (context, options) {
return {
name: 'docusaurus-plugin-katex-client',
getClientModules() {
return [path.resolve(__dirname, "./render")];
},
};
};
ExecutionEnvironment
DocusaurusのSSGについてはこちらに説明があります.プラグインは開発やビルドにも影響があるため、ブラウザに表示されるときのみ処理するようにしたいです.そのために ExecutionEnvironment を使います.ExecutionEnvironment.canUseDOM
でブラウザに表示されるかどうかが判定できます.
import ExecutionEnvironment from "@docusaurus/ExecutionEnvironment";
export default (function () {
if (!ExecutionEnvironment.canUseDOM) {
return null;
}
// browser only
}
次に、ページがロードし終わったときに処理するためには Client Module のライフサイクル関数 onRouteDidUpdate
を使います.これはルートが変わってDOMに描画し終わった後に呼ばれます.ドキュメントによれば
- The user clicks a link, which causes the router to change its current location.
- Docusaurus preloads the next route's assets, while keeping displaying the current page's content.
- The next route's assets have loaded.
- The new location's route component gets rendered to DOM.
onRouteUpdate
will be called at event (2), andonRouteDidUpdate
will be called at (4). They both receive the current location and the previous location (which can be null, if this is the first screen).
となっています.この関数の中で renderMathInElement
を呼び出します. 次のようになります.
import ExecutionEnvironment from "@docusaurus/ExecutionEnvironment";
export default (function () {
if (!ExecutionEnvironment.canUseDOM) {
return null;
}
return {
onRouteDidUpdate({ location }) {
renderMathInElement(document.body, {
delimiters: [
{left: '$$', right: '$$', display: true},
{left: '$', right: '$', display: false},
{left: '\\(', right: '\\)', display: false},
{left: '\\[', right: '\\]', display: true}
],
throwOnError : false,
strict: false
});
},
};
})();
onRouteDidUpdate
の引数にある location
でルートを取得できます.これを使って処理するページを特定します.パスは
location.pathname
です.katex-client
プラグインのClient Moduleであるrender.js
は次のようになります.
import ExecutionEnvironment from "@docusaurus/ExecutionEnvironment";
export default (function () {
if (!ExecutionEnvironment.canUseDOM) {
return null;
}
return {
onRouteDidUpdate({ location }) {
const pathIsIncluded =
location.pathname.startsWith("/docs/note") ||
location.pathname.startsWith("/blog");
if (pathIsIncluded == false) {
return null;
}
renderMathInElement(document.body, {
delimiters: [
{left: '$$', right: '$$', display: true},
{left: '$', right: '$', display: false},
{left: '\\(', right: '\\)', display: false},
{left: '\\[', right: '\\]', display: true}
],
throwOnError : false,
strict: false
});
},
};
})();