Next.jsでカスタムローダーを使ってmdxをAMP対応させる
TL;DR
markdownファイルやmdxファイルはそのままだと<img>タグなどを使う。さらにAMP下での数式のレンダリングやコードシンタクスに対応させることも出ない。なので、mdxのカスタムローダーを自作することでAMPに対応する。
カスタムローダーに関してはmdxの公式などが詳しい。
JSXを使ってamp対応
mdxフォーマットはjsxに対応している。そして、jsxにはamp-componentsが存在する。なので、amp対応するには、それぞれのdefaultのタグ(imgなど)をamp-components(<amp-img ... />)に変換してしまえば良い。
基本
astの中でjsx記法は以下のように表される。
なので、あるタグを含むnodeを見つけたら、そのタグが対応するamp-componentsをvalueの中に埋め込んだJSXノードに変換してしまえばいい。
数式
数式をレンダリングするAMPタグは<amp-mathml>を使う。また、インラインの数式では<amp-mathml inline>すればインライン数式になる。
remark-mathを使えば、$$で囲まれた部分がmathに$で囲まれた部分がinlineMathに変換されるので、mathを<amp-mathml>に、inlineMathを<amp-mathml inline>に変換する。
img
数式に関しては単純に変換するだけなので単純で良かった。しかし、imgタグに対応するのamp-componentsは<amp-img />なのだが、このタグはwidthとheightが必須という特徴がある。一つの対応策としてはCSSなどでうまくresizeしてしまうことらしいのだが(参考)、widthかheightのどちらかは固定する必要があり、固定された側の大きさに引っ張られる。なので、スマホとかを見ると画像の上下に不自然な空白が生まれてしまうことがある。
今回は、どうせmdxをパースする作業はサーバーサイドでやるので、nodeモジュールで対応するイメージのsizeをとってきてちゃんとサイズを入れることにした。
image-sizeというパッケージで簡単にサイズを取得できる。また、urlからサイズを取ってくるときが少しめんどうで、非同期処理を使えない。使ってしまうとparseが終わった後にやっとwidthとheightがわかる、みたいなことになるっぽい。このあたりしっかり理解しきれていないのだが、sync-requestという同期処理でrequestするモジュールを使って強引に解決した。
注意
ただsync-requestは非推奨らしいので(参考)、使用する場合は自己責任で...。問題になってるのはクライアント側がクラッシュしやすくなるとかなので、buildするときに走るだけだから問題ないと思いたいのだが。dynamic importとか始めると問題になるかもしれない。
syntax highlight
prismjs側でやる処理であるTokenizeをカスタムローダー側でやるだけ。refactor.registerのところで使いたい言語をロードすればよい。これに関してはamdxのコードをそのまま使用させていただいた。というかこのレポジトリは熟読させていただいています。ありがとうございます。