Grunt + BrowserSync: The right way

If you do any kind of frontend development, that’s a bit more complicated than the “Hello World” example, you should be doing it with Grunt and it’s wingman, BrowserSync.

What is BrowserSync?

BrowserSync is built on Node.js, and can be installed like any other module, it’s configured like any other module, but doesn’t really look like any of the other modules. Most of these have very specific and isolated functionalities, and that’s ok, but BrowserSync does a lot of cool stuff.
It’s core features are related to browser “things”, like the name suggests, but it really becomes an overall frontend testing application, easing the procedure a lot!

Features

Its base feature is a built in private server, just like grunt connect. This means I don’t need to setup any virtual host to test my application on an actual local server. Attached to that, there are a lot of cool features like:

  • Sharing the app to external devices, via web. This is incredibly useful to test your app in any mobile device, or any other machine, without having to put the whole project online;


  • Synchronizing interactions between any device. This basically means that you can interact with the app through a smartphone, and have the desktop version respond to it. In other words, you can interact with a smartphone, and have all the debug goodies of a desktop browser;

  • Watching changes in any files you want, and automatically refresh those files in the browser, without requiring an actual page reload.

A word on code injection

To add BrowserSync to your Gruntfile, I'm not going to suggest following up the traditional grunt package install. It can certainly and most likely be made to work but, assuming you are using Grunt to also run other routines like minifying, compressing or pre-compiling code, you may have to fine tune a lot down the road to make the code injection work properly.
Basically, BrowserSync injects file changes into the browser as soon as those changes are detected, adding them before the other Grunt tasks are done rendering the final files. You can add hardcode a delay on the code injection, but that's not pretty at all.

Having said that, I'm going to hook Grunt up with BrowserSync
without the classic grunt package, only triggering the code injection after all the Grunt modules have finished performing their tasks. So the workflow will be:

  • Change file
  • Detect the file changes through a watcher
  • Preform other Grunt tasks
  • Inject code changes into the browser

Install

First, install BrowserSync through NPM, like so

npm install browser-sync --save-dev  

Then, add BrowserSync to your Gruntfile, like so

var browserSync = require("browser-sync");

module.exports = function (grunt) {

       // some other grunt tasks

        watch: {
            options: {
                cwd: '/',
                spawn: false    // This is very important
            },
            files: ['html/**/*.html', 'scss/**/*.scss','js/**/*.js'],
            tasks: ['other-task-1', 'other-task-2', 'bs-inject']
        }
    });


    // Init BrowserSync manually
    grunt.registerTask("bs-init", function () {
        var done = this.async();
        browserSync({
            open: "ui",
            logLevel:'debug',
            timestamps: false,
            server: {
                baseDir: "html"
            }
        }, function (err, bs) {
            done();
        });
    });

    // Inject CSS files to the browser
    grunt.registerTask("bs-inject", function () {
        browserSync.reload(["app.css", "some-other-file.css"]);
    });

    // Launch Browser-sync & watch files
    grunt.registerTask('watcher', ['bs-init', 'watch']);
};

In this example, I'm creating a watch task that will watch out for file changes on .js, .css, .hbs and .html files. This file update will not trigger BrowserSync. Instead, when choosing to trigger BrowserSync through it's own tasks (bs-init and bs-inject), I can specify where they belong in the Grunt task workflow. Specifically, I'm loading the BrowserSync settings just before watching out for file changes, and triggering the injection only after all the remaining tasks have been done.

Also, keep in mind that the BrowserSync settings can vary. For a list of all the Grunt options, check out the documentation page.

Anything else?

If this doesn't convince you of how much this helps with frontend development & testing, be warned that BrowserSync also packs a few more features, like command-line and API integration. I hope these two terms are enough to get your juices flowing.

comments powered by Disqus