Salin dan Bagikan
Hugo Plugin Development: Membuat Custom Functionality - Panduan lengkap membuat plugin dan custom functionality untuk Hugo. Pelajari Go module development, …

Hugo Plugin Development: Membuat Custom Functionality

Hugo Plugin Development: Membuat Custom Functionality

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 functions

import (
    "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 main

import (
    "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 hooks

import (
    "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 actions

import (
    "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 resources

import (
    "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 events

import (
    "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.yml

README.md untuk Module

# Hugo Awesome Module

Custom template functions and hooks for Hugo.

## Installation

```bash
hugo mod get github.com/username/hugo-awesome

Usage

Import in config:

[module]
  imports = [
    { path = "github.com/username/hugo-awesome" }
  ]

Available Functions

slugify

Converts string to URL-friendly slug.

readTime

Calculates estimated reading time.


## 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
}

Documentation

Selalu dokumentasikan functions dan hooks dengan Go doc comments:

// Mul returns the product of two numbers.
// 
// Example:
//
//     {{ mul 3 4 }} → 12
func Mul(a, b float64) float64 {
    return a * b
}

Testing

// 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)
        }
    }
}

Kesimpulan

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.

Artikel Terkait

Link Postingan : https://www.tirinfo.com/hugo-plugin-development/

Hendra WIjaya
Tirinfo
6 minutes.
4 February 2026