A year in the life of an OSS project on ice
I’ve been developing and maintaining fetch-mock - a widely used npm package for mocking http requests - for about 5 years. By the end of 2018 it felt fairly feature complete, and a recent overhaul of the documentation meant support requests from confused users were reduced down to a trickle.
I looked forward to a 2019 where I could put my feet up.
Things did not, however, work out that way.
So, I think it’d be illuminating to share what goes into maintaining an open source project, even during the “quiet” periods. What are the forces that drag it away from a steady state?
I’ve waded through 2019’s pull requests, issues and releases, and here are the leading causes of me not putting on a pair of fetch-mock branded flip-flops and sipping nodejs pina-colada in front of an npm red sunset.
1. ECMAScript does not stand still
As a user of the new features continually being added to the JS language, it’s been a pretty positive experience, from the dawning of arrow functions, right through to the eagerly anticipated optional chaining proposal. I am also in awe of the power Babel and related tools give us to upgrade the web seamlessly.
But as a package maintainer - an isomorphic package, no less - this can be a headache. Most of the JS ecosystem can be made to run in a wide variety of environments, using different JS runtimes. If I want fetch-mock to satisfy its users, then it must be able to keep pace with the environments in which JS runs.
fetch-mock supports JS all the way back to ES5, shipping a transpiled and polyfilled ES5 build. Without explicitly including the polyfills in the source code, it would break for some users’ toolchains. A consequence of this is that the built files required the core-js polyfill library at version 2, but fetch-mock did not declare it as a dependency (babel, which added the dependency to the built files, was a development dependency).
In mid 2019 this previously benign oversight became problematic. A new major version of corejs was released, and projects that used this (often required transitively via their toolchain) could no longer bundle their tests. Things were about to get a whole lot worse too, because the release of Angular 8, which shipped with a babel/core-js upgrade, meant that fetch-mock would be incompatible with Angular.
One of my users eventually submitted a fix, but not before my having to reject other attempts. This whole episode did stir me to further work, and I ended up giving fetch-mock a tooling overhaul to follow babel best practice, upgrade core-js and add support for ES modules.
Because of the kind of tooling clashes I’ve outlined above, I felt it was unwise to release these changes as a minor, so I went through quite a rigorous process of creating a new major version, inviting comment from users on various alpha releases. Later in 2020 I anticipate another major version to rewrite the source using ES modules, and tidy up some tooling I missed this time around. That’ll be fun.
2. Jest, my nemesis
Jest is the test runner du jour, with built-in mocking support that works very differently, and less transparently, than previous tools. On the last day of 2018 I received a bug report that fetch-mock did not work with Jest on the server side. By the end of January I had found a solution, but it was far from ideal.
Throughout 2019 I have racked my brains and continually revisited the problem. I’ve raised issues on other libraries asking for help, raised issues with Jest, and invested a lot of time stepping through its source code and documentation, and experimenting with a variety of workarounds.
This was easily the most difficult problem to solve this year, and has literally kept me awake at night. It took 11 months to hit upon the 5 lines of ugly code that gave me closure… almost - Once released, it smoothed the way to writing a wrapper for fetch-mock that contains a more idiomatic way of inspecting fetch calls in Jest, fetch-mock-jest. That took at least another week of evenings to complete.
All this anguish, and I don’t even like Jest! I think it’s pretty over-rated, and it wouldn’t surprise me if JS fashion will swing back towards more composable, less black-boxy tools. But in the meantime, I have to be compatible with the toolchains fetch-mock users are using.
3. Abortable fetches
One thing I’m really proud of is how much of the fetch API fetch-mock covers. This does, however, mean that when the fetch API grows, fetch-mock must grow with it. This year was particularly busy in this respect because of the more widespread use of abortable fetch.
Users first started asking for support for abortable fetch in mid 2018, and support was added in October 2018. This year, what’s followed has been a bunch of minor niggles. Fortunately, most fixes have been submitted by users (for which I’m grateful - you fetch aborters are a nice bunch :-) ), and I’ve mainly had to put effort into reading the spec to verify their PRs address valid concerns, and on one occasion passed on a request for spec compliance to node-fetch.
It does serve me right* that I had to deal with all this because I opened the original issue which led to the formal discussion that eventually resulted in Abortable fetches.
*I’m not seriously taking credit for abortable fetch.
4. Types
I don’t know if anyone has heard of Typescript, but apparently it’s the hip new way to write code that can run on Windows 95 and other platforms. Its killer feature is that it developers can be 100x more productive and never ever write bugs. On the downside, it makes libraries 100% unusable if they don’t ship with types.
I exaggerate, and am in fact Typescript-curious, but I do agree with my colleague Rowan’s appraisal:
Eventually somebody stepped up to the plate for fetch-mock. Fortunately, the definitely typed project had already written fetch-mock types, but I still had to either learn typescript properly (yeah, right!) or reverse engineer the tooling from definitely typed to work in fetch-mock. After that initial outlay, I now have the maintenance overhead of keeping types up to date. It’s a lot to ask of somebody giving their time for free.
So, my feedback to the Typescript community at large is, unless you’re a Typescript novice, never raise an issue asking for types without making a serious offer* to write and maintain them on an ongoing basis.
Never!
*I’m grateful to Dave Cooper for making such an offer.
5. Cloudflare workers
As mentioned in section 1 above, fetch-mock supports a vast range of environments, both server and client side. Cloudflare workers - inspired by the Service Worker standard, and amazingly more than two years old - took fetch for its first foray into edge computing. Cloudflare’s non-standard extensions meant that eventually an issue was raised asking for fetch-mock to support this new environment. I was happy to oblige, especially as all it meant was reading some specs and merging a PR.
I’ve spent a lot of time in recent years writing VCL code to run on the edge, and I found it quite satisfying to learn that fetch-mock was helping others embrace edge computing.
6. Security
I’ve been lucky when it comes to security issues (although, I do try to avoid adding too many dependencies, which helps matters), and only one pull request was solely to fix a vulnerability. I feel it’s still worth a mention because it’s another important responsibility package maintainers have.
Interval
I’m barely half way through my summary of the year, but think I’ll pause here to reflect on one very important thing that all the above have in common - They have very little to do with fetch-mock.
No bugs, no new features, no documentation mistakes, no assisting people with the API.
They are all tasks that the rising tide of the JS ecosystem has floated up to my door. It reminds me of this scene from The Ring:
Doctor: You don’t want to hurt anyone.
Samara: But I do, and I’m sorry, it won’t stop.
It won’t stop. It will never stop. This - at a bare minimum - is what maintaining a reasonably well-used open source project entails. Still want in?
Now, on with the show.
7. Skip to the end…
Part of me would like to catalog all the other issues, large and small, in full detail, but I doubt it’d make for interesting reading, so here’s a summary:
- 6 non-trivial feature requests, of which 1 was rejected, 4 were implemented by me, and 1 was implemented by my erstwhile colleague.
- 3 significant documentation rewrites, either of content or structure, and at one point completely breaking the docs site.
- 21 support requests (8 of which reated to old version of the library) ranging from fairly cryptic issues with little information on how to reproduce, to uncovering that there’s a bug in how ES proxies are polyfilled in IE11.
- 6 user PRs merged, mainly fixing typos in the docs.
- 7 bug fixes
- 4 issues with my build and release tooling
- 2 sizeable refactors and evolutions of the API
It’s perhaps difficult to appreciate how much work went into that when it’s expressed in such a condensed form. Maybe a useful heuristic is to imagine that all these tickets arrived in your team’s backlog. How much time and effort would it take? Would you do it for free?
8. Chains
At times, it’s quite satisfying maintaining a successful open source project, and during those times knowing that people all over the world use something I made is reward enough. But for much of this year, during the summer in particular, it became a burden.
All the chores I’ve mentioned above took a significant amount of time to implement or otherwise deal with. But what the list of tasks above does not capture is the cognitive load of dealing with one, or a few, expectant users on a particular problem, while also considering the thousands of others that use the project. It’s a lot of pressure.
In the past I’ve accidentally broken features, and while I’ve not been abused for it (though some users could do with learning some manners), it is quite stressful. For much of the summer, I carried the dilemma of how to resolve the core-js & babel issue around with me, watching the rising tide of upvotes from impatient Angular users. Eventually I succumbed to the weight of responsibility and reluctantly spent my autumn evenings clearing the backlog.
And this. Was supposed to be. A quiet year.
9. Funding
Hopefully I’ve persuaded you that maintaining an open source project is a big commitment. Others have written about the need to fund OSS properly, and they are damn right!
So I urge you to run npm fund
and find out which maintainers would appreciate some of your tech dollars. And, further, I urge you to persuade your employer to do likewise. You, personally, may get some utility from using the tools I and others write, but your employer makes money from it. If they don’t already, it’s high time they put something back.
You could start here, by donating to my charity of choice, refugee support.
Cheers.
Yaffle