sujingjhong.com


Hugo / 如何在 Hugo 中用 Prism.js 提供程式碼色彩標註

如何在 Hugo 中用 Prism.js 提供程式碼色彩標註 #

為什麼不用 Hugo 內建的程式碼標註? #

理由一:不方便 #

Hugo 本身有提供語法標註功能沒錯,他是用 golang通用性程式碼標註套件 Chroma 產生,並且提供許多語法支援以及行標註,只是它還需要使用 Hugo 本身提供的短碼(short code)。所以就功能面而言是相當完善的,但缺點就是不方便。

例如說,當我需要標注一段 JavaScript 程式碼時,我必須在文件內打:

{{< highlight javascript >}}
console.log('hello world');
{{< / highlight >}}

但既然我們都用 markdown 了,更常的撰寫習慣就是:

​```javascript
console.log('hello world');
​```

理由二:產出的code比較多 #

Chroma 產出的標注,是在產出頁面時分析他的語法結構,所以點開他的 HTML ,上面那段的標註程式碼會長這個樣子:

<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-javascript" data-lang="javascript"><span style="color:#a6e22e">console</span>.<span style="color:#a6e22e">log</span>(<span style="color:#e6db74">'hello world'</span>);
</code></pre></div>

但如果是一般的 Markdown 產出的 HTML 就只是:

<pre><code class="language-javascript">
console.log('hello world');
</code></pre>

非常的簡單、非常的直白。

為什麼是選擇 Prism.js #

我的需求 #

所以從上而言,我的需求是:

  • 單純用 Markdown 就好,不用再寫一些 short code 之類的語法。
  • 不要在產出的時候分析語法結構,在客戶端 ( client side ) 處理就好。
  • 可以簡單在 Hugo 中應用。

其他替代品 #

接著在 google 或者 github 上搜尋 syntax highlight 可以找到蠻多的:

選擇 Prism.js 的理由 #

理由一:現在仍持續更新中 #

大概看一下各套件最後的更新時間,會發現仍持續在運作的蠻少的,到今年還有發布更新的僅有 Prism.js 以及 hihglight.js

最新發布版本最新發布時間
Prism.jsv1.16.02019-3-25
highlight.jsv9.15.82019-5-29
SHJSv0.62008-12-15
SyntaxHighlighterv3.0.902016-2-13
Rainbowv2.1.42018-6-2

理由二:支援語言廣泛、樣式選擇多 #

大部分的套件不像 Prism.js 以及 highlight.js 提供非常廣泛的支援。像是 Prism.js 就目前為止,提供 176 種語言支援,8 種樣式風格。highlight.js 則提供 185 種語言支援、89 種風格。反之,像是 Rainbow 目前僅提供 21 種語言支援而已。而這些都是倚賴社群的力量呀!

理由三:預設將會自動執行程式碼標註 #

所以就上述兩點理由看來,Prism.jshighlight.js 根本是分庭抗禮、不分上下,但最後讓我選擇 Prism.js 的原因在於, highlight.js 需要再另外呼叫。highlight.js 的教學使用中

The bare minimum for using highlight.js on a web page is linking to the library along with one of the styles and callinginitHighlightingOnLoad:

也就是除了載入 js 檔案後,還需要加入一行程式碼來執行,這實在有點不方便呀。

反之,Prism.js 則會自動對所有 <code> 屬性進行標註,除非你想要手動標註,才需要在匯入時,增加一個屬性標籤 data-manual 來告訴 Prism.js 要手動標註:

<script src="prism.js" data-manual></script>

該如何使用 Prism.js #

選擇一:使用本地端檔案 #

先到 Prism.js 網站去依照自己的需求以及選擇樣式,生成 JS 以及 CSS 檔案。目前官網提供的內建樣式有 8 種。如果覺得這些樣式都不喜歡的話,官方還有另外提供一些額外的選擇,可以到 PrismJS/prism-themes 看看有沒有喜歡的。

