Front-End Dev Boilerplate with Automatic Build, Watch & Live Reload

Although there are a lot of boilerplates out there, creating your own makes sure that you have full understanding and control over your codebase.

This article will guide you through creating of an initial setup for modern front-end development, including:

  • CommonJS Modules
  • Gulp as build tool
  • App config file
  • Local dev server with gulp-live-server
  • Live reload with BrowserSync
  • SASS compilation and bundling with gulp-concat and gulp-sass
  • JS compilation and bundling with Browserify and vinyl-source-stream
  • JS linting with gulp-eslint

Global Setup: Node & Gulp

If the world of Node.js has escaped you until now, here is a quick definition:

Node.js® is a JavaScript runtime built on Chrome’s V8 JavaScript engine. Node.js uses an event-driven, non-blocking I/O model that makes it lightweight and efficient. Node.js’ package ecosystem, npm, is the largest ecosystem of open source libraries in the world.

Gulp is a build tool that is comparable to Grunt, with one important distinction: it uses streams during the build process, which makes it faster.

Make sure that Node and Gulp are installed globally:

  • Node: download and install from the Node.js download page
  • Gulp: run this command in your terminal: npm install --global gulp

A side note: if you run into permission jungle during the installs, it’s worth to check out this article from NPM before you resolve to installing everything with sudo: Fixing npm permissions

package.json

Create a new directory for your project:

mkdir myproject
cd myproject

Now, run the init command:

npm init

Answer the questions if you wish, or resolve to defaults by pressing ENTER. This will create a package.json file.

/myproject/package.json:

{
  "name": "My App",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "Mary Lane",
  "license": "ISC"
}

Gulp and Del

Next, we will install Gulp and del locally:

npm install --save-dev gulp del

Although we have Gulp installed globally, we also need to install it locally.

Del lets you clean up your build directory before you write updated files into it:

Delete files/folders using globs. Pretty much rimraf with a Promise API and support for multiple files and globbing. It also protects you against deleting the current working directory and above.

The --save-dev flag will add the packages to our devDependencies in package.json, which basically means that those packages will be installed in the development environment, but not in production.

Global Configuration File

This is a convenient way to store all the information about the project in one central place. It will hold paths, directory and file names.

In the project’s root directory, create a file app.config.js:

touch app.config.js

Add the initial config data to app.config.js. We are using the CommonJS pattern here, that will allow us to import the configuration data into any other CommonJS module.

/myproject/app.config.js:

"use strict";

var config = {
    src: {
        dir: 'src'
    },
    dist: {
        dir: 'dist'
    }
}

module.exports = config;

Gulpfile.js

In the project’s root directory, create a file gulpfile.js:

touch gulpfile.js

Open up gulpfile.js. First, lets load the two packages we just installed:

var gulp = require('gulp'); // Node.js task runner
var del = require('del'); // Deletes files and folders

Also, we are going to load the app.config.js file, to get access to the config data:

var config = require('./app.config'); // Project's configuration data

Next, create a default task. For now it’s empty and doesn’t do anything:

// Default task
gulp.task('default', []);

Source Directory

Lets create a directory that will hold all our source code. You can call it src, app, or whatever you want:

mkdir src

In the src folder, create a file index.html.

/myproject/src/index.html:

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>My App</title>
    </head>
    <body>
        <h1>My App</h1>
        <p>Hello Universe!</p>
    </body>
</html>

Gulp Clean Task Using Del

Now lets write some build code. We will setup our build process so that every time we build, we will populate our distribution directory with fresh code ready to pushed to production. We will name the distribution directory dist. You don’t need to create it manually, as the build script will do it for us.

Our first Gulp task will remove all files and folders from the dist directory. I prefer to do so instead of just letting the build script overwrite older versions of file. In case we decide to rename or move files around in the src directory, we won’t end up with old versions of these files lingering in the dist directory.

/myproject/gulpfile.js:

// Recursively delete the contents of the dist folder
gulp.task('clean', function() {
    return del(config.dist.dir + '/**/*');
});

// Default task
gulp.task('default', ['clean']);

