Lewati ke konten
Kembali ke Blog

Hugo Plugin Development: Membuat Custom Functionality

Β· Β· 7 menit baca

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"

&quot;github.com/gohugoio/hugo/markup/asciidoctor&quot;
&quot;github.com/gohugoio/hugo/markup/markup&quot;

)

// 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 &lt; watermark.Bounds().Dy(); y++ {
    for x := 0; x &lt; 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"

&quot;github.com/gohugoio/hugo/hugolib&quot;

)

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


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 = &quot;github.com/username/hugo-awesome&quot; }
  ]
</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(&quot;cannot divide by zero&quot;)
    }
    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 (
    &quot;testing&quot;
)

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(&quot;Mul(%f, %f) = %f, want %f&quot;, 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

Tinggalkan Komentar

Email tidak akan ditampilkan.