Handlebars Templates with Browserify

This post documents how to use the hbsfy precompiler plugin for Browserify to compile Handlebars templates into javascript. With this design, templates can be required as javascript modules in the views that use them.

Requirements

Global Packages

List the global packages that are installed with the npm list –global command. Then install these if they are not already installed or need to be updated.

Install Browserify

After installing Node.js, using a Command shell, install Browserify globally. *

# global browserify install
npm install -g browserify

* If you have an older version of global Browserify, run npm rm –global browserify to remove the old version before installing the newer one.

Install Gulp

Install gulp globally with the npm install -g command as follows: *

# global gulp install
npm install -g gulp

* If you have an older version of global Gulp, run npm rm –global gulp to remove the old version before installing the newer one.

Project Packages

Create package.json

The package.json file contains project metadata and lists dependencies that are available on npm.

# create package.json
npm init

Install Packages

These are the project node module packages needed to browserify and bundle the Handlebars template modules. Since all of the modules are bundled into a single minified javascript file, source maps are included in the bundle task to make it easier to debug in the browser’s developer tools. For convenience, a local web server module has been included.

$ npm install browserify --save-dev
$ npm install handlebars --save-dev
$ npm install hbsfy --save-dev
$ npm install gulp --save-dev
$ npm install gulp-sourcemaps --save-dev
$ npm install gulp-uglify --save-dev
$ npm install gulp-util --save-dev
$ npm install gulp-webserver --save-dev
$ npm install vinyl-source-stream --save-dev
$ npm install vinyl-buffer --save-dev

Create a gulpfile.js in the project root to define and configure the Gulp tasks. The default task is simply gulp. When gulp is executed, it will run the browserify bundle task, start a local web server and open the default web page in a browser.

gulpfile.js
'use strict';

var gulp        = require('gulp'),
    browserify  = require('browserify'),
    buffer      = require('vinyl-buffer'),
    gutil       = require('gulp-util'),
    source      = require('vinyl-source-stream'),
    sourcemaps  = require('gulp-sourcemaps'),
    uglify      = require('gulp-uglify'),
    webserver   = require('gulp-webserver');

var hbsfy = require('hbsfy');

gulp.task('bundle', function () {
  var b = browserify({
    entries: './src/js/index.js',
    debug: true
  });

  return b.transform(hbsfy).bundle()
    .pipe(source('bundle.js'))
    .pipe(buffer())
    .pipe(sourcemaps.init({loadMaps: true}))
        .pipe(uglify())
        .on('error', gutil.log)
    .pipe(sourcemaps.write('./'))
    .pipe(gulp.dest('./js/'));
});

// static web server
gulp.task('server', function() {
    gulp.src('./')
        .pipe(webserver({
            port: 3000,
            directoryListing: false,
            open: true
        }));
});

gulp.task('default', ['bundle', 'server']);

Source

Create a Handlebars template in /src/hbs/

