Mobile-optimized template with Live Reload

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

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

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

  • it serves all the files associated with the web page
  • it detects file changes within the project and notifies 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 an SSE 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, we can connect from multiple devices 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 which 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 contain the file name and 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.

Grunt-based build system

The project comes with a grunt-based system that takes care of running the server in authoring mode and also generating the dist version, which does not contain the aforementioned JavaScript file for page reloads. grunt based build system, with bower for client side dependencies

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.