How do I make a local HTML page auto-refresh on file change?
Asked Answered
C

4

12

I view local HTML files in my default browser via the file:// protocol.

I would like to add some code/script to the HTML file, so that on change of the file (and ideally on change of the sucked-in CSS files) the browser refreshes the page.

I tried including Live.js via

<script type="text/javascript" src="http://livejs.com/live.js"></script>

but it does not seem to have any effect for files accessed via file://. - Any solution known which works here?

PS 1: I found another question relating to this problem, but it does not address the local file case.

PS 2: I know I can reload the page periodically via

<meta http-equiv="refresh" content="1">

but that is not what I need; I need reload on change.

Chaing answered 19/2, 2018 at 2:20 Comment(13)
What happens if you just download live.js and link to your local copy instead of to livejs.com/live.js? I would try that first.Inheritrix
What editor are you using?Aerialist
@Inheritrix Good idea, but doesn't work. (I've just tried it.)Chaing
@Aerialist Emacs. I am certain the file gets written to disk (with the new timestamp). When I issue a manual reload in the browser, the changes come through.Chaing
Live.js doesn't support local files. Check your console output.Hagerman
Brackets io can update a preview on refreshAerialist
@Aerialist Thanks, but I do not want to change the editor - just add a page refresh. An extension for the browser which does the refresh-on-change would be fine too.Chaing
Is this helpful? wikemacs.org/wiki/Markdown#Live_preview_as_you_typeAerialist
Live.js does support local files: "Just include Live.js and it will monitor the current page including local CSS and Javascript by sending consecutive HEAD requests to the server." But it doesn't sound as if you are actually serving the files. There is a plugin for doing so: elpa.gnu.org/packages/web-server.htmlInheritrix
@Aerialist The flymd package is a super-interesting pointer, but it works only with Markdown buffers.Chaing
@Inheritrix I mean local as in from the file:/// protocol. If you are serving them locally on localhost or something it should be fine.Hagerman
live.js might not work with file:/// (haven't tried) but it does work with localhost.Gaiseric
Okay I checked, flymd does nothing special. It either just directly do jQuery.get() (which works on Firefox), or pass --allow-file-access-from-files flag to Chrome.Thorvald
M
5

A More General Solution

Javascript alone does not seem to be able to solve this problem. Until browsers add back in the support they used to have for doing this, I don't think there's a perfectly general solution.

While I think my previous Emacs solution is a good one, for people who use text editors that do not have builtin web servers, here's another answer which is a bit broader.

Use inotifywait

Many OSes can setup a program to execute whenever a file is modified without having to poll. There is no one API for all OSes, but Linux's inotify works better than most and is easy to use.

Here is a shell script which, when run in the directory where your HTML and CSS files are, will tell Firefox to reload whenever changes are saved. You could also call it with specific filenames if you want it to only watch a few files.

#!/bin/bash
# htmlreload
# When an HTML or CSS file changes, reload any visible browser windows.
# Usage:
# 
#     htmlreload [ --browsername ] [ files ... ]
#
# If no files to watch are specified, all files (recursively) in the
# current working directory are monitored. (Note: this can take a long
# time to initially setup if you have a lot of files).
#
# An argument that begins with a dash is the browser to control.
# `htmlreload --chrom` will match both Chromium and Chrome.

set -o errexit
set -o nounset

browser="firefox"      # Default browser name. (Technically "X11 Class")
keystroke="CTRL+F5"    # The key that tells the browser to reload.

sendkey() {
    # Given an application name and a keystroke,
    # type the key in all windows owned by that application.
    xdotool search --all --onlyvisible --class "$1" \
        key --window %@ "$2"
}

# You may specify the browser name after one or more dashes (e.g., --chromium)
if [[ "${1:-}" == -* ]]; then
    browser="${1##*-}"
    shift
fi