people.hbs
{{#each people}}
<li>{{firstName}} {{lastName}}</li>
{{/each}}

Create a data module in /src/js/modules/ for this template demo since we are not calling a rest API to get data.

data.js
module.exports = {
    people: [
        {firstName: "Yehuda", lastName: "Katz"},
        {firstName: "Carl", lastName: "Lerche"},
        {firstName: "Alan", lastName: "Johnson"}
    ]
}

In the /src/js folder, create the main node, index.js.

index.js
var data = require('./modules/data');
var list = require('../hbs/people.hbs');

document.querySelector('#people').innerHTML = list(data);

Web Page

Create this html file to load the javascript and render the template output.

index.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Handlebars with Browserify</title>
</head>
<body>
    <ul id="people"></ul>
    <script src="js/bundle.js"></script>
</body>
</html>

Project Folders and Files

Here is a tree view of the projects folders and files. Browse, clone or download the source code if you prefer.

    • js
    • src
      • hbs
        • people.hbs
      • js
        • modules
          • data.js
        • index.js
    • gulpfile.js
    • package.json
    • index.html

The next page covers custom Handlebars helper registration


Google Maps API with Browserify

This post documents how to use local JavaScript modules with Browserify and the Google Maps JavaScript API. In this example, the Google Map contains a marker that can be dragged to reset the browser form with the marker position location data. Additionally, Browserify shim is used to require jQuery since it is already being loaded from a CDN.

Requirements

Install Browserify

After installing Node.js, using a Command shell, install Browserify globally. *

# global browserify install
npm install -g browserify

* If you have an older version of global Browserify, run npm rm –global browserify to remove the old version before installing the newer one.

Project Folders and Files

Create these folders and files. Browse, clone or download the source code if you prefer.

    • css
      • style.css
    • js
    • src
      • js
        • modules
          • gapi.js
          • gmap.js
          • jsonp.js
          • location.js
        • index.js
    • gulpfile.js
    • index.html

Web Page

This web page contains the map container, address and geocode location inputs. jQuery is being loaded from the Google Hosted Libraries CDN.

index.html
<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="utf-8">
	<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
	<title>Page Title</title>
	<link rel="stylesheet" href="/css/style.css">
</head>
<body>
	
	<p>Drag the marker to a location on the map and set input values.</p>
	
	<div id="map-canvas"></div>
	<label>Street
		<input type="text" id="street" name="street" />		
	</label>
	<label>Locality
		<input type="text" id="locality" name="locality" />
	</label>
	<label>Region
		<input type="text" id="region" name="region" />
	</label>
	<label>Country
		<input type="text" id="country" name="country" />
	</label>
	<label>Latitude
		<input type="text" id="latitude" name="latitude" />
	</label>
	<label>Longitude
		<input type="text" id="longitude" name="longitude" />
	</label>
	
	<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js"></script>

        <!-- Prod / built version -->
	<!--script src="/js/bundle.js"></script-->

</body>
</html>

Application Entry Point

The index module is the starting point for the map application. The module dependency requirements are defined followed by a call to the load function exported by the gapi module. gapi.load uses a callback function before the location.init.

index.js
var $ = require('jQuery');
var gapi = require('./modules/gapi');
var location = require('./modules/location');

gapi.load( function () {
    location.init();
});

Google Map JavaScript API

Loading the Google Maps JavaScript API requires a global callback. For this I have created a jsonp module which accepts three parameters, a URL (url), a global callback function name (callbackname), and a callback function (done).

jsonp.js
module.exports = function (url, callbackname, done) {
    var script = document.createElement('script');
    script.type = 'text/javascript';
    script.src = url;
    var s = document.getElementsByTagName('script')[0];
    s.parentNode.insertBefore(script, s);

    window[callbackname] = done;
};

The gapi module makes use of the jsonp module to load the API:

gapi.js
var jsonp = require('./jsonp');

var gapiurl = 'http://maps.googleapis.com/maps/api/js?callback=__googleMapsApiOnLoadCallback';

exports.load = function (done) {
    jsonp(gapiurl, '__googleMapsApiOnLoadCallback', done);
};

Location Module

The location module initiates the google map using the gmap.init function exported by the gmap module. The gmap.init callback results data is handled by the location module to populate the address inputs.

location.js
var gmap = require('./gmap');

var latitude = 39.084014922903;
var longitude = -77.51372591791;

exports.init = function () {
    gmap.init(
        latitude,
        longitude,
        function(result){
            console.log('RESULT',result);

            if (result.address) {
                _setAddress(result.address);
            }

            $('#latitude').val(result.latitude);
            $('#longitude').val(result.longitude);
        }
    );
};

_setAddress = function(address) {
    var street = [];
    if (address.street_number) {
        street.push(address.street_number);
    }
    if (address.route) {
        street.push(address.route);
    }
    if (street.length > 0) {
        $('#street').val(street.join(' '));
    }
    if (address.locality) {
        $('#locality').val(address.locality);
    }
    if (address.administrative_area_level_1) {
        $('#region').val(address.administrative_area_level_1);
    }
    if (address.country) {
        $('#country').val(address.country);
    }
};

Google Map

The gmap module uses the Google Maps JavaScript API.

gmap.js
exports.init = function (lat, lng, callback) {
    var geocoder = new google.maps.Geocoder();
    var marker = new google.maps.Marker({
        position: new google.maps.LatLng(lat, lng),
        anchorPoint: new google.maps.Point(0, -29),
        draggable: true
    });
    map = new google.maps.Map(document.getElementById('map-canvas'), {zoom: 10});
    map.setCenter(marker.position);
    marker.setMap(map);

    google.maps.event.addListener(marker, 'dragend', function (evt) {

        var latlng = {lat: evt.latLng.lat(), lng: evt.latLng.lng()};
        var addrComponents = {
            street_number: 'short_name',
            route: 'long_name',
            establishment: 'long_name',
            locality: 'long_name',
            administrative_area_level_1: 'short_name',
            country: 'short_name',
            postal_code: 'short_name'
        };
        result = {
            address: {},
            latitude: 0,
            longitude: 0
        };

        geocoder.geocode({'location': latlng}, function(results, status) {
            if (status === google.maps.GeocoderStatus.OK) {
                if (results[0]) {

                    var i; var type = null;
                    for (i = 0; i < results[0].address_components.length; i++) {
                        type = results[0].address_components[i].types[0];
                        if (addrComponents[type]) {
                            result.address[type] = results[0].address_components[i][addrComponents[type]];
                        }
                    }
                    result.latitude = latlng.lat;
                    result.longitude = latlng.lng;
                    if(typeof callback == "function"){
                        callback(result);
                    }

                } else {
                    window.alert('No results found');
                }
            } else {
                window.alert('Geocoder failed due to: ' + status);
            }
        });
    });
}
style.css

Add this file into the css folder to declare some basic styles.

label {
	display: block;
	width: 300px;
	margin-bottom: 1em;
}
input[type='text'] {
	display: block;
	width: 100%;
}
#map-canvas {
	width: 300px;
	height: 300px;
	margin-bottom: 1em;
}

Build


Use the Gulp task and build runner to configure and run the build process. Install Gulp and the Node modules locally that are needed to properly bundle, minify and sourcemap the Browserify modules.

Install Gulp

Install gulp globally with the npm install -g command as follows: *

# global gulp install
npm install -g gulp

* If you have an older version of global Gulp, run npm rm –global gulp to remove the old version before installing the newer one.

Create package.json

The package.json file contains project metadata and lists dependencies that are available on npm.

# create package.json
npm init

Install node modules

# install dependencies and save to package.json
npm install browserify --save-dev
npm install browserify-shim --save-dev
npm install gulp --save-dev
npm install gulp-sourcemaps --save-dev
npm install gulp-uglify --save-dev
npm install gulp-util --save-dev
npm install gulp-webserver --save-dev
npm install vinyl-buffer --save-dev
npm install vinyl-source-stream --save-dev

Browserify Shim

Browserify shim is used so global jQuery can be required by modules. Near the bottom of the package.json file, add these browserify and browserify-shim properties. View the source code if needed.

"browserify": {
  "transform": ["browserify-shim"]
},
"browserify-shim": {
  "jQuery": "global:jQuery"
}

Create a gulpfile.js in the project root to define and configure the Gulp tasks. The default task is simply gulp. When gulp is executed, it will run the bundle task, start a local web server with livereload and open the app in a web browser.

gulpfile.js
'use strict';

var gulp        = require('gulp'),
    browserify  = require('browserify'),
    buffer      = require('vinyl-buffer'),
    gutil       = require('gulp-util'),
    source      = require('vinyl-source-stream'),
    sourcemaps  = require('gulp-sourcemaps'),
    uglify      = require('gulp-uglify'),
    webserver   = require('gulp-webserver');

gulp.task('bundle', function () {
  var b = browserify({
    entries: './src/js/index.js',
    debug: true
  });

  return b.bundle()
    .pipe(source('bundle.js'))
    .pipe(buffer())
    .pipe(sourcemaps.init({loadMaps: true}))
        .pipe(uglify())
        .on('error', gutil.log)
    .pipe(sourcemaps.write('./'))
    .pipe(gulp.dest('./js/'));
});

// static web server w/ livereload
gulp.task('server', function() {
    gulp.src('./')
        .pipe(webserver({
            livereload: true,
            directoryListing: false,
            open: true
        }));
});

gulp.task('default', ['bundle','server']);

Add watchify to the gulp workflow on the next page.

Source Code

Sass Workflow Using cssnano and Autoprefixer

This Sass preprocessor workflow uses cssnano for postprocessing minification and optimization that includes Autoprefixer to add vendor prefixes for only the browsers that need to be supported. UPDATE: Webpack 3 version (11-12-2017)

Autoprefixer

Without Autoprefixer, adding in vender prefixes is done in the Sass code, typically with a mixin to append every known prefix for a property. This more advanced mixin to prefix properties at least accepts prefix parameters for more control over which ones to use. However, you still need to include the mixin with applicable parameters throughout your Sass code which requires knowing which properties might need prefixes.

With Autoprefixer, you do not need to think at all about which properties need prefixes. Simply use only the-un prefixed property name in your Sass or CSS code and Autoprefixer will check the Can I use database to append prefixes as needed while understanding the specification differences.

cssnano

I don’t think I could write it any better than the folks at cssnano did, cssnano takes your nicely formatted CSS and runs it through many focused optimisations, to ensure that the final result is as small as possible for a production environment.. cssnano includes Autoprefixer in it’s list of transforms, therefore, when you install cssnano you also get Autoprefixer.

Workflow

This simple workflow uses the Gulp streaming build system to process the Sass code, add vendor prefixes with Autoprefixer via cssnano to optimize into production ready css. In this gulpfile.js, note cssnano’s autoprefixer option which accepts an array of browsers to support.

gulpfile.js
'use strict';

var gulp         = require('gulp'),
    cssnano      = require('gulp-cssnano'),
    sass         = require('gulp-sass');

var supported = [
    'last 2 versions',
    'safari >= 8',
    'ie >= 10',
    'ff >= 20',
    'ios 6',
    'android 4'
];

gulp.task('css', function(){
    return gulp.src(['src/sass/**/*.scss'])
        .pipe(sass())
        .pipe(cssnano({
            autoprefixer: {browsers: supported, add: true}
        }))
        .pipe(gulp.dest('css'));
});

Run the Workflow Example

In order to use the Node Package Manager (NPM) and Gulp task runner used in this workflow example, you will need to install Node.js.

Using a Command shell in the project root, follow these steps to get the local development environment setup.

Create package.json

The package.json file contains project metadata and lists dependencies that are available on npm. This is useful to have when you want to re-install or update the dependencies with the npm install command.

# create package.json
npm init

Install node modules

# install dependencies and save to package.json
npm install gulp --save-dev
npm install gulp-cssnano --save-dev
npm install gulp-sass --save-dev

Workflow Example Folders and Files

    • src
      • sass
        • modules
          • _js.scss
          • _loader.scss
        • main.scss
    • gulpfile.js
    • index.html
    • package.json
source code

Google Maps API RequireJS Module

This post documents how to create a RequireJS module that loads the Google Maps JavaScript API and is used by a web page to populate form inputs with location data. The Google Map contains a marker that can be dragged to reset the form with the marker position location data.

source code

Development Environment

In order to use the package managers, Gulp task runner, BrowserSync and RequireJS optimizer, you will need to have the following installed.

  1. Node.js
  2. Bower

Using a Command shell in the project root, follow these steps to get the local development environment setup.

Create package.json

The package.json file contains project metadata and lists dependencies that are available on npm. This is useful to have when you want to re-install or update the dependencies with the npm install command.

# create package.json
npm init

Install node modules

# install dependencies and save to package.json
npm install browser-sync --save-dev
npm install gulp --save-dev
npm install requirejs --save-dev

Create bower.json

The bower.json file contains project metadata and lists dependencies that are available on bower. Re-install / update bower components saved to this file with the bower install command.

# create bower.json
bower init

Install bower components

Only using the async plugin from this requirejs-plugins bundle for async dependency loading of the Google Maps JavaScript API.

# install bower components and save to bower.json
bower install --save requirejs-plugins

Project Folders and Files

Create these folders and files. Browse, clone or download the source code if you prefer.

  • css
  • js
  • src
    • js
      • modules
        • gmap.js
        • location.js
      • require
      • main.js
    • gulpfile.js
    • index.html
    • require.build.js

Web Page

This web page contains the map container, address and geocode location inputs. jQuery is loaded separately before the require.js module loader to demonstrate how to define jQuery when it is external.

index.html
<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="utf-8">
	<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
	<title>Page Title</title>
	<link rel="stylesheet" href="/css/style.css">
</head>
<body>
	
	<p>Drag the marker to a location on the map and set input values.</p>
	
	<div id="map-canvas"></div>
	<label>Street
		<input type="text" id="street" name="street" />		
	</label>
	<label>Locality
		<input type="text" id="locality" name="locality" />
	</label>
	<label>Region
		<input type="text" id="region" name="region" />
	</label>
	<label>Country
		<input type="text" id="country" name="country" />
	</label>
	<label>Latitude
		<input type="text" id="latitude" name="latitude" />
	</label>
	<label>Longitude
		<input type="text" id="longitude" name="longitude" />
	</label>
	
	<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js"></script>
	
	<!-- Prod / built version -->
	<!--script data-main="/js/main" src="/js/require/require.js"></script-->
	<!-- Dev version -->
	<script data-main="/src/js/main" src="/src/js/require/require.js"></script>

</body>
</html>

Google Map JavaScript API

This module is dependent upon the RequireJS async plugin to load the google maps api. The gmap function accepts three parameters: the default latitude, default longitude and a callback function to handle the result data.

gmap.js
define(['async!//maps.googleapis.com/maps/api/js'],
    function(async){
    var gmap = {
        map: {},
        init: function gmap(lat, lng, callback) {
            var geocoder = new google.maps.Geocoder();
            var marker = new google.maps.Marker({
                position: new google.maps.LatLng(lat, lng),
                anchorPoint: new google.maps.Point(0, -29),
                draggable: true
            });
            map = new google.maps.Map(document.getElementById('map-canvas'), {zoom: 10});
            map.setCenter(marker.position);
            marker.setMap(map);

            google.maps.event.addListener(marker, 'dragend', function (evt) {

                var latlng = {lat: evt.latLng.lat(), lng: evt.latLng.lng()};
                var addrComponents = {
                    street_number: 'short_name',
                    route: 'long_name',
                    establishment: 'long_name',
                    locality: 'long_name',
                    administrative_area_level_1: 'short_name',
                    country: 'short_name',
                    postal_code: 'short_name'
                };
                result = {
                    address: {},
                    latitude: 0,
                    longitude: 0
                };

                geocoder.geocode({'location': latlng}, function(results, status) {
                    if (status === google.maps.GeocoderStatus.OK) {
                        if (results[0]) {

                            var i; var type = null;
                            for (i = 0; i < results[0].address_components.length; i++) {
                                type = results[0].address_components[i].types[0];
                                if (addrComponents[type]) {
                                    result.address[type] = results[0].address_components[i][addrComponents[type]];
                                }
                            }
                            result.latitude = latlng.lat;
                            result.longitude = latlng.lng;
                            if(typeof callback == "function"){
                                callback(result);
                            }

                        } else {
                            window.alert('No results found');
                        }
                    } else {
                        window.alert('Geocoder failed due to: ' + status);
                    }
                });
            });
        }
    };
    return gmap;
});

Location module

This module handles the result data from the gmap.js module and sets the input values.

location.js
define(['jquery','modules/gmap'],
    function($, gmap){
    var location = {
        // default map coordinates
        latitude: 39.084014922903,
        longitude: -77.51372591791,
        init: function(){
            gmap.init(
                location.latitude,
                location.longitude,
                function(result){
                    console.log('RESULT',result);

                    if (result.address) {
                        location._setAddress(result.address);
                    }

                    $('#latitude').val(result.latitude);
                    $('#longitude').val(result.longitude);
                }
            );
        },
        _setAddress: function(address) {
            var street = [];
            if (address.street_number) {
                street.push(address.street_number);
            }
            if (address.route) {
                street.push(address.route);
            }
            if (street.length > 0) {
                $('#street').val(street.join(' '));
            }
            if (address.locality) {
                $('#locality').val(address.locality);
            }
            if (address.administrative_area_level_1) {
                $('#region').val(address.administrative_area_level_1);
            }
            if (address.country) {
                $('#country').val(address.country);
            }
        }
    };
    location.init();
    return location;
});
main.js

JavaScript application entry point.

'use strict';

// since jQuery is already loaded from google cdn
define('jquery', [], function() {
    return jQuery;
});

require.config({
    paths : {
        //create alias to plugins (not needed if plugins are on the baseUrl)
        async: 'require/async'
    }
});

require(['modules/location'], function(location) {
     location.init();
});
gulpfile.js

This gulp file defines two tasks. 1. gulp copy copies node modules and bower components into the application. 2. gulp server runs the application in a static BrowserSync web server.

'use strict';
var gulp = require('gulp');
var browserSync = require('browser-sync').create();

// copy vendor libraries into app
gulp.task('copy', function() {
    gulp.src([
        './bower_components/requirejs-plugins/src/async.js',
        './node_modules/requirejs/require.js'
        ])
        .pipe(gulp.dest('./src/js/require/'));
});

// BrowserSync static server
gulp.task('server', function() {
    browserSync.init({
        server: {
            baseDir: './',
            directory: false,
            index: 'index.html'
        }
    });
});
require.build.js

This is the RequireJS Optimizer build configuration file. Once the entry point and modules are created, run node node_modules/requirejs/bin/r.js -o require.build.js to compile the modules into a single js/main.js file.

({
    baseUrl: './src/js',
    dir: './js',
    modules: [
        {
            name: 'main'
        }
    ],
    fileExclusionRegExp: /^(r|require.build)\.js$/,
    removeCombined: true,
    paths: {
        async: 'require/async'
    },
    preserveLicenseComments: false
})
style.css

Add this file into the css folder to declare some basic styles.

label {
	display: block;
	width: 300px;
	margin-bottom: 1em;
}
input[type='text'] {
	display: block;
	width: 100%;
}
#map-canvas {
	width: 300px;
	height: 300px;
	margin-bottom: 1em;
}

