DEV Community

Cover image for How Many Dependencies Does Your Project Really Have?
Syki
Syki

Posted on

How Many Dependencies Does Your Project Really Have?

Introduction: The Light-Hearted Side of "node_modules"

Ah, node_modules – every JavaScript developer's favorite folder (not really, but let's pretend). It's where tiny utility packages, like is-odd, find their place alongside larger, more comprehensive libraries. Who hasn't felt a mix of amusement and bewilderment on discovering packages that accomplish tasks which seem... trivial?

Speaking of amusing, remember that security advisory which cautioned against a particular npm install? If you thought that was rare, think again!

Is It As Bad As They Say?

Spoiler alert: It might be even worse.

You might argue, "It's just a tiny package, what harm can it do?" That's where the actual cascade begins. Every package you add brings along its own plethora of dependencies, and those dependencies have their own dependencies, and so on. It's like the matryoshka dolls of the JavaScript world.

To provide a bit of perspective, most small random projects can have over a whopping 1000 dependencies. Yep, you read that right.

Diving Into Your Dependency Tree

The real eye-opener is when you delve into the intricacies of your dependency tree. Just a simple audit can reveal the magnitude of what your seemingly innocent package.json file drags into your project.

Tool I used:

My project written in Next.js

Package.json

{
    "dependencies": {
        "@monaco-editor/react": "^4.6.0",
        "@types/node": "^20.8.3",
        "@types/react": "^18.2.25",
        "@vercel/analytics": "^1.1.0",
        "@vercel/postgres": "^0.5.0",
        "axios": "^1.5.1",
        "dayjs": "^1.11.10",
        "flowbite": "^1.8.1",
        "flowbite-react": "^0.6.4",
        "formik": "^2.4.5",
        "jwt-decode": "^3.1.2",
        "monaco-editor": "^0.44.0",
        "next": "^13.5.4",
        "next-auth": "^4.23.2",
        "next-intl": "3.0.0-beta.7",
        "next-themes": "^0.2.1",
        "qs": "^6.11.2",
        "react": "^18.2.0",
        "react-dom": "^18.2.0",
        "react-hot-toast": "^2.4.1",
        "react-select": "^5.7.7",
        "react-sweet-state": "^2.7.1",
        "tailwind-merge": "^1.14.0",
        "uuid": "^9.0.1",
        "yup": "^1.3.2"
    },
    "devDependencies": {
        "@svgr/webpack": "^8.1.0",
        "@types/qs": "^6.9.8",
        "@types/uuid": "^9.0.5",
        "@typescript-eslint/eslint-plugin": "^6.7.4",
        "autoprefixer": "^10.4.16",
        "eslint": "8.51.0",
        "eslint-config-next": "^13.5.4",
        "eslint-config-prettier": "^9.0.0",
        "eslint-plugin-simple-import-sort": "^10.0.0",
        "postcss": "^8.4.31",
        "prettier": "^3.0.3",
        "prettier-plugin-tailwindcss": "^0.5.5",
        "tailwindcss": "^3.3.3",
        "typescript": "^5.2.2"
    }
}
Enter fullscreen mode Exit fullscreen mode

Audit

My Next.js project have 652 dependencies, as you see in package.json, project is not that big, but still we have buch of dependencies, but wait this is nothing.

CSV

Tree

Next.js project tree

TreeMap

Next.js project TreeMap

Example Nest.js backend project

Package.json

