Dear Node module developers, do not use caret ranges
I mean it. Bugs will creep in in the most unexpected ways, and they will bite. An example ? A few days ago we started using gulp-watch in the build process of a project in our company where we already used gulp.
The following day a colleague complained that gulp-watch broke his build: “Hey you broke everything, this stupid program now copies the files to the wrong folder”. This was surprising, since the changes had been tested. Even more strange, the same build process on the same source code would work on a machine, but not on the other. Since the most relevant difference between the two machines was the operating system (one had Windows, the other Linux), we decided that maybe it was a bug in the code handling Windows paths, and we delved in the library’s code.
A little after we started our bug hunt, we found a little surprise: one of the libraries used was completely different between the two machines. How come ?
Checking the library dependencies gave the obvious response.
gulp-watch 3.0.0
depends on path2glob 0.0.2
, which in turn depends on glob ^4.0.5
.
A quick note for those not familiar with NPM (which is used by gulp and gulp-watch): NPM handle dependencies using Semantic Versioning. According to it, versions are specified using a triple of numbers … In particular when a module specifies a dependency with a caret, it means it will accept any revision greater or equal than the one specified as long as it has the same same major version number. In our case ^4.0.5 means >= 4.0.5 and < 5.0.0. In the end, one machine (the working one) ended up with version 4.2.2, the other one with 4.3.0. In theory if the module correctly follows the rules of
Semantic Versioning this shouldn’t make any difference: a change in the minor version means that only backward compatible changes are being introduced leaving the old API unaffected.
Unfortunately, if you are like me, when you’re introducing new features you’re also introducing new bugs.
Automatic and manual testing may help reducing the risk, but sooner or later they will find their path into a release. But in our case the problem was a bit more subtle, and hid deeper in the dependency tree.
glob 4.2.5
depends on minimatch ^1.0.0
while glob 4.3.0
depends on minimatch ^2.0.0
, and there we found our problem. We realized that by that time the error was already fixed, we blasted away our node_modules and executed again npm install. We received glob 4.3.1
together with the fixed minimatch 2.0.1
.
A couple of lessons learned
For us: the same set of dependencies may install different libraries, even when all the dependencies are specified with an exact version. In hindsight this is obvious, but it seems it can be forgotten :). When a problem appears only on a specific machine, it may be necessary to check indirect dependencies, or refetch the whole set of dependencies.
For module developers: be careful and possibly avoid using the caret in version numbers. Prefer a “tilde version number” instead (e.g. ~1.1.3 which allows going from 1.1.3 to 1.1.6, but not to 1.2.0). A newer version may have (i.e. will have) new bugs, and may in turn use completely new or even different libraries. In fact since Semantic Versioning concerns only about the public API of a library, it may be a complete rewrite. You can’t exclude that someone may do this also with a patch update, but since patch updates are supposed to be only for a bugfixes, you can have a reasonable expectation that only the minimum amount of code required for the fix has been changed and that if a new(er) library is introduced the issue was worth the risk. If the library is used on a frontend application, you most probably want to use a single version of any library. In this case a more relaxed approach can be reasonable. If that’s the case though, you’re probably using something like Bower and not NPM. If you’re using NPM, don’t worry too much though: NPM itself has been doing it wrong too for some time.