Wibbly Stuff

Gulp + Browserify = Awesome!

If anyone used Grunt, he/she would know how much time it could save. I loved Grunt so much until I came across Gulp, a faster and better task runner, which uses file streams making it faster. Browserify is an awesome tool that let's us require files from any JavaScript file, taking care of dependency issues. Usually we create a bundle with browserify for use in the browser, that's where the gulp task comes in. But the gulp-browserify plugin is no longer maintained, as it doesn't provide any extra functionailty over browserify. We would need to write code to use Browserify in gulp. Let's see how.

First, let's install the required modules,

npm install browserify vinyl-source-stream event-stream

Now we will write a function named bundle which accepts a file to browserify and returns a stream so that it can be piped to another function.

var browserify = require("browserify"),
    source = require("vinyl-source-stream");

// Make browserify bundle
function bundle(files) {
    return browserify("./" + file)
    .bundle()
    .pipe(source(file.split(/[\\/]/).pop()));
}

But you have more files, right? Then we need to modify the function to accept an array as well. Since different files will have different streams, we also need to combine the streams into one.

First, we will make a bundler function, which will accept a file name and return a stream. Next, we'll create streams for each items in the files array. Then we will combine the streams and return the value. We also need to convert the stream to buffer so that plugins which don't support streaming, can use it.

The final code will look like,

var browserify = require("browserify"),
    source = require("vinyl-source-stream"),
    buffer = require("vinyl-buffer"),
    eventstream = require("event-stream");

// Make browserify bundle
function bundle(files) {
    var streams = [],
        bundler = function(file) {
            return browserify("./" + file)
            .bundle()
            .pipe(source(file.split(/[\\/]/).pop()));
        };

    if (files && files instanceof Array) {
        for (var i = 0, l = files.length; i < l; i++) {
            if (typeof files[i] === "string") {
                streams.push(bundler(files[i]));
            }
        }
    } else if (typeof files === "string") {
        streams.push(bundler(files));
    }

    return eventstream.merge.apply(null, streams).pipe(buffer());
}

This would work. But probably we would want to pass some options to browserify, like debug. So, let's modify the function to accept options.

Also, we're not handling errors in browserify. So, if browserify throws an error, gulp will crash. Hence tasks like gulp watch have to be started again manually when it happens. Let's fix it too.

var browserify = require("browserify"),
    source = require("vinyl-source-stream"),
    eventstream = require("event-stream");

// Make browserify bundle
function bundle(files, opts) {
    var streams = [],
        bundler = function(file) {
            opts.entries = "./" + file;

            return browserify(opts).bundle()
            .on("error", function(err) {
                console.log(err);
                // End the stream to prevent gulp from crashing
                this.end();
            })
            .pipe(source(file.split(/[\\/]/).pop()));
        };

    opts = opts || {};

    if (files && files instanceof Array) {
        for (var i = 0, l = files.length; i < l; i++) {
            if (typeof files[i] === "string") {
                streams.push(bundler(files[i]));
            }
        }
    } else if (typeof files === "string") {
        streams.push(bundler(files));
    }

    return eventstream.merge.apply(null, streams).pipe(buffer());
}

And that's it. The function is ready. Let's create a gulp task using the function. Shall we?

var gulp = require("gulp");

gulp.task("bundle", function() {
    return bundle("app.js", { debug: true })
    .pipe(rename({ suffix: ".bundle.min" }))
    .pipe(gulp.dest("dist"));
});

Easy, right?

If you have any suggestions to make this better, please drop a comment.