{
    "dependencies": {
        "@nestjs/common": "^10.0.0",
        "@nestjs/config": "^3.0.0",
        "@nestjs/core": "^10.0.0",
        "@nestjs/jwt": "^10.1.0",
        "@nestjs/passport": "^10.0.0",
        "@nestjs/platform-express": "^10.0.0",
        "@nestjs/swagger": "^7.1.11",
        "argon2": "^0.31.0",
        "class-transformer": "^0.5.1",
        "class-validator": "^0.14.0",
        "passport": "^0.6.0",
        "passport-jwt": "^4.0.1",
        "reflect-metadata": "^0.1.13",
        "rxjs": "^7.8.1"
    },
    "devDependencies": {
        "@nestjs/cli": "^10.0.0",
        "@nestjs/schematics": "^10.0.0",
        "@nestjs/testing": "^10.0.0",
        "@prisma/client": "^5.1.1",
        "@types/express": "^4.17.17",
        "@types/jest": "^29.5.2",
        "@types/node": "^20.3.1",
        "@types/passport-jwt": "^3.0.9",
        "@types/supertest": "^2.0.12",
        "@typescript-eslint/eslint-plugin": "^6.0.0",
        "@typescript-eslint/parser": "^6.0.0",
        "eslint": "^8.42.0",
        "eslint-config-prettier": "^9.0.0",
        "eslint-plugin-prettier": "^5.0.0",
        "jest": "^29.5.0",
        "prettier": "^3.0.0",
        "prisma": "^5.1.1",
        "source-map-support": "^0.5.21",
        "supertest": "^6.3.3",
        "ts-jest": "^29.1.0",
        "ts-loader": "^9.4.3",
        "ts-node": "^10.9.1",
        "tsconfig-paths": "^4.2.0",
        "typescript": "^5.1.3"
    }
}
Enter fullscreen mode Exit fullscreen mode

Audit

My NestJS project have 719 dependencies, after i saw the audit of next.js, nest.js didn't suprice me at all.

CSV

Tree

NestJS project Tree

TreeMap

NextJS project TreeMap

Example of a simple React Native project

Package.json

{
    "dependencies": {
        "@expo-google-fonts/inter": "^0.2.3",
        "@expo-google-fonts/maven-pro": "^0.2.3",
        "@expo/webpack-config": "^18.1.3",
        "@gorhom/bottom-sheet": "^4.5.1",
        "@react-native-async-storage/async-storage": "1.19.3",
        "@react-native-community/datetimepicker": "7.2.0",
        "@react-native-picker/picker": "2.5.0",
        "@types/styled-components-react-native": "^5.2.1",
        "axios": "^1.5.0",
        "dayjs": "^1.11.9",
        "expo": "~49.0.11",
        "expo-checkbox": "~2.5.0",
        "expo-constants": "~14.4.2",
        "expo-font": "~11.4.0",
        "expo-image": "~1.5.1",
        "expo-linking": "~6.0.0",
        "expo-localization": "~14.5.0",
        "expo-router": "^2.0.6",
        "expo-secure-store": "~12.3.1",
        "expo-status-bar": "~1.7.1",
        "i18next": "^23.5.1",
        "jwt-decode": "^3.1.2",
        "react": "18.2.0",
        "react-hook-form": "^7.46.1",
        "react-i18next": "^13.2.2",
        "react-native": "0.72.4",
        "react-native-gesture-handler": "~2.12.0",
        "react-native-reanimated": "~3.3.0",
        "react-native-safe-area-context": "4.7.2",
        "react-native-screens": "~3.25.0",
        "react-native-select-dropdown": "^3.4.0",
        "react-native-svg": "13.13.0",
        "react-native-swiper-flatlist": "^3.2.3",
        "react-native-web": "~0.19.8",
        "react-sweet-state": "^2.7.1",
        "styled-components": "^5.3.11"
    },
    "devDependencies": {
        "@babel/core": "^7.22.19",
        "@types/metro-config": "^0.76.3",
        "@types/react": "~18.2.21",
        "@types/react-native-snap-carousel": "^3.8.5",
        "@types/styled-components": "^5.1.26",
        "@typescript-eslint/eslint-plugin": "^6.7.0",
        "@typescript-eslint/parser": "^6.7.0",
        "babel-plugin-styled-components": "^2.1.3",
        "eslint": "^8.49.0",
        "eslint-config-universe": "^12.0.0",
        "prettier": "^3.0.3",
        "react-native-svg-transformer": "^1.1.0",
        "typescript": "^5.2.2"
    }
}
Enter fullscreen mode Exit fullscreen mode

