Most solutions to automatic browser refresh for development are silly:
- Browser extensions for automatic reloading every n seconds (like Auto Reload Tab or Tab Reloader) cause lots of unnecessary load in client and server, and can’t react directly to file changes.
- Some browser extensions (like Live Reload) employ some sort of polling against chosen remote files to decide if they should reload the entire tab. This lowers the load somewhat, but it still doesn’t react directly to file changes.
- The browser extension FileWatcher used to be the perfect tool for the job, but it hasn’t worked ever since Mozilla killed proper Firefox extensions, and currently doesn’t appear to run on Linux at all, being developed exclusive on Windows.
- Live.js, which is very popular on StackExchange, is JavaScript code to include in your opened webpage. Like Live Reload, it polls for changes and reloads the site. By default, it loads remotely from
https://livejs.com
(which seems crazy to me when you’re developing locally). You can also host the code locally, but there’s still the same problems as with the browser extensions. Also, Live.js employs a decreasing polling frequency, so if you turn away from developing and come back a while later, your changes may take minutes to show up in the browser. Also, Live.js has frustrated users in local development by complaining:Live.js doesn't support the file protocol. It needs http.
Here, I describe a way to solve all of these problems and refresh your browser whenever any local file changes. It is guaranteed to work with any of the five browsers currently in existence: Firefox, Chrome, Chrome in a hat, Chrome with a moustache and Chrome in Bermuda shorts.
You’ll need:
- Linux with
inotify-tools
installed (should be available in every distribution) - an X server (sorry, Wayland can’t do this) and
xdotool
installed - any web server, which can be running:
- locally on the machine where you’re editing files, like this script using Python 3’s
http.server
module - remotely on another machine, as long as you’re editing the hosted files through a local mount – I’ve successfully tested this with NFS and
sshfs
- locally on the machine where you’re editing files, like this script using Python 3’s
The basic idea is a bash while
loop in which inotifywait
continually waits for one or more files to be locally modified, which it does by hooking itself to Linux kernel calls, requiring no polling whatsoever. When such a change happens, xdotool
is called to find a window with a specific title, activate that window and send it an F5 keypress, and finally activate whichever window was active before. (This may sound like a very roundabout way to send a refresh command to a browser, but I’ve found that Firefox is thoroughly nailed shut and can’t be controlled from the console worth a damn.)
Here’s my script serverefresh.bash
:
#!/bin/bash trap 'kill $PYID; exit' INT # kill the server when script is CTRL+C'd python3 -m http.server & # run the server PYID=$! # record its PID (for killing) # main loop runs once whenever the files # listed here change -- modify as needed while inotifywait -qq -e attrib *.html *.css; do # you might have to replace attrib in the # above line with move_self or some other # event -- see blogpost OWID=$(xdotool getwindowfocus) # record which window is active right now RWID=$(xdotool search --name "$(<serverefresh.title)" | head -1) # the text file serverefresh.title contains # the title of the browser window to be # refreshed -- can be changed on the fly if [ -z "$RWID" ]; then echo "No such window!" # warn if no window with that title found continue fi xdotool windowactivate $RWID # activate (=click) window with title xdotool key F5 # send F5 keypress xdotool windowactivate $OWID # re-activate previously active window done
A couple of notes:
inotifywait
is called with-e attrib
here, but you may need to specify a different event. It appears to depend on the editor you’re using: with KDE’s editors,ATTRIB
is the first change that inotifywait registers when saving a file. With vim, I’ve found it to beMOVE_SELF
. Beware thatinotifywait -qq
may silently fail with return code 1 if something entirely different from the specified event happens to the file first. You can test which events are fired by using a small loop that watches a file you are handling. You run the following command:$ while inotifywait testfile; do true; done
- … watch its output and play around with the file by opening, editing and saving it in your editor. You’ll hopefully see which event is the right one to feed to
inotifywait -e <event>
. Don’t try to be clever and drop-e
completely, or you’ll run into a comical infinite loop, in which theOPEN
event triggers the loop, which refreshes the browser, which causes the web server toOPEN
the file, … you get the picture. - In my script, the files to be watched are hardcoded as
*.html *.css
. You can obviously modify this, but you might also specify them in a file, similar to howserverefresh.title
is handled. Or you might take them from the command line arguments to this script, in which case thewhile
line could look like this:while inotifywait -qq -e <event> $@; do
- When
xdotool windowactivate
activates a window, it is un-minimised and raised above other windows, as if you had clicked it. If this bothers you, try to set other windows to be Always on top, or a similar function of your window manager. - Sometimes, the
xdotool search
apparently returns a bogus window ID that doesn’t work. I’ve managed to circumvent this problem by being as specific as possible with the window title. Also, remember that most browsers display the content of the<title>
tag in the window title of the application, so you might include a unique string there to reliably guidexdotool
to its target.
Happy coding!