- Published on
Why Listing Dependencies in package.json is a Good Practice
- Authors
- Name
- Sujan Tamang
- @SujanTa88350485
It's my first tech article of 2023 and I hope everyone is having a great year. This article is all about managing dependencies in Nodejs. I know sometimes it can feel like spider's web (like the picture above) and only small amount of time is spent managing it but still understanding it is very crutial. Today, I want to share something that I just recently faced at my work.
A Short Story
One day, I was upgrading a npm library to it's latest version. I looked at it's migration guide and found no breaking changes for our project. It was a necessary upgrade so, I just ran some commands, upgraded it and pushed the code. Later, the system crashed due to the code I pushed. I was amazed by the error message because it was of another library that I didn't even touch. so, I was asking myself why the hell did that happen.
If something like this has already happened to you before and you've solved it, still you can read this article to the end because I'm going to cover some extra things. Let's get started.
Prerequisites
This is not a step-by-step tutorial and I'm assuming that you are already familiar with Node.js & Package management. I'm going to write some simple commands & little amount of code. If you want, you can follow along me. But if you just want to get the idea, please skip the next section.
Recreating the error
I'm going to install some npm libraries. You don't have to know about them. It's just for demonstration purpose. If you're going to follow along, you'll need to have Node.js installed. I'm going to use npm but you can use yarn instead if you like.
Create a new folder and inside it run the following command:
$ npm init -y
This will create a new file named package.json
.
Now install a library called firebase-admin with version 8.0.0.
$ npm i firebase-admin@8.0.0
We're installing v8.0.0 so, that later we can simulate the error that I got while upgrading the package.
Inside the folder create a new file named index.js
.
$ touch index.js
Inside the file, add the following lines of code.
const uuid = require('uuid/v4')
console.log('Result: ' + uuid())
We haven't installed any library named uuid but how are we able to use it?
It's possible because firebase-admin
is using uuid
as a dependency so, it got installed alongside and
we're able to use firebase-admin
library's dependencies directly in our code without having it in our package.json
.
This is not a good practice and can produce unexpected error later on which I'm going to show.
Let's get the uuid
library's version that got installed by going into the node_modules
folder. Inside it, look for a folder named uuid
and inside that folder is another package.json
file which is of uuid
library. Remember the version for now which is "3.4.0".
Now let's run the file using the following command:
$ node index
It shoud log something like below to the console.
Result: 608bf07c-8af6-42a5-b8b5-50f0005f2de5
Okay, till now everything is fine. Now we're going to upgrade the firebase-admin
to latest and face the same error I faced at my work.
$ npm i firebase-admin@11.5.0
instead of @11.5.0, you can use @latest but for now I recommend you to use @11.5.0 to follow along the article.
Now, try to run the file again and this time you will get an error something like this.
$ node indexnode:internal/modules/cjs/loader:535 throw e; ^
Error [ERR_PACKAGE_PATH_NOT_EXPORTED]: Package subpath './v4' is not defined by "exports" in ...
The error is not coming from firebase-admin
library that we upgraded. It's actually coming from uuid
library.
I will explain later on but for now, let's fix this error by updating the code.
const { v4: uuidv4 } = require('uuid')
console.log('Result: ' + uuidv4())
If you now run the file, it will work like before. What just happened? I was also shocked for the first time I faced this. Let's get to the answer.
Analyzing the whole process
So, error appeared for uuid
library after upgrading firebase-admin
to latest.
It's becuase the latest version of this library is using different version of uuid
library.
When we upgraded firebase-admin
library, it's dependencies also got upgraded.
Let's verify this by again looking into uuid
folder inside node_modules
.
If you now see the version inside package.json
file, it should be "9.0.0" but before it was "3.4.0".
In v9.0.0 of uuid
library, the previous syntax won't work so, we updated the code to fix the error.
We'll never want something like this to happen in the future again while upgrading libraries. so, let's get to the solution.
Solution
As might you've guessed, we shouldn't use firebase-admin
library's uuid
dependency.
If you've installed the uuid
library in your project this error might not have occured.
so, it's a good practice to install the library that you're using directly in your project.
(i.e. make sure it is listed in your project's package.json
file)
If you follow the same process by installing uuid
library before upgrading firebase-admin
,
it would work without any errors and you can upgrade uuid
library whenever you want.
Sometimes, we might forgot to install them because when we use libraries like that, no error is thrown.
For time like this, you can use a tool that'll check your package.json
file with the
libraries that you're using in your project and let you know of missing dependencies.
There are a lot of libraries for this. One of them is depcheck.
It not only shows the missing dependencies from package.json
but also how each dependencies are being used and
which dependencies are useless (i.e. not used).
If you install and run depcheck
then it'll output something like
Unused dependencies* firebase-adminMissing dependencies* uuid: .\index.js
That's it. Now you know your unused and missing dependencies and act on them accordingly.
Pnpm - An alternative solution
Pnpm a is fast, disk space efficient package manager. How can this new thing become a solution?
A little time back, I said "Sometimes, we might forgot to install them because when we use libraries like that, no error is thrown."
But let me tell you this, If you use pnpm
as your package manager instead of npm
or yarn
, it'll throw an error. But how and why?
In short, it has non-flat node_modules directory which is very different than others.
When installing dependencies with npm
or yarn
, all packages are hoisted to the root of the modules directory.
(i.e. they'll have flat node_modules directory). As a result, source code has access to dependencies that are not added as dependencies to the project.
By default, pnpm uses symlinks to add only the direct dependencies of the project into the root of the modules directory.
You can also have flat node_modules directory when using pnpm if you want and
make it behave like npm
or yarn
with some changes to hoisting settings.
You can find it here.
You can easily transition to pnpm from npm
or yarn
. I totally love using it in my projects.
The DX that it provides is great. I'll be writing an article on pnpm
in the future and cover it in more detail.
Closing thoughts
I hope this article helped you to know why listing dependencies in package.json file is essential for efficient and secure code management. This was a new lesson for me too. I hope to share more things like this if I faced them. This much for today and have a nice day.