Node.js

Maintainers Should Consider Following Node.js’ Release Schedule

This blog was written by Benjamin Coe. Ben works on the open-source libraries yargs, nyc, and c8, and is a core collaborator on Node.js.

Maintainers Should Consider Following Node.js’ Release Schedule

This blog was written by Benjamin Coe. Ben works on the open-source libraries yargs, nyc, and c8, and is a core collaborator on Node.js. He works on the client libraries team at Google. This piece originally appeared on the Node.js Collection. Node.js is an impact project of the OpenJS Foundation.

tldr; Node.js has a tried and true release schedule, supporting LTS versions for 30 months. It offers significant benefits to the community for library maintainers to follow this same schedule:

  • ensuring the ability to take security patches.
  • reducing the burden on maintainers.
  • allowing module authors to take advantage of new platform features sooner.

My opinion of what Node.js versions library maintainers should aim to support has evolved over the years. Let me explain why…

The JavaScript ecosystem in 2014

I joined npm, Inc in April 2014. During this period, releases of Node.js had stalled. Node.js v0.10.x was released in April 2013, and Node.js v0.12.x wouldn’t be released until February 2015.

At the same time, the npm package registry was going through growing pains (see: “Outage Postmortem”“Four hours of partial outage”, etc.).

The state of Node.js and npm in 2014 had side effects on how folks thought about writing libraries: maintainers didn’t need to put mental overhead into deciding what Node.js versions they supported (for years, the answer was 0.10.x); partially owing to npm’s instability, and partially owing to frontend communities not having fully embraced npm for distribution, package dependency trees were smaller.

Small building blocks, like mkdirp, still represented a significant portion of the registry in 2014.

Things would change in the intervening six years…

The JavaScript ecosystem today

In February of 2015, motivated by the io.js fork, The Node.js Foundation was announced. In September of that same year, Node.js v4.0.0 was released. Node.js v4.0.0 merged the io.js and Node.js projects unblocked the release logjam and introduced the 30-month LTS cycle advocated in this article.

Since Node.js v4.0.0, maintainers have been able to count on a regular cadence of releases, pulling in new JavaScript language features (through V8 updates), additions to the standard library (like HTTP/2), and important bug and security fixes.

In parallel, during the period between 2014 and today, npm significantly improved stability, and the frontend community began to consolidate on npm for distribution. The side effect was that many more packages were being published to npm (numbers grew from 50,000 in 2014, to 700,000 in 2018). At the same time, dependency trees grew (the average number of dependencies in 2016 was 35.3, the average number of dependencies in 2018 was 86).

A library maintainer in 2020 has at least three versions of Node.js to think about (the Current, Active, and Maintenance versions). Also, on average, their libraries rely on an increasing number of dependencies… it can be a bit daunting!

A library maintainer in 2020 has at least three versions of Node.js to think about (the Current, Active, and Maintenance versions).

A great way for maintainers to handle the increasing complexity of the JavaScript ecosystem is to consider adopting Node.js’ 30-month LTS schedule for their own libraries.

Here’s how adopting this schedule benefits both the library authors and the community…

Being able to take security patches

A security vulnerability was recently reported for the library minimistminimist is the direct dependency of 14,000 libraries… it’s a transitive dependency of the universe.

The popular templating library Handlebars was bitten by this report through an indirect dependency (optimist). Handlebars was put in a position where it was difficult to silence this security warning without disrupting its users:

  • optimist, deprecated several years earlier, was pinned to an unpatched version(~0.0.1) of minimist.
  • Handlebars itself supported Node.js v0.4.7, making it a breaking change to update to yargs (optimist’s pirate-themed successor).

Although motivated by good intentions (“why not support as many environments as possible?”), when libraries support end-of-life versions of Node.js, it can ultimately end in disruptions for users. Maintainers find themselves bumping a major version as a fire drill, rather than as a scheduled update.

Dropping support for old @nodejs release is a breaking change and it should be released in a major version.— Matteo Collina

The wide adoption of Node.js’ LTS schedule for modules ensures that security patches can always be taken.

Reducing the burden on maintainers

Keeping dependencies up to date is a lot of work (my team at Google landed 1483 pull requests updating dependencies last month), it’s also important:

  • the closer to a dependency’s release you catch an unintended breakage, the more likely it will be quickly fixed or rolled back.
  • keeping dependencies fresh helps ensure that critical vulnerabilities and bug fixes are rolled out to your own users (this avoids the Handlebars/minimist issue discussed).

Tools like Dependabot and Renovate make sure updating dependencies isn’t a maintainer’s full-time job. However, if libraries don’t adhere to the same version support policy, it makes automation difficult. As an example, because of falling behind the scheduled deprecation of Node.js v8.x.x, the library yargs turned off automatic updates for decamelize (opening itself up to all the risks that go along with this).

A lot of open-source is made possible by the volunteer work of maintainers. I can’t think of many things less exciting than the constant auditing of the SemVer ranges advertised in the “engines” fields of dependencies.

The wide adoption of Node.js’ LTS schedule for modules creates consistency and reduces the maintainer burden around updating dependencies.

Helping to evolve the platform

For the last couple of years, I’ve been involved in the Node.js Tooling Group. We’ve advocated for a variety of API improvements for tooling authors, such as recursive directory creationrecursive directory removal, and Source Map support for stack traces.

In Node.js v8.4.0, http2 support was addedThis addition is near and dear to my heart since it allows Google’s client libraries (which rely on HTTP/2) to run natively on Node.js.

JavaScript itself is an evolving platform. Node.js regularly updates the V8 JavaScript engine, pulling in new language features, such as async iterators, async/await, and spread operators.

Keeping the Node.js core small will always be an architectural goal of the project. Node.js is, however, an evolving platform.

The wide adoption of Node.js’ LTS release schedule allows module authors to leverage exciting new features that are added to the platform.

What actions am I advocating that library maintainers take?

  1. When you release a new library, set the engines field to the oldest active LTS version of Node.js.
  2. When a Node.js version reaches end-of-life, even if your library is finished, bump a major version updating the engines field to the current oldest active LTS of Node.js.
  3. Consider throwing a helpful exception if your library is used on an unsupported Node.js version.
  4. Consider documenting your version support policy (here’s an example of the one we wrote for my team).

The Node.js Package Maintenance Working Group is developing further recommendations related to library support policies, such as Cloud Native’s Long Term Support for Node.js Modules. This policy takes this article’s recommendations a step further and suggests that module maintainers support a major library version for the lifetime of the Node.js runtime it was released under. This means that, if your library releases v1.0.0 with support for Node v10.x.x, you would continue to backport security and bug fixes to v1.0.0 until Node v10.x.x reaches end-of-life. Committing to this level of support can be a great move for libraries targeting enterprise users (and, they even have a badge!).

Tracking Node.js’ release schedule saves you time, makes libraries more secure, allows you to use Node.js’ fancy new features, and benefits the community as a whole. Let’s get good about doing this together.