Last month I talked about one of my favorite tools for JavaScript on the front end, Browserify, which allows you to create modular code for the browser using CommonJS modules and npm. It does this by combining the dependencies into an all-in-one bundle. In development, typically you will watch your JavaScript files for changes and then recompile the bundle.
If you’re including some large dependencies in your bundle, though, you may have noticed that it can take several seconds to regenerate that bundle each time you edit a module. This usually isn’t a big problem, but when you want to rapidly hack on a UI feature and frequently refresh to see your changes this several second display can end up seriously slowing you down.
One of the most effective things you can do to speed up this process is build multiple bundles - one for your application code and one for your dependencies. When your application code changes you only have to recompile that bundle, not all of your dependencies.
On the command line
If you’re using the command line interface, creating an additional bundle is as easy as invoking browserify with the –require option. You don’t have to point it at a source file, you can simply pass -r my-module for each module you want to include in the bundle. This will pull the requirement into the bundle and make that requirement available from outside the bundle with require('my-module')
.
$ browserify -r react -r q -r loglevel > build/vendor.js
If you include vendor.js from the example in your HTML, you can then require('react')
or require('q')
from anywhere. You may notice, however, that your application bundle is still pulling these requirements in, defeating the whole purpose. This brings us to the next command line option we need to use, –external.
The –external option tells browserify that the given module will be provided externally and doesn’t need to be included in this bundle. When used with your application bundle, it will filter out the dependencies that you will be compiling into your vendor bundle.
$ browserify -x react -x q -x loglevel src/index.js > build/app.js
Make sure to include the vendor.js file before app.js in your HTML, so that the dependencies will be available when app.js is loaded.
*** Update: watchify
which can be invoked with its companions gulp-watchify
and grunt-browserify
are now the recommended way to watch browserify bundles. The rest of this post details using watch
together with browserify
which will result in slower builds and more complexity.
With Gulp
Gulp has recently overtaken Grunt as our task manager of choice. We typically set Gulp up with our major tasks in a gulp/ subfolder, and then we require each of those modules in our gulpfile.js:
The vendor bundle
I’ll start with our vendor bundle, and break it up to explain a few things.
I assign an array of dependency names to a variable, because we’ll be using this in a couple of places.
I’m using the NODE_ENV environment variable to determine whether this is a production build or not.
Since Gulp is a file-based build system, it needs a file to open the stream with. We don’t really need this on the vendor bundle, so my workaround is to point it to an empty file called noop.js.
This is where the magic happens. The gulp-browserify plugin doesn’t have an option to handle the –require command, so I simply listen for the “prebundle” event that it sends, and interact with browserify’s API directly. The bundle.require()
method is documented here. I iterate over the list of dependencies, and call bundle.require()
for each one.
The rest of the task is pretty basic. I minify it if this is a production build, give the bundle an appropriate name, and save it to the build directory. I assign the list of dependencies to exports.libs
so that it will be available to other modules, like our application bundle.
The application bundle
The application bundle follows a very similar pattern:
I import the list of dependencies that I exported from the vendor bundle with require('./vendor').libs
.
I include some settings for browserify, including a transform and additional extension definition for working with “.jsx” modules from React.
Just as I did with the vendor bundle, I iterate over the list of dependencies. This time, however, I use bundle.external()
. It’s documented (briefly) here.
The rest of the task is identical to the vendor bundle.
Watching for changes
Now, when something changes I can rebuild only the affected bundle. Here’s an example of my watch.js:
It’s a little verbose, because I’m using the “change” event in order to start the task and then trigger a liveReload as a callback. It works great, though! On one of our applications, the vendor bundle takes 5 or 6 seconds to compile, but the app bundle takes less than a second. This makes active development quite speedy!
With Grunt
For Grunt, we like the load-grunt-config plugin that loads configuration blocks from modules in a grunt/ folder, similar to how we’re handling Gulp above.
The browserify task
Both the app and vendor bundles are configured inside the browserify.js task file:
The app bundle uses the “external” config option from grunt-browserify. I add the app bundle settings as the default, top-level options because I sometimes have more than one bundle that uses very similar settings. I don’t add the dependencies to an array, because - as you’ll see in the next step - I can’t reuse the array.
Here’s the configuration for the vendor bundle:
The bundle.require()
API is exposed through grunt-browserify‘s “alias” configuration. The reason I can’t reuse the array is because the plugin uses a colon to separate the module name from an optional alias (which corresponds to the “expose” property from browserify’s bundle.require()
method).
The watch task
The watch task uses grunt-contrib-watch, and the configuration is quite simple:
Now you’re ready to hack away, and your app bundle can be regenerated in a fraction of the time it took before!