Getting started with Browserify

Bring Node modules to the browser with Browserify

Author's image
Tamás Sallai
10 mins

Browserify bundles your code and brings all the power of Node to the browser. It not only lets you require files, but also provides a range of Node libraries for a seamless front-end dev experience. If you've used to write code for the server-side, you'll find it familiar.

Install

The easiest way to get started is to install globally with NPM:

npm install -g browserify

(Note: You might need sudo)

Then create an index.html, with a reference to the bundle.js Browserify will build:

<!doctype html>
<html>
    <body>
        <script src="bundle.js"></script>
    </body>
</html>

Finally, an app.js, that will be packaged:

console.log("Hello world!");

To bundle it, use the global Browserify install, specify the main entry point and the output file:

browserify app.js -o bundle.js

This creates the bundle.js, that is referenced in the index.html. Host the files in an HTTP server of your choice, and verify it works.

Add a dependency

Let's modify our Hello world to use some third-party library! In this example, lodash is used to transform a map in a cross-browser way.

const _ = require("lodash");

console.log(_.map([1, 2], (i) => i + 1));

Note the Node-style require call. Browserify supports the CommonJS module format, the same you use on the server.

Building the app with browserify app.js -o bundle.js raises an error. Unmet dependencies are compile-time errors, rather than run-time ones.

Browserify supports the NPM dependency resolution process out of the box, therefore installing lodash is done with NPM:

npm install lodash

The bundle call browserify app.js -o bundle.js will succeed this time, and the code works.

Since Browserify supports CommonJS, it allows breaking the app into modules.

For a simple example, a list.js can export a value:

module.exports = [5, 6];

And the app.js can import and use it, just like any other module:

const _ = require("lodash");
const list = require("./list");

console.log(_.map(list, (i) => i + 1));

Integrate with NPM

Unlike JSPM, Browserify does not require using its own CLI tools to manage dependencies; everything is managed by NPM. This makes it easy to integrate into NPM scripts, as you only use a handful (ideally two, one for production and one for development) of scripts.

The first step is to init an NPM package. There is a built-in next-next-finish-style wizard you can use:

npm init

After you have a package, npm install <library> --save will add the library to the dependencies section in the package.json. Use --save-dev to install to the devDependencies section, as it's compile-time.

Add browserify as a dependency to the package.json:

"devDependencies": {
	"browserify": "13.0.1"
	...
},

Then add a build script that calls Browserify to produce the bundle.js:

"scripts": {
	"build": "browserify app.js -o bundle.js",
	...
},

After this, npm run build will regenerate the bundle.js. The upside is that Browserify is no longer needed globally, it is managed on a per-project basis. Any developer who has NPM set up can check out your code, run npm install followed by npm run build, and they have a freshly-built bundle.

Development

Constant manual rebuilds are tedious and hinder development. There are some tools that add automatic watch and rebuild functionality to Browserify, one such is watchify (note that most Browserify tools end with -ify).

To install it, list it as a dependency (or devDependency) in the package.json, then run npm install.

"devDependencies": {
	"watchify": "3.8.0"
	...
},

Then add a new script that will be the "dev mode" from now on:

"scripts": {
	"dev": "watchify app.js -v -o bundle.js"
	...
},

