Mobile-optimized template with Live Reload

Live HTML5 is an HTML5 template that features a node.js based authoring server. This authoring server live reloads the page during authoring whenever a file of the project changes. When changes in any of the HTML, the JavaScript files or associated images occur, the page simply reloads. CSS file changes are handled more gracefully: the layout just refreshes, without a reload that affects the page state (and most importantly the scroll position).

Here is how that looks like:

This project came about while trying to learn more about Flexible Box Layout layout, a fairly new feature in CSS3. The author was immensely frustrated about having to reload the page in browsers in order to see every tiny change in the CSS, particularly when involving mobile browsers on Android, iPhone or iPad. And then the reload would of course change the scroll position, making it unnecessarily complicated to notice minor but important layout tweaks.

The live reloading is achieved by using a node.js based server when in authoring mode. This server has two features:

  • Serving all the files associated with the web page
  • Detecting file changes within the project and notifying the page about those changes using a Server Sent Event stream.

Here is how the server and client components of the live refresh feature work together:

Server Side: node.js

On the server side, a node.js application serves static files and at the same time detects file system changes. Whenever such a file change is detected, the server sends the name of the file to the client via a Server Sent Event stream.

This is the node application that makes this possible:

server.js Lines 1-49 server.js
 var chok      = require('chokidar');
var SSE       = require('sse');
var http      = require('http');
var urlModule = require("url");
var path      = require("path");
var fs        = require("fs");
var port      = process.argv[2] || 8888;

var clients = [];
var watcher = chok.watch(process.cwd(), { ignored: /bower_components+/, persistent: true });
watcher.on('change', function (path) {
    clients.forEach(function(client) { client.send(path); }); // send path of changed file
});

/** basic web server, serving static files */
var fileServer = http.createServer(function(request, response) {
    var filename = path.join(process.cwd(), urlModule.parse(request.url).pathname);

    fs.exists(filename, function(exists) {
        if(!exists) {
            response.writeHead(404);
            response.write("not found");
            response.end();
            return;
        }

        if (fs.statSync(filename).isDirectory()) { filename = 'index.html'; }

        fs.readFile(filename, "binary", function(err, file) {
            if(err) {
                response.writeHead(500, {"Content-Type": "text/plain"});
                response.write(err + "\n");
                response.end();
                return;
            }
            response.writeHead(200);
            response.write(file, "binary");
            response.end();
        });
    });
});

/** wraps basic web server in an SSE connection over which file system changes are broadcast */
fileServer.listen(parseInt(port, 10), function () {
    var sse = new SSE(fileServer);
    sse.on('connection', function (client) { // register watcher when connection starts
        clients.push(client);
    });
});

Note that there is a clients array which holds all SSE clients that want to be notified when a file system change happens. That way, multiple devices can connect simultaneously. Upon a change, the callback function, which is passed to the watcher, cycles through the clients array and notifies every single one of them. Further down, in the filerServer.listen block, the file server is wrapped by the SSE module, in which every new connection registers the client that will be called with names of changed files by pushing the client into the clients array.

Client side: simple JavaScript file

All that is needed on the client side for these live reloads is a short script, which should only be present during authoring mode. This short piece of code reloads the page, or, if applicable, triggers a reload of only the changed styles using less.js. Using LESS comes highly recommended for anything CSS related. Less is really simple to use and certainly makes CSS authoring a lot easier. However LESS is not required for the graceful style reloads to work; you can also load a CSS file and have less.js refresh the page (see the comments in index.html).

Let's have a look at the client-side script that enables the page refresh:

reload.js Lines 1-9 reload.js
 (function() {
    var sse = new EventSource("/sse");
    sse.onmessage = function (event) {
        if (event.data.indexOf(".less") !== -1 || event.data.indexOf(".css") !== -1) {
            less.refresh();
        } // refresh LESS, rebuild site CSS
        else { window.location.reload(); }                  // refresh other resources (e.g. markdown, JSON)
    };
}());

The script above first creates an EventSource object that establishes the Server Sent Event stream. Next, a callback function is created, which gets called for each new message on the stream. The payload of the messages contains the file name and the path of a changed file. Depending on the type of the file that has changed, either less.refresh() is called or the page is reloaded. Modify this for the behavior you desire. If you do not want to use less.js at all, you can simply perform window.location.reload() in any case. The live reload / refresh feature can be made to work with any other server technology. You can find a short explanation in the associated blog post.

Grunt-based build system

The project comes with a grunt-based system that takes care of building the dist version of the page. This task generates the ready-for-distribution version of the page with one simple call by cleaning up the HTML, minifying it, generating CSS from LESS, minifying and inlining the CSS and then compressing everything using gzip.

Flexbox based layout & valid HTML5

A flexbox based CSS3 layout was used because it makes the CSS very concise. Please find out more about the reasoning behind this on the related blog post. The sample page of the template is also valid HTML5, as you can check here.

Performance

This template is optimized for load speed, particularly on mobile devices. Accordingly it achieves a Google PageSpeed Insights result of 100/100 for mobile. Note that an optimized version of the page is used, which only differs by not having the social media buttons. These would otherwise delay the rendering of the page. You can look at the optimized page here. The index.html file is also very small, at 7.7KB compressed including the entire CSS it will load fast even on a suboptimal mobile connection. At a GTMetrix test the DOM loaded event occurred after a blazing fast 86ms.