I have noticed lately that all my projects (and there are a lot of them, see for yourself at glebbahmutov.com) can be classified into 3 large groups. This division has evolved naturally over time, but was strongly influenced by the following two ideas
- One-line node modules by Sindre Sorhus
- The Twelve-factor app from Heroku engineers
Single function modules
The great majority of my repositories published on NPM are modules that export a single function. Small partial application utilities like spots or larger pieces like ng-describe - they all have a tiny user surface API - just a single function.
I do think about the function's signature design a lot, trying to make
a function both powerful and simple to use. If possible, I prefer exporting a function with
a valid toString()
representation. Anyone who does not have immediate access to the documentation
(check out manpm) can still run fn.toString()
to see the source code! For example, imagine I am writing a module called add-one
and all
it does is exports a function that returns the sum argument + 1
.
I could just take a regular addition function and partially apply first argument using
lodash.partial
1 | // BAD |
If I am using this function, I have to use the documentation to find how it works. I cannot simply look at the code
1 | // user code |
We are getting the wrapper source code instead of add1
logic. Here is how I would have exported
the function for easy user inspection.
1 | // GOOD |
A user can get meaningful information now
1 | // user code |
Modules with a single function are simple to write, test and modify. When the API changes, or a bug is fixed, we can use the semantic release in pretty much automated mode to avoid breaking the existing users.
Flat libraries
My next set of tools are collections of related functions in a flat library. A flat library has every function available right away usually as a property on the exported object. Good examples are my collections of utilities to deal with NPM npm-tools and Git ggit.
Whenever one returns an object with methods in JavaScript, one must be careful not to use this
variable inside the methods. Otherwise the user has to call the method using the object reference,
or perform context binding, and that is extra thing the user does not need to worry.
For example, if we export an object with add
and add1
methods, I don't like using this.add
from inside the add1
method
1 | // BAD - adder module |
Instead, I prefer to export an object that only refers to plain functions.
1 | // GOOD - adder module |
Flat libraries of functions are simple to test, use and even document, check out xplain
Applications
The last type of modules I publish are (mostly CLI) applications. Ideally, these application
are just thin argument parsers on top of other modules (flat libraries and single functions).
I try to keep the number of source files inside the application small and flat. If the number
of source files inside src
folder has grown to more than five files, it is a red flag -
time to factor out some code to its own separate module.
Of course, sometimes I break my own and The Twelve-factor app rules.
Most often this happens when I have a working application and then I come up with another
application that scales it up. For example, my dependency testing and upgrade CLI
application next-update deals with a single
package at a time.
I wrote next-updater that can update
multiple packages, for example all my NPM modules one after another. So I placed
the main functionality in next-update
into main: index.js
file and then used it
as a dependency in the next-updater
module. This goes against the 12 factor
Codebase principle because we build both the app and the
library from the same code base.
Eventually I will refactor the common feature into its own module
(and this module will probably export a single function!)
There a couple of utilities I use a lot in my applications
- Very simple sanity check simple-bin-help that includes checking for available updates
- Verbose logging for debug purposes using debug or if I want Markdown support debug-logdown
- Simple ES6 features that can run without transpiling on Node 4