<!DOCTYPE html><html lang="en-us"><head><meta charset="utf-8"><title>live-html5</title><meta name="HandheldFriendly" content="True"><meta name="viewport" content="width=device-width, minimum-scale=1, maximum-scale=2, initial-scale=1"><meta name="apple-mobile-web-app-capable" content="yes"><style>@font-face{font-family:FontAwesome;src:url(//netdna.bootstrapcdn.com/font-awesome/4.0.1/fonts/fontawesome-webfont.eot?v=4.0.1);src:url(//netdna.bootstrapcdn.com/font-awesome/4.0.1/fonts/fontawesome-webfont.eot?#iefix&v=4.0.1) format('embedded-opentype'),url(//netdna.bootstrapcdn.com/font-awesome/4.0.1/fonts/fontawesome-webfont.woff?v=4.0.1) format('woff'),url(//netdna.bootstrapcdn.com/font-awesome/4.0.1/fonts/fontawesome-webfont.ttf?v=4.0.1) format('truetype'),url(//netdna.bootstrapcdn.com/font-awesome/4.0.1/fonts/fontawesome-webfont.svg?v=4.0.1#fontawesomeregular) format('svg');font-weight:400;font-style:normal}.fa{display:inline-block;font-family:FontAwesome;font-style:normal;font-weight:400;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.fa-lg{font-size:1.3333333333333333em;line-height:.75em;vertical-align:-15%}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-html5:before{content:"\f13b"}article,aside,details,figcaption,figure,footer,header,hgroup,main,nav,section,summary{display:block}audio,canvas,video{display:inline-block}audio:not([controls]){display:none;height:0}[hidden],template{display:none}html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}a{background:0 0}a:focus{outline:thin dotted}a:active,a:hover{outline:0}h1{font-size:2em;margin:.67em 0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}dfn{font-style:italic}hr{-moz-box-sizing:content-box;box-sizing:content-box;height:0}mark{background:#ff0;color:#000}code,kbd,pre,samp{font-family:monospace,serif;font-size:1em}pre{white-space:pre-wrap}q{quotes:"\201C" "\201D" "\2018" "\2019"}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:0}fieldset{border:1px solid silver;margin:0 2px;padding:.35em .625em .75em}legend{border:0;padding:0}button,input,select,textarea{font-family:inherit;font-size:100%;margin:0}button,input{line-height:normal}button,select{text-transform:none}button,html input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0}input[type=search]{-webkit-appearance:textfield;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}textarea{overflow:auto;vertical-align:top}table{border-collapse:collapse;border-spacing:0}@media print{*{background:transparent!important;color:#000!important;box-shadow:none!important;text-shadow:none!important}a,a:visited{text-decoration:underline}a[href]:after{content:" (" attr(href) ")"}abbr[title]:after{content:" (" attr(title) ")"}.ir a:after,a[href^="javascript:"]:after,a[href^="#"]:after{content:""}blockquote,pre{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}img,tr{page-break-inside:avoid}img{max-width:100%!important}h2,h3,p{orphans:3;widows:3}h2,h3{page-break-after:avoid}}.flex-display{display:-webkit-box;display:-moz-box;display:-ms-flexbox;display:-webkit-flex;display:flex}.flex-1{-webkit-box-flex:1 auto;-moz-box-flex:1 auto;-webkit-flex:1 auto;-ms-flex:1 auto;flex:1 auto}.flex-order-1{-webkit-box-ordinal-group:1;-moz-box-ordinal-group:1;-ms-flex-order:1;-webkit-order:1;order:1}.flex-order-2{-webkit-box-ordinal-group:2;-moz-box-ordinal-group:2;-ms-flex-order:2;-webkit-order:2;order:2}.flex-align-center{-webkit-align-items:center;-webkit-box-align:center;-moz-box-align:center;-ms-flex-align:center;align-items:center}.flex-direction-col{flex-direction:column;-webkit-flex-direction:column;-ms-flex-direction:column;-webkit-box-orient:vertical;-moz-box-orient:vertical}.flex-direction-row{flex-direction:row;-webkit-flex-direction:row;-ms-flex-direction:row;-webkit-box-orient:horizontal;-moz-box-orient:horizontal}.justify-center{-webkit-justify-content:center;-ms-flex-pack:center;-webkit-box-pack:center;-moz-box-pack:center;justify-content:center}.justify-space-between{-webkit-justify-content:space-between;-ms-flex-pack:space-between;-webkit-box-pack:justify;-moz-box-pack:justify;justify-content:space-between}.justify-space-around{justify-content:space-around}.header{display:-webkit-box;display:-moz-box;display:-ms-flexbox;display:-webkit-flex;display:flex;-webkit-align-items:center;-webkit-box-align:center;-moz-box-align:center;-ms-flex-align:center;align-items:center;flex-direction:column;-webkit-flex-direction:column;-ms-flex-direction:column;-webkit-box-orient:vertical;-moz-box-orient:vertical;padding:15px 0 10px}.header strong{color:#AAA}.site-logo{color:#f09b00;font-weight:700;font-size:24px;line-height:32px;text-decoration:none;text-transform:uppercase}.site-nav{width:100%;display:-webkit-box;display:-moz-box;display:-ms-flexbox;display:-webkit-flex;display:flex;-webkit-justify-content:center;-ms-flex-pack:center;-webkit-box-pack:center;-moz-box-pack:center;justify-content:center;flex-direction:row;-webkit-flex-direction:row;-ms-flex-direction:row;-webkit-box-orient:horizontal;-moz-box-orient:horizontal;margin-top:5px}.site-nav .links{text-transform:uppercase;display:-webkit-box;display:-moz-box;display:-ms-flexbox;display:-webkit-flex;display:flex;-webkit-justify-content:space-between;-ms-flex-pack:space-between;-webkit-box-pack:justify;-moz-box-pack:justify;justify-content:space-between;max-width:300px;width:90%}.site-nav .site-nav a:active,.site-nav .site-nav a:focus,.site-nav a:hover{color:#fff}.site-promo{text-align:center;width:100%;color:#ddd;padding:15px}.site-promo h1{margin:10px 15px 0}.site-promo .description{font-size:1.25em;margin:20px 15px}.site-promo .banner{background-color:#444;padding:10px 0 10px 25px;display:-webkit-box;display:-moz-box;display:-ms-flexbox;display:-webkit-flex;display:flex;-webkit-justify-content:center;-ms-flex-pack:center;-webkit-box-pack:center;-moz-box-pack:center;justify-content:center}.site-promo .banner .sharing{display:-webkit-box;display:-moz-box;display:-ms-flexbox;display:-webkit-flex;display:flex;max-width:320px;width:auto}p,ul li{line-height:1.42}.content{display:-webkit-box;display:-moz-box;display:-ms-flexbox;display:-webkit-flex;display:flex;-webkit-justify-content:center;-ms-flex-pack:center;-webkit-box-pack:center;-moz-box-pack:center;justify-content:center;padding:15px;background:#fff}.page-wrap{display:-webkit-box;display:-moz-box;display:-ms-flexbox;display:-webkit-flex;display:flex;width:100%;max-width:1140px;flex-direction:column;-webkit-flex-direction:column;-ms-flex-direction:column;-webkit-box-orient:vertical;-moz-box-orient:vertical}.main-content{-webkit-box-ordinal-group:1;-moz-box-ordinal-group:1;-ms-flex-order:1;-webkit-order:1;order:1}.main-sidebar{-webkit-box-flex:1 auto;-moz-box-flex:1 auto;-webkit-flex:1 auto;-ms-flex:1 auto;flex:1 auto;width:300px;-webkit-box-ordinal-group:2;-moz-box-ordinal-group:2;-ms-flex-order:2;-webkit-order:2;order:2;padding-left:0}@media (min-width:900px){.content{padding:25px}.page-wrap{flex-direction:row;-webkit-flex-direction:row;-ms-flex-direction:row;-webkit-box-orient:horizontal;-moz-box-orient:horizontal}.main-content{width:75%}.main-sidebar{width:25%;padding-left:25px}}pre .clojure .built_in,pre .http .title,pre .keyword,pre .list .title,pre .method,pre .nginx .title,pre .request,pre .setting .value,pre .status,pre .tag .title,pre .tex .command,pre .winutils{color:#00f}pre .envvar,pre .tex .special{color:#660}pre .clojure .attribute,pre .css .function,pre .css .value .number,pre .decorator,pre .hexcolor,pre .ini .title,pre .literal,pre .number,pre .pi,pre .prompt,pre .ruby .symbol .string,pre .rules .value,pre .shebang,pre .sub .identifier,pre .symbol,pre .tag,pre .tag .keyword{color:#099}pre .css .tag,pre .pseudo,pre .rules .property,pre .subst{color:#000}pre .value .important{color:#f70;font-weight:700}pre .annotation,pre .apache .sqbracket,pre .nginx .built_in{color:#FF859D}pre .tex .formula{background-color:#EEE;font-style:italic}pre .chunk,pre .diff .header{color:gray;font-weight:700}.snippet{overflow-x:auto;font-size:11px;position:relative;margin-top:20px;background:#fff;border:1px solid #ccc;line-height:14px}.snippet pre{word-wrap:normal;white-space:pre;word-break:normal;position:relative;border-width:0;overflow-x:auto;z-index:10;padding:0 10px}.snippet pre code{white-space:pre}.snippet pre .comment,.snippet pre .javadoc,.snippet pre .template_comment{color:#0a0}.snippet pre .preprocessor{color:#444}.snippet pre .css .class,.snippet pre pre .css .id{color:#9B703F}.snippet pre .rules .keyword{color:#C5AF75}.snippet pre .diff .change{background-color:#BCCFF9}.snippet pre .addition{background-color:#BAEEBA}.snippet pre .deletion{background-color:#FFC8BD}.snippet pre .comment .yardoctag{font-weight:700}.snippet pre .attr_selector,.snippet pre .cdata,.snippet pre .date,.snippet pre .filter .argument,.snippet pre .regexp,.snippet pre .string,.snippet pre .tag .value{color:#f30}.snippet pre .built_in,.snippet pre .class .id,.snippet pre .class .title,.snippet pre .clojure .title,.snippet pre .doctype,.snippet pre .javadoctag,.snippet pre .params,.snippet pre .setting,.snippet pre .tag .attribute,.snippet pre .typename,.snippet pre .variable{color:#a05}.snippet .code-container{height:auto;position:relative}.snippet .code-header{background-color:#888;color:#fff;font-family:Monaco,Menlo,Consolas,"Courier New",monospace;padding:4px 4px 4px 10px}.snippet .code-header a{font-weight:700;float:right;color:#67d9ff}.snippet .code-header a:hover{color:#0087b4}.snippet .code-header .lines{float:right;padding-left:20px}body{font-weight:300;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;padding-bottom:15px;text-rendering:optimizeLegibility;font-size:14px;background-color:#222;color:#222}a{font-weight:600;text-decoration:none;color:#009acd}a:focus,a:hover{text-decoration:none}h1,h2,h3{margin-top:1em;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif}h1 a,h2 a,h3 a{color:#222;font-weight:600}blockquote{font-style:italic}blockquote p{font-size:14px}img{max-width:100%}::-moz-selection{background:#b3d4fc;text-shadow:none}::selection{background:#b3d4fc;text-shadow:none}img{vertical-align:middle}textarea{resize:vertical}.footer-container{color:#eee;text-align:center}.gh-button{border:0}</style><link rel="icon" href="favicon.ico" type="image/x-icon"></head><body data-ng-app="ngBlog"><header class="header" role="banner"><div class="site-logo">Live <i class="fa fa-html5"></i> HTML5</div><div class="site-nav"><div class="links"><a href="https://github.com/matthiasn/live-html5" target="_blank" data-ga-category="Outbound links" data-ga-action="Nav click" data-ga-label="GitHub">GitHub</a> <a href="https://github.com/matthiasn/live-html5/archive/master.zip" target="_blank" data-ga-category="Outbound links" data-ga-action="Nav click" data-ga-label="Download"> Download </a> <a href="http://matthiasnehlsen.com" target="_blank" data-ga-category="Outbound links" data-ga-action="Nav click" data-ga-label="Blog">Blog</a></div></div><div class="site-promo"><h1>This page reloads automatically during authoring.</h1><p class="description">Better yet, <strong>CSS</strong> changes happen instantly, without reload. Works on <strong>mobile</strong>, too.</p></div></header><div class="content" data-ng-controller="blogCtrl"><div class="page-wrap"><section class="main-content" role="main"><article class="ng-scope ng-isolate-scope" data-markdown="" data-md="markdown.data" data-config="config.data"><h1 id="mobileoptimizedtemplatewithlivereload" class="ng-scope">Mobile-optimized template with Live Reload</h1><p class="ng-scope"><a href="https://github.com/matthiasn/live-html5" target="_blank" data-ga-category="Outbound links" data-ga-action="Nav click" data-ga-label="GitHub">Live <i class="fa fa-html5"></i> HTML5</a> 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.</p><p class="ng-scope">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.</p><p class="ng-scope">The live reloading is achieved by using a node.js based server in authoring mode. This server has two features:</p><ul class="ng-scope"><li>it serves all the files associated with the web page</li><li>it detects file changes within the project and notifies the page about those changes using a Server Sent Event stream.</li></ul><p class="ng-scope">Here is how the server and client components of the live refresh feature work together:</p><h3 id="serversidenodejs" class="ng-scope">Server Side: node.js</h3><p class="ng-scope">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.</p><p class="ng-scope">This is the node application that makes this possible:</p><p class="ng-scope"><span snippet="config.snippets.server_js" class="ng-isolate-scope"><div class="snippet"><div class="code-header ng-binding">server.js <span class="lines ng-binding">Lines 1-49</span> <a ng-href="https://github.com/matthiasn/live-html5/blob/e1622b7451755c535b4c0c43886716d9ebb139aa/src/js/reload.js" target="_blank" class="ng-binding" href="https://github.com/matthiasn/live-html5/blob/e1622b7451755c535b4c0c43886716d9ebb139aa/src/js/reload.js">server.js</a></div><div class="code-container"><pre><code class="language-javascript"> <span data-hljs="" data-code="codeRange" class="ng-isolate-scope"><span class="keyword">var</span> chok = require(<span class="string">'chokidar'</span>); <span class="keyword">var</span> SSE = require(<span class="string">'sse'</span>); <span class="keyword">var</span> http = require(<span class="string">'http'</span>); <span class="keyword">var</span> urlModule = require(<span class="string">"url"</span>); <span class="keyword">var</span> path = require(<span class="string">"path"</span>); <span class="keyword">var</span> fs = require(<span class="string">"fs"</span>); <span class="keyword">var</span> port = process.argv[<span class="number">2</span>] || <span class="number">8888</span>; <span class="keyword">var</span> clients = []; <span class="keyword">var</span> watcher = chok.watch(process.cwd(), { ignored: /bower_components+/, persistent: <span class="keyword">true</span> }); watcher.on(<span class="string">'change'</span>, function (path) { clients.forEach(function(client) { client.send(path); }); <span class="comment">// send path of changed file</span> }); <span class="javadoc">/** basic web server, serving static files */</span> <span class="keyword">var</span> fileServer = http.createServer(function(request, response) { <span class="keyword">var</span> filename = path.join(process.cwd(), urlModule.parse(request.url).pathname); fs.exists(filename, function(exists) { <span class="keyword">if</span>(!exists) { response.writeHead(<span class="number">404</span>); response.write(<span class="string">"not found"</span>); response.end(); <span class="keyword">return</span>; } <span class="keyword">if</span> (fs.statSync(filename).isDirectory()) { filename = <span class="string">'index.html'</span>; } fs.readFile(filename, <span class="string">"binary"</span>, function(err, file) { <span class="keyword">if</span>(err) { response.writeHead(<span class="number">500</span>, {<span class="string">"Content-Type"</span>: <span class="string">"text/plain"</span>}); response.write(err + <span class="string">"\n"</span>); response.end(); <span class="keyword">return</span>; } response.writeHead(<span class="number">200</span>); response.write(file, <span class="string">"binary"</span>); response.end(); }); }); }); <span class="javadoc">/** wraps basic web server in an SSE connection over which file system changes are broadcast */</span> fileServer.listen(parseInt(port, <span class="number">10</span>), function () { <span class="keyword">var</span> sse = <span class="keyword">new</span> SSE(fileServer); sse.on(<span class="string">'connection'</span>, function (client) { <span class="comment">// register watcher when connection starts</span> clients.push(client); }); });</span></code></pre></div></div></span></p><p class="ng-scope">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.</p><h3 id="clientsidesimplejavascriptfile" class="ng-scope">Client side: simple JavaScript file</h3><p class="ng-scope">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 <strong><a href="http://lesscss.org">less.js</a></strong>. 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).</p><p class="ng-scope">Let's have a look at the client-side script that enables the page refresh:</p><p class="ng-scope"><span snippet="config.snippets.reload_js" class="ng-isolate-scope"><div class="snippet"><div class="code-header ng-binding">reload.js <span class="lines ng-binding">Lines 1-9</span> <a ng-href="https://github.com/matthiasn/live-html5/blob/e1622b7451755c535b4c0c43886716d9ebb139aa/src/js/reload.js" target="_blank" class="ng-binding" href="https://github.com/matthiasn/live-html5/blob/e1622b7451755c535b4c0c43886716d9ebb139aa/src/js/reload.js">reload.js</a></div><div class="code-container"><pre><code class="language-javascript"> <span data-hljs="" data-code="codeRange" class="ng-isolate-scope">(<span class="keyword">function</span>() { <span class="keyword">var</span> sse = <span class="keyword">new</span> EventSource(<span class="string">"/sse"</span>); sse.onmessage = <span class="function"><span class="keyword">function</span> <span class="params">(event)</span> {</span> <span class="keyword">if</span> (event.data.indexOf(<span class="string">".less"</span>) !== -<span class="number">1</span> || event.data.indexOf(<span class="string">".css"</span>) !== -<span class="number">1</span>) { less.refresh(); } <span class="comment">// refresh LESS, rebuild site CSS</span> <span class="keyword">else</span> { window.location.reload(); } <span class="comment">// refresh other resources (e.g. markdown, JSON)</span> }; }());</span></code></pre></div></div></span></p><p class="ng-scope">The script above first creates an <strong><a href="http://www.w3.org/TR/2009/WD-eventsource-20090423/">EventSource</a></strong> 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.</p><h2 id="gruntbasedbuildsystem" class="ng-scope">Grunt-based build system</h2><p class="ng-scope">The project comes with a grunt-based system that takes care of running the server in authoring mode and also generating the <strong>dist</strong> version, which does not contain the aforementioned JavaScript file for page reloads. <strong><a href="http://gruntjs.com/">grunt</a></strong> based build system, with <strong><a href="http://bower.io/">bower</a></strong> for client side dependencies</p><h2 id="flexboxbasedlayoutvalidhtml5" class="ng-scope">Flexbox based layout & valid HTML5</h2><p class="ng-scope">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 <strong><a href="http://blog/2013/11/23/live-page-reload-on-any-device/">blog post</a></strong>. The sample page of the template is also valid HTML5, as you can check <strong><a href="http://validator.w3.org/check?uri=http%3A%2F%2Fmatthiasn.github.io%2Flive-html5%2F&charset=%28detect+automatically%29&doctype=Inline&group=0">here</a></strong>.</p><h2 id="performance" class="ng-scope">Performance</h2><p class="ng-scope">This template is optimized for load speed, particularly on mobile devices. Accordingly it achieves a Google PageSpeed Insights result of <strong><a href="https://developers.google.com/speed/pagespeed/insights/?url=http%3A%2F%2Fmatthiasn.github.io%2Flive-html5%2Foptimal.html">100/100 for mobile</a></strong>. 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 <strong><a href="http://matthiasn.github.io/live-html/optimal.html">here</a></strong>.</p></article></section><aside class="main-sidebar" role="complementary"><img src="//www.w3.org/html/logo/downloads/HTML5_Logo_256.png" alt="HTML5 Logo"><h3>About</h3><p>Hello everyone. My name is HTML5 and I am awesome. So is my best friend, CSS3..</p></aside></div></div><div class="footer-container"><footer class="wrapper"><p>Copyright © 2013 <a href="matthiasnehlsen.com" target="_blank">Matthias Nehlsen</a></p></footer></div></body></html>