What your frontend app can learn from Node.js

The practicality of small modules

November 19th 2015 Will Binns-Smith in Node.js, Bitbucket

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 filesystem, 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 payload size of the JavaScript delivered to the browser. In newer 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 import.

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 default. Oh, and it also puts the majority of the npm registry's 100,000+ modules at your disposal with a simple npm install.

tools like browserify make thousands of small modules on npm available to your frontend

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!

Keep in mind that with npm and most package managers, one package doesn't necessarily require one dedicated repository. If the burden of many pull requests impedes the flow of publishing new modules, consider creating a monorepo while still publishing individual packages. Babel, an open source JavaScript compiler, uses this to great effect by maintaining more than one hundred packages in a single repository, while still publishing each as its own package to npm.

It's worth noting that a limitation of Bower, another JavaScript package manager, is that it uses git repositories (or tarballs) as a means of retrieving a module's code, so it requires a repository for every package. My personal advice? Use npm.

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 package. At Atlassian, we have many small JavaScript clients for accessing 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.

Have thoughts on small modules? Want to sing their praises or have second thoughts? Reply below, ping me at @wbinnssmith on Twitter, or contact me via wbinnssmith.com.

Many thanks to my fellow Atlassians who reviewed and provided feedback for this series: Tim Pettersen, Chris Darroch, Travis Smith, Marcin Szczepanski, Trey Shugart, and Jon Mooring.

And an equally special shout-out to my small module heroes, Matt DesLauriers and Sindre Sorhus for their review, input, and overall inspiration for this series.