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
}
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)
}
}
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)
}
}
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"
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
And that's it. Now go to the working directory of your Go application and run the command.
golo -d= ./ --file=main.go
The source-code can be found here
Top comments (0)