Qwik City製ブログのコードブロックにシンタックスハイライトを導入してみた
Published
当ブログのコードブロックにシンタックスハイライトが適用されておらず、イケてない感じだったので、シンタックスハイライトを導入してみました。
導入を試みるにあたって Qwik or Qwik City で検索してヒットしたもののコピペだけではうまくいかず、自分で修正する必要があったので、今回はその内容を備忘録的にまとめたいと思います。
前提
- Qwik v1.5.1
- CMS に microCMS を使用
- microcms-js-sdk を使用してコンテンツを取得している
ということで、検索してヒットした Creating a Custom 'code-block' Builder Component with QWIK を参考に実装したコードがこちらです。
まずはコンポーネントのコードから。
import { component$, useSignal, useTask$ } from '@builder.io/qwik'
import rehypeHighlight from 'rehype-highlight'
import rehypeParse from 'rehype-parse'
import rehypeSanitize from 'rehype-sanitize'
import rehypeStringify from 'rehype-stringify'
import { unified } from 'unified'
type Props = {
postContent: string
}
const highlight = async (content: string) => {
const regex = /(<pre><code class="language-.*?">[\s\S]*?<\/code><\/pre>)/g
let match
const matches = []
while ((match = regex.exec(content)) !== null) {
matches.push(match[1])
}
for (const match of matches) {
const matchLanguage = match.match(/language-(.*?)"/)
const language = matchLanguage ? matchLanguage[1] : 'plaintext'
const file = await unified()
.use(rehypeParse, { fragment: true })
.use(rehypeSanitize)
.use(rehypeHighlight)
.use(rehypeStringify)
.process(`<pre><code class="language-${language}">${match}</code></pre>`)
const highlighted = String(file)
content = content.replace(match, highlighted)
}
return content
}
export default component$(({ postContent }: Props) => {
const highlightedContent = useSignal('')
useTask$(async () => {
if (!postContent) return
const content = await highlight(postContent)
highlightedContent.value = content
})
return <div dangerouslySetInnerHTML={highlightedContent.value}></div>
})
内容について簡単に説明すると、highlight
関数を使用してHTML文字列内の<pre><code class="language-...">...</code></pre>
形式のコードブロックをハイライトします。この関数は、正規表現を使用して該当するコードブロックを見つけ、それぞれのブロックに対してunified
ライブラリを使用してパース、サニタイズ、ハイライト、文字列化の処理を行います。
unified
ライブラリは、rehypeParse
、rehypeSanitize
、rehypeHighlight
、rehypeStringify
といったプラグインを使用してこれらの処理を行います。これによって、安全にHTMLをパースし、サニタイズ(XSS攻撃を防ぐため)、ハイライトし、再度文字列化することができます。
最後に、highlight
関数はハイライトされたHTML文字列を返します。この文字列は、dangerouslySetInnerHTML
プロパティを使用してブログ記事のコードブロックに適用されます。
参考サイトのコードから追加した点は「該当のコードブロックを見つける処理」の追加です。正規表現を使って愚直に対象のコードブロックをシンタックスハイライト用のクラスが追加されたコードブロックに置き換えています。
さて、コードブロックの生成が完了したので、最後に CSS の追加をしてすべて完成になります。CSS の追加は参考サイトにあるように好みの highlight.js のCSS ファイルを CDN 経由で読み込んでも良いですし、個別にダウンロードして中身をプロジェクトで使用している CSS ファイルに転記しても良いと思います。以下は CDN 経由で読み込む例です。
import { component$ } from '@builder.io/qwik'
import { QwikCityProvider, RouterOutlet, ServiceWorkerRegister } from '@builder.io/qwik-city'
import { RouterHead } from './components/router-head/router-head'
import '@fontsource/overpass/400.css'
import '@fontsource/overpass/600.css'
import { css } from '~/styled-system/css'
import './global.css'
export default component$(() => {
return (
<QwikCityProvider>
<head>
<meta charSet='utf-8' />
<link href='/manifest.json' rel='manifest' />
<link
href='https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.8.0/styles/base16/summerfruit-dark.min.css'
rel='stylesheet'
/>
<RouterHead />
<ServiceWorkerRegister />
</head>
<body class={css({ bg: '#fde047' })} lang='ja'>
<RouterOutlet />
</body>
</QwikCityProvider>
)
})
ちなみに使えるシンタックスハイライトのスタイル一覧はこちらにあります!
以上です。