Audit

My React Native project have 1434 dependencies, that suprised me a lot. I've just created this project, and work on it maybe for a week.

CSV

Tree

React Native project tree

TreeMap

React Native project TreeMap

To Contribute or Not to Contribute

The Case for Creating Your Own Packages

Before you add another random package for a function that's probably ten lines of code, pause and reflect. Do you really need it? More often than not, you'll realize that writing that function yourself or simply copying what you need will suffice.

Unraveling the Nested Dependencies

However, if you find that you're relying on a package that itself has numerous unnecessary dependencies, consider contributing. Trim down the fat, so to speak. If you've got the time and expertise, pitch in and help the community streamline things. Your future self (and your fellow devs) will thank you.

The Message: Think Before You "npm install"

Every time you're on the brink of adding another package, think about the cascade it might bring along. Every additional dependency is potential technical debt, not to mention the security implications.

Moreover, if you're in the privileged position of having time on your hands, consider contributing to existing packages or even creating your own streamlined versions. The npm ecosystem thrives because of contributors, and you could be the next one to make it better!

Conclusion

Dependencies are both a boon and a bane. They save time and provide functionality but also bloat projects and introduce risks. Striking a balance is key. Use what you need, contribute when you can, and always be judicious with your npm install commands. Remember, with great power (to install) comes great responsibility!

Top comments (8)

Collapse
 
adaptive-shield-matrix profile image
Adaptive Shield Matrix • Edited
  • using intelligent package managers like bun or pnpm -> you do not increase disk space on your machine by re-installing project dependencies in same or different projects
  • server side: package / dependency size is mostly irrelevant (if using docker builds the right way the layers with dependencies will be cached)
  • package size of dependencies is very important on the frontend, because much more data has to be transferred (in any of the modes: CSR, SSR, ISR)
Collapse
 
trueromanus profile image
Roman Vladimirov

"server side: package / dependency size is mostly irrelevant (if using docker builds the right way the layers with dependencies will be cached)" it relevant if machine where you deployed app is has internet access, but if not dependency size become very important because all dependencies become part of you installer.

Collapse
 
rickdelpo1 profile image
Rick Delpo

hey THANKS Syki, One word stands out and that is "bewilderment" which is the first thing I experienced when learning Node. I much prefer Plain Vanilla Javascript with no dependencies or libraries but I guess we can't avoid Node.js if we are students. Node can be overkill in my humble opinion. I use it sparingly.

Collapse
 
syki profile image
Syki

Yes, it may not sound good from this article, but npm dependencies are a beautiful world and in large projects they are impossible to avoid if you don't work at Google. However, it is worth remembering that each dependency, in addition to new functionalities, also has opportunity costs, such as potential vulnerabilities, increasingly larger node_modules, technical dept or conflicts in dependencies.

Collapse
 
renhiyama profile image
Ren Hiyama

Could we say that URL imports can fix this? esm.sh and other providers provide bundling of deps, or cherry pick exported functions too, wouldn't that make things a lot faster? I've been using this on my reejs framework (ree.js.org) and it's been working awesome-ly fast and smaller project sizes!

Collapse
 
artxe2 profile image
Yeom suyun

This is a topic worth considering, but I think a significant number of the dependencies shown in the tree are just depDependencies.

Collapse
 
syki profile image
Syki

Sandworm by default doesn't include devDeps in tree, but you are right the number i wrote is with devDeps.

Without devDeps:
My Next.JS project: 653 -> 170
My NestJS project: 719 -> 147
My React Native project: 1434 -> 1116

Collapse
 
kaamkiya profile image
Kaamkiya

A perfect example of dependencies with dependencies: left-pad incident. Someone deleted an unused NPM package (or so they thought), but in reality, one of React's dependencies used it. So the internet crashed.