DEV Community

Kshitij (kd)
Kshitij (kd)

Posted on

Abstract to Go: Lets create a hot reloader

Being part of a couple Go communities, there is one question that is asked pretty frequently: What's the best hot reloader for Go? Hot reloading automatically detects changes in code and restarts the application. So there is no need to go to the terminal to build and run the programme again and again.

So this time, instead of finding out the best hot reload app for Go, we will create a simple hot reloader that just does exactly what we want: Restart on each update in any Go file in the project.

Design

To have a working hot reload software, we need an application that is able to

  • Watch files for changes
  • Execute the go program
  • Kill the existing program if there is any change, and start a new one.

So from the command line, we would need to know the target, i.e the file we need to run, as well as which directory it is in. This information is important, as we don't want our software to watch unnecessary folders for updates.
We will be using the exec package to start and restart the application.
To watch the files for changes, I am going to use the fsnotify package.

type Watcher struct {
    directory  string
    command    string
    w          *fsnotify.Watcher
    cmd        *exec.Cmd
    lastUpdate time.Time

}
Enter fullscreen mode Exit fullscreen mode

Start/Restart the Application

Whenever we execute using the start method from exec Package, a processID gets attached, which can be used as a reference when we kill the program and start it again.
This processID corresponds to the command that we are executing on the shell. It won't work if what we are executing itself creates a child process.

So "go run" won't work as it creates a child process. Instead, we will build an executable and run it.

So whenever the method is called, we will just check if an instance is already running. If it is, we will kill that instance.

func (wg *Watcher) startCommand() {
    cmdArgs := strings.Split(wg.command, " ")
// If the instance is running
    if wg.cmd != nil {
// Kill Process
        wg.cmd.Process.Kill()

    }
// build the executable and call it ff
    cmd := exec.Command("go", "build", "-o", "./ff", cmdArgs[0])
    cmd.Dir = wg.directory
    cmd.Stdout = os.Stdout
    cmd.Stderr = os.Stderr
// Run the build command
    cmd.Start()


    wg.cmd = exec.Command("./ff")
    wg.cmd.Dir = wg.directory
    wg.cmd.Stdout = os.Stdout
    wg.cmd.Stderr = os.Stderr
    wg.lastUpdate = time.Now()

// Run the executable
    err := wg.cmd.Start()
    if err != nil {
        fmt.Println("Process Killed", err)
    }

}
Enter fullscreen mode Exit fullscreen mode

Watch Events

We need our application to restart whenever there is a write/edit in any file that has the extension "go" to it.
It will call the startCommand method, which will start/restart our application.

    // Start an event loop to handle events
    for {
        select {
        case event, ok := <-wr.w.Events:
            if !ok {
                return
            }
            if event.Op == fsnotify.Write {

                // if event.Op
                f := strings.Split(event.Name, ".")

                if f[len(f)-1] == "go" {

                    // if time.Since(wr.lastUpdate) > 1*time.Second {
                    wr.startCommand()
                    // }

                }

            }

        case err, ok := <-wr.w.Errors:
            if !ok {
                return
            }
            log.Printf("Error: %s\n", err)
        }
    }
Enter fullscreen mode Exit fullscreen mode

And that would be enough to have a minimal version of hot reload. I am taking the to input parameters : working directory and the filename to be executed.

go run main.go -d=../book_five --file="main.go"
Enter fullscreen mode Exit fullscreen mode

Set the Hot Reload Software

Now we wouldn't want to run all our applications through these projects. Its better if we create a binary of the program and set an alias for it or move it to /usr/local/bin/ from which we can just directly reference our hot reloader. This works for Linux and should work for Mac as well.
Lets name our hot reload executable, golo

go build -o ./golo
sudo mv ./golo /usr/local/bin/golo
Enter fullscreen mode Exit fullscreen mode

And that's it. Now go to the working directory of your Go application and run the command.

golo -d= ./ --file=main.go
Enter fullscreen mode Exit fullscreen mode

The source-code can be found here

Top comments (0)