reloader

package module
v0.2.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Dec 26, 2024 License: MIT Imports: 7 Imported by: 0

README

Go-Again 🔄

Go-Again is a lightweight live-update solution for Go web applications. It automatically updates your browser content when template files change, making development faster and more efficient. Unlike server hot-reloading tools (like Air) that require restarting the entire Go server, Go-Again only updates the affected content while keeping your server running and preserving client-side state.

Perfect for rapid development of:

  • Template modifications
  • CSS styling changes
  • Content updates
  • Layout adjustments

Features

  • 🔥 Hot reloading of HTML templates, css stylesheets, and static files
  • 🎯 Minimal setup required
  • 🌐 WebSocket-based DOM element replacement
  • 📁 Multiple directory watching
  • 🛠️ Framework agnostic (with growing compatibility list)
  • 🪶 Lightweight with minimal dependencies
  • 💾 State Preservation: Maintains client-side state (form inputs, counters, etc.) during updates

Contents

Getting Started

To install the module, run the following command:

go get github.com/mpmcintyre/go-again

Then import the package in your main routing component:

import reloader "github.com/mpmcintyre/go-again"

Next, initialize the reloader:

rel, err := reloader.New(
    func() { app.LoadHTMLGlob("templates/**/*") },
    9000,
    reloader.WithLogs(true),
)
if err != nil {
    log.Fatal(err)
}
defer rel.Close()

Now that the reloader is set up, you can add directories to watch:

rel.Add("templates/components")
rel.Add("templates/views")

Note: If you are using a tool like air you must ensure that the templates directories are ignored in the .air.toml configuration files.

Then add the LiveReload function to your templates:

app.SetFuncMap(template.FuncMap{
    "LiveReload": rel.TemplateFunc()["LiveReload"],
})

Finally, include the LiveReload tag in your base template:

{{ LiveReload }}

You can also tell the HMR script to ignore client-side state values to preserve the view of a value using the data-client-state tag:

<span data-client-state data-bind="clientCount">0</span>

Complete Example

Checkout the full examles in the example directory

package main

import (
    "html/template"
    "log"
    "net/http"

    "github.com/gin-gonic/gin"
    reloader "github.com/mpmcintyre/go-again"
)

func main() {
    app := gin.Default()

    // Initialize reloader
    rel, err := reloader.New(
        func() { app.LoadHTMLGlob("templates/**/*") },
        9000,
        reloader.WithLogs(true),
    )
    if err != nil {
        log.Fatal(err)
    }
    defer rel.Close()

    // Watch template directories
    rel.Add("templates/components")
    rel.Add("templates/views")
    rel.Add("static")

    // Register LiveReload function
    app.SetFuncMap(template.FuncMap{
        "LiveReload": rel.TemplateFunc()["LiveReload"],
    })

    // Load templates
    app.LoadHTMLGlob("templates/**/*")

    // Routes
    count := 0
    app.GET("/", func(g *gin.Context) {
        count += 1
        g.HTML(http.StatusOK, "index.html", gin.H{
            "title": "Go Again",
            "count": count,
        })
    })

    app.Run(":8080")
}

With your template as:

<!DOCTYPE html>
<html lang="en">
  <head>
    {{LiveReload}}
    <title>{{ .title }}</title>
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <meta charset="UTF-8" />
    <script>
      var clientCount = 0;
      // Function to update all elements that display the counter
      function updateCounterDisplays() {
        document
          .querySelectorAll('[data-bind="clientCount"]')
          .forEach((element) => {
            element.textContent = clientCount;
          });
      }

      // Function to increment counter
      function incrementCounter() {
        console.log("Increment");
        clientCount++;
        updateCounterDisplays();
      }

      // Initialize displays when page loads
      document.addEventListener("DOMContentLoaded", updateCounterDisplays);
    </script>
  </head>
  <body>
    <div>
      <div>Server side count: {{.count}}</div>
      <div>
        Client counter value:
        <span data-client-state data-bind="clientCount">0</span>
      </div>
      <button onclick="incrementCounter()">Increment Counter</button>
    </div>
  </body>
</html>

Framework Compatibility

Framework Compatibility
Gin
Echo
Fiber
Chi
Buffalo
Beego
Gorilla Mux
Iris
Revel
Martini

✅ - Compatible ❓ - Not tested yet ❌ - Not compatible

Configuration Options

WithLogs

Enable or disable logging:

reloader.New(callback, 9000, reloader.WithLogs(true))

Contributing

Contributions are welcome! Feel free to:

  1. Fork the repository
  2. Create a feature branch
  3. Submit a Pull Request

Please ensure you test your changes and update the framework compatibility table if you've verified compatibility with additional frameworks.

License

MIT License - see LICENSE file for details.

Support

If you encounter any issues or have questions, please file an issue on GitHub.

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Reloader

type Reloader struct {
	// contains filtered or unexported fields
}

func New

func New(callback func(), wsPort int, opts ...ReloaderOption) (*Reloader, error)

New creates a new Reloader instance

func (*Reloader) Add

func (r *Reloader) Add(path string) error

Add path to folder or file to be watched

func (*Reloader) Close

func (r *Reloader) Close()

Close ensures all resources are properly cleaned up

func (*Reloader) TemplateFunc

func (r *Reloader) TemplateFunc() template.FuncMap

TemplateFunc returns a template function that injects the reload script

type ReloaderOption

type ReloaderOption func(*Reloader)

ReloaderOption allows for functional options pattern

func WithLogs

func WithLogs(enabled bool) ReloaderOption

WithLogs enables or disables logging

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL