Hugo 學習筆記

/

本篇要解決的問題

幾年前本站有寫一篇介紹 Publii 這個免費生成網站的工具,前陣子更新完後才發現,哎,要收錢啦?剛好前幾天在 VuePress 的官網中看到了 Hexo,又在 Google 上爬了一下文,再發現另一個叫 Hugo 的工具,生成頁面的速度可以說是秒生,就決定來研究一下。

Hugo,是一個靜態網站的生成工具,寫完後可以產出成 HTML 的檔案,再結合 GitHub Pages,便能夠做出一個免費的、屬於自己的網站。

如果不需要客製樣式或是功能,選好想要的 theme(佈景主題) 後,照著官方的「Quick Start」一步步走,很簡單就可以產生一個網站出來,剩下的就是撰寫內容文章。

但如果像 August 一樣,想要客製的部份多一點,或是看了程式碼後想知道為什麼的話,就得去看他們的官方說明文件。

幸好,在落落長的文件之外,Hugo 有推一個 Youtube 的教學影片系列,本篇就是一邊看教學,一邊翻文件並實際寫 code 測試後,整理的筆記文。

本篇筆記文要解決的問題就是,下載了 theme 後,看到那些程式碼時不會一頭霧水,以及想自行開發想要的樣式、功能時,可以從本篇找到需要的程式碼。

在看本篇前,請先看過官方的 Quick Start 教學,並實際操作過一遍喔,很多裡面有寫的東西本篇不會再另行說明。

參考資源:

Youtube:Hugo – Static Site Generator | Tutorial

這是廣告,點擊一下可以幫本站多個一點點的廣告收入,謝謝

官方文件:Hugo Documentation

注:原本的 config.toml 檔,改為 config.yml,所以寫 config 的部份會是用 YAML


基本 Command-Line

開發

hugo serve -D

生成靜態檔

hugo -D

有沒有 -D 的差別在於要不要 顯示 / 產出 drafttrue 的文章。


資料夾:archetypes

會影響 new 一個檔案出來時的預設值。

假設我們的 content 資料夾的結構如下:

├── content
|   ├── demo1
|   |   ├── _index.md
|   |   ├── demo1-01.md
|   |   └── demo1-02.md
|   └── demo2
|   |   ├── _index.md
|   |   ├── demo2-01.md
|   |   └── demo2-02.md

在 Front Matter(檔案被 --- 符號包起來的部份) 上,demo1 的預設作者是「August」,demo2 的預設作者是「Let’s Write」,那我們就可以用 archetypes 這個資料夾來設定預設值。

我們在 archetypes 資料夾中建立二個檔案:demo1.md、demo2.md:

├── archetypes
|   ├── demo1.md
|   └── demo2.md

demo1.md 的內容為:

---
title: "{{ replace .Name "-" " " | title }}"
date: {{ .Date }}
draft: true
author: August
---

demo2.md 的內容為:

這是廣告,點擊一下可以幫本站多個一點點的廣告收入,謝謝

---
title: "{{ replace .Name "-" " " | title }}"
date: {{ .Date }}
draft: true
author: Let's Write
---

之後我們 new 檔案出來時,預設的 Front Matter 就會對應到 archetypes 裡的檔案設定。

