Some time ago I was working on a back-end application using NestJS. Everything was running fine for weeks when, out of nowhere, they informed me that the solution was crashing at run time after a new build.

SyntaxError: Unexpected token ...

After some inspection, I found out the error was originating from the ws package. The build process was changing one instruction which broke the whole application!

// Original instruction
Object.assign({}, this._options.zlibInflateOptions, { windowBits })

// After build
{ ...this._options.zlibInflateOptions, ...windowBits }

I started looking for the problem on Google. There were issues at GitHub and questions at StackOverflow similar to mine. All the answers I was finding advised adding a specific plugin for Babel.

This plugin allows Babel to transform rest properties for object destructuring assignment and spread properties for object literals.

There was only one problem: I wasn't using Babel at all.  This was not a solution for me, but it pointed me in the right direction. I read in some of the answers that the spread operator was introduced in ES6 (or ES2015). I thought that, maybe, the Node.js version I was using to run the solution (6.9.5) did not support ES6.

I went to Node.js ES2015 Support to check if I was right. I found the feature compatibility under "object spread properties" and found out that the version I had doesn't support said feature. The fastest solution would be to update Node to version 8.3.0, at least, but I could not do that because of requirements, so I had to make sure I could make it work in that Node version. Anyways, it was running fine earlier, so what changed?

@nestjs/websockets
└─  socket.io
    └─  engine.io
        └─  ws

I began the "dissection" of the packages, in search of the problem. The first thing I did was to find the chain of dependencies that lead to this problem.

After that, I checked the release notes for each of those packages and found out that the ws package dropped the support for Node 6 on their version 7 on their GitHub.

"How did this happen?", I wondered. The package.json of the project wasn't updated, so breaking changes shouldn't have occurred. I started investigating again, this time going up to the root, and took note of every referenced version of the dependencies.

@nestjs/websockets@^4.6.6
└─  socket.io@^2.0.3
    └─  engine.io@~3.1.0
        └─  ws@~2.3.1

At first glance, I thought there was no problem with it since ws is pointed to the version 3.3.1, but then I realized there's one dependency which range specifies that it can be installed with any minor above 2.0.3: socket.io. Turns out the version of socket.io installed was 2.3.0.

[email protected]
└─  engine.io@~3.4.0
    └─  ws@^7.1.2
Dependency tree of socket.io 2.3.0

There's the problem: this version of socket.io needs engine.io 3.4.0, which depends on ws 7.1.2. The solution I went with was to find a version of socket.io that would allow me to use ws 6 (that version being 2.2.0) and add it to the dependencies of the project with a fixed version so it doesn't upgrade automatically.

[email protected]
└─  engine.io@~3.3.2
    └─  ws@^6.1.0
Dependency tree of socket.io 2.2.0

What about Semantic Versioning?

Semantic Versioning (or SemVer) is a way of assigning unique identifiers (either names or numbers) to unique builds of software. Their introduction reads:

In the world of software management there exists a dreaded place called “dependency hell.” The bigger your system grows and the more packages you integrate into your software, the more likely you are to find yourself, one day, in this pit of despair.

This is exactly what I experienced throughout this story. The ws package developers were kind enough to warn in their release notes that their new version would break applications running on Node 6.

The programmers of socket.io and engine.io should have noticed this change. From engine.io 3.3.2 to 3.4.0 were introduced breaking changes, causing applications like the one I was working on to crash, and that would not have happened if they increased their version as Semantic Versioning suggests.

Given a version number MAJOR.MINOR.PATCH, increment the:

1. MAJOR version when you make incompatible API changes,
2. MINOR version when you add functionality in a backwards compatible manner, and
3. PATCH version when you make backwards compatible bug fixes.

I would have increased the version to 4.0.0 instead of 3.4.0, following the Semantic Versioning protocol.

Take this post with a grain of salt, this is just a personal rant about something I experienced at work. I would have preferred to update the Node version to a recent one but, unfortunately, this was the solution I had to go with.