接著把下載的檔案,放到 <你的hugo部落格資料夾位置>/static 底下。然後更改 parital 檔案,但是更改的檔案以及位置,則要看用的主題而不同。例如,目前本部落格使用的是 hugo-book ,那就是在 <你的hugo部落格資料夾位置>/layouts/partials/docs/inject/ 底下新增兩個檔案,一個 head.html 另一個則是 body.html 。接著分別在該兩個檔案新增 HTML 碼:

// 在 head.html
<link rel="stylesheet" href="/<剛下載的CSS檔案名稱>.css"/>

// 在 body.html
<script src="/<剛下載的JS檔案名稱>.js"></script>

例如剛從 Prism.js 下載下來的檔案分別叫做 prism.js 以及 prism.css,然後把它放在 static 資料夾底下。那檔案結構應該會長這個樣子:

你的Hugo部落格資料夾位置
├── static
│   ├── prism.js
│   └── prism.css

而在 head.html 裡面引用的<link> 應該會是:

<link rel="stylesheet" href="/prism.css"/>

而在 body.html 底下引用的 <script> 則是:

<script src="/prism.js"></script>

這樣就大功告成了。

選擇二:使用CDN載入 #

另外,Prism.js 目前也有提供 CDN 載入,現在 cdnjs.com 以及 jsDelivr 皆可以找到 Prism.js

不過相較於把檔案下載下來,目前 CDN 也支援用 prism-autoloader 方式自動幫你載入需要的語言設定檔。不過這方面的設定呢,就目前文件上是找不到,而是藏在 github 中的議題裡面:Website: Added basic usage for CDNs

簡單而言呢,就是除了 CSS 樣式檔載入方法是一樣的,載入 Prism.js 核心庫則有點不一樣。在 Prism.js 改成要載入 components/prism-core 以及 plugins/autoloader/prism-autoloader 兩支程式,然後有先後順序, 此外在載入完 prism-autoloader 這隻程式檔案後,需要加一行自動載入的語言設定檔路徑在哪裡。

以下以 jsDelivr 提供的 [email protected] 版為例:

<script src="https://cdn.jsdelivr.net/npm/[email protected]/components/prism-core.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/plugins/autoloader/prism-autoloader.min.js"></script>
<script>Prism.plugins.autoloader.languages_path='https://cdn.jsdelivr.net/npm/[email protected]/components/';</script>

不過有時候載入 CDN 如果遇到延遲,整個網站可能就會卡住,使用者體驗會非常不好。這時候就可以用 HTML5 提供的 async/defer 來幫助我們。現在網路上對於 async/defer 資料應該蠻多的,在這裡就不再贅述,有興趣者可以參考:

不過相較於 async 會在載入後執行,還是會阻塞網頁,defer 則是留到最後才執行,相較於前者體驗更佳,所以我們可以選擇全部加入 defer 屬性:

<script defer src="https://cdn.jsdelivr.net/npm/[email protected]/components/prism-core.min.js"></script>
<script defer src="https://cdn.jsdelivr.net/npm/[email protected]/plugins/autoloader/prism-autoloader.min.js"></script>

不過這樣會有個問題,最後那個設定怎辦?畢竟它必須等到前面兩個都載入後才能執行,不然就會跳出錯誤:

Uncaught ReferenceError: Prism is not defined at HTMLScriptElement.onload ((index):45)

這時候就可以善用 onload 事件,他會等到所有資源載入後才會觸發,相當適用我們的情境,所以我們就可以在 <script> 裡面新增 onload 事件處理。將原本的:

<script defer src="https://cdn.jsdelivr.net/npm/[email protected]/plugins/autoloader/prism-autoloader.min.js"></script>

改成:

<script defer src="https://cdn.jsdelivr.net/npm/[email protected]/plugins/autoloader/prism-autoloader.min.js" onload="Prism.plugins.autoloader.languages_path='https://cdn.jsdelivr.net/npm/[email protected]/components/';"></script>

最後來看看效果 #

console.log('hello, prism.js')

在本站上面這句程式碼的色彩應該會長這樣(如果主題色彩沒改的話):

hello, prism.js

如果沒有的話⋯⋯可能就是發生bug了⋯⋯趕緊來信通知。