content/demo1/**.md 的 Front Matter 就是 archetypes/demo1.md 的。

content/demo2/**.md 的 Front Matter 就是 archetypes/demo2.md 的。

如果我們沒有在 archetypes 資料夾針對不同的 content 去建對應的檔案,那在 new 檔案出來時會抓的是 archetypes/default.md 的設定。

注:以上的資料夾及檔案對應方式,會因為 themes 而有不同。


資料夾:layouts

當我們安裝了 theme,每個 theme 裡面都會有 layouts 這個資料夾,裡面的檔案就是各個頁面的模版。

如果我們不想用 themes 裡的模版,想用自己的,就在 layouts 資料夾裡建立跟 theme/theme的名稱/layouts 資料夾裡一樣的檔案路徑,Hugo 會優先使用我們自行建立的模版檔。

layouts 裡預設的列表頁資料夾規則如下:

  • * Base Template 全域模版:layouts/_default/baseof.html
  • 首頁:layouts/index.html
  • 404頁:layouts/404.html
  • * 列表頁:layouts/_default/list.html
  • 單文頁:layouts/_default/single.html
  • 分類頁(類別、標籤):layouts/_default/taxonomy.html
  • * Partials 模組:layouts/partials/**
  • * Shortcodes 短代碼 :layouts/shortcodes/**

開頭有打 * 的部份,以下分成幾個段落來各別說明。


Base Template 全域模版

好吧,「全域模版」這個名稱是 August 自行取的,因為這樣子比較好想像 XD~ 原文是叫「Base Template」,總覺得這個名稱無法突顯它的重要性。

簡單來說,這就是我們在寫 Pug 時(舊名叫 Jade)會建的那個 template.pug 檔,那個讓其它頁直接「extends template」引用的 template 檔。當我們想要全站的某個東西要修改時,只要改了 template.pug,所有引用這個 template 的頁面就會同時被修改。

這個重要的模版,就是 Base Template,是不是翻作「全域模版」會比較好想像?

block

跟 Pug 相同,我們寫 Pug 建立 template 時,會用 block xxx 去挖洞給其它頁面使用,比方 block main,其它頁面引用 template 後,一樣寫一行 block main 便可以針對這個區塊去給不同內容。

Hugo 也有 block。

使用方式如下:

layouts/_default/baseof.html

<!-- ... -->
<body>
  {{ block "main" . }}
  {{ end }}
</body>

layouts/_default/list.html

<!-- ... -->
<body>
  {{ define "main" }}
    <!-- 這邊就是寫要放進 "main" 裡的程式 -->
  {{ end }}
</body>

當然,各頁都是可以用 block 功能的,不限定只能在 baseof.html 裡使用。


列表頁

content 資料夾裡的子資料夾,會是列表頁的主頁,如 content/posts/,當輸入網址:

localhost:1313/posts/

就會是 posts 這個資料夾底下的列表主頁。

這頁是可以編輯的,當檔名取為「_index.md」時,便可以編輯此頁。


列表頁、單文頁 VS. Section Template 使用不同的模版

除了 Base Template 負責寫全域的模版。

列表頁還會使用 layouts/_default/list.html 這個模版檔。

單文頁還會使用 layouts/_default/single.html 這個模版檔。

但如果我們有幾個頁面就是要不一樣,要用不同的模版呢?比方 Header 都會放上會員名字,但登入頁因為還未登入所以要改放登入按鈕。

假設我們 content 資料夾結構如下:

├── content
|   ├── demo1
|   |   ├── _index.md
|   |   ├── demo1-01.md
|   |   └── demo1-02.md
|   └── demo2
|   |   ├── _index.md
|   |   ├── demo2-01.md
|   |   └── demo2-02.md

我們的目標是讓 demo2 這個資料夾下的頁面改用別種模版。

很簡單,對應資料夾的名稱,我們在 layouts 資料夾裡也建一個同樣名稱的資料夾:

├── layouts
|   ├── _default
|   |   ├── list.html
|   |   └── single.html
|   └── demo2
|   |   ├── list.html
|   |   └── single.html

這樣子 content/demo2 裡的檔案,使用的模版就會是 layouts/demo2/ 裡的。


Partials 模組

對,「模組」也是 August 自己的翻譯,原因也是因為比較好懂。

partials,如果有寫 Pug,它就像是 Pug 裡可以讓我們 include 的 partials。

如果有寫 Vue,它就像是可以讓我們 export、import 的 components。

我們在切版的時候,會把一些共用的東西拆成幾個 partials,各頁面要用的時候就是 include 進去,比方 Header、Footer,這樣當共用的這些區塊有東西要修改時,改一個檔案就可以修改好全部的頁面。

Hugo 的 layouts,就是把這些 partials 存在 layouts/partials 的資料夾裡。

假設我們有 header.html、footer.html 這二個 partials 的檔案:

├── layouts
|   ├── partials
|   |   ├── header.html
|   |   └── footer.html

模板檔裡要引用時,方式為:

{{ partials "header.html" . }}
<!-- 中間內容的部份 -->
{{ partials "footer.html" }}

Shortcode 短代碼

短代碼在 WordPress 上也有,啊不過,August 真的懶得再記那些有的沒的短代碼,就沒在本站中使用。

shortcode 跟 partials 的差別在於,shortcode 比較像是 partials 裡的 partials,通常不會用在一整個區塊,而是用在這個東西的 HTML 可能很長,我們懶得每次都寫得落落長,像是 iframe Youtube 影片。

shortcode 用在 content,partials 用在 layouts。

內建 Shortcodes

Hugo 有內建幾個 shortcode,大部份是社群跟影音,如下:

  • figure:輸出成 figure > img + figcaption
  • gist:可以在頁面上嵌入 Gist 的程式碼
  • highlight:將程式碼的部份加入樣式
  • instagram:嵌入 IG 貼文
  • tweet:嵌入 Twitter 貼文
  • vimeo:嵌入 Vimeo 影片
  • youtube:嵌入 Youtube 影片

自定 Shortcodes

假設我們今天要自己寫一個 shortcode,讓包在這個 shortcode 裡的文字改變顏色。

首先,在 layouts/shortcodes/ 的資料夾裡建立一個 shortcode 檔,我們取叫 setColor.html:

├── layouts
|   ├── shortcodes
|   |   └── setColor.html

setColor.html 的內容:

{{ $color := default "red" (.Get "color") }}
<div style="color: {{ $color }}">
  {{ .Inner }}
</div>

{{ .Get "color" }} 是抓取外邊來的參數,就像 Vue.js 裡的 props: ["color"]

{{ .Inner }} 就像 Vue.js 的 <slot>

對,直接用 Vue.js 的 component 來解釋對前端會比較好懂 XD~

{{ $color }} 這邊還作了一個給預設值的處理 default "red",如果頁面使用了 colorRed,卻沒有給 color 時,預設就給 red。

使用 shortcode 的方式為:

{{< shortcode-file-name param >}}
  <!-- HTML 的內容 -->
{{< /shortcode-file-name >}}

因為我們的檔名叫 setColor.html,所以我們要取用時就寫:

{{< setColor color="blue" >}}
  <p>有給 coloe,所以字的顏色是 blue</p>
{{< /setColor >}}
{{< setColor >}}
  <p>沒給 coloe,所以字的顏色是預設的 red</p>
{{< /setColor >}}

資料夾:content

所有的頁面檔案都會放在這邊。

Hugo 生成靜態檔案時,會生成這邊所有的 .md 檔為 .html 檔。


資料夾:data

可以放 JSON、YAML、TOML、XML 的檔案。

假設我們在 data 資料夾裡放了 member.json 這個檔案:

├── data
|   └── member.json

member.json 的檔案內容:

[{
  "id": 1,
  "name": "Leanne Graham"
}, {
  "id": 2,
  "name": "Ervin Howell"
}]

模版檔要取得 data 裡的資料,方式就是:

{{ .Site.Data.檔案名稱 }}

所以當我們要在單文頁的模版檔中印出 data/member.json 的資料:

{{ range .Site.Data.member }}
  <p>{{ .id }} / {{ .name }}</p>
{{ end }}

取得 API 的資料

除了把資料放在 data 資料夾,也可以用 getJSON 的函式來取得 API 的資料。

這邊用 JSONPlaceholder 當作 Demo:

{{ $demoJson := getJSON "https://jsonplaceholder.typicode.com/users" }}
{{ range $demoJson }}
  <p>{{ .id }} / {{ .name }}</p>
{{ end }}

文章分類:Categories、Tags

Hugo 針對文章分類的方式有二種:Categories(分類)、Tags(標籤)。

這點跟 WordPress 一樣,我們在 WordPress 上建立文章時,也是會選擇文章的分類、標籤。

設定文章分類、標籤的方式很簡單,寫在每篇文章的 Front Matter 上就行,如:

---
title: "Test Post"
date: 2022-06-05T11:44:19+08:00
draft: true
tags: ['tag1', 'tag2']
categories: ['cat1']
---

當頁面上點擊分類連結時,網址會像這樣:

localhost:1313/categories/cat1

標籤的連結會像這樣:

localhost:1313/tags/tag1

客製分類

如果要客製自己的分類,就要在 config 的檔案上新增 taxonomies

假設我們要新增一種分類方式叫「technology」,則可以寫成以下:

baseURL: /
languageCode: zh-tw
title: My New Hugo Site
theme: ananke
taxonomies:
  tag: tags
  category: categories
  technology: technologies

上面的範例,taxonomies 撰寫的方式就是:

單數: 複數

所以這邊是寫:

technology: technologies

五歲能抬頭的朋友一定會注意到,Hugo 預設的二個分類方式 tags、categories 也寫了上去,這是因為要用到客製分類,會整個打掉原本的分類方法,所以預設的二個分類方式必須也寫進去:

tag: tags
category: categories

注:以上的分類方式,會因為 themes 而有不同。


Variables 變數

變數只能寫在 layouts 資料夾的檔案裡,用 {{ }} 包起來。

以下是會常用到的變數:

{{ .Title }}:文章標題。

{{ .Date }}:日期。

{{ .URL }}:本頁網址。

自訂變數

方法 1:在每個文章檔案裡的 Front Matter 上寫入自訂變數,如下:

content/posts/demo1.md

---
title: "This is Demo1"
date: 2022-06-05T11:44:19+08:00
draft: true
myVar: "myValue"
---

layouts/_default/list.html

<p>{{ .Params.myVar }}</p>

方法 2:用 Go 語言的聲明變量。

Hugo 的後端是用 Go,所以可以用 Go 的變量來寫。

August 還沒學過 Go,因此以下範例是參考教學影片及文件的說明:

{{ $myVar := "myVal" }}
<p>{{ $myVar }}</p>

$myVar := "myVal",是 var myVar string = "myVal" 的簡寫。

Go 語言,:= 是聲明變量並賦值。= 是賦值。

如果要重新定義變量:

{{ $var := "Hugo Page" }}
{{ if .IsHome }}
  {{ $var = "Hugo Home" }}
{{ end }}
<p>{{ $var }}</p>

Hugo 的 Variables 有很多很多,詳情請見 官方文件


Functions 函式

Hugo 使用 function 的方式就是 Go 語言的方式,只是一樣要用 {{ }} 包起來。

寫到這,覺得 {{ }} 好像 Vue.js 的 v-bind,裡面可以寫變數也可以執行方法。

使用 function 的方式為:

{{ funcName param1 param2 }}

例如:

<p>{{ add 1 2 }}</p> <!-- 輸出為 <p>3</p> -->

Hugo 是用 Go 寫的,裡面就內建了許多 Go 的函式,可參考 官方文件

這邊要筆記一下,最常會用到的是 range,常用程度就是我們寫 JS 時的 for

列表頁的模版檔(list.html)常常用 range 把所有頁面的列表給印出來:

<section>
  {{ range .Pages }}
    <a href="{{ .RelPermalink }}">
      {{ .Title }}
    </a>
  {{ end }}
</section>

然後看了文件才發現 .Pages 就有許多種,只能說,與其從 0 開始手刻一網站出來,不如直接找一個想要的 theme 後再來改會比較省時省力。


if … else

Hugo 的 if 判斷寫法……前端常寫 JS 的話,第一次看到會覺得很不習慣。

JS 的話我們會寫這樣:

var a = 1, b = 2, c = 0;
if(a === b) {
  c = a + b;
} else {
  c = 4;
}

Hugo 則是寫成這樣:

{{ $a := 1 }}
{{ $b := 2 }}
{{ $c := 0 }}
{{ if eq $a $b }}
  {{ $c = add $a $b }}
{{ else }}
  {{ $c = 4 }}
{{ end }}

eq 這個 function 去比對後面二個參數。

查了一下,Go 好像就有這樣子的寫法,叫 text/template

以下整理各種比對的 function:

  • eq A B:A == B
  • ne A B:A != B
  • ge A B:A >= B
  • gt A B:A > B
  • le A B:A <= B
  • lt A B:A < B

還有一個特別的,就是 not,用 not 時,後面比對的部份要用括號包起來,這邊用 A != B 來當例子:

{{ $a := 1 }}
{{ $b := 2 }}
{{ $c := 0 }}
{{ if not (eq $a $b) }}
  {{ $c = 3 }}
{{ end }}

這邊有一個簡單的範例,在單文頁模版上(single.html)插入所有文章的列表,當列表裡的標題跟本頁的標題相同時,就給不同的 class:

{{ $title := .Title }}
{{ range .Site.RegularPages }}
  {{ $textColor := "text-blue" }}
  {{ if eq $title .Title }}
    {{ $textColor = "text-red" }}
  {{ end }}
  <a class="{{ $textColor }}" href="{{ .RelPermalink }}">{{ .Title }}</a>
{{ end }}

筆記後心得

今天整理的,是 Youtube 的講解 + 文件說明。

這還只是冰山一角,但整理完這一篇,覺得又學到更多了,畢竟 Hugo 就是 SSG 的一個工具啊。

之後看 August 有沒有成功拿 Hugo 來做些什麼事,會再寫相關的筆記文。

這篇如果對你有幫助,請按個讚或分享,你的小小動作,對本站都是大大的鼓勵。

Summary
Hugo 學習筆記
Article Name
Hugo 學習筆記
Description
本篇大綱:本篇要解決的問題。Command-Line。archetypes。layouts。Base Template。列表頁、單文頁 VS. Section Template。Partials。Shortcode。content。data。文章分類。Variables。Functions。if else。
Augustus
Let's Write
Let's Write
https://letswrite.tw/wp-content/uploads/2020/08/logo_512.jpg

隨選筆記文

API

用 Performance API 檢測檔案讀取時間

Google Others

用 GCP 建立 Cloud Functions

Google Maps

Google Maps API 學習筆記 – 5:抓目前位置、計算到各點距離

Analytics Google

GA 工具 Google Analytics Debugger 介紹及使用

PWA

PWA 學習筆記-1:Cache、Workbox 基本使用

Apps Script Google

Google Apps Script 抓 RSS 資料

Front-End

OSM + Leaflet 學習筆記1:建地圖、marker、事件、換圖層

Front-End

用 JavaScript 將 JSON 轉為 CSV 檔下載

Front-End

改變 iframe src 時不增加瀏覽歷史紀錄

API

如何用 Postman Mock Server 快速建立 API Server

以下是留言,但關於留言的部份必需先讓你們知道:

本站的文章都是 August 因為覺得有趣,才會實作並整理成筆記文而後進行發表。

如果留言是希望把 Demo 改成「你想要」的樣子,或是把功能改成「符合你需求」的樣子,

Sorry~ 除非那修改是 August 也有興趣的,不然不會幫你們寫程式去面對工作或是交作業。

未來這類的留言不會再主動回覆。😎

另外,公開信箱是為了讓金流驗證用,

因為之前遇過幾次回信協助解決問題後,對方卻一聲謝謝也沒有,就這樣拿去幫工作交差。

因此決定不再回覆信件,有疑問就利用留言功能囉。