Hugo 跨版本升級
使用 Hugo 一年半了,終於有了升級的動力。趁著最近事情不多,著手搞定了這個事情,記錄下來分享給需要的同學。
背景
在這一年半里,我一直使用著的是老版本: v0.20.7
,執行非常穩定,寫完文章 Git Push
後,GitLab Runner 自動更新預覽地址,瀏覽沒問題就可以一鍵釋出了。
但是下面兩個問題讓我有了升級的想法。
構建速度隨著內容增多變慢
去年十月,在網站架構簡化之後,我的完整發布編譯時間從 1分鐘 進入了 40s 的階段,但是隨著內容的膨脹、編譯時間越來越慢了,可以看到不少釋出時間變長。
看著 Hugo 升級 Golang 版本, 重構優化速度變的越來越快,不免心動。
需要維護兩套不同版本的站點
一月初將部落格主題重構之後,分享給了公司技術團隊部落格使用,考慮可維護性,新站點使用的是最新版本 0.53
,因為技術團隊站點功能更簡單,所以升級過程中不相容的部分其實並不多。
但是這個站點,因為自定義了“年月日”格式的歸檔,以及使用的是老版本的模板查詢邏輯,生成頁面連結也不完全相容,所以直接升級是不行的。
可以預期的是,隨著使用時間越來越長,這兩個站點的差異會越來越大, 為了可維護性,必須將這兩個站點使用的 Hugo 版本統一 。
梳理主要問題
- 官方支援 RSS 檔案直接輸出,是否還需要自定義站點 RSS 檔案?
- 官方直接提供壓縮能力,是否足夠替換 Pipeline 中定製的壓縮服務?
- 頁面模板查詢邏輯、模板語法、站點配置檔案變更,現有模板無法直接使用。
- 分類標籤系統扁平化,不再支援樹形層級巢狀,連結相容如何處理?
下面我來逐個擊破。
Hugo RSS 解決方案
官方支援了 RSS 格式的輸出,只要在 layouts
根目錄建立一個檔案即可, index.rss.xml
,模板可以自定義,參考官方文件 。
另外官方生成文件,預設會輸出正確的 XML Version,所以可以檢查並刪除己配置的文件模板中下面的內容。
<?xml version="1.0" encoding="utf-8" standalone="yes" ?>
但是這樣會有兩個額外的問題,第一個是額外生成了許多不需要生成的“訂閱源”,像是下面這樣。
find public/**/*.xml public/tags/標籤A/index.xml public/tags/標籤A/index.xml public/tags/標籤C/index.xml public/topics/code/index.xml public/topics/funny/index.xml public/topics/index.xml public/topics/life/index.xml public/topics/share/index.xml public/topics/website/index.xml
公司的技術團隊部落格可以保留這個功能,但是我個人一來更新頻率沒有那麼高,二來我希望訂閱源唯一可控,所以這些多餘的內容我是要幹掉的。
第二個問題是官方 RSS 輸出內容不支援自定義路徑,你的訂閱地址就只能是下面這樣:
網站地址/index.xml
使用老版本的 RSS 方案,建立一個 /feed
,然後放置自定義的 RSS 模板,你會發現生成內容,僅支援該目錄之下的文章… ORZ
如果你有類似的需求,這裡更好的方案是“禁用官方RSS生成能力”、“自定義RSS模板”,可以做到按照你的需求在你期望的路徑生成你期望數量的 RSS 內容。
首先是禁用官方RSS生成能力,在站點 config.toml
配置檔案中新增下面的內容:
disableKinds= ["RSS"]
如果你有定義 output
格式,幷包含 RSS 定義,也需要刪除該內容。
[outputs] -page = [ "HTML", "RSS" ] +page = [ "HTML" ]
接著分別建立 layouts/feed/index.html
和 content/feed/index.md
兩個檔案。
模板檔案內容可以參考:
{{ `<?xml version="1.0" encoding="utf-8" standalone="yes" ?>` | safeHTML }} <rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:sy="http://purl.org/rss/1.0/modules/syndication/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/"> <channel> <title>{{ if eq .Title .Site.Title }}{{ .Site.Title }}{{ else }}{{ with .Title }}{{.}} - {{ end }}{{ .Site.Title }}{{ end }}</title> <link>{{ .Site.BaseURL }}feed/</link> <description>{{ .Site.Title }}最近更新內容。</description>{{ with .Site.LanguageCode }} <language>{{.}}</language>{{end}}{{ with .Site.Author.email }} <managingEditor>{{.}}{{ with $.Site.Author.name }} ({{.}}){{end}}</managingEditor>{{end}}{{ with .Site.Author.email }} <webMaster>{{.}}{{ with $.Site.Author.name }} ({{.}}){{end}}</webMaster>{{end}}{{ with .Site.Copyright }} <copyright>{{.}}</copyright>{{end}}{{ if not .Date.IsZero }} <lastBuildDate>{{ .Date.Format "Mon, 02 Jan 2006 15:04:05 -0700" | safeHTML }}</lastBuildDate>{{ end }} <sy:updatePeriod>hourly</sy:updatePeriod> <sy:updateFrequency>1</sy:updateFrequency> <generator>hugo</generator> <atom:link href="{{ .Site.BaseURL }}feed/" rel="self" type="application/rss+xml"/> {{ range first 10 (where .Site.Pages "Type" "post") }} <item> <title>{{ .Title }}</title> <link>{{ .Permalink }}</link> <pubDate>{{ .Date.Format "Mon, 02 Jan 2006 15:04:05 -0700" | safeHTML }}</pubDate> <dc:creator>{{`<![CDATA[`|safeHTML}}蘇洋(soulteary){{`]]>`|safeHTML}}</dc:creator> <author>{{`<![CDATA[`|safeHTML}}蘇洋(soulteary){{`]]>`|safeHTML}}</author> <guid>{{ .Permalink }}</guid> <description>{{`<![CDATA[`|safeHTML}}{{ .Description | safeHTML }}{{`]]>`|safeHTML}}</description> <content:encoded>{{`<![CDATA[`|safeHTML}}{{ .Description | safeHTML }}{{`]]>`|safeHTML}}</content:encoded> </item>{{ end }} </channel> </rss>
頁面資料檔案,示例檔案:
--- title: "Rss Feed" author: "soulteary" date: "2019-01-24" type: feed draft: false isCJKLanguage: true outputs: [ "HTML" ] ---
再次執行 hugo
進行站點生成,會發現生成的頁面內容已經大幅減少:
自定義之前: | EN +------------------+------+ Pages | 4419 Paginator pages | 594 Non-page files | 0 Static files | 197 Processed images | 0 Aliases | 1796 Sitemaps | 1 Cleaned | 0 自定義之後: | EN +------------------+------+ Pages | 3285 Paginator pages | 594 Non-page files | 0 Static files | 197 Processed images | 0 Aliases | 1796 Sitemaps | 1 Cleaned | 0
最後別忘記通過構建指令碼,將生成檔案進行重新命名。
mv feed/index.html feed/index.xml
更好的Hugo頁面壓縮能力
在使用 Hugo 版本和之前的壓縮模式進行對比,發現 Hugo 壓縮確實效率高不少,新增壓縮引數 --minify
執行 Hugo ,生成時間幾乎沒有變化,還能省下 GitLab Pipeline 呼叫 Job 過程中的時間損耗,真的是太讚了。
但是壓縮結果完全使用了 HTML 寬鬆模式,所有的 HTML Tag 屬性都失去了引號,像是下面這樣。
<span class=crayon-o>/</span>
個人傾向使用相對嚴格的模式進行頁面結構編寫,即使是程式生成的程式碼也是如此,因為一旦要進行除錯,相對嚴格的標準,可以消除很多歧義,減少不必要的除錯時間。
由於老版本不支援 --minify
引數,所以我使用 Node.js
簡單寫了一個指令碼,用於替換頁面內的空白內容,程式處理內容比較多,貼一下主要替換邏輯供參考。
const trimTags = (s) => s.replace(/>\s+</gm, '><').replace(/>(\s+\n|\r)/g, '>'); const content = trimTags(content);
如果你和我一樣,定製了程式碼高亮,有更復雜的 HTML 結構,那麼還需要額外處理一下程式碼,避免出現 ReDoS
問題。
模板配置相關處理
首先貼出我的站點主題的 layouts
目錄結構(部分),它也代表了網站的整個抽象邏輯。
layouts ├── 404.html ├── _default │ ├── baseof.html │ ├── list.html │ ├── section.html │ ├── summary.html │ ├── tag.html │ ├── topics.html │ └── topics.terms.html ├── about │ └── single.html ├── about-site │ └── single.html ├── archives │ ├── list.html │ └── single.html ├── booklist │ └── single.html ├── contact │ └── single.html ├── feed │ └── single.html ├── index.html ├── index.redir ├── links │ └── single.html ├── partials │ ├── bloc │ │ ├── content │ │ │ ├── badges.html │ │ │ ├── comments.html │ │ │ ├── content.html │ │ │ ├── navigation.html │ │ │ ├── pagination.html │ │ │ ├── sidebar.html │ │ │ └── summary.html │ ├── loop.html │ ├── modules │ │ ├── footer │ │ │ ├── index.html │ │ │ └── script.html │ │ ├── header │ │ │ ├── custom.html │ │ │ ├── index.html │ │ │ ├── link-style.html │ │ │ ├── meta-robots.html │ │ │ ├── meta.html │ │ │ └── script.html │ │ ├── meta │ │ │ ├── page-home-meta.html │ │ │ ├── page-topic-meta.html │ │ │ ├── post-meta-bottom.html │ │ │ └── post-meta-top.html │ │ ├── sidebar │ │ │ └── index.html │ │ └── site │ │ │ ├── social │ │ │ │ └── rss.html │ │ │ └── social.html │ │ ├── pagination.html │ │ └── topline.html │ ├── pages │ │ ├── about │ │ │ └── index.html │ │ ├── about-site │ │ │ └── index.html │ │ ├── archives │ │ │ ├── all.html │ │ │ ├── index.html │ │ │ ├── month.html │ │ │ └── year.html │ │ ├── booklist │ │ │ └── index.html │ │ ├── contact │ │ │ └── index.html │ │ ├── error-page │ │ │ └── index.html │ │ ├── home │ │ │ ├── index.html │ │ │ ├── list.html │ │ │ └── summary.html │ │ ├── links │ │ │ └── index.html │ │ ├── post │ │ │ └── index.html │ │ ├── subject │ │ │ ├── index.html │ │ │ └── item.html │ │ ├── tag │ │ │ ├── index.html │ │ │ ├── list.html │ │ │ └── summary.html │ │ └── topic │ │ ├── index.html │ │ ├── list.html │ │ └── summary.html │ └── static │ ├── error-track.js.html │ ├── global-config.js.html │ ├── init.js.html │ ├── page-debug.js.html │ └── web-pref.js.html ├── post │ ├── single.html │ ├── single.md ├── robots.txt ├── shortcodes │ └── crayonCode.html ├── sitemap.xml └── subject └── single.html
現在的查詢邏輯不是十分合理,為了避免構建時的警告資訊,我使用 layouts/_default
接管了標籤和分類的模板入口,其餘的入口頁面依舊放在 layouts
子目錄的各同名目錄下,比如 layouts/post/single.html
。
所有頁面的真實處理邏輯放置在 layouts/partials/pages
中,可以最大限度保障頁面主題的相容性,比如這次,我就只是修改了入口頁面的位置,而頁面處理邏輯沒有大動。
額外說明一點, _default
目錄下的檔案,只有 tag
和 topics
相關三個檔案存在內容,其餘檔案保持為空即可。
新版本 Hugo 針對 config.toml 也有升級策略,直接執行 hugo
,配置檔案中的問題,它會進行報錯提示,並示例你如何更改,比如這樣:
@@ -61,30 +61,13 @@ [mediaTypes] [mediaTypes."text/plain"] -suffix = "md" - -[mediaTypes."application/rss"] -suffix = "xml" +suffixes = ["md"] [outputFormats.MD] Path = "/" mediaType = "text/plain" isPlainText = true -[outputFormats.FEED] -mediatype = "application/rss" -baseName = "feed" - [outputs] -page = [ "HTML" ,"MD", "FEED" ] +page = [ "HTML" ,"MD" ]
分類和標籤扁平化以及其他相容處理
在 Hugo 升級之前,我使用的是這樣的分類結構:
topics: [ "知識點滴/容器化" ]
老版本的 Hugo 會自動生成兩級分類目錄,並且兩個目錄都支援索引,像是下面這樣。
/知識點滴 /知識點滴/index.html /知識點滴/page/2.html /知識點滴/容器化 /知識點滴/容器化/index.html /知識點滴/容器化/page/2.html
而新版本會生成唯一的分類,並且使用自己的策略 轉義連結地址中的空格和斜槓為連字元 。
/知識點滴-容器化 /知識點滴-容器化/index.html /知識點滴-容器化/page/2.html
在思考之後,我發現除了做介面需要表明資源從屬關係之外,除非寫書似乎文章還真的不太需要那麼明確的層級隔離,一級目錄外加標籤就能提供良好的閱讀和解決簡單檢索需求。
所以我進行了簡化,去掉了所有的文章分類從屬關係。但是我還有一個按照年月日進行日期歸檔的路徑,這裡還是希望能夠保持從屬關係的,該怎麼解決呢?
首先歸檔內容,暫時還是需要自己手動生成並維護的,下面是我使用另外一個指令碼在每次文章釋出時生成的目錄結構(部分)。
./content/archives ├── 2018 │ ├── 11 │ │ └── index.md │ ├── 12 │ │ └── index.md │ └── _index.md ├── 2019 │ ├── 01 │ │ └── index.md │ └── _index.md └── _index.md
這裡使用了一個取巧的方案,使用 _index.md
會列舉目錄中所有內容的特性,可以輕鬆生成下面的結構。
./public/archives ├── 2018 │ ├── 12 │ │ └── index.html │ └── index.html ├── 2018.html ├── 2019 │ ├── 01 │ │ └── index.html │ └── index.html ├── 2019.html └── index.html
接下來只需要修正原本模板中引用地址 /archives/YEAR/
為 /archives/YEAR.html
即可
上文提到過 Hugo 新版本對於連結的一些額外轉義處理,除了分類會被影響外,標籤也被波及到了。
舉個例子,我原本有一個標籤叫做 : Linux/Mac
,在舊版本的 Hugo 中的輸出結果是這樣:
/public/tags/linux/mac/index.html
但是在新版本變成了這樣:
/public/tags/linux-mac/index.html
因為我禁用了 RSS ,暫時不提供標籤的訂閱,文章內直接引用標籤目前也比較少,訪問地址變了就變了,但是模板中如果直接使用老版本的語法,標籤地址生成的還是老樣子(生成連結策略和渲染邏輯不一致),結果就是含有空格和斜槓的標籤頁面是無法正常瀏覽的!
舉個例子,老版本語法:
{{ $tagLink | urlize }}
解決方式比較 trick,需要手動在模板中進行轉義,並補全 .html
字尾:
{ replace (replace (lower $tag) "/" "-") " " "-"}}.html"
至此,升級過程中的主要問題就都講完了,我們接下來聊聊效能提升和其他的話題。
效能和一些其他的事情
這次重構之後,完整發布時間縮短了 10s
,構建時間減少了 3s
,後續計劃將預覽和生產的流水線進行分割,應該還能進一步提升效率。
在做這次升級重構之前,我首先考慮了最低成本升級到的完全相容的可用版本。
v0.20.7 @bep bep released this on 3 May 2017 · 1666 commits to master since this release
在試驗之後,我發現唯一和我當前版本相容的是 v0.21
,也是一個古董版本,沒有實質的變化。
v0.47.1 Hugo Static Site Generator v0.47.1 darwin/amd64 BuildDate: 2018-08-20T08:16:52Z
稍微做一些重構,能夠接觸到的最新版本則是上面的 0.47.1 ,也還是低版本的 Golang,並且和主流版本差異還是不小。
我使用 hugo benchmark
比較了 0.20.x
和 0.40.x
發現效能不升反降,期初我也很猶豫是否值得進行升級。
v0.20.x
Average time per operation: 3955ms Average memory allocated per operation: 1651040kB Average allocations per operation: 22417100
v0.40.x
Average time per operation: 4056ms Average memory allocated per operation: 1650683kB Average allocations per operation: 22396305
於是我認真瀏覽了這一年半以來的 版本釋出記錄 ,覺得還是要與時俱進,如果不升級,將一直鎖定在低版本的 Golang 執行時,所有的問題也都只能自己定製解決,完全不能使用社群新功能、也不把折騰內容貢獻社群。
在使用兩個簡單站點分別使用 v0.20 和 v0.50 進行測試的時候,我發現提升還是很明顯的,於是便著手進行了升級,附加除錯完整花費了6個小時。
很可惜在 v0.50.3
版本之後,官方廢棄了 hugo benchmark
這個命令,所以我們不能夠和以往一樣輸出效能報告,不過直接使用站點生成時間來進行對比,也是一樣的(站點實際構建時間)。
Total in 3452 ms Total in 3547 ms Total in 3730 ms
可以看到效果還是不錯的,另外我也終於可以使用 brew install hugo
安裝的 Hugo 更方便的進行本地預覽了。
最後
下篇內容,我們繼續聊聊 Wiki 系統的搭建。
—EOF