Overview

  1. Create package.json and bower.json
  2. Install node modules and bower components
  3. Create /index.html
  4. Create /src/js/modules/gmap.js
  5. Create /src/js/modules/location.js
  6. Create /src/js/main.js
  7. Create /gulpfile.js
  8. Create /require.build.js
  9. Create /css/style.css
  10. Copy the node modules and bower components into the application. In a command shell,
    run gulp copy
  11. Load the application in the web browser. In a command shell,
    run gulp server
  12. Optimize the application. In a command shell,
    run node node_modules/requirejs/bin/r.js -o require.build.js

Reference

BrowserSync SSI Recipe

BrowserSync is my Node.js web browser app development server of choice. This gulpfile is a work in progress to address support for server side includes when using a BrowserSync static server.

gulpfile.js
var gulp = require('gulp');
var browserSync = require('browser-sync').create();
var minimist = require('minimist');

var serverConfig = {
    site: './src',
    directory: false,
    host: 'localhost',
    ssi: '.inc',
    index: 'index.html'
}

// from https://github.com/gulpjs/gulp/blob/master/docs/recipes/pass-arguments-from-cli.md
// seems to make sense since gulp itself uses minimist.
var optionsConfig = {
    string: 'host',
    default: { host: process.env.NODE_ENV || serverConfig.host }
};
var options = minimist(process.argv.slice(2), optionsConfig);

