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/