Automate your development workflow using Gulp (Part 2)

Automate your development workflow using Gulp (Part 2)

Let us enhance our development workflow with more awesome Gulp tasks!

Introduction

This was what we achieved in part 1 of the tutorial.

  1. Installed Node.js, npm and Gulp CLI
  2. Wrote a basic gulp task
  3. Created a basic HTML page
  4. Installed gulp packages

Our first actual Gulp task

Time to code! We will start simple and write a task that starts the development server and opens the default browser.

Add the following code to gulpfile.js.

const { series, parallel, src, gulp } = require('gulp');
const connect = require('gulp-connect'); // Runs a local webserver
const open = require('gulp-open'); // Opens a URL in a web browser

// Gulp plugin to run a webserver (with LiveReload)
// https://www.npmjs.com/package/gulp-connect
function server(done) {
    return new Promise(function(resolve, reject) {
        connect.server({
        root: config.paths.dist,
        port: config.port,
        debug: true,
        });
        resolve();
    });
}

// Launch Chrome web browser
// https://www.npmjs.com/package/gulp-open
function openBrowser(done) {
    var options = {
    uri: 'http://localhost:8080',
    app: 'Google Chrome'
    };
    return src('./src/index.html')
    .pipe(open(options));
    done();
}

/*
The default gulp command.
*/
exports.default = series(openBrowser, parallel(server));

Run gulp.

$ gulp

Sweet!

More awesome tasks

Replace the code in gulpfile.js with the following.

const { series, parallel, src, dest, gulp } = require('gulp');
const rename = require('gulp-rename'); // Rename files after compile
const cache = require('gulp-cache'); // A temp file based caching proxy task for gulp.
const sass = require('gulp-sass'); // Gulp Sass plugin
const sassPartialsImported = require('gulp-sass-partials-imported'); // Import Sass partials
const cleanCSS = require('gulp-clean-css'); // CSS Minifier
const inject = require('gulp-inject'); // Injects CSS/JS into html
const connect = require('gulp-connect'); // Runs a local webserver
const open = require('gulp-open'); // Opens a URL in a web browser

// Compile Bootstrap SASS
function bootstrapCompile(done) {
    return src('./src/scss/bootstrap.scss')
    .pipe(sassPartialsImported('./src/scss/', './src/scss/'))
    .pipe(cache(sass({ includePaths: './src/scss/' }).on('error', sass.logError)))
    .pipe(cleanCSS())
    .pipe(rename({ extname: '.css' }))
    .pipe(dest('./dist/css/'));
    done();
}

// Move files to dist
function moveFiles(done) {
    return src('./src/*.html')
    .pipe(dest('./dist/'));
    done();
}

// A stylesheet, javascript and webcomponent reference injection plugin for gulp.
// https://www.npmjs.com/package/gulp-inject
function injectFiles(done) {
    var target = src('./dist/' + 'index.html');
    var sources = src([
    './dist/css/' + '*.css'
    ], {read: false});
    return target.pipe(inject(sources, {relative: true}))
    .pipe(dest('./dist/'));
    done()
}

// Gulp plugin to run a webserver (with LiveReload)
// https://www.npmjs.com/package/gulp-connect
function server(done) {
    return new Promise(function(resolve, reject) {
        connect.server({
        root: config.paths.dist,
        port: config.port,
        debug: true,
        });
        resolve();
    });
}

// Launch Chrome web browser
// https://www.npmjs.com/package/gulp-open
function openBrowser(done) {
    var options = {
    uri: 'http://localhost:8080',
    app: 'Google Chrome'
    };
    return src('./dist/index.html')
    .pipe(open(options));
    done();
}

/*
The default gulp command.
*/
exports.default = series(bootstrapCompile, moveFiles, injectFiles, openBrowser, server);

Edit src/index.html.

...
    <title>A Gulp Bootstrap Sass Boilerplate</title>
    <!-- inject:css -->
    <!-- endinject -->