// Static server
gulp.task('server', function() {
    browserSync.init({
        server: {
            baseDir: serverConfig.site,
            directory: serverConfig.directory,
            index: serverConfig.index
        }
    });
});

// BrowserSync static server with ssi support
// https://www.npmjs.com/package/ssi#supported-instructions
// ------------------------------------------------
// Known issues:
//   virtual include directives to an absolute URL path
//   do not work as expected. For example, given this file context,
//   http://localhost:3000/subdir/index.html
//     works:
//     <!--#include virtual="../incl/head.inc" -->
//     does NOT work correctly, include directives in this include may not be found:
//     <!--#include virtual="/incl/head.inc" -->
//
//     workaround: use gulp server-proxy task
// ------------------------------------------------
function ssiMiddleware (req, res, next) {

    var fs  = require('fs');
    var path = require('path');
    var ssi = require("ssi");
    var url = require('url');

    var pathname = url.parse(req.originalUrl || req.url).pathname;
    var filename = path.join(serverConfig.site, pathname.substr(-1) === '/' ? pathname + serverConfig.index : pathname);

    if (fs.existsSync(filename)) {

        var matcher = '/**/*' + serverConfig.ssi;
        var parser = new ssi(serverConfig.site, serverConfig.site, matcher);

        var contents = parser.parse(filename, fs.readFileSync(filename, {
            encoding: 'utf8'
        })).contents;

        res.writeHead(200, {
            'Content-Type': 'text/html'
        });
        res.end(contents);

        browserSync.emitter.on('service:running', function(data) {
            if (!snippet) {
                snippet = data.options.get('snippet');
            }
            inject();
        });
    }
    else {
        next();
    }
}
gulp.task('server-ssi', function() {
    browserSync.init({
        server: {
            baseDir: serverConfig.site,
            directory: serverConfig.directory,
            index: serverConfig.index
        },
        middleware: ssiMiddleware
    });
});

