Watchify and Grunt
One of the things that inevitably happens as your Browserify project gets large is that your build starts becoming slow. What starts out as a one-second build starts taking four to five to ten to fifteen seconds to build as you add more libraries and modules. This really can slow down your development process, as the edit → save → rebuild → livereload → debug → edit cycle starts taking longer and longer.
Enter Watchify
Luckily there is a tool called Watchify designed to work around this. It essentially is a tool that caches the incremental results of Browserify, sets up a watcher for every file in the dependency graph of your app, and quickly rebuilds when any of those files changes. A five-second Browserify build can be rebuilt in 100 milliseconds if only a single file changes.
What does this look like? Lets take the command from the end of my last article:
$ browserify app/lib/lib.js app/main.js -t browserify-shim -r lodash -o dist/js/app.js
After you npm install -g watchify
, this simply becomes:
$ watchify app/lib/lib.js app/main.js -t browserify-shim -r lodash -o dist/js/app.js
Watchify has the exact same interface as Browserify. When it starts up, it will do the lengthy full-build process, but touch
one of your source files in another terminal, and the rebuild will be instantaneous in comparison — usually around 100ms.
You'll notice that I'm only building a single bundle, rather than the lib/main build I've described in past articles. The only reason I recommended the dual-bundle strategy in the past was to shorten the rebuild step in the development cycle. However, Watchify is fast enough that the dual-bundle strategy is no longer necessary. This greatly streamlines your build configuration.
Integrating with Grunt
This is great and good, but how would you integrate Watchify in the Grunt workflow? Simple. First of all, Watchify is already included in grunt-browserify 2.x, so all you have to do is add a watch: true
flag to the options.
With a dual-bundle strategy, you may have had a browserify config that looked something like this:
//...
browserify: {
lib: {
files: {
"tmp/lib.browserify.js": ["app/vendor/lib.js"]
},
options: {
transform: ["browserify-shim"],
require: sharedModules
}
},
main: {
files: {
"tmp/main.browserify.js": ["app/main.js"]
},
options: {
external: sharedModules
}
}
}
//..
The require
/external
juggling between bundles no longer has to happen, so this can be condensed to:
browserify: {
all: {
files: {
"public/app.browserify.js": ["app/vendor/lib.js", "app/main.js"]
},
options: {
transform: ["browserify-shim"],
watch: true
}
}
}
Much cleaner. Watchify will play nicely with transforms, such as browserify-shim
or es6ify
. It's also fast enough that we don't have to worry about browserify-shim
being run on every module. Also, source maps will be created properly as well. Normally source maps would be lost in the concat
step with a dual-bundle strategy, but since there is now a single bundle that information doesn't get wiped out.
grunt-contrib-watch
You will have to remove the existing watch configuration for browserify. grunt-browserify
with watch: true
will handle file-system watching.
grunt.initConfig({
watch: {
lint: {
files: ["lib/**/*.js", "test/**/*.js"],
tasks: ["jshint"]
},
livereload: {
files: ["public/**/*"]
options: {
livereload: true;
}
}
// no configuration for browserify
//...
},
connect: { // local server with livereload support
all: {
options: {
base: "public",
livereload: true
}
}
}
//...
});
However, the programmatic interface to watchify (that grunt-browserify uses) will only stay running as long as the parent process (in this case Grunt) stays open. You will need to rely on grunt-contrib-watch
to keep the process running. To accomplish this, just run the browserify
before you run the usual watch
task.
$ grunt browserify watch
I usually create a dev
task that handles all of this, so you only have to type grunt dev
to kick everything off.
grunt.registerTask("dev", ["build_non_js", "browserify", "connect", "watch"])
Doing a one-off build requires no configuration — watchify will just do its initial build, then exit.
$ grunt browserify
Closing thoughts
I am still in awe at how fast watchify is, even after using it for a month. I'll reiterate that the dual-bundle strategy as described in past articles is now pointless. It's much better to use a single bundle now.
Another observation: CommonJS/Browserify's one objective weakness as compared to something like RequireJS/AMD is that it does require a build step. With RequireJS, you can simply define a base directory, and all of your individual files can be loaded without any transformation. Just Ctrl+S, Alt+Tab, and Ctrl+R to test out a javascript change — very fast, very convenient. However, now that a Browserify bundle can be rebuilt in 100 milliseconds, and can be coupled with a livereload setup, that advantage is not as great as it used to be...
Enjoy your sub-second rebuilds!