</head>
<body>
...

Test our code injection.

$ gulp

Like Magic!

We've achieved a lot with a few lines of code.

  1. Created a task that reads and compiles bootstrap.scss with the bootstrapCompile task.
  2. Compiled and renamed bootstrap.scss to bootstrap.css and moved the file to ./dist/css/.
  3. Copied our files from /.src to /.dist with the moveFiles task.
  4. Injected the link to bootstrap.css into index.html.
  5. Started development server and served index.html in the browser.

Define global variables

You may have realised that we have some code repetition with the path names.

Let’s refactor them into global variables. Add the following to gulpfile.js.

...nect = require('gulp-connect'); // Runs a local webserver
const open = require('gulp-open'); // Opens a URL in a web browser

// General Config Vars
const config = {
    port: 8080,
    devBaseUrl: 'http://localhost',
    paths: {
        root: './src/',
        html: './src/*.html',
        scss: './src/scss/',
        js: './src/js/*.js',
        images: './src/img/**',
        dist: './dist/',
        distCSSDir: './dist/css/',
        distJSDir: './dist/js/',
        distIMGDir: './dist/img/',
        node_modules:'./node_modules/'
    }
}

// Compile Bootstrap SASS
function boot...

We created an object that stores all our folder paths. Let’s replace the path names to reference the object values.

const { series, parallel, src, dest, gulp } = require('gulp');
const rename = require('gulp-rename'); // Rename files after compile
const cache = require('gulp-cache'); // A temp file based caching proxy task for gulp.
const sass = require('gulp-sass'); // Gulp Sass plugin
const sassPartialsImported = require('gulp-sass-partials-imported'); // Import Sass partials
const cleanCSS = require('gulp-clean-css'); // CSS Minifier
const inject = require('gulp-inject'); // Injects CSS/JS into html
const connect = require('gulp-connect'); // Runs a local webserver
const open = require('gulp-open'); // Opens a URL in a web browser

// General Config Vars
const config = {
    port: 8080,
    devBaseUrl: 'http://localhost',
    paths: {
        root: './src/',
        html: './src/*.html',
        scss: './src/scss/',
        js: './src/js/*.js',
        images: './src/img/**',
        dist: './dist/',
        distCSSDir: './dist/css/',
        distJSDir: './dist/js/',
        distIMGDir: './dist/img/',
        node_modules:'./node_modules/'
    }
}

// Compile Bootstrap SASS
function bootstrapCompile(done) {
    return src(config.paths.scss + 'bootstrap.scss')
    .pipe(sassPartialsImported(config.paths.scss, config.paths.scss))
    .pipe(cache(sass({ includePaths: config.paths.scss }).on('error', sass.logError)))
    .pipe(cleanCSS())
    .pipe(rename({ extname: '.css' }))
    .pipe(dest(config.paths.distCSSDir));
    done();
}

// Move files to dist
function moveFiles(done) {
    return src(config.paths.html)
    .pipe(dest(config.paths.dist));
    done();
}

// A stylesheet, javascript and webcomponent reference injection plugin for gulp.
// https://www.npmjs.com/package/gulp-inject
function injectFiles(done) {
    var target = src(config.paths.dist + 'index.html');
    var sources = src([
    config.paths.distJSDir + '*.js',
    config.paths.distCSSDir + '*.css'
    ], {read: false});
    return target.pipe(inject(sources, {relative: true}))
    .pipe(dest(config.paths.dist));
    done();
}

// Gulp plugin to run a webserver (with LiveReload)
// https://www.npmjs.com/package/gulp-connect
function server(done) {
    return new Promise(function(resolve, reject) {
        connect.server({
        root: config.paths.dist,
        port: config.port,
        debug: true,
        });
        resolve();
    });
}

