Adam Innes · Blog

Node.js 4.0: io.js Comes Home, and How to Upgrade From 0.10 or 0.12

· 7 min · node.js, io.js, javascript, es2015

A week ago today, on Tuesday, September 8, Node.js 4.0.0 came out. If your servers still run 0.10 or 0.12, that version number looks like somebody fat fingered a release script. It isn’t. This is the first release built from a single codebase that combines Node.js and io.js, and the Node.js Foundation’s press release yesterday says it’s called 4.0.0 because it carries the major updates from io.js 3.0.0. So after most of a year of two runtimes, there’s one again.

There are three things worth understanding here: why the split happened and what changed in how the project is run, what 4.0 actually gives you when you write application code, and how to get a production app from 0.10 or 0.12 onto it without a bad week.

Why io.js happened

io.js 1.0.0 shipped on January 14, 2015. Its README said plainly that the repository began as a GitHub fork of joyent/node. The io.js FAQ gave two reasons for existing. One was faster and predictable release cycles that pull in the latest V8 along with updates to libuv and other base libraries. The other was development under an open governance model rather than corporate stewardship. The V8 gap was real: the io.js 1.0.0 changelog notes it jumped from V8 3.14.5.9 in Node 0.10.35 to 3.31.74.1. io.js also moved to semver, which is why it started at 1.0.0 instead of continuing the 0.x numbering.

What I find most useful is that Joyent’s side tells a similar story. In May, Scott Hammond of Joyent wrote on nodejs.org that the project had been run fairly tightly by a small core of developers in a BDFL model, that it was hard for new people to join, and that there wasn’t enough transparency for the community to feel ownership. A group that wanted more open governance, he wrote, created the io.js fork. When both sides describe the cause the same way, you can take it as the actual cause.

How governance changed

The Node.js Foundation was publicly announced on February 10 at the Node.js Summit. On May 13 the io.js Technical Committee voted to join it, rename the iojs GitHub organization to nodejs, and invite the Node.js TC onto its own committee as the basis of a Foundation TSC. Earlier in May, Joyent had already said the project was moving to a model with no project lead.

On June 16 the Linux Foundation announced the merge and the ratified governance. The split is clean. A Technical Steering Committee directs technical decisions, oversees working groups and manages contributions to the code, while a Board of Directors guides business decisions. The Node.js and io.js technical committees merged to form that TSC, and an individual membership class will have representation on the board.

The rules the project actually runs by live in GOVERNANCE.md at v4.0.0, and it reads almost line for line like the io.js file from January. Anyone with a GitHub account can open a pull request, a Collaborator with the right expertise has to review and accept it, and a pull request from a Collaborator needs sign off from a second one. Contentious changes go to the committee, which seeks consensus and falls back to a simple majority vote. My favorite line is the one saying no more than one third of the committee may be affiliated with the same employer. That rule is the concrete answer to the corporate stewardship complaint. The release post says the project now has 44 collaborators, 15 of them on the TSC.

What 4.0 gives you when writing code

Node 4.0.0 ships V8 4.5 (4.5.103.30 in the changelog), which the release post says is the same V8 shipping in Chrome right now. The release notes list the ES6 features enabled by default with no flags: block scoping, classes, typed arrays (Buffer is now backed by Uint8Array), generators, Promises, Symbols, template strings, collections like Map and Set, and arrow functions, which are new in V8 4.5.

One catch that isn’t in the release notes but is on the project’s ES6 page: block scoped declarations are limited to strict mode for now, and classes work only in strict mode. So put 'use strict'; at the top of any file that uses let, const or class.

Here’s a small example that sticks to features from that default list.

'use strict';

function* idMaker(prefix) {
  let n = 0;
  while (true) {
    n += 1;
    yield `${prefix}-${n}`;
  }
}

class Sessions {
  constructor() {
    this.ids = idMaker('sess');
    this.active = new Map();
    this.users = new Set();
  }

  open(user) {
    const id = this.ids.next().value;
    this.active.set(id, user);
    this.users.add(user);
    return id;
  }

  closeLater(id, ms) {
    return new Promise((resolve) => {
      setTimeout(() => {
        this.active.delete(id);
        resolve(`${id} closed, ${this.active.size} still open`);
      }, ms);
    });
  }
}

const sessions = new Sessions();
const first = sessions.open('ada');
sessions.open('grace');
sessions.open('ada');

