なぜやるのか
このブログは microCMS でコンテンツを管理しています。作ってしばらくはリッチエディタ機能を使っていましたが、スキーマを平文テキストに変更、マークダウン形式でコンテンツを持つようにしました。
microCMS のリッチエディタで気になる点
- p 要素で段落分けする設定にした場合、段落内改行をする方法がない
- 箇条書きリスト内での改行ができない
- エディタ画面にマークダウンのテキストをコピペしても書式が反映されない
手元のメモでマークダウンで書いてるんだし、最初からマークダウンでコンテンツを持つようにするか、となりました。
そもそもリッチエディタを使っていたのは、img 要素に自動で width/height を付けてくれるのを気に入ったのが理由です。そこだけ自力で実装することにしました。
やりたいこと
- microCMS で管理しているマークダウン文書を、HTMLにパースする
- その際に、img 要素に microCMS で管理しているメディアの width/height を指定する
- 同時に、遅延ロードのアトリビュートを追加する
- 外部リンクは別タブで開くようにする
作業メモ
マークダウンをHTMLにパース
next のチュートリアルでは remark 使ってたので、それでいきます。
要素の書き換え
microCMS で管理している画像メディアのメタ情報をAPIで取得してセットします。
import { remark } from 'remark'
import html from 'remark-html'
...
export async function getPostData(id) {
const thisPost = await client.get({
endpoint: 'posts',
contentId: id,
})
const processedContent = await remark().use(html).process(thisPost.body)
const contentHtml = await parseHtml(processedContent.toString())
...
return {
thisPost: { ...thisPost, body: contentHtml },
...
}
import { parse } from 'node-html-parser'
const parseImgEl = async (imgEl) => {
const src = imgEl.getAttribute('src')
if (!src.startsWith('https://images.microcms-assets.io/')) {
return imgEl
}
const format = await fetch(`${src}?fm=json`).then((res) => res.json())
imgEl.setAttribute('height', format.PixelHeight)
imgEl.setAttribute('width', format.PixelWidth)
imgEl.setAttribute('loading', 'lazy')
return imgEl
}
const parseAnchorEl = (anchorEl) => {
const href = anchorEl.getAttribute('href')
if (href.startsWith('/')) {
return anchorEl
}
anchorEl.setAttribute('target', '_blank')
anchorEl.setAttribute('rel', 'noopener')
return anchorEl
}
export async function parseHtml(content) {
const root = parse(content, {
blockTextElements: {
code: true,
},
})
await Promise.all([
...root.querySelectorAll('img').map((imgEl) => parseImgEl(imgEl)),
...root.querySelectorAll('a').map((anchorEl) => parseAnchorEl(anchorEl)),
])
return root.toString()
}
後から気づいたのですが、remark-external-links を使えば a 要素の別タブで開く設定とかはできたっぽいです。