// Launch Chrome web browser
// https://www.npmjs.com/package/gulp-open
function openBrowser(done) {
    var options = {
    uri: 'http://localhost:' + config.port,
    app: 'Google Chrome'
    };
    return src(config.paths.dist + 'index.html')
    .pipe(open(options));
    done();
}

/*
The default gulp command.
*/
exports.default = series(bootstrapCompile, moveFiles, injectFiles, openBrowser ,server);

Test our scripts to make sure they still work.

$ gulp

What about JavaScript files?

Up till this point, we have not written any tasks for our JavaScript files.

Let’s create a task that minifies our .js files, renames .js to .min.js and copies the minified files over to dist. We will then inject the link to the .js file into index.html.

Replace the code in gulpfile.js.

const { series, parallel, src, dest, gulp } = require('gulp');
const rename = require('gulp-rename'); // Rename files after compile
const cache = require('gulp-cache'); // A temp file based caching proxy task for gulp.
const uglify = require('gulp-uglify'); // JavaScript Minifier
const sass = require('gulp-sass'); // Gulp Sass plugin
const sassPartialsImported = require('gulp-sass-partials-imported'); // Import Sass partials
const cleanCSS = require('gulp-clean-css'); // CSS Minifier
const inject = require('gulp-inject'); // Injects CSS/JS into html
const connect = require('gulp-connect'); // Runs a local webserver
const open = require('gulp-open'); // Opens a URL in a web browser

// General Config Vars
const config = {
    port: 8080,
    devBaseUrl: 'http://localhost',
    paths: {
        root: './src/',
        html: './src/*.html',
        scss: './src/scss/',
        js: './src/js/*.js',
        images: './src/img/**',
        dist: './dist/',
        distCSSDir: './dist/css/',
        distJSDir: './dist/js/',
        distIMGDir: './dist/img/',
        node_modules:'./node_modules/'
    }
}

// Compile Bootstrap SASS
function bootstrapCompile(done) {
    return src(config.paths.scss + 'bootstrap.scss')
    .pipe(sassPartialsImported(config.paths.scss, config.paths.scss))
    .pipe(cache(sass({ includePaths: config.paths.scss }).on('error', sass.logError)))
    .pipe(cleanCSS())
    .pipe(rename({ extname: '.css' }))
    .pipe(dest(config.paths.distCSSDir));
    done();
}

// Compile any JS files in JS folder
function jsCompile(done) {
    return src(config.paths.js)
    .pipe(uglify())
    .pipe(rename({ extname: '.min.js' }))
    .pipe(dest(config.paths.distJSDir));
    done();
}

// Move files to dist
function moveFiles(done) {
    return src(config.paths.html)
    .pipe(dest(config.paths.dist));
    done();
}

// A stylesheet, javascript and webcomponent reference injection plugin for gulp.
// https://www.npmjs.com/package/gulp-inject
function injectFiles(done) {
    var target = src(config.paths.dist + 'index.html');
    var sources = src([
    config.paths.distJSDir + '*.js',
    config.paths.distCSSDir + '*.css'
    ], {read: false});
    return target.pipe(inject(sources, {relative: true}))
    .pipe(dest(config.paths.dist));
    done();
}

// Gulp plugin to run a webserver (with LiveReload)
// https://www.npmjs.com/package/gulp-connect
function server(done) {
    return new Promise(function(resolve, reject) {
        connect.server({
        root: config.paths.dist,
        port: config.port,
        debug: true,
        });
        resolve();
    });
}

// Launch Chrome web browser
// https://www.npmjs.com/package/gulp-open
function openBrowser(done) {
    var options = {
    uri: 'http://localhost:' + config.port,
    app: 'Google Chrome'
    };
    return src(config.paths.dist + 'index.html')
    .pipe(open(options));
    done();
}

/*
The default gulp command.
*/
exports.default = series(bootstrapCompile, jsCompile, moveFiles, injectFiles, openBrowser, server);

Edit index.html to include the injection tags.

