Motivation
As someone who started coding JavaScript in 2007, I’ve had a hard time re-introducing myself to the modern JavaScript development ecosystem. There has been a lot of contention around the npm ecosystem and why its so fragile. Npm gets a lot of misplaced blame in this situation that is beyond their control. Yes, until recent npm fell short and we were all using yarn… to pull from npm. YES, there was a really complicated and dramatic situation involving a copyright claim and a rebellious anti-establishment move…. BUT… For the last two years it’s been a pretty decent thing.
The problem is a cultural one. It bothers me that a one line package broke as many as 3.4 million build pipelines. However, these two massive breakdowns are just the high profile cases; I’ve been affected by create react app breaking last June because of a dependency issue. We will never know how many builds are breaking because of this nightmare dependency chain.
What I Wish I knew
Many common / major dependencies break… often
Semantic Versioning is a fantastic contract to have with your dependencies… when it goes right. In python, I have gotten very used to trusting major libraries. While I’ve occasionally run into problems with smaller projects, the major ones have been true to this contract. This break in contract is precisely why the JavaScript ecosystem is a dumpster fire. If isPromise were to have incremented a major version with its breaking import changes we’d never have even known this disaster of a project existed, ignorantly deploying this code.
Minimize 3rd Party Dependencies
Let me be clear here; This is more than just a build issue. Each of your dependencies introduces a new potential bug in your code or worse; an attack vector for malicious users. npm does not vet it’s packages, that’s on you and your dependency authors. Do not mistake me, I’m not advocating for re-inventing the wheel. I’m advocating for responsible dependencies from trusted sources.
Many of the fractured and incestuous dependency chains are to “save” you from writing code, usually aimed at JavaScript’s shortcomings (isPromise). In reality, a simpler dependency chain means less maintenance, fewer bugs, and less work in the long run. There is a trend these days in JavaScript; Those who can’t, import(). I’d like to propose a wild solution; learn JavaScript. There are very few actual use cases for isPromise, a project with 3.4 million dependents. This package isn’t even named properly; its more accurately isThenable.
Side Note: You can see the breaking changes have been fixed, and re-introduced to the code base with two major versions. Good job guy.
Working with lock files
Since we cannot trust anyone ever; your will learn to cherish your lock files. npm, yarn, whatever you’re using commit your lock files early and often. Quite literally the moment your lock file is modified it should be commited. You do not want to be in the position of your builds randomly failing. Usually it’s my fault; With JavaScript I’ve lost that confidence.
Reverting your lock file with git
IF you get caught by this; In git you can easily checkout your lock file and package.json from a specific branch or commit:
git checkout {SHA/branch} -- your-lock-file package.json
Build From Your Lockfile
Yarn and npm look up and update any dependencies from package.json and write to your lock file when building. Dependency resolution in npm is a fairly complex operation to grok and is beyond the scope of this post. tl;dir;: npm uses semantic versioning and package.json will write to your lock files when installing packages. Think ci builds.
npm
npm ci
Yarn
yarn install --frozen-lockfile
Pin Unreliable Dependencies
As a last resort you can tell your builds to NEVER EVER EVERRRR even download a patch (HINT:isPromise==2.1.0
). Dont do this unless you have zero confidence in a project not breaking. But you should be asking why have this as a dependency if you can’t count on them not breaking things w/ a patch.
Next Week
I will be doing a deep dive into Create React App and weigh out which dependencies are actually needed in django-redux. I will be ejecting and gutting anything I dont need and building out a minimal react app that works with django. I suspect several of the 1,600 dependencies will be unnecessary for me.
Update: 8/22/2021
I’ve actually decided that I dislike JavaScript SO much that I will not be working in it unless absolutely necessary. When I need JavaScript it will be injected in small apps.