With frontend development moving as fast as it does at Bitly, things can get pretty messy. We found ourselves with piles of unmanaged script tags and little indication of what was still being used in the app’s current iteration. There had to be a better way! Enter Browserify! A few months ago, we embarked upon […]
With frontend development moving as fast as it does at Bitly, things can get pretty messy. We found ourselves with piles of unmanaged script tags and little indication of what was still being used in the app’s current iteration. There had to be a better way!
Enter Browserify! A few months ago, we embarked upon a project to totally overhaul the “Your Bitlinks” interface, and with that came the opportunity to rethink our practices and introduce frontend dependency management. Now the page looks as good behind the scenes as it does in your browser!
Browserify is a great tool that lets you write your client-side scripts like it’s node, allowing you to use node’s package management and module systems. Each file should be a module and explicitly
require() all its dependencies. Then you simply give Browserify your main entry point, such as an overarching app file, and the name of a destination file, e.g.
browserify app.js > bundle.js
This example takes your network of dependencies starting at
app.js and streams all the files from your network of dependencies into
(Side note: Being familiar with the node.js environment and NPM, node’s package manager, is essential for picking up Browserify. In the weeks before we started working with Browserify, I wrote and published a node module as a side project. Going through the process, making sure all the pieces were in place— specifically the
package.json— so that I could add it into the NPM registry made figuring out Browserify much easier.)
There was no question in our minds that using a frontend dependency management tool was an important step in our app’s development. We considered the field of options, ultimately settling on Browserify. We didn’t need the async loading functionality of Require.js and appreciated the power of using NPM, leaving Browserify as the natural choice.
We especially noticed that using Browserify helps us reduce the amount of code we’re sending to our users, thanks to the module convention and NPM. Because each module must explicitly
require() its dependencies, dead code is less likely to stick around. Granted, unused
require() statements can still linger in files, but because these statements are conventionally listed at the beginning of the file, it’s easier to catch unused code. Relying on NPM also helps make sure we don’t forget to include code we need. If you don’t
require() a necessary package, you’ll get an error when bundling your components. NPM also takes care of resolving versions; if module foo needs one version of a package and module bar needs another, it’s all figured out for you. Being able to
npm install modules right from NPM’s registry is a cinch, much easier than, say, hunting down the jQuery source code. NPM also updates packages as the author pushes updates to the registry, provided you’ve declared the module in your
package.json file in a way that green-lights new versions using node’s semver syntax. Furthermore, node comes with built-in modules that too can be
require()-ed and therefore bundled. We use the node
url module, which make sense because URLs are a little important to our day-to-day. Even more, you don’t have to worry about include order because if a module needs another module, it’ll
require() it itself, ensuring the necessary code is included before it is ever used.
browserify -t coffeeify app.coffee > bundle.js
Transforms depend heavily on node’s streaming interface, but you don’t need to know much of anything about streams to plug in other people’s transforms (they’re fascinating though; read up here if you’d like). I personally liken transforms to Legos— the ones I need generally already exist and it’s just a matter of rummaging through NPM to find them. Some other transforms we use are:
It all gets even better when combined with Browserify’s
All in all, we were able to get Browserify up and and running pretty smoothly. Granted, our job was much easier because we weren’t trying to migrate much existing code, and thus did not need to translate it to adhere to the CommonJS standard. Still, transitioning to Browserify was no small feat. There were some interesting roadblocks we ran into because of some of our established development practices.
Something we needed to reconcile early on was the fact that Backbone requires Underscore, but we use LoDash for all its extra gadgets. Terin Stock has an excellent solution involving browserify-swap in this blog post. Long story short, we needed to request that
underscore.js itself resolve to LoDash. (Tip: browserify-swap can be a little finicky; make sure you bundle from the directory where
node_modules is located.)
We also heavily use Handlebars, and therefore historically depended on Handlebars helpers. However, the fact that they get concatenated together into one huge file doesn’t stick to the modular mentality we’re striving for. Thus, we decided to forgo the Handlebars helpers lifestyle in favor of utility modules called in our views that preprocess the data before it gets fed to Handlebars. Now, if one view needs a function to format large numbers to have commas, all of the other views don’t necesarily know about that function. This will also ensure that once a helper becomes obsolete, it won’t be included, which is a peril with the usual monolithic Handlebars helper registering system.
Another challenge we faced was that our organizational preferences for our code lead to an extremely nested directory structure. Because of this, remapify has been a great asset. Before using the package, relative paths for
require() statements for files not in
node_modules often needed four or five levels of
../../. This was both hard to parse and hard to figure out right the first time. Remapify allows us to reference files top-down from where we knew they lived, i.e. in our “models” or “views” directories, instead of relative to where they were being required from. We needed to do some forking to fit our exact needs, however.
Well, maybe a little special… but you are too, and you too can use Browserify! If you’re not starting a new project, the most cumbersome part will be likely be making all your modules adhere to Common JS— “node-able” as I like to say. This means using the
module.exports syntaxes and making sure everything is
npm install-ed, and is in a
package.json. From there, you’re just a few keystrokes away from getting Browserify running from the command line!
So, if you’re deciding whether or not to use Browserify, even in production, I would highly recommend it. It’s a bundle of fun, will transform your code, and make your app load(s) better!