sessions.closeLater(first, 10).then((message) => {
  console.log(message); // sess-1 closed, 2 still open
  console.log(sessions.users.size); // 2
});

The generator hands out ids forever without a counter hanging off the class, the Set quietly ignores the second ada, and the arrow functions inside closeLater keep this pointing at the instance, so there’s no var self = this dance. Notice what’s missing, too. There’s no destructuring, no default parameters and no shorthand object properties, because none of those are on the release notes’ list (destructuring, for one, still sits behind an in progress flag according to the ES6 page). If you use --harmony today, the ES6 page says it now only turns on staged features that V8 doesn’t consider stable, and suggests removing it in production.

A few non language changes matter as well. npm 2.14.2 is bundled, the util.is*() functions are deprecated in the docs, child_process send() is now asynchronous everywhere with an optional callback, and the bundled node-gyp only downloads a headers tarball when building addons instead of the whole source.

Which line to run

This is where 4.0 is different from any Node release before it. The release post lays out a plan: the first LTS release comes in October from the 4.x line, and at the same time a 5.x branch starts as a new Stable line that will likely get a newer V8 and any breaking changes. 4.x then gets 18 months of LTS focused on stability and security, followed by 12 months of Maintenance for critical bugs and security fixes. After that, new Stable lines branch every six months, in October and April, and every second one continues into LTS when October comes around.

The LTS working group’s README as of September 8 fills in the old lines. There will be no LTS releases from the io.js stream. 0.10 stays in Maintenance for roughly a year after the first converged LTS release, and 0.12 gets six more months of LTS and then 12 months of Maintenance. Its example dates are explicitly hypothetical, so treat those dates as a plan and not a calendar.

For a production app, my advice is to aim for 4.x. It’s the line that’s planned to become LTS, the project has said the 4.x branch won’t take breaking changes, and 5.x is where the churn is going. There’s no need to panic off 0.12 this month, but the clock on both old lines has started.

Upgrading from 0.10 or 0.12

Start with native modules, because they’re what will break loudly. Every Node build has a module version baked in: 11 for 0.10, 14 for 0.12 and 46 for 4.0.0. An addon compiled for an old version won’t load: a 0.12 build fails with a “Module version mismatch” error, and a 0.10 build fails with errors such as “Module did not self-register”. Never copy a node_modules folder or build artifact from a 0.12 box onto 4.0. Do a clean npm install on the new version, or run npm rebuild, which npm’s own docs describe as the thing to do after installing a new version of node.

A rebuild only helps if the addon’s C++ compiles against V8 4.5, and V8’s API has changed a lot since 0.12. The release post points addon authors at NAN, whose 2.0.0 release in July renamed much of its API and added io.js 3 support. Its 2.0.9 changelog from September 8 lists Node 4.0.0 as current. Walk your dependency tree (npm ls prints it), find every package with a native build, and check that each has a release that builds on 4. Also check your build machines. node-gyp needs Python 2 (2.7 is recommended and 3 isn’t supported), make and a C++ toolchain. The Node 4.0.0 README asks for gcc 4.8 or clang 3.4 or newer just to build Node itself, which is a decent hint that an old compiler on a CI box is worth testing early.

Then read the breaking changes between 0.12 and the next LTS as it stood on September 9. SSLv2 and SSLv3 are disabled at compile time and RC4 is gone from the default cipher list for TLS servers, which matters if you talk to old internal services. os.tmpdir() no longer returns a trailing slash, Buffer.concat() always returns a new Buffer, and assert.deepEqual() now compares prototypes, so some tests may start failing even though nothing is wrong with your app code. The release notes also list known issues, like a crash if you call dns.setServers() while a query is in flight.

For testing, I’d run your whole suite on both your current version and 4.0.0 in CI until 4 is green. Add an engines entry in package.json, but remember npm’s docs say it’s advisory unless someone sets engine-strict. Once deployed, node -p process.versions.v8 is a quick way to confirm a host really runs the new V8. Roll out to a slice of traffic first, watch memory and latency, and only then move the rest.

Node 4.0 is really two releases in one. It’s the moment the community stopped being split, under rules that deliberately keep any single company from running the show, and it’s the first Node with a published support plan you can schedule around. The runtime part will take some rebuild work. The planning part is the bigger deal for anyone who has to keep a server running for the next two years.

← all posts