Hugo 語法上色 Syntax Highlighter
Published:Updated:
這是把語法上色從 chroma 改成 prism.js/highlight.js 又改成 shiki 的心得,網路上你哪裡能找到全部試過一輪的心得呢,想改的原因不外乎就是顏色醜,辨識度低,不好看。
Chroma
這是 Hugo 內建的語法上色方式,以 Blowfish 主題而言,可以在 assets/css/components/chroma.css 找到。保持一勞永逸的想法(雖然真的一勞永逸的次數好像滿少的)先去查哪個 highlighter 好,2018 2019年那些文章大概都說 Prism.js,說維護的比較勤 contributor 比較多,那就先選 Prism.js 好了。
Prism.js 和 Highlight.js
Prism.js 裝好沒半小時去官方 Github 看發現上次更新已經是 2022,既然沒在維護就不用了,於是果斷換成 Highlight.js,也很輕鬆兩行 <link> <script> 就搞定,只是痛苦這裡才開始。首先,我已經知道 PageSpeed Insights 這個邪惡的東西,裝完之後手賤去測試果然慢了不少,因為他是用戶載入頁面後即時渲染的;再來是暗色模式支援,還自己寫了一堆 javascript 搞定亮暗主題轉換問題。
加個 highlight 為什麼要這麼多 JS?除了寫太爛打掉重練,還有客製化會導致其他依賴原有 HTML 架構的功能失效,否則單純只加上 Highlight.js 確實是像教學1一樣複製貼上就結束了,但是改成 Highlight.js 後缺失的主題切換和複製按鈕都要重新寫,關於 CSP referrerpolicy, defer/async, crossorigin, integrity 的設定教學也沒講到,查到 CSP 之後又用 Cloudflare Workers 寫了一個修改 HTTP headers 的程式:
// Cloudflare Workers
const cdns = `
https://cdnjs.cloudflare.com
https://cdn.jsdelivr.net
https://fonts.googleapis.com
https://fonts.gstatic.com
`.trim().replace(/\n\s+/g, ' ');
addEventListener('fetch', event => {
event.respondWith(handleRequest(event.request))
})
async function handleRequest(request) {
let response = await fetch(request)
let newHeaders = new Headers(response.headers)
newHeaders.set('Strict-Transport-Security', 'max-age=31536000; includeSubDomains; preload')
newHeaders.set('X-Content-Type-Options', 'nosniff')
newHeaders.set('X-XSS-Protection', '1; mode=block')
newHeaders.set('Content-Security-Policy', `
object-src 'none';
default-src 'self';
manifest-src 'self' https://*.zsl0621.cc;
connect-src 'self' ${cdns} https://www.google-analytics.com;
font-src 'self' ${cdns};
img-src 'self' https://*.zsl0621.cc;
script-src 'self' https://*.zsl0621.cc ${cdns} https://www.googletagmanager.com https://analytics.google.com https://static.cloudflareinsights.com 'unsafe-inline';
style-src 'self' https://*.zsl0621.cc ${cdns} 'unsafe-inline'
`.replace(/\n/g, ' ').trim())
newHeaders.set('X-Frame-Options', 'SAMEORIGIN')
newHeaders.set('Referrer-Policy', 'strict-origin')
newHeaders.set('Permissions-Policy', 'geolocation=(self), microphone=(), camera=()')
newHeaders.set('Cache-Control', 'public, max-age=31536000')
const origin = request.headers.get('Origin')
if (origin && origin.match(/^https:\/\/.*zsl0621\.cc$/)) {
newHeaders.set('Access-Control-Allow-Origin', origin)
} else {
newHeaders.set('Access-Control-Allow-Origin', '*')
}
return new Response(response.body, {
status: response.status,
statusText: response.statusText,
headers: newHeaders
})
}
越搞越多跟想像的三行優雅解決完全不一樣…都破百行了,最重要的是原本用好的超過100分經過字體和高亮 CDN 以及我外行人寫的 javascript,速度直接噴到剩下50分:

剛部屬完 Highlight.js 馬上測,手機版有時候甚至還跑不到50分。
Shiki
我辛苦弄這麼久的結果雖然是好看了但是分數有夠爛,那我之前的努力算什麼,就在我覺得好像沒救的時候看到了這篇文章,插入也有夠簡單而且內建亮暗主題切換,重點是純靜態,拿他的網頁去跑分即使程式碼數量比我多的也是輕鬆跑到99, 100,好啊心態沒了,馬上改全刪,於是現在的跑分成績:

完美,回來了。
eallion 使用 rehype 工具執行 shiki,執行速度非常慢且不支援編輯時預覽,因此我寫了一個腳本優化,請見在 Hugo 中使用 Shiki 並且加快執行速度超過百倍。
後記
eallion 寫的 Shiki 教學就是我最喜歡的那種,有介紹原因,直接教學,沒有廢話,沒有拖泥帶水,清楚了當。
最後,你說70分和100分體感有差嗎?完全沒差,但是我比較爽。
發現速度除了 Highlight.js 以外字型影響也很大
當初看了這篇文章,照他的建議把字體從 Cloudflare Fonts 換成 Google Fonts 結果 Lighthouse 分數大暴跌,那時沒注意到導致花了很久除錯,這邊附上使用 LXGW 字體各種 CDN 的速度:
| 來源 | 手機 / 桌面 |
|---|---|
| Default font | 99 / 100 |
| Google Fonts | 55 / 72 |
| Cloudflare Fonts (cdnjs) | 60 / 85 |
jsdelivr lxgw-wenkai-* | |
| - tc-web, screen-webfont | 55 / 70±5 |
| - tc-webfont | 58 / 82 |
| - screen-web | 59 / 87 |
| - screen-web + preload/preconnect | 86 / 92 |
| - screen-web + preload/preconnect/CDN cache | 90 / 99 |
網路世界變化很快不到一年就大洗牌,也沒多久他的文章已經完全過時了。不過重點是文章中的這句話:
這個服務還在 beta 測試版,以後可能還會變更好。
應該寫在文章第一行…我認真看完全文照作才發現現在已經改善很多。