Using Browserify in Rails


Browserify
Browserify is a popular approach to managing your javascript dependencies, I won't go into why you should use it, apart from to say it is an excellent way to manage your javascript. If you want more information on what it does and why you should use it there is an excellent set of articles on it here

While I don't want to go deeply into the whole browerify vs requirejs debate, I thought it might be worth explain why I sided for browserify over requirejs for our rails builds.

I had implemented requirejs into one of our rails apps, but in honesty I found it a complete pain in the A**. Why? Well firstly it often doesn't play nice with libraries that aren't amd compliant, setting it up with the standard jasmine running that sync nicely with asset pipeline was a headache. Finally as of rails 4 by default rails will only serve pre-compiled fingerprinted assets, which means it will break in production. while I did find a few ways to make it work and there is a gem (which wasn't very up to date when I tried to use it), I was spending a lot of time battling it to make it work with our stack. Browserify on the other hand has in comparison been pretty much a breeze to implement into our rails stack, and while there is a few bits I'd like to improve, it is more about I haven't figured out the right or minor inconveniences rather than feeling like I was battling the require with rails. Well that's my 10 pence anyway, but I'd be interested to hear anyone else's experience with it.

Runners

Now to use browserify you'll need to use a task runner, this will watch for changes in your js and run the browserify compiler. I guess you could use a gem or something like guard to run this, I haven't seen a good gem and guard would probably do the trick. However it does seem a bit crazy to run a ruby process to run a node process in my book, but if anyone has figured it out please let me knwo and I'll link to it here!

Grunt vs Gulp

Ok so like Browserify and requirejs there has been a lot of debate about this, personally I think there both excellent and for the most part comes down to personal choice. I've started using gulp now though for a couple of reason, firstly it seems to be a lot faster, secondly it uses browserify directly as opposed to plugin like grunt. This means if you need to upgrade it super easy and relatively easy if you need to plugin in any new transforms or various other plugins. That said if you prefer grunt that is an excellent choice too and there is a gist here which does pretty much the same as I discuss here in grunt.

Setting up gulp

Ok so the first thing we need to do install node if you haven't already, if your not on a mac there is plenty of installer here. However if you are on mac I suggest using homebrew and using:

brew install node

Next we'll need to set up the package.json, think of it nodes version of bundler, so run:

npm init

This will take you through a few options, don't stress to much about them, you can remove or edit the json after it's been created. A lot of the details are for if your creating a module, but it's a useful way to manage the dependencies and you'll need it to run browserify transitions.

Next add all the npm modules, firstly there is gulp, you'll probably want to install this globally, so you can access it in other projects, but you can just save it locally.

npm install -g gulp

Now for the rest of the node modules we'll need, running them locally makes the most sense as we don't want to get into version issues. So for the basic set up you'll need browserify (obviously) and something called vinyl-source-stream, so run:

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

What vinyl-source-stream does is allow us to to run node modules in gulp environment, in particular in this case browserify. So next we need to create a gulpfile.js to manage your gulp tasks so create a new file in the root of your rails project.

In this file first require gulp, vinyl-source-stream and browserify like so:

var gulp       = require('gulp');
var browserify = require('browserify');
var source     = require('vinyl-source-stream');

This is just the node way of adding modules to the scope. We now set up the gulp task that will run browserify for us:

// Main App
gulp.task("app", function () {
var b = browserify({entries: ['./app/assets/javascripts/app/module.js'],extensions: ['.js']});
return b.bundle()
.pipe(source("app.js"))
.pipe(gulp.dest('./app/assets/javascripts/'));
});

Now what we doing here is targeting a module in app folder under the javascript folder like so:

gulp app

Then when gulp runs it use browserify to compile it into app.js in the root of the javascript folder. This app.js file can then be added into our application.js using spork in the normal way. However it would be quite a pain to run this everytime we make a change to our js, so for we add a new task into our gulpfile.js:

gulp.task('watch', function() {
gulp.watch('./app/assets/javascripts/app/**', ['app']);
})

So now now running "gulp watch" in the terminal and gulp will now watch for changes in our javascript/app folder and autorun the browserify task.

Using coffeescript and bower

Next lets set up a few transforms, if your used to the rails stack you'll probably be used to running coffeescript so we'll install coffeeify, obviously if it isn't your bag then don't include it. I also like to use bower so you'll need debowerify, so you can add bower dependancies:

npm install coffeeify debowerify --save-dev

NB: if you fancy trying es6 you can use es6ify instead or coffeeify.

Now in you package.json add the following

{
...
"browserify": {
"transform": [
"coffeeify",
'debowerify'
]
},
"devDependencies": ..
}

we'll then have to update the app task to look for .coffee instead of .js like so:

// Main App
gulp.task("app", function () {
var b = browserify({entries: ['./app/assets/javascripts/app/module.coffee'],extensions: ['.coffee']});
...
});

Which will now allow you to add coffeescript files to your modules, if you want to add .js as well as coffeescript, simply add ".js" to the extension list. The use for bower is even easier, now simply install the dependency as normal then you can require it in your module as needed:

Angular = require('angularjs')
module.exports = ()->
...

Optimising the build

Now if your including a few libraries you probably don't want it to be compiling the libraries each time you make a change. So to improve compiling time we can split the compile into a libraries build which can be run manually each time you add a library and your main app build that you can add a watch task so it update on every change.

So if your using npm modules it's actually quite simple, first we create our vendor task, which will create a lib.js file with all are libraries in like so

// Vendor set up
gulp.task("vendor", function () {
var b = browserify();
b.require('jquery');
b.require('lodash');
return b.bundle()
.pipe(source("lib.js"))
.pipe(gulp.dest('app/assets/javascripts/'));
});

using the require directive adds the libraries into the build so we don't have to add them into a separate file. Now just run "gulp vendor" and our libs.js is created in our javascript folder. If we now add this to our spork manifest above our app.js so it loads in first.

Next we need to update are main app task so it won't include those libraries into the build but use the ones created in our lib.js. We do this using the external directive in the task like so:

// Main App
gulp.task("app", function () {
var b = browserify({entries: ['./app/assets/javascripts/app/module.coffee'],extensions: ['.coffee']});
b.external('jquery');
b.external('lodash');
...
});

Now if your using debowerify this becomes a little bit more complicated as debowerify isn't triggered to later in the compile process. Thankfully there is a fairly simple module that allows to work round the problem called bower-resolve, from eugeneware who created debowerify. So we update are vendor and app tasks like so:

...
var bowerResolve = require('bower-resolve');
// Vendor set up
gulp.task("vendor", function () {
var b = browserify();
bowerResolve.init(function () {
b.require(bowerResolve('angular'), { expose: 'angular' });
b.require(bowerResolve('lodash'), { expose: 'lodash' });
});
...
});
// Main app build
gulp.task("app", function () {
var b = browserify({entries: ['./app/assets/javascripts/app/module.coffee'],extensions: ['.js', ".coffee"]});
bowerResolve.init(function () {
b.external(bowerResolve('angular'));
b.external(bowerResolve('lodash'));
});
...
});

Testing

Ok so that's your basic set up for browserify, while I appreciate this means running another build task, I think the benefits of using browserify definitely make this work while. Hang on I hear you say, what about writing tests? Well as this is going to take a bit more explaining I am going to split this into my next blog post, so stay tuned!


0 Comment


Got Something to Say?

Don’t use these services? Create an account or