In the default task, we’ve added a clean task as a dependency of the default task. This will make sure that the clean task will be executed.

Note that config here is loaded from app.config.js file that we created earlier.

Gulp Html Task

Lets add a path that will match all html files to app.config.js.

/myproject/app.config.js:

config.paths = {
    html: './' + config.src.dir + '/*.html' // Matches all html files
};

Now, lets create a task that will copy all the .html files from app to dist.

/myproject/gulpfile.js:

// Copy all HTML files to the dist folder
gulp.task('html', function() {
    gulp.src(config.paths.html)
        .pipe(gulp.dest(config.dist.dir))
});

How do we run this task? If we make it as dependency of the default task, as we did for our clean task, we may run into issues.

Since Gulp runs all tasks asynchronously by default, there is a possibility that the html task will start writing files to the dist directory at the same time as the clean tasks removes the files from the same directory. That’s not good.

To get around this, we’ll need to create another level of dependencies:

// This task will fire up all the other tasks in the build
gulp.task('all', ['html']);

// Make sure that the 'clean' task is complete before running the rest of the tasks
gulp.task('init', ['clean'], function() {
    gulp.start('all');
});

// Default task
gulp.task('default', ['init']);

As you can see, we’ve created an init task, that will make sure that the clean task is complete before starting the all task, which in turn will fire up our html task and other tasks we’ll create down the road.

This setup will make sure that clean runs first, and all the following tasks will be able to write to a clean dist directory.

Note: Gulp version 4 (not released at the time of writing) will have added support for running tasks in sequence.

Run Time!

Lets put our build script to test. In your terminal, make sure that you are located in the root of your project, and run gulp:

cd myproject
gulp

This will delete the contents of the dist folder, and copy over index.html from app to dist folder.

You should see the following in your terminal:

[11:03:49] Using gulpfile ~/myproject/gulpfile.js
[11:03:49] Starting 'clean'...
[11:03:49] Finished 'clean' after 7.47 ms
[11:03:49] Starting 'init'...
[11:03:49] Starting 'html'...
[11:03:49] Finished 'html' after 9.91 ms
[11:03:49] Starting 'all'...
[11:03:49] Finished 'all' after 6.89 μs
[11:03:49] Finished 'init' after 11 ms
[11:03:49] Starting 'default'...
[11:03:49] Finished 'default' after 3.61 μs

You should have a newly created directory in your root folder: dist, containing a copy of our index.html file.

Local Dev Server with Gulp-Live-Server

Next, we want Gulp to fire up a local dev server for us. This is convenient and doesn’t require you to have any web servers installed on your machine. Down the road you will also be able to create a custom server configuration for every one of your projects.

We will use Gulp Live Server for this purpose. It’s lightweight and easy to use, and will allow you to setup our own script file. Lets install it:

npm install --save-dev gulp-live-server

Next, include the reference to the package in your gulpfile.js:

var liveServer = require('gulp-live-server');

Lets open up our app.config.js file and add dev.protocol, dev.host, dev.port and dev.proxy.port:

var config = {
    ...
    dev: {
        protocol: 'http',
        host: 'localhost',
        port: '9006',
        proxy: {
            port: '7777'
        }
    }
};

We will need a separate proxy port to setup live reload in the next step.

Now we are ready to create the Gulp task:

// Start a local dev server
gulp.task('live-server', function() {
    var server = liveServer.static(config.dist.dir, config.dev.proxy.port);
    server.start();
});

This will start a server at this URL: http://localhost:7777 and serve the contents of the dist directory.

Open up your browser and navigate to http://localhost:7777. You should see our index.html file served with the “Hello Universe!” message.

Live Reload with BrowserSync

Next, we will install BrowserSync:

BrowserSync makes your browser testing workflow faster by synchronizing URLs, interactions and code changes across multiple devices. It’s wicked-fast and totally free.

npm install --save-dev browser-sync

In our gulpfile.js, lets add a serve task and make it dependent on live-server task:

