Browserify vs. Webpack
Note: This is a very old article (2014) — everything here is likely outdated, so you should read this with a huge grain of salt.
It’s amazing, and slightly terrifying, how the landscape of JavaScript and its tooling can change in the blink of an eye. But, right now, we have a pretty good thing going with npm and browserify. We get thousands of useful functions and modules that compose well together, promote the Unix philosophy of small and decoupled tools, and blur the lines between “browser” and “client” (e.g. see headless-gl). And, amazingly, we can utilize all of these modules without worrying about paths, version hell, or cumbersome configuration files.
Webpack seems like an amazing tool, and does present some great features (hot module replacement, code splitting, sync vs. async requires, etc). However, it does not promote code re-use in the way that NPM and Browserify does. It encourages a “whole new way” of writing modules and often requires manually-maintained config files. This is essentially a step backward: it may create further fragmentation in npm or perhaps discourage new code from being published to npm altogether.
To explain some of the issues, take the simple example of inlining a JSON file into your browser bundle.
require overloading #
The Webpack-style approach to inlining a JSON file would be to use a loader. First, npm install json-loader --save-dev
, then you can use it in your code:
var version = require("json!./package.json").version;
Generally I’m wary of require()
overloading; the function has a specific purpose in Node and changing that can lead to confusion and errors down the road. But it seems to be working pretty well for Webpack; plus it has some nice features like emitting images to an output directory.
The problem is, this code is needlessly incompatible with Node and browserify, and doesn’t encourage re-usability via npm. Authoring modules with Webpack-style requires will lead to fragmentation and inevitable “fork-and-patch” situations for Node and browserify users.
node compatibility #
So, let’s try for something more compatible. In Node and browserify, we can just require it explicitly, like so:
var version = require('./package.json').version;
Unfortunately, Webpack does not support this by default (and maybe never will), so you might find a lot of NPM modules breaking without defining a JSON loader in your config.
To consume npm and browserify modules with the above code, or to require JSON in a node-compatible way, you’ll need to add a webpack.config.js
file like so:
module.exports = {
context: __dirname,
entry: "./index.js", //entry point
module: {
loaders: [
{
test: /\.json$/,
loader: "json-loader"
}
]
}
}
using brfs #
Another common way to parse a JSON file in Node/Browserify is to use fs.readFileSync
and the brfs
transform. Your code looks like this:
var fs = require('fs');
var file = fs.readFileSync('./package.json', 'utf8');
var version = JSON.parse(file).version;
Lots of modules use readFileSync
to inline HTML and CSS (e.g. soundcloud-badge), templates, GLSL and other textual files into their bundles. Often, the module is compatible with node: see browser-menu, for example.
Unfortunately; Webpack isn’t be able to consume these modules unless you set up a transform-loader in your config.
If you have a large dependency tree of Browserify modules, it may be hard to know which transforms they are all using, and annoying to have to set them all up manually in your Webpack config files. It becomes a big ordeal if dependencies are using different transform arguments between versions.
package config #
An important feature of Browserify is the way it handles transforms across dependencies. Let’s say I’ve authored a module called foo
in npm that utilizes brfs
like in the above code. I can mark brfs
as a dependency and list it under browserify-transform in my package.json
.
Then, somebody else can install and require('foo')
, and when they browserify their main application, it all “just works.” When resolving foo
, browserify will know to apply the brfs
transform, even though the user didn’t explicitly set it up.
This allows a browserify build to resolve dependencies correctly, regardless of what transforms your dependencies are using.
Without this feature for loaders/transforms in Webpack, code-reuse is going to be a nightmare across many dependencies that are using different loaders. Issue: #378.
final thoughts… #
Webpack is still a young tool; hopefully these issues can be resolved so that we aren’t throwing away all the awesome progress we’ve made so far with npm/browserify.
In the mean time, I’ll stick with browserify. :)