...
    <h1>Hello World!</h1>
    </div>
    <!-- inject:js -->
    <!-- endinject -->
</body>
</html>

Create src/js/app.js and add the following code.

alert('It Works!');

Abracadabra

$ gulp

There may be situations where old files are not replaced with new ones. Let’s fix that by writing a task to clear all the files in dist folder.

Insert the following code to gulpfile.js.

const del = require('del'); // Empty folders before compiling

// General Config Vars
...

// Removes files and folders. Deprecated but still works.
// https://www.npmjs.com/package/gulp-clean
function clean(done) {
    return del([config.paths.dist + '*']);
    done();
}
...

...
// Empty Folders
/*
Run gulp clean command for a clean slate in dist directory.
You will need to run the command gulp build again to prevent errors.
*/
exports.clean = function(done) {
    clean();
    done();
}
...

See the new task in action by running the following command.

$ gulp clean

Browser refresh and watch tasks

Our workflow automation is looking pretty nifty. It’s missing one element though.

Manually refreshing the browser to view our changes is tedious. Let’s write a task that watches our folders for changes and reloads the browser automatically.

Add the following to gulpfile.js.

const { watch, series, parallel, src, dest, gulp } = require('gulp');
const livereload = require('gulp-livereload'); // Triggers livereload on file changes
...

...
// A stylesheet, javascript and webcomponent reference injection plugin for gulp.
// https://www.npmjs.com/package/gulp-inject
function injectFiles(done) {
    var target = src(config.paths.dist + 'index.html');
    var sources = src([
    config.paths.distJSDir + '*.js',
    config.paths.distCSSDir + '*.css'
    ], {read: false});
    return target.pipe(inject(sources, {relative: true}))
    .pipe(dest(config.paths.dist))
    .pipe(livereload());
    done();
}
...

...
// Build Tasks
function buildTasks(done) {
    return series(clean, bootstrapCompile, jsCompile, moveFiles, injectFiles);
    done();
}

// Watch Task
// Gulp will watch all on events with a set delay followed by build task.
function watchTasks(done) {
    return new Promise(function(resolve, reject) {
        watch([config.paths.html, config.paths.scss, config.paths.js], { events: 'all', delay: 200}, buildTasks(), livereload.listen());
        resolve();
    });
    done();
}

/*
The default gulp command.
*/
exports.default = series(clean, bootstrapCompile, jsCompile, moveFiles, injectFiles, openBrowser, parallel(server, watchTasks));

Install the LiveReload Chrome extension if you are using Chrome.

Run gulp

$ gulp

Here is the gulpfile.js in its entirety.

const { watch, series, parallel, src, dest, gulp } = require('gulp');
const livereload = require('gulp-livereload'); // Triggers livereload on file changes
const del = require('del'); // Empty folders before compiling
const rename = require('gulp-rename'); // Rename files after compile
const cache = require('gulp-cache'); // A temp file based caching proxy task for gulp.
const uglify = require('gulp-uglify'); // JavaScript Minifier
const sass = require('gulp-sass'); // Gulp Sass plugin
const sassPartialsImported = require('gulp-sass-partials-imported'); // Import Sass partials
const cleanCSS = require('gulp-clean-css'); // CSS Minifier
const inject = require('gulp-inject'); // Injects CSS/JS into html
const connect = require('gulp-connect'); // Runs a local webserver
const open = require('gulp-open'); // Opens a URL in a web browser

// General Config Vars
const config = {
    port: 8080,
    devBaseUrl: 'http://localhost',
    paths: {
        root: './src/',
        html: './src/*.html',
        scss: './src/scss/',
        js: './src/js/*.js',
        images: './src/img/**',
        dist: './dist/',
        distCSSDir: './dist/css/',
        distJSDir: './dist/js/',
        distIMGDir: './dist/img/',
        node_modules:'./node_modules/'
    }
}

