In What the Web Platform can learn from Node.js, we explored the benefits of narrowly scoped abstractions created by developers for developers. Let's learn how and why you should bring this same style of development to your own web frontend.
Choose your own adventure
As a user of small modules, if a dependency takes a turn you don't agree
with, you can simply use another. Or maybe your app uses a newer
version of a module (let's say 2.x), while another dependency of
your app uses another (call this 1.x). In Node, since [dependencies
are looked up in neighboring
node_modules directories on up the
e_modules_folders), each can be satisfied even if they require the same
module with differing version requirements. If the version requirements align,
only one copy will be used.
npm modules in the browser? Isn't that a Node thing?
You might be wondering how one could maintain so many dependencies
without thousands of script tags, or even thousands of entries in a
RequireJS config file. Or, maybe you might be wondering how it's even
possible to use a module from npm in the browser to easily create SVG elements.
Modern tooling like Browserify and Webpack
have made this possible by tracing the dependency graph of your app through the same CommonJS
require statement that Node uses. They make each module available to
one another in a large bundle file that you can include with a single
script tag in your page.
Another common question I receive is whether this could bloat the
versions of npm, this tree of modules is de-duplicated to its flattest
form while still providing desired versions to each dependency in your
app. This way, you'll never ship unnecessary copies of an individual
library while the needs of every module are met. There's even a newer
bundler called rollup that uses the ES2015 module
format to only bundle the subsets of the module you
Most folks I know are horrified by the idea of shipping multiple
copies of jQuery to the browser. And yes, that would be horrible,
because even minified and gzipped, jQuery is still about 30
kilobytes. But shipping two, three or even four copies of a 2KB
library is negligible in comparison, especially if it saves you
from manually resolving dependencies and upgrading jQuery along
with your installed plugins in lockstep.
Even so, this would only occur if your app includes modules that depend on many
incompatible versions of such dependencies, since [npm version 3
deduplicates and flattens the modules directory as much as possible by
g-deal-for-windows/). Oh, and it also puts the majority of the npm
registry's 100,000+ modules at your disposal with a simple
Where to draw the line
But first, some terminology: a package is a unit that can be published, for example, to the npm registry or consumable through a git dependency. In CommonJS, however, modules map one-to-one with files. Therefore, a package can contain many modules, but often an npm package is itself a single module.
Deciding a package's responsibility is arguably the most difficult part of authoring one. If a package's scope is too large, breaking changes become far too common and their consequences can ripple throughout the ecosystem. Likewise, if a package has many dependents, breaking changes and bugs can lead to a cascading series of updates throughout the system, whether open source or internal. An excellent principle to fall back on when designing the scope of a package is that of software component cohesion. Essentially, if components change together, they belong together in the same package. If not, extract away!
Yes, you can try this at home
Building your app from small modules is a lot easier than you might think. Your app probably already has a collection of abstractions, and it's often deciding which deserve their own packages that is the difficult part. Firstly, if you only abstract the platform and provide a general-purpose facade, you're best off providing an open source package. Services like GitHub and Bitbucket are great for this, and if you're using Node or the browser, you should certainly publish your work to the public npm registry. Of course, other language ecosystems have their own package management solutions.
If your app provides reusable abstractions for internal business
logic, such as a wrapper for an internal service or API, others
within your organization can benefit greatly from an independent
services like reporting and analytics. There's even a general-purpose
package for kickstarting an implementation of Atlassian
Connect in a new product. For managing
source code, I'd recommend a service that doesn't charge you on a
per-repository basis so that you can create an internal ecosystem of
many small modules. Both Bitbucket Cloud
and Bitbucket Server
scale with the size of your team, not how you decide to structure
your software into repositories. When it comes to publishing
your packages, npm offers private modules on its cloud
service and a self-hosted
offering to complement how you might
manage your source code repositories. There's even a convenient shorthand
to install a npm module straight from a Bitbucket Cloud repository: just run
npm install bitbucket:user/repo and you're all set.
Once your small modules have found a home, you're free to iterate on their designs and compose together others to build higher and higher levels of abstraction. Oh, and you can fearlessly (but not carelessly!) break APIs since modern tooling and Semantic Versioning ensure that consumers opt into these kinds of changes, all while rapidly improving upon what you do best. That's what change is all about.