// Proxy
// http://www.browsersync.io/docs/options/#option-proxy
// useful if apache needed for .htaccess, ssi, rewrite, etc.
// usage: $ gulp server-proxy --host [host]
// e.g.,  $ gulp server-proxy --host local.dev
gulp.task('server-proxy', function() {
    browserSync.init({
        proxy: options.host
    });
});
Required Modules

$ gulp server-ssi

Browserify with Sourcemaps

Browserify lets you write modular JavaScript in node.js style. At the beginning of each module you write, the respective dependencies are added using require statements. Then Browserify compiles the modules along with all of the dependencies into an optimized JavaScript file for use in the browser.

If jQuery has already been loaded by a CDN or otherwise, a Browserify Shim can be used to include global jQuery as a module.

Requirements

Install Browserify

Using a Command shell, enter npm commands as follows: *

# global browserify install
npm install -g browserify

* If you have an older version of global Browserify, run npm rm –global browserify to remove the old version before installing the newer one.

Create package.json

The package.json file contains project metadata and lists dependencies that are available on npm.

# create package.json
npm init

Install both jQuery and Lo-Dash from npm

# install dependencies
npm install jquery lodash --save
Updated on October 24, 2015

Local Modules

In the /src/js/modules folder, create a local application module. In this example, the service.js local module uses jQuery ajax to get data from a rest endpoint that returns json data. Then Lo-Dash is used to output the json data in the browser with a simple template.

