Gulp + Browserify: The Everything Post
Dan Tello, Former Senior Front-End Developer
Article Category:
Posted on
I picked up Gulp and Browserify just days after learning Grunt and Require.js. It was love at first compile.
Updates
Check out Blendid! The ideas and starter files that were born in this post have grown and matured over the last 3 years. Their current iteration has been packaged up into a single, yarn installable dependency!
Gulp Starter continues to evolve! Webpack has replaced Browserify because it's more flexible in the following ways:
- Can output separate bundles out of the box
- Can auto-extract shared dependencies into a separate bundle.
- Can do async module loading
- Generally more powerful, configurable, and extendable
- Heavily used in the React ecosystem (we're doing a lot of React these days)
Browserify is still great But as your compiling and optimizing needs get more complex, you may consider switching.
Gulp Starter has evolved quite a bit since this post! The content below is still solid—just note that minor implementation details may have changed since it was first written. Here are a few:
- auto require tasks in gulp/tasks
- configure tasks with gulp/config.js
- swapped out LiveReload with BrowserSync
Getting Started
One weekend, I decided to really immerse myself in Grunt and RequireJS. Gotta stay up on these things right? Done. Then Monday rolls around, "and just like that Grunt and RequireJS are out, it’s all about Gulp and Browserify now."
(╯°□°)╯︵ ┻━┻
When I was done flipping tables, I set aside my newly acquired Grunt + RequireJS skills, and started over again with Gulp and Browserify to see what all the fuss was about.
You guys. The internet was right. To save you some googling, doc crawling, and trial and error I went through, I've assembled some resources and information I think you'll find helpful in getting started.
┬─┬ノ( º _ ºノ)
Gulp + Browserify starter repo
I've created a Gulp + Browserify starter repo with examples of how to accomplish some common tasks and workflows.
- Compile CoffeeScript (with source maps!)
- Compile Handlebars Templates
- Compile SASS with Compass
- LiveReload
- Browserify-shim:
require
non-CommonJS code, with dependencies - Set up module aliases
- Run a static Node server (with logging)
- Pop open your app in a Browser
- Report Errors through Notification Center
- Image processing
Frequently Asked Questions Wiki
Node, npm, CommonJS Modules, package.json...wat? When I dove into this stuff, much of the documentation out there assumed a familiarity with things with which I was not at familiar all. I've compiled some background knowledge into a FAQ Wiki attached to the above mentioned starter repo to help fill in any knowledge gaps.
- What is a Module?
- What is Node?
- What is npm?
- What is package.json?
- What is Browserify?
- What is Gulp?
Why Gulp is Great
It makes sense.
I picked up Gulp just days after learning Grunt. For whatever reason, I found Gulp to be immediately easier and more enjoyable to work with. The idea of piping a stream a files through different processes makes a lot of sense.
gulp's use of streams and code-over-configuration makes for a simpler and more intuitive build.
- gulpjs.com
Here's what a basic image processing task might look like:
var gulp = require('gulp');
var imagemin = require('gulp-imagemin');
gulp.task('images', function(){
return gulp.src('./src/images/**')
.pipe(imagemin())
.pipe(gulp.dest('./build/images'));
});
First, gulp.src
sucks in a stream of files and gets them ready to be piped through whatever tasks you've made available. In this instance, I'm running all the files through gulp-imagemin
, then outputting them to my build
folder using gulp.dest
. To add additional processing (renaming, resizing, liveReloading, etc.), just tack on more pipes with tasks to run.
Speed!
It's really fast! I just finished building a fairly complex JS app. It handled compiling SASS, CoffeeScript with source maps, Handlebars Templates, and running LiveReload like it was no big deal.
By harnessing the power of node's streams you get fast builds that don't write intermediary files to disk.
- gulpjs.com
This killer way to break up your gulpfile.js
A gulpfile
is what gulp uses to kick things off when you run gulp
. If you're coming from Grunt, it's just like a gruntfile
. After some experimenting, some Pull Request suggestions, and learning how awesome Node/CommonJS modules are (more on that later), I broke out all my tasks into individual files, and came up with this gulpfile.js
. I'm kind of in love with it.
var gulp = require('./gulp')([
'browserify',
'compass',
'images',
'open',
'watch',
'serve'
]);
gulp.task('build', ['browserify', 'compass', 'images']);
gulp.task('default', ['build', 'watch', 'serve', 'open']);
~200 characters. So clean, right? Here's what's happening: I'm requireing a gulp module I've created at ./gulp/index.js
, and am passing it a list of tasks that correspond to task files I've saved in ./gulp/tasks
.
var gulp = require('gulp');
module.exports = function(tasks) {
tasks.forEach(function(name) {
gulp.task(name, require('./tasks/' + name));
});
return gulp;
};
For each task name in the array we're passing to this method, a gulp.task
gets created with that name, and with the method exported by a file of the same name in my ./tasks/
folder. Now that each individual task has been registered, we can use them in the bulk tasks like default
that we defined at the bottom of our gulpfile
.
This makes reusing and setting up tasks on new projects really easy. Check out the starter repo, and read the Gulp docs to learn more.
Why Browserify is Great
"Browserify lets you require('modules') in the browser by bundling up all of your dependencies."
- Browserify.org
Browserify looks at a single JavaScript file, and follows the require
dependency tree, and bundles them into a new file. You can use Browserify on the command line, or through its API in Node (using Gulp in this case).
Basic API example
app.js
var hideElement = require('./hideElement');
hideElement('#some-id');
hideElement.js
var $ = require('jquery');
module.exports = function(selector) {
return $(selector).hide();
};
gulpfile.js
var browserify = require('browserify');
var bundle = browserify('./app.js').bundle()
Running app.js
through Browserify does the following:
- See that
app.js
requireshideElement.js
- See that
hideElement.js
requires a module calledjquery
- Bundles together jQuery, hideElement.js, and app.js into one file, making sure each dependency is available when and where it needs to be.
CommonJS > AMD
Our team had already moved towards module-based js with Require.js and Almond.js, which both are implementations of the AMD module pattern. We loved the organization and benefits of this provided, but...
AMD / RequireJS Modules felt cumbersome and awkward.
require([
'./thing1',
'./thing2',
'./thing3'
], function(thing1, thing2, thing3) {
// Tell the module what to return/export
return function() {
console.log(thing1, thing2, thing3);
};
});
The first time using CommonJS (Node) modules was a breath of fresh air.
var thing1 = require('./thing1');
var thing2 = require('./thing2');
var thing3 = require('./thing3');
// Tell the module what to return/export
module.exports = function() {
console.log(thing1, thing2, thing3);
};
Make sure to read up on how require
calls resolve to files, folders, and node_modules.
Browserify is awesome because Node and NPM are awesome.
Node uses the CommonJS pattern for requiring modules. What really makes it powerful though is the ability to quickly install, update, and manage dependencies with Node Package Manager (npm). Once you've tasted this combination, you'll want that power for always. Browserify is what lets us have it in the browser.
Say you need jQuery. Traditionally, you might open you your browser, find the latest version on jQuery.com, download the file, save it to a vendor folder, then add a script tag to your layout, and let it attach itself to window
as a global object.
With npm and Browserify, all you have to do is this:
Command Line
npm install jquery --save
app.js
var $ = require('jquery');
$('.haters').fadeOut();
This fetches the latest version of jQuery from NPM, and downloads the module into a node_modules folder at the root of your project. The --save
flag automatically adds the package to your dependencies
object in your package.json
file. Now you can require('jquery')
in any file that needs it. The jQuery
object gets exported locally to var $
, instead of globally on window
. This was especially nice when I built a script that needed to live on unknown third party sites that may or may not already have another version of jQuery loaded. The jQuery packaged with my script is completely private to the js that requires it, eliminating the possibility of version conflict issues.
The Power of Transforms
Before bundling your JavaScript, Browserify makes it easy for you to preprocess your files through a number of transforms before including them in the bundle. This is how you'd compile .coffee
or .hbs
files into your bundle as valid JavaScript.
The most common way to do this is by listing your transforms in a browserify.transform
object your package.json
file. Browserify will apply the transforms in the order in which they're listed. This assumes you've npm install
'd them already.
"browserify": {
"transform": ["coffeeify", "hbsfy" ]
},
"devDependencies": {
"browserify": "~3.36.0",
"coffeeify": "~0.6.0",
"hbsfy": "~1.3.2",
"gulp": "~3.6.0",
"vinyl-source-stream": "~0.1.1"
}
Notice that I've listed the transforms under devDependencies
since they're only used for preprocessing, and not in our final javascript output. You can do this automatically by adding the --save-dev
or -D
flag when you install.
npm install someTransformModule --save-dev
Now we can require('./view.coffee')
and require('./template.hbs')
like we would any other javascript file! We can also use the extentions
option with the Browserify API to tell browserify to recognize these extensions, so we don't have to explicitly type them in our require
s.
browserify({ entries: ['./src/javascript/app.coffee'], extensions: ['.coffee', '.hbs'] }) .bundle() ...
See this in action here.
Using them together: Gulp + Browserify
Initially, I started out using the gulp-browserify
plugin. A few weeks later though, Gulp added it to their blacklist. Turns out the plugin was unnecessary - you can can node-browserify API straight up, with a little help from vinyl-source-stream
This just converts the bundle into the type of stream gulp is expecting. Using browserify directly is great because you'll always have access to 100% of the features, as well as the most up-to-date version.
Basic Usage
var browserify = require('browserify');
var gulp = require('gulp');
var source = require('vinyl-source-stream');
gulp.task('browserify', function() {
return browserify('./src/javascript/app.js')
.bundle()
//Pass desired output filename to vinyl-source-stream
.pipe(source('bundle.js'))
// Start piping stream to tasks!
.pipe(gulp.dest('./build/'));
});
Awesome Usage
Take a look at the browserify.js
task and package.json in my starter repo to see how to apply transforms for CoffeeScript and Handlebars, set up non-common js modules and dependencies with browserify-shim
, and handle compile errors through Notification Center. To Learn more about everything else you can do with Browserify, read through the API. I hope you have as much fun with it as I'm having. Enjoy!