// Removes files and folders. Deprecated but still works.
// https://www.npmjs.com/package/gulp-clean
function clean(done) {
    return del([config.paths.dist + '*']);
    done();
}

// Compile Bootstrap SASS
function bootstrapCompile(done) {
    return src(config.paths.scss + 'bootstrap.scss')
    .pipe(sassPartialsImported(config.paths.scss, config.paths.scss))
    .pipe(cache(sass({ includePaths: config.paths.scss }).on('error', sass.logError)))
    .pipe(cleanCSS())
    .pipe(rename({ extname: '.css' }))
    .pipe(dest(config.paths.distCSSDir));
    done();
}

// Compile any JS files in JS folder
function jsCompile(done) {
    return src(config.paths.js)
    .pipe(uglify())
    .pipe(rename({ extname: '.min.js' }))
    .pipe(dest(config.paths.distJSDir));
    done();
}

// Move files to dist
function moveFiles(done) {
    return src(config.paths.html)
    .pipe(dest(config.paths.dist));
    done();
}

// A stylesheet, javascript and webcomponent reference injection plugin for gulp.
// https://www.npmjs.com/package/gulp-inject
function injectFiles(done) {
    var target = src(config.paths.dist + 'index.html');
    var sources = src([
    config.paths.distJSDir + '*.js',
    config.paths.distCSSDir + '*.css'
    ], {read: false});
    return target.pipe(inject(sources, {relative: true}))
    .pipe(dest(config.paths.dist))
    .pipe(livereload());
    done();
}

// Launch Chrome web browser
// https://www.npmjs.com/package/gulp-open
function openBrowser(done) {
    var options = {
    uri: 'http://localhost:' + config.port,
    app: 'Google Chrome'
    };
    return src(config.paths.dist + 'index.html')
    .pipe(open(options));
    done();
}

// Gulp plugin to run a webserver (with LiveReload)
// https://www.npmjs.com/package/gulp-connect
function server(done) {
    return new Promise(function(resolve, reject) {
        connect.server({
        root: config.paths.dist,
        port: config.port,
        debug: true,
        });
        resolve();
    });
}

// Build Tasks
function buildTasks(done) {
    return series(clean, bootstrapCompile, jsCompile, moveFiles, injectFiles);
    done();
}

// Watch Task
// Gulp will watch all on events with a set delay followed by build task.
function watchTasks(done) {
    return new Promise(function(resolve, reject) {
        watch([config.paths.html, config.paths.scss, config.paths.js], { events: 'all', delay: 200}, buildTasks(), livereload.listen());
        resolve();
    });
    done();
}

// Empty Folders
/*
Run gulp clean command for a clean slate in dist directory.
You will need to run the command gulp build again to prevent errors.
*/
exports.clean = function(done) {
    clean();
    done();
}

/*
The default gulp command.
*/
exports.default = series(clean, bootstrapCompile, jsCompile, moveFiles, injectFiles, openBrowser, parallel(server, watchTasks));

Connect to LiveReload by clicking on the chrome extension.

Make changes to index.html and save. The browser will reload automatically and display the changes.

Try editing bootstrap.scss and app.js. What happens?

Conclusion

Congrats. You survived this lengthy tutorial.

This is a simple introduction to Gulp.

It is a fantastic toolkit to have in your development arsenal and helps to streamline your development workflow by automating tedious tweks.

As programmers, we tend to dive into coding immediately at the start of a project and deal with code packaging later on.

You will enjoy a great pay off in the long run if you invest a bit of time in the beginning to automate your development workflow.

We barely scratched the surface of what Gulp can do. Check out the official documentation to explore further.

You can clone the entire source code of this project from GitHub.

Latest Posts

Top ten things I Google at workTop ten things I Google at work
How to order pizza the RPA way!How to order pizza the RPA way!
How Gordon Ramsay saved our struggling development teamHow Gordon Ramsay saved our struggling development team