# If no filenames given to watch, watch current working directory.
if [[ $# -eq 0 ]]; then
    echo "Watching all files under `pwd`"
    set - --recursive "`pwd`" #Added quotes for whitespace in path
fi

inotifywait --monitor --event CLOSE_WRITE "$@" | while read; do
    #echo "$REPLY"
    sendkey $browser $keystroke
done

Prerequisites: inotifywait and xdotool

You'll need inotifywait and xdotool installed for this to work. On Debian GNU/Linux (and descendants, such as Ubuntu and Mint) you can get those programs using a single command:

sudo apt install inotify-tools xdotool

Optional: Working with Chromium

I suggest using Firefox due to a strangeness in the way Chromium (and Chrome) handle input in windows that do not have focus. If you absolutely must use Chromium, you can use this sendkey() routine instead:

sendkeywithfocus() {
    # Given an application name and a keystroke, give each window
    # focus and type the key in all windows owned by that application.

    # This is apparently needed by chromium, but is annoying because
    # whatever you're typing in your text editor shortly after saving
    # will also go to the chromium window. 

    # Save previous window id so we can restore focus.
    local current_focus="$(xdotool getwindowfocus)"

    # For each visible window, focus it and send the keystroke.
    xdotool search --all --onlyvisible --class "$1" \
        windowfocus \
        key --window %@ "$2"

    # Restore previous focus.
    xdotool windowfocus "$current_focus" 
}

Optional: Working in Wayland

I have not tested it out, but read that Wayland now has a program called ydotool which is a drop in replacement for xdotool.

Mays answered 11/11, 2019 at 10:14 Comment(1)
Chrome on Mac has a better replacement for xdotool using osascript : https://mcmap.net/q/143801/-auto-reload-browser-when-i-save-changes-to-html-file-in-chromeThorvald
M
4

Emacs Impatient Mode

In one of the comments, the questioner mentioned that they use the Emacs text editor. Emacs has a simple solution for live updating HTML (and CSS) as you type: Impatient Mode.

It uses an Emacs web server to serve a page with Javascript that shows live updates on each keystroke.

Installation

If you have setup MELPA, Impatient Mode can be easily installed with

M-x package-install

Alternately, if you prefer to install by hand, see my instructions below.

Using impatient-mode

There are just three steps:

  1. Run once:

     M-x httpd-start
    
  2. Run in any HTML or CSS buffer you're editing:

     M-x impatient-mode
    
  3. Open your browser to http://localhost:8080/imp and click on the name of the buffer.

Now, just type in Emacs and watch the magic happen!

Usage Side Note

I've submitted a patch to the maintainer of Impatient Mode that automatically starts the web server and opens up the proper URL in your browser when you run M-x impatient-mode. Hopefully that will be accepted and you'll only need one step to do everything. I will edit this answer if that occurs.


Optional: Installing by hand

The following is not necessary, but some people would prefer to not add MELPA to their Emacs package repository list. If that is the case, you can install Impatient Mode like so:

cd ~/.emacs.d
mkdir lisp
cd lisp
git clone https://github.com/skeeto/impatient-mode
git clone https://github.com/skeeto/simple-httpd
git clone https://github.com/hniksic/emacs-htmlize/

Now edit your .emacs file so it adds subdirs of ~/.emacs.d/lisp/ to the load-path:

;; Add subdirectories of my lisp directory. 
(let ((default-directory  "~/.emacs.d/lisp"))
     (normal-top-level-add-subdirs-to-load-path))
(autoload 'impatient-mode "~/.emacs.d/lisp/impatient-mode/impatient-mode" "\
Serves the buffer live over HTTP.

\(fn &optional ARG)" t nil)

That should be enough for Impatient Mode to work, but if you'd like it to be slightly faster, you can byte-compile the emacs lisp files.

Mays answered 7/11, 2019 at 9:41 Comment(1)
My question was meant a bit broader, but that's a super-cool solution when working with Emacs, thanks. :-)Chaing
H
2

Browsers restrict access to the file:/// protocol for security reasons. In Firefox, even extensions no longer have access to local files, so you will most likely have to serve the files locally in order to use a live reload script. If you do that you could just use Live.js, but something like this might be slightly simpler to set up. (Requires Node.js)

Hagerman answered 19/2, 2018 at 3:58 Comment(4)
Makes sense - however the flymd package Jacob mentioned, seem to be able to do so. I just briefly played with it: It has an HTML master file which reads and renders a Markdown file via Javascript. It indeed seem to pick up life changes from the local Markdown file and the master file is opened in the browser as file:///path/to/flymd.html.Chaing
Does that do what you want? That seems to only deal with rendering Markdown and not full HTML/CSS.Hagerman
No, it doesn't do what I want (because the source is a Markdown file, not HTML). But it shows to me that the browser (via javascript) somehow can look down to the file system...Chaing
If you look at the browser compatibility they note that Chrome doesn't allow file access (with a workaround). While it says Firefox is fine, that was last updated in 2016, so may not be accurate for current browsers.Hagerman
C
2

The lightweight web browser Gnome-Web (aka epiphany), automatically updates when a local HTML file is modified.

# Either containerized for security:
sudo snap install epiphany
# or traditional, lightweight install directly into your host:
sudo apt install epiphany

# Then launch the browser on your html using file:// protocol
epiphany my-guide.html

...and any modifications to 'my-guide.html' will be displayed automatically.

Another alternative on Linux is 'falkon', which I guess is the KDE equivalent lightweight browser.

Croak answered 16/1, 2023 at 21:41 Comment(1)
That's interesting! Unfortunately I'm on macOS where -- I guess -- epiphany doesn't run.Chaing

© 2022 - 2024 — McMap. All rights reserved.