service.js
var $ = require('jquery');
var _ = require('lodash');
var setElementHtml = require('./element');
module.exports = function() {

    var module = {

        init: function() {

            var tmplSource = $('#tmpl-artist').html(),
                template;

            $.getJSON('http://jimfrenette.com/chinook/api/artists').done(function(data) {
                if (data) {

                    template = _.template(tmplSource)({
                        data: data
                    });

                    $(".artist-list").html(template);

                    setElementHtml('#some-id', 'Done');
                }
            });
        }
    };

    return module.init();

};

In the /src/js/modules folder, create a second local application module. This module depends on jQuery and contains a small function to update element HTML. Here we are simply using it to change the page heading as various steps are completed in this small application. It also shows how multiple module dependencies are defined and how they interact with each other.

element.js
var $ = require('jquery');
module.exports = function(selector, data) {

    return $(selector).html(data);

};

Application Entry Point

In the /src/js folder, create an index.js JavaScript file.

index.js
var setElementHtml = require('./modules/element');
var getServiceData = require('./modules/service');

setElementHtml('#some-id', 'Step 2...');

getServiceData();

Build


Use the Gulp task and build runner to configure and run the build process. Install Gulp and the Node modules locally that are needed to properly minify and sourcemap the Browserify JavaScript bundle.