// Run live reload using BrowserSync
gulp.task('serve', ['live-server'], function() {
    browserSync.init(null, {// 'null': we already have our server setup
        proxy: config.dev.protocol + '://' + config.dev.host + ':' + config.dev.proxy.port, // server URL
        port: config.dev.port // port for the new connection
    });
});

Next, in our html task, we want to notify BrowserSync to reflect the changes to html files. We can do so by piping the updated html stream to BrowserSync: browserSync.stream().

// Copy all HTML files to the dist folder
gulp.task('html', function() {
    gulp.src(config.paths.html)
        .pipe(gulp.dest(config.dist.dir))
        .pipe(browserSync.stream());
});

And finally, the last step is to let Gulp run the html task every time we update an html file in the src directory:

// Watch file changes
gulp.task('watch', function() {
    gulp.watch(config.paths.html, ['html']); // Run the "html" task every time a file is changed in the html dir
});

Run Time!

This was a lot of configuration, but it will make our development a smooth sailing. Lets put our build script to test. In your terminal, run the gulp command:

gulp

If everything worked well, your browser should open up and load localhost:9006. You should see a short notification that BrowserSync is connected. Your local dev server is now running and serving the contents of the dist folder.

Now, go ahead and make a change in /myproject/app/index.html. Your change should be immediately reflected in the browser. In your terminal, you will see that the html task was executed:

[12:44:27] Starting 'html'...
[12:44:27] Finished 'html' after 3.49 ms
[BS] 1 file changed (index.html)

SASS Compilation & Bundling with Gulp-Sass & Gulp-Concat

This step will be easy since we already have all the infrastructure ready. First, lets install gulp-sass and gulp-concat:

npm install gulp-sass --save-dev
npm install --save-dev gulp-concat

Next, lets include these packages in our gulpfile.

/myproject/gulpfile.js:

var concat = require('gulp-concat'); // Concatenates files
var sass = require('gulp-sass'); // Compile SASS into CSS

In app.config.js, add a name of the css directory and bundled css file. Also, add a path to the source sass files to config.paths.

/myproject/app.config.js:

var config = {
    ...
    css: {
        dir: 'css',
        bundleFile: 'styles.css'
    },
    ...
};

config.paths = {
    ...
    css: './' + config.src.dir + '/' + config.src.dir + '/**/*.scss' // Matches all scss files, recursively
};

Lets create a couple of sass files and add some sass to them:

Create two files:

cd myproject/src
mkdir css
touch colors.scss
touch typography.scss

/myproject/src/scss/colors.scss:

$background-color: #efefef;
body {
    background-color: $background-color;
}

/myproject/src/scss/typography.scss:

$font-stack: Helvetica, sans-serif;
body {
    font: 100% $font-stack;
}

Now we are ready to write our gulp task:

// Compile sass into CSS, bundle to styles.css, copy to the dist folder & auto-inject into browsers
gulp.task('css', function() {
    return gulp.src(config.paths.css)
        .pipe(sass())
        .pipe(concat(config.css.bundleFile))
        .pipe(gulp.dest(config.dist.dir + '/' + config.css.dir))
        .pipe(browserSync.stream());
});

Add css task to all and watch tasks:

// Watch file changes
gulp.task('watch', function() {
        ...
    gulp.watch(config.paths.css, ['css']); // Run the "css" task every time a sass file is modified
});

// This task will fire up all the other tasks in the build
gulp.task('all', ['html', 'css'], function() {
    ...
});

Last but not least, lets include the reference to the bundled css file in our index.html.

/myproject/app/index.html:

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

Run Time!

Run gulp in your terminal. If all goes well, your two .scss files (colors.scss and typography.scss) should have compiled to CSS, concatenated into one file – styles.css, and copied over to the dist directory. Try making a change in any of the .scss files and see it reflected in the browser immediately.

JavaScript Compilation & Bundling Using Browserify & Vinyl-Source-Stream

As the last step, we’ll setup the JavaScript work flow. We will use CommonJS pattern to keep our functionality modularized. Then, we will bundle all the JS into a single file and serve it to the client, thus improving the loading time.

Browserify lets you require(‘modules’) in the browser by bundling up all of your dependencies.

Install browserify:

