Something interesting is happening: developers are abandoning Bower and installing frontend dependencies with npm. This seems wrong—npm was designed for Node.js, and browser code has different constraints. But the trend is real, and understanding why reveals tensions in how we think about frontend packaging.
The Two Package Managers Problem
For the past two years, the standard workflow has been:
- npm for build tools (Grunt, Gulp, testing frameworks)
- Bower for frontend libraries (jQuery, Bootstrap, Angular)
This separation made sense. npm's nested dependency trees work for Node's require() but not for browsers with a single global scope. Bower's flat trees match browser constraints.
But maintaining two package managers is friction. Two config files (package.json and bower.json), two commands, two mental models. When a library exists in both registries, which do you use? When versions diverge, how do you reconcile them?
Developers are increasingly choosing npm for everything, accepting the impedance mismatch rather than managing two systems.
Why npm is Winning
Several factors are pushing frontend toward npm:
The registry is larger. npm has ~65,000 packages. Bower has ~16,000. If you need a library, it's more likely on npm. This network effect is self-reinforcing.
Build tools are already using it. Your Gulpfile requires npm packages. Your test setup requires npm. Why add Bower just for runtime dependencies?
CommonJS works with browserify. Tools like browserify let you require() npm modules in browser code. This unifies the module story—same syntax, same packages, same mental model.
The ecosystem is more active. npm packages update frequently. Many libraries publish to npm first, Bower second or never. Being on npm means faster access to updates.
One lockfile, one set of scripts. Having all dependencies in package.json simplifies scripts, CI configuration, and dependency management.
The Nested Dependencies Problem
But npm's nested dependencies remain problematic for browsers:
node_modules/
angular/
node_modules/
[email protected]/
bootstrap/
node_modules/
[email protected]/
Two jQuery versions? In Node, this works—each module gets its own version. In browsers, you load scripts globally. Having multiple versions causes conflicts.
Bower's flat tree forces you to resolve this:
bower_components/
angular/
bootstrap/
[email protected]/ (you chose which version)
npm users solve this with tools:
- browserify bundles everything
- dedupe commands flatten when possible
- peerDependencies declare "I need this globally"
These workarounds work, but they're workarounds. Bower's constraint—single version per package—isn't a limitation, it's matching reality.
The Browserify Shift
What's enabling npm for frontend is browserify and similar tools. Instead of loading scripts globally, you write CommonJS:
var $ = require('jquery');
var angular = require('angular');
module.exports = MyModule;
Browserify crawls requires, bundles everything, handles nested dependencies. You deploy one JavaScript file.
This is elegant for developers: one module syntax everywhere. But it's not without cost:
- Build step is now mandatory
- Bundle sizes can bloat
- Debugging requires source maps
- Breaking changes in nested dependencies are opaque
For complex applications, these costs are worth it. For simple sites, adding a build tool to load jQuery seems excessive.
The Module System Gap
The underlying issue is that browsers don't have a native module system. We're working around this with tools:
- AMD (RequireJS) – modules in browsers, verbose syntax
- CommonJS (Node/browserify) – simple syntax, needs bundling
- Bower – avoids the problem by not doing modules
ES6 modules are coming, but they're not here yet. When they land, they'll have import/export syntax and work natively in browsers. At that point, the package management story might change.
Until then, we're using tools that weren't designed for browsers (npm) or tools that avoid the hard problems (Bower).
When npm Makes Sense for Frontend
npm for frontend works well when:
- You're building a bundled SPA
- You're using browserify or webpack
- You want unified tooling
- Your team already knows npm
Bower still makes sense when:
- You're loading scripts globally
- You need flat dependencies
- You're building traditional multi-page sites
- You want minimal build process
There's no universal answer. It depends on your architecture and trade-offs.
The Bigger Trend
What's interesting is the direction: toward unified packaging, common module syntax, and treating frontend like backend code. We're applying server-side patterns to browsers—module systems, package management, build pipelines.
This is both progress and complexity. The tools get more powerful, but the learning curve steepens. A developer who just wants to add jQuery to a page now needs to understand npm, browserify, and bundling.
Is this better? For complex applications, yes. For simple sites, it's over-engineering. The challenge is that tools and best practices tend to assume complex applications, leaving simpler use cases poorly served.
The peerDependencies Compromise
npm added peerDependencies to address the global dependency problem:
{
"peerDependencies": {
"jquery": "^2.0.0"
}
}
This says "I need jQuery 2.x available, but I won't install it—that's your responsibility." It's acknowledging that some dependencies must be singletons.
This is npm meeting Bower halfway—maintaining nested dependencies for most things but allowing flat dependencies where needed. Whether it's elegant or a hack depends on your perspective.
Looking Forward
My prediction: npm becomes the dominant package manager for both frontend and backend. Bower's simplicity is appealing, but network effects favor npm. Tools like webpack and browserify will get better at handling browser constraints.
The module system question remains open. ES6 modules might change everything when browsers support them natively. Or we might continue bundling indefinitely because optimization trumps native support.
For now, the ecosystem is transitioning. Both npm and Bower work, but momentum is toward npm for everything. Whether that's progress or just consolidation around one tool's limitations is a judgment call.
The accidental frontend package manager is becoming the intentional one. That's pragmatism more than design, but pragmatism often wins.
Resources:
- npm – Official registry
- browserify – CommonJS for browsers
- Bower vs npm discussion – Community debate
- peerDependencies – Handling shared dependencies