(Note: With -v, watchify is going to print a message to the console for each rebuild. It's quite useful to see if it correctly picks up the changes)

Then npm run dev can be used during development, as it monitors changes to the app.js as well as its dependencies and automatically rebuilds the bundle. It is suitable for development, as you only need to reload the browser.

To further aid the development, add the --debug flag which includes source maps into the bundle. It's super useful.

"scripts": {
	"dev": "watchify app.js --debug -v -o bundle.js"
	...
},

Nodejs goodness

A distinctive feature of Browserify is that it replaces some Node-only libraries and values with browser-compatible counterparts. As a result, code written specifically for the server-side might work in the browser.

For example, the Buffer module, which is missing from the browsers, is usable:

const buffer = require("buffer").Buffer;

console.log(buffer.from("hello world").toString("hex"));

There are quite a few modules supported. This gives a good chance of cross-compiling server-side modules.

Apart from modules, some node-only variables are also supported. One such is __filename, which contains the currently executing file.

As an illustration, this prints the js file it is defined in:

console.log(__filename);

The __dirname also works.

Add transforms

Transforms are the primary way to add functionality to Browserify. If you need to support something, first take a look at the list of transforms, then google around at GitHub; it's likely that you will find a suitable one.

Transforms are installed via NPM, therefore you can simply list them in the package.json. As an illustration, let's configure the versionify transform, which replaces a placeholder string with the package version.

To install, add it to the devDependencies section, then run npm install:

"devDependencies": {
	"browserify-versionify": "1.0.6"
	...
},

After the transform is successfully installed, use -t from the CLI to run it during the Browserify build: -t browserify-versionify.

The default placeholder is __VERSION__. To test it, simply print the string:

console.log("__VERSION__");

Transforms can also be configured. In this case, the placeholder string is a variable. To pass configuration via the CLI, use the array notation; for example to change the placeholder, use -t [ browserify-versionify --placeholder __NPMVER__ ]. This changes the placeholder to __NPMVER__.

The downside of using the CLI to add transforms is that you need to add them to all your NPM scripts. Since it's likely that all transforms are needed for all the scripts, it's better not to copy-paste the config, but define them in a single place.

Fortunately, Browserify can be configured using the browserify property in the package.json. Transforms, along with their configuration, can be added there, and all scripts will use them as defaults.

To add the versionify transform with a custom placeholder, add the following to the package.json:

"browserify": {
	"transform": [
			["browserify-versionify", {"placeholder": "__NPMVER__"}]
	]
},

CSS & SCSS

CSS and SCSS support can be added with the scssify transform. To install, list it as a dependency in the package.json, then run npm install:

"devDependencies": {
	"scssify": "2.2.1"
	...
},

Then add the transform to the browserify property in the package.json:

"browserify": {
	"transform": [
		"scssify"
	]
},

Now that the configuration is ready, let's add some CSS. Create a style.css with some basic but easily-recognisable content:

body {
    background-color: red;
}

Then require it from the app.js:

require("./style.css");

After a build and a refresh, the background is red, indicating that the CSS is loaded.

To demonstrate SCSS support, modify the stylesheet and rename it to style.scss:

$color: blue;

body {
    background-color: $color;
}

And modify the app.js to require the new file:

require("./style.scss");

After a rebuild and refresh, the background is now blue, indicating the SCSS compilation is configured.

Bootstrap

In order to integrate Bootstrap, add it as a dependency to the package.json, then run npm install:

"devDependencies": {
	"bootstrap": "3.3.7"
	...
},

To add the Bootstrap CSS, reference it using a relative path:

require("./node_modules/bootstrap/dist/css/bootstrap.css");

Unfortunately, Browserify does not package assets, so fonts are still referenced at /fonts. In effect, you need to serve them from under the node_modules directory, which requires some server-side config.

Bootstrap javascript requires jQuery to be present in the global scope, so the first step is to install that, by adding "jquery": "3.1.1" as a dependency and then run npm install.

From there, you have two options. The first is to require jQuery and attach it to the global scope manually:

window.jQuery = $ = require("jquery");

require("bootstrap");

The other one is to use browserify-shim to do this automagically wherever it's needed:

"devDependencies": {
	"browserify-shim": "3.8.12",
	...
},
...
"browserify": {
	"transform": [
		"browserify-shim",
		...
	]
},
...
"browserify-shim": {
	"./node_modules/bootstrap/dist/js/bootstrap.js": {"depends": ["jquery:jQuery"]}
},

Then use it as:

require('./node_modules/bootstrap/dist/js/bootstrap.js');

This takes care of putting jQuery to the global scope whenever the bootstrap.js is needed. The downside is that it still pollutes the global scope with jQuery, so it's still far from ideal.

Package to production

Browserify does not do any production packaging; you can deploy the same bundle.js you use during development. On top of that, you can add any third-party minification, cache busting, and obfuscation as you'd like.

One notable exception is uglifyify, which can minify individual modules and not just the whole bundle, and it can save a few bytes on conditional requires. To minimize the code size, you should use this transform as part of the production build, and also use UglifyJS on the final bundle.

On a side note, keep in mind that UglifyJS does not support the new language features of ES6. Therefore, if you use any of them, you'll need a transpiler like Babel to process it.

Pros

There is a vibrant community around Browserify, and it results in many transforms. Basically, if your use case is not that special, it's likely that there is a project for that already.

It's easy to get started. Just a few lines of configuration and you can use all the goodness of CommonJS, which is a great step towards modularized applications.

It also integrates with NPM. Apart from the bundling, you don't need to use the Browserify CLI, and all dependencies are managed solely by NPM. With just a few simple scripts, you can make the build fully transparent, which in turn eases developer onboarding.

And finally, it can bring Node-only modules to the browser. If you have modules you want to reuse, it is possible to simply browserify them. There are many limitations, but it's a chance that might save you a considerable amount of time.

Cons

While Browserify was the leading bundler in the JS ecosystem, its usage is declining now. It is still a major player, and it is unlikely to go away in the near future, but it's losing ground every month.

The handling of static resources is far from complete. It packages CSS with a transform, but images, fonts, and other static assets are not bundled; you need to take special care to handle them yourself.

And finally, only CommonJS is supported out of the box. This might not seem a big drawback, but third-party projects might not support it. Configure Babel if you need other module formats.

Conclusion

It's not a coincidence that Browserify was the leading bundler for years. It solves the modularization problem nicely and follows the best practices from Node development. It's widely supported by a great community, and it stood the test of time.

But taking into account its declining popularity and the alternatives, I wouldn't choose it for my next project. But if you use it, there is no need to migrate your projects. It will serve you well in the times to come.

January 31, 2017
In this article