When using npm, you have access to packages that you don’t reference in your project’s
package.json. This is possible because npm creates a flat
Let me explain this by an example.
- create an empty project:
mkdir project && npm init -y
npm install express --save
- go to the
node_modulesfolder that was created
You’ll get an output similar to this one:
As you can see, even though you installed only
express, a lot more packages are available in the
node_modules folder. Node.js doesn’t care what is in your
package.json file, you will have access to all the packages that are in the root of
node_modules. So you can start using all those packages without even installing them explicitly.
Let’s say you need
debug in your project. You can start using it and forget to add it to
package.json. Your project will work fine. You will commit your changes and publish it to production. It will work well even in production. However, after a few weeks or months, one of two things can very likely happen:
- a new major version of
debugwith a new API can get published. Your code was written for
debug@1and it needs some updates to work with
debug@2. You are safe until
debug@1is installed to the root of
expresscan update its dependency in any moment, fix all usages of
debugand publish a new patch (a patch, because
expressdidn’t have any breaking changes). Next time you do
expresswill be updated and
debug@2installed into the root of your
node_modules. The project will break, even though you did not make any changes!
- another possibility is that
debug. It can just remove it from dependencies and publish a new version. When you’ll update
express, your code will break because the
debugpackage won’t be in
Now imagine that your project is not a web app but a package used by many other people. If you don’t include a package used in code into the
package.json, your package is a timed bomb. Do you remember left-pad? I can imagine a similar catastrophe happening in the future because of a forgotten dependency.
How does pnpm help with avoiding these type of bugs?
Unlike npm, pnpm does not try to move everything to the root of
node_modules. If you remove the
node_modules folder created by npm in the previous example and run
pnpm install express, you’ll see that
ls -1 will print only one symlink in the
On next run, your app will immediately fail, because it won’t be able to find
debug. Which is the way it should be! To fix the project, you’ll just run
pnpm install debug and
debug will be added to your
package.json (pnpm saves packages to
package.json by default, even when the
--save parameter is not passed). With
debug in your
package.json, you can be sure that it will always be installed into
node_modules and it will work with your code as expected.
pnpm gets a lot of issues opened because some packages/toolings don’t work. These issues are mostly happening because the packages/toolings have
package.jsons that miss dependencies. Some developers even think that it is fine to not include dependencies in
package.json because “it works with npm/yarn”. It is not OK! It might work today but it will break tomorrow.
You might not use pnpm. But please, publish valid packages. If you don’t use pnpm, use some tooling like dependency-check to validate your package before publishing it to the registry.
npm’s global style installation
A month later after publishing this article, I have found out that it is possible to avoid these kind of bugs with npm as well! It is not the default behavior but npm can install node_modules in a different layout, in which
Only your direct dependencies will show in
node_modulesand everything they depend on will be flattened in their
To achieve this, just run npm with the
--global-style option or change its value in the configs via
npm c set global-style true.
Do you want to give pnpm a try?
Just install pnpm via npm:
npm install -g pnpm. And use it instead of npm whenever you want to install something:
pnpm i foo.
(This article was originally posted on https://www.kochan.io/nodejs/pnpms-strictness-helps-to-avoid-silly-bugs.html)