npm install --save-dev browserify

vinyl-source-stream lets you use conventional text streams at the start of your gulp or vinyl pipelines

Install vinyl-source-stream:

npm install --save-dev vinyl-source-stream

Now, lets import these two packages into our gulpfile.js:

var browserify = require('browserify'); // Bundles JS
var source = require('vinyl-source-stream'); // Use conventional text streams with Gulp

Next, lets update our configuration file with paths and file names for JavaScript:

var config = {
    app: {
        ...
        mainJsFile: 'main.js'
    },
    js: {
        dir: 'js',
        bundleFile: 'scripts.js'
    }
};

config.paths = {
    ...
    js: config.src.dir + '/' + config.js.dir + '/**/*.js'
};

Ok, we are ready to create the js task:

// Bundle all javascript using Browserify
gulp.task('js', function() {
    browserify(config.src.dir + '/' + config.js.dir + '/' + config.app.mainJsFile)
        .bundle() // Put it all in one file
        .pipe(source(config.js.bundleFile)) // Define the name of the bundle
        .pipe(gulp.dest(config.dist.dir + '/' + config.js.dir)) // Destination for the bundle
        .pipe(browserSync.stream()); // Reload the browser
});

Lets add this task to our all task, to make sure that it gets executed:

gulp.task('all', ['html', 'css', 'js'], function() {...});

Finally, lets add it to the watch task:

gulp.watch(config.paths.js, ['js']); // Run the "js" task every time a js file is modified

Ok, it’s time to put it all to test. We need to create some javascript:

cd myproject/src/
mkdir js
cd js
touch main.js
mkdir components
cd components
touch app.js

Ok, in the above steps, we’ve created a js directory with a main.js file in it, and a js/components directory, with an app.js file in it. Now, add this test code to these two files:

/myproject/src/js/components/app.js:

var App = {
    init: function() {
        console.log('App is initialized!');
    }
};

module.exports = App;

/myproject/src/js/main.js:

var app = require('./components/app.js');  
app.init();

In app.js, we are using CommonJS pattern to define the App module – that only runs in Node.js. But, Browserify will help us to bundle it into JavaScript that can be run in the browser.

In main.js, we import the App module and run its init function, that will print a message to the console.

Run Time!

Lets put it all to test. In your console, run gulp.

If everything went well, your page should open in the browser. Open the Developer Tools, and check the console. You should see a message printed: “App is initialized!”. This means that Browserify resolved all the dependencies and create the JS bundle.

JavaScript Linting Using Gulp-Eslint

Now, lets setup a process that will help us validate JavaScript in “compile” time. We will use gulp-eslint that will lint our js every time a file is modified, and print an error message in the Terminal.

Lets install gulp-eslint:

npm install --save-dev gulp-eslint

Import it into gulpfile.js:

var eslint = require('gulp-eslint'); // Lint JS files, including JSX

Next, we’ll create a configuration file that will contain all the rules for JavaScript linting. Create a file in your project’s root directory: eslint.config.json:

/myproject/eslint.config.json:

{
    "env": {
        "browser": true,
        "node": true
    },
    "rules": {
        "quotes": 0,
        "no-trailing-spaces": 0,
        "eol-last": 0,
        "no-unused-vars": 0,
        "no-underscore-dangle": 0,
        "no-alert": 0,
        "no-lone-blocks": 0
    }
}

You can modify these rules as it suits you.

Now, lets create the Gulp task:

// Run ESLint on all javascript
gulp.task('lint', function() {
    return gulp.src(config.paths.js)
        .pipe(eslint('eslint.config.json'))
        .pipe(eslint.format());
});

Lastly, lets run lint task every time we compile JavaScript:

gulp.task('js', ['lint'], function() {...});

Run Time!

Run gulp and change one of the js files so that it contains an error (for example, 0 = 0;). Check your Terminal window. You should see an error message printed.

This concludes the initial boilerplate setup. I hope it was helpful.

A complete working source code for this boilerplate can be found on Github: – Front-end dev boilerplate on Github

Leave a Reply

Your email address will not be published. Required fields are marked *