Install Gulp

Install gulp globally with the npm install -g command as follows: *

# global gulp install
npm install -g gulp

* If you have an older version of global Gulp, run npm rm –global gulp to remove the old version before installing the newer one.

Install Remaining Node Modules

$ npm install gulp --save
$ npm install browserify --save
$ npm install vinyl-source-stream --save
$ npm install vinyl-buffer --save
$ npm install gulp-uglify --save
$ npm install gulp-sourcemaps --save
$ npm install gulp-util --save

Create a gulpfile.js in the project root for instructing gulp to perform the tasks.

gulpfile.js
'use strict';

var browserify = require('browserify');
var browserSync = require('browser-sync').create();
var gulp = require('gulp');
var source = require('vinyl-source-stream');
var buffer = require('vinyl-buffer');
var uglify = require('gulp-uglify');
var sourcemaps = require('gulp-sourcemaps');
var gutil = require('gulp-util');

gulp.task('javascript', function () {
  // set up the browserify instance on a task basis
  var b = browserify({
    entries: './src/js/index.js',
    debug: true
  });

  return b.bundle()
    .pipe(source('app.js'))
    .pipe(buffer())
    .pipe(sourcemaps.init({loadMaps: true}))
        // Add transformation tasks to the pipeline here.
        .pipe(uglify())
        .on('error', gutil.log)
    .pipe(sourcemaps.write('./'))
    .pipe(gulp.dest('./js/'));
});

// Static server
gulp.task('server', function() {
    browserSync.init({
        server: {
            baseDir: "./"
        }
    });

    gulp.watch('./js/*.js').on('change', browserSync.reload);

});
index.html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
  <title>Browserify App</title>
</head>
<body>
	<h1 id="some-id">Step 1 ...</h1>

	<!-- template wrapper -->
	<ul class="artist-list"></ul>

	<!-- template -->
	<script id="tmpl-artist" type="text/tmpl">
		<% _.forEach(data,function(artist) { %>
		<li data-artist-id="<%= artist.ArtistId %>"><%= artist.Name %></li>
		<% }); %>
	</script>
	<script type="text/javascript" src="/js/app.js"></script>
</body>
</html>

Run

Using a Command shell, run the gulp javascript task to Browserify the modules into a single JavaScript file, /js/app.js, with respective source map file, /js/app.js.map.

# run the task
$ gulp javascript

Server

Run the gulp server task to load the app in the browser using a BrowseSync static server. The gulp.watch event handler provides live reloading when the /js/app.js file is regenerated by the gulp javascript task.

# run the task
$ gulp server
Source Code

UPDATE: March 26, 2016

Browserify Shim jQuery

Browserify shim to use external / global jQuery loaded from a CDN. Source code is available for browsing and download at GitHub.

Source Code

Resources

HTML 5 Template

Template with html5.js shiv *
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta http-equiv="x-ua-compatible" content="ie=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>My Super Site!</title>
  <meta name="description" content="My Super Site!">
  <meta name="author" content="My Name">

  <link rel="stylesheet" href="css/styles.css">

  <!--[if lt IE 9]>
  <script src="http://html5shiv.googlecode.com/svn/trunk/html5.js"></script>
  <![endif]-->
</head>
<body>

</body>
</html>

* Inspired by HTML5 Boilerplate

Emmet

If the Emmet plugin is installed in your code editor, you can use the ! alias or html:5 abbreviation then tab to stub out the template.


Easy Browsersync Server

Easy Browsersync Server is an HTML 5 Template that comes with a NPM package config to install a minimal set of development tools including a Browsersync server and Gulp task runner. After installation, a simple gulp server command loads the html 5 template in a browser using Browsersync. Any edits made to the html or sass (scss) files are processed instantly and reloaded into the browser.

source code

  1. Requires Node.js

  2. Download or clone this repsoitory.

  3. Change directory to /uiCookbook/easybs

  4. Run npm install from a bash terminal or command prompt. This will read the package.json file and install the dependencies into the easybs project.

$ npm install
  • Finally, run gulp server to load the index.html file in a web browser
$ gulp server

If you wish to include normalize.scss in your sass build, run this task:

$ gulp cp

Then uncomment the normalize import statement at the top of /src/sass/base/_base.scss

Additional reading:


Easy Web Server

If you don’t need all the overhead of the Browsersync server, this Gulp webserver plugin with LiveReload is a good alternative. The uiCookbook – gulp-webserver branch contains an example to get you started.

source code

Or static http-server that can run from any project folder, npm install -g http-server