Abstract to Go: Lets create a hot reloader
Tags: go, development, tooling, productivity
Published: 2023-11-06 18:01:26

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](https://pkg.go.dev/github.com/fsnotify/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](https://github.com/kdsama/goloader)