Stop using "npm install"

You’re setting up an existing JavaScript project, run npm install. And the developement build or the CI now fails, and you did nothing but following the project setup instructions.

How can this happen?

Ok, so the thing is this command may re-resolve some of your dependencies to newer ones, and these newer ones may have breaking changes, hence breaking your build.

This is something you can usually spot when after running those commands, your lock file are updated (package-lock.json for npm).

But I use the caret ^ notation for my packages, so no breaking changes should be introduced, as my packages should never upgrade to a new major version, so my build should not break.

In theory yes, in practice no.

Each of your package may not introduce a breaking change, but the integration between some of them may break.

A personal example is between Next.js and next-transpile-modules. A Next.js minor (and sometimes even a patch) may break next-transpile-modules (due to some Webpack configuration changes).

Ok, how to prevent this scenario from happening?

We need to tell npm to not-reresolve the dependencies versions when installing everything to node_modules, and to use the ones already available in the lock file.

This is how you do it:

  • npm install -> npm ci*

    it stands for “clean install”, not “continous integration”.

This is also the recommended command to run on your CI/deployment pipeline, or in any Dockerfile: you want to be sure the dependencies your app will use on production are the same than the ones you used when developing the app.

And this is the problem lock files solve.

ps: it also means it is sometimes a good idea to reject PRs that see their lock files updated, but no versions explicitely changed in your package.json. Better safe than sorry.


edit 1: removed mentions of yarn due to some misunderstanding on how it uses lockfiles

Do you disagree? Have I made a typo? Is there still redemption for my soul? Drop me a tweet or an email!