Meskipun Hugo sudah sangat feature-rich, ada kalanya Anda membutuhkan functionality khusus yang tidak tersedia secara default. Hugo menyediakan beberapa mechanisms untuk extend functionality, termasuk custom template functions, hooks system, dan Hugo modules. Panduan ini akan membahas cara mengembangkan custom functionality untuk Hugo dengan Go.
Plugin development untuk Hugo membutuhkan pemahaman tentang Go programming dan Hugo internals. Namun, dengan arsitektur yang modular, Anda dapat extend Hugo functionality dengan berbagai cara tanpa harus fork repository utama.
Hugo Modules untuk Custom Functionality
Membuat Hugo Module
Hugo modules memungkinkan Anda mengorganisir dan distribute custom functionality sebagai reusable packages.
// go.mod
module github.com/username/hugo-mymodule
go 1.21
require github.com/gohugoio/hugo v0.139.2
Struktur Module
hugo-mymodule/
βββ go.mod
βββ main.go
βββ functions/
β βββ math.go
β βββ strings.go
βββ hooks/
β βββ myhooks.go
βββ LICENSE
Implementasi Custom Template Function
// functions/math.go
package functions
import (
"math"
)
// Mul returns the product of a and b
func Mul(a, b float64) float64 {
return a * b
}
// Div returns a divided by b
func Div(a, b float64) (float64, error) {
if b == 0 {
return 0, errors.New("division by zero")
}
return a / b, nil
}
// Percentage calculates percentage
func Percentage(value, total float64) float64 {
if total == 0 {
return 0
}
return (value / total) * 100
}
// Round rounds a number to specified precision
func Round(val float64, precision int) float64 {
ratio := math.Pow(10, float64(precision))
return math.Round(val*ratio) / ratio
}
// Floor returns the greatest integer less than or equal to x
func Floor(val float64) int {
return int(math.Floor(val))
}
// Ceil returns the smallest integer greater than or equal to x
func Ceil(val float64) int {
return int(math.Ceil(val))
}
Implementasi String Functions
// functions/strings.go package functionsimport ( "strings" "unicode/utf8" )
// Slugify converts string to URL-friendly slug func Slugify(s string) string { s = strings.ToLower(s) s = strings.ReplaceAll(s, " ", "-") s = strings.ReplaceAll(s, "_", "-")
// Remove non-alphanumeric characters (except -) var result strings.Builder for _, r := range s { if r == '-' || (r >= 'a' && r <= 'z') || (r >= '0' && r <= '9') { result.WriteRune(r) } } return strings.Trim(result.String(), "-")}
// TruncateWithEllipsis truncates string to max length with ellipsis
func TruncateWithEllipsis(s string, maxLength int) string {
if len(s) <= maxLength {
return s
}
if maxLength <= 3 {
return strings.Repeat(".", maxLength)
}
return s[:maxLength-3] + "..."
}// CapitalizeFirst capitalizes the first letter
func CapitalizeFirst(s string) string {
if s == "" {
return s
}
return strings.ToUpper(s[:1]) + s[1:]
}// WordCount returns the number of words
func WordCount(s string) int {
return len(strings.Fields(s))
}// ReadTime calculates estimated read time
func ReadTime(wordCount int, wordsPerMinute int) int {
if wordsPerMinute <= 0 {
wordsPerMinute = 200 // default
}
return int(math.Ceil(float64(wordCount) / float64(wordsPerMinute)))
}Register Functions dengan Hugo
// main.go package mainimport ( "github.com/username/hugo-mymodule/functions" "github.com/gohugoio/hugo/hugolib" )
func init() { hugolib.RegisterFunction("mul", functions.Mul) hugolib.RegisterFunction("div", functions.Div) hugolib.RegisterFunction("percentage", functions.Percentage) hugolib.RegisterFunction("round", functions.Round) hugolib.RegisterFunction("slugify", functions.Slugify) }
Hugo Hooks System
Membuat Custom Hooks
Hugo hooks memungkinkan Anda intercept dan modify behavior pada berbagai points dalam rendering process.
// hooks/render.go package hooksimport ( "bytes" "html/template" "io"
"github.com/gohugoio/hugo/markup/asciidoctor" "github.com/gohugoio/hugo/markup/markup")
// RenderHook is the interface for render hooks
type RenderHook interface {
Render(w io.Writer, src []byte) error
}// ImageHook for customizing image rendering
type ImageHook struct {
BaseURL string
}func (h ImageHook) Render(w io.Writer, src []byte) error {
// Custom image rendering logic
return nil
}// LinkHook for customizing link rendering
type LinkHook struct {
Page *page
Destination string
}func (h LinkHook) Render(w io.Writer, src []byte) error {
// Custom link rendering logic
return nil
}Template Integration
Menggunakan Custom Functions di Templates
{{/* Di template Hugo */}} {{ $price := mul 0.15 100 }} {{/* Result: 15 */}}{{ $slug := slugify "Hello World!" }} {{/ Result: "hello-world" /}}
{{ $readTime := readTime 1500 }} {{/ Result: 8 /}}
{{ $percentage := percentage 25 200 }} {{/ Result: 12.5 /}}
Membuat Custom Template Actions
// actions/cache.go package actionsimport ( "sync" )
var ( cache = make(map[string]interface{}) mu sync.RWMutex )
// CacheAction caches a value with a key func CacheAction(key string, value interface{}) { mu.Lock() defer mu.Unlock() cache[key] = value }
// GetCachedAction retrieves a cached value func GetCachedAction(key string) (interface{}, bool) { mu.RLock() defer mu.RUnlock() val, ok := cache[key] return val, ok }
// ClearCacheAction clears the cache func ClearCacheAction() { mu.Lock() defer mu.Unlock() cache = make(map[string]interface{}) }
Custom Output Formats
Mendefinisikan Custom Output Format
# hugo.toml [outputs] home = ["HTML", "JSON", "AMP"] page = ["HTML", "JSON"][mediaTypes] [mediaTypes.json] suffix = "json" type = "application/json"
[outputFormats] [outputFormats.json] mediaType = "mediaTypes.json" baseName = "index" isPlainText = true notAlternative = true
Implementasi JSON Output Format
// layouts/_default/index.json {{- $.Scratch.Add "posts" slice -}} {{- range where .Site.RegularPages "Section" "posts" -}} {{- $.Scratch.Add "posts" (dict "title" .Title "slug" .Slug "date" .Date "description" .Description "content" (.Summary | plainify) ) -}} {{- end -}} {{- printf "%s" (jsonify (dict "posts" ($.Scratch.Get "posts"))) -}}Resource Transformation
Custom Resource Processing
// resources/custom.go package resourcesimport ( "image" "image/color" "image/draw" )
// ApplyWatermark applies a watermark to an image func ApplyWatermark(img image.Image, watermark image.Image, opacity float64) image.Image { bounds := img.Bounds() watermark = resizeImage(watermark, bounds.Dx()/4)
offset := image.Pt(bounds.Dx()-watermark.Bounds().Dx()-10, bounds.Dy()-watermark.Bounds().Dy()-10) // Create overlay image with opacity overlay := image.NewRGBA(watermark.Bounds()) for y := 0; y < watermark.Bounds().Dy(); y++ { for x := 0; x < watermark.Bounds().Dx(); x++ { r, g, b, a := watermark.At(x, y).RGBA() overlay.Set(x, y, color.RGBA{ uint8(r), uint8(g), uint8(b), uint8(float64(a) * opacity), }) } } draw.Draw(img.(draw.Image), watermark.Bounds().Add(offset), overlay, image.Point{}, draw.Over) return img}
func resizeImage(img image.Image, width int) image.Image {
// Implement resize logic
return img
}Event Handlers
Membuat Event Handler
// events/handlers.go package eventsimport ( "log"
"github.com/gohugoio/hugo/hugolib")
// OnBuildStart is called when build starts
func OnBuildStart(h *hugolib.HugoSites) error {
log.Println("Build started!")
return nil
}// OnBuildEnd is called when build completes
func OnBuildEnd(h *hugolib.HugoSites) error {
log.Println("Build completed!")
return nil
}// OnPageRender is called when a page is rendered
func OnPageRender(p *hugolib.Page) error {
log.Printf("Page rendered: %s", p.File.Path())
return nil
}Publishing Module ke GitHub
Module Structure untuk Distribution
github.com/username/hugo-awesome/ βββ go.mod βββ README.md βββ LICENSE βββ functions/ β βββ math.go β βββ strings.go βββ hooks/ β βββ custom.go βββ package.ymlREADME.md untuk Module
# Hugo Awesome ModuleCustom template functions and hooks for Hugo.
Installation
hugo mod get github.com/username/hugo-awesome </code></pre> <h2>Usage</h2> <p>Import in config:</p> <pre><code class="language-toml">[module] imports = [ { path = "github.com/username/hugo-awesome" } ] </code></pre> <h2>Available Functions</h2> <h3>slugify</h3> <p>Converts string to URL-friendly slug.</p> <h3>readTime</h3> <p>Calculates estimated reading time.</p> <pre><code> ## Best Practices ### Error Handling ```go func SafeDiv(a, b float64) (float64, error) { if b == 0 { return 0, errors.New("cannot divide by zero") } return a / b, nil } </code></pre> <h3>Documentation</h3> <p>Selalu dokumentasikan functions dan hooks dengan Go doc comments:</p> <pre><code class="language-go">// Mul returns the product of two numbers. // // Example: // // {{ mul 3 4 }} β 12 func Mul(a, b float64) float64 { return a * b } </code></pre> <h3>Testing</h3> <pre><code class="language-go">// functions/math_test.go package functions import ( "testing" ) func TestMul(t *testing.T) { tests := []struct { a, b, expected float64 }{ {2, 3, 6}, {0, 5, 0}, {-2, 3, -6}, } for _, tt := range tests { result := Mul(tt.a, tt.b) if result != tt.expected { t.Errorf("Mul(%f, %f) = %f, want %f", tt.a, tt.b, result, tt.expected) } } } </code></pre> <h2>Kesimpulan</h2> <p>Hugo plugin development membuka kemungkinan untuk extend functionality sesuai kebutuhan spesifik proyek. Dengan memahami Go programming dan Hugo internals, Anda dapat create powerful extensions yang dapat di-reuse across projects.</p> <h2>Artikel Terkait</h2> <ul> <li><a href="/tutorial-hugo-templating-layouts-templates-partial/">Tutorial Hugo Templating: Memahami Layouts, Templates, dan Partial</a></li> <li><a href="/hugo-asset-pipeline/">Hugo Asset Pipeline: Minifikasi dan Bundling Assets</a></li> <li><a href="/hugo-custom-output-formats/">Hugo Custom Output Formats: JSON, AMP, dan Custom Output</a></li> <li><a href="/hugo-debugging-development-tools/">Hugo Debugging dan Development Tools</a></li> </ul>
Ditulis oleh
Hendra Wijaya