We all want to develop features quickly. We don’t want to redevelop a solution to a problem that has already been solved and battle-tested by someone else. So we often turn to an existing package—you know, browsing NPM!
Adding an external package is a double-edged sword, as it comes with its own risks as well—extra bundle size, security vulnerabilities, breaking changes, etc.
In order to tackle this dependency management, I will show you exactly:
- How you can assess the necessity of a new package
- What are the best practices
- How you can set rules in your team
- How you can make sure rules are followed
How Do You Keep Your Dependencies Under Control?
First step to successfully managing dependencies is *drum rolls* – not to have any! But that doesn’t happen often, does it? That would require us to reinvent everything and we don’t want to do that. Avoiding dependencies entirely would require us to reinvent everything, and we don’t want to do that. So the second best thing is to find a way how to assess, which dependency we will add to our project and which not.
Let’s look at the best practices for deciding whether a particular dependency is needed in our project.
Necessity of the Dependency
We need to assess whether dependency is truly necessary. We can do this by asking ourselves following questions:
- Ease of implementation – Can the functionality be implemented reasonably, without adding external package? Ask your peers, senior members of the team, or even ChatGPT!
- Native solution – Do native solutions exists? Whether it is in your existing codebase, existing library or current native browser features. Remember to also check capabilities of frameworks you use.
- Evaluate the need – Dependencies should add significant value or solve complex problems, not just address easily solved issues in-house.
Quality And Long-Term Viability
Once you decide a dependency is needed, it’s crucial to ensure it meets a certain level of quality and offers long-term reliability. The last thing you want is to add a new dependency that becomes unsupported shortly after, forcing you to replace it. So how do you quantify quality and long-term viability?
- Active Maintenance – this one is easy to spot, and most of us are already doing it. When was the latest version of the package published? What about previous versions. Are they released regularly? Weekly downloads? Most of this data is already visible on NPM page of the specific package.
- Community Support – Is it only creators of the package who contribute to new features and maintenance? Or are other developers also contributing? Check GitHub page of the package and “Issues” section. Look at open and closed issues, to understand how responsive the maintainers are. Remember that active user community is a strong indicator of package reliability and longevity.
- Up-to-date Documentation – last but not least is documentation. Is it comprehensive, clear and up-to-date? Usually, these are signs of well-maintained package. Clear installation instructions, usage guides, examples and API references are must have.
It’s easy to install a package and just use it. But you need to be aware that it can have legal implications. Even more so, when your software is used for commercial purposes. Therefore, checking a license of a package is essential before adding it to your project. Here is the brief list of most common incenses. (Disclaimer: do your own license research, for your particular case)
- ISC License (details) – similar to MIT license, it is permissive free software license. It allows for commercial use, modification, distribution and private use. Requires including the full text of license in modified software.
- Apache License 2.0 (details) – also similar to MIT license in it’s permissiveness. Although, requires modified versions to state the changes made, when distributing software.
- Proprietary Licenses – some packages might be under proprietary licenses, where the copyright holder maintains control over the use and distribution of the software. These often come with more restrictions, especially for commercial use.
It’s good practice to consult with legal counsel when incorporating open-source software into commercial projects, especially if you’re dealing with a variety of licenses or large codebases.
There is no such a thing as bulletproof package. So when you decide to add a new one to your code, you are opening yourself to potential vulnerabilities. Therefore it’s crucial to assess the current state of the dependency. Here is how you can do it:
- Vulnerability Scanning – if you are using NPM package manager you can run command npm audit, which asks for report of known vulnerabilities of your packages, and if any are found, then follow steps which can be taken to fix those. Alternatively, you can use more comprehensive tools such as Snyk.
- Dependency Pinning – if you know the package well, and you are happy with it as it is, you can also pin a version of package to avoid automatically updating to newer versions, which might introduce new vulnerabilities. However, this needs to be balanced with the need to update for security patches.
- Regular Updates – many security vulnerabilities are fixed in newer versions. Therefore regularly updating is important. So if your repository is on GitHub, you can take advantage of Dependabot and configure it to check your dependencies regularly, and make pull requests for any new versions.
- Checking Deprecated Functions – ensure that dependency does not use deprecated or unsafe functions, which can be removed in future releases, or are not maintained anymore.
Automating The Best Practices
Having rules and guidelines is a great first step. But how do you make sure they are followed? How do you do it with as little overhead as possible?
In order to be aware what dependencies are being added or removed, you can specify a person or a team members, which will need to approve any changes in regards to dependencies in your project, such as any changes in your package.json file.
In your root of the repository create a folder called .github and inside of it a file called CODEOWNERS. Here you can specify a rules you want. For example if you want to require a specific team member approval for any changes in package.json file you can do the following:
// Inside .github/CODEOWNERS **/package.json @username
This rule will apply to all package.json files in your repo. Approval of @username will be required for PR to be merged.
In order to automate dependencies with Dependabot, we need to configure it. We do this by creating root of our project, inside .github folder a dependabot.yml file.
# dependabot.yml configuration file version: 2 updates: # Package manager to be used - package-ecosystem: "npm" # Look through all directories directory: "/" schedule: # daily | weekly | monthly interval: "weekly" open-pull-requests-limit: 10 ignore: # For all packages, ignore all patch updates - dependancy-name: "*" update-types: ["version-update:semver-patch"]
With this configuration file, we use npm as package manager. Dependabot looks through all directories and check package updates on weekly basis. It will open maximum 10 pull requests at a time.
If you have a big project and you didn’t have Dependabot before, I suggest setting a rule of ignoring “patch” versions on dependencies in the beginning so you can focus on major and minor versions. And once you have all dependencies up to date, you can remove that rule. As often patch versions contains bugfixes and security patches.
If you want to find out performance impact of your npm packages and it’s effect on your bundle size or see historical trends, then this tool is for you. You can either use it online, by searching for specific package name, or you can upload your package.json file.
As I already mentioned, you need to be aware of the licenses associated with the packages you use. You can utilize a tool like FOSSA to help you protect your software against license violations. Additionally, you can achieve continuous compliance by integrating it into your CI pipeline.
Managing project dependencies in a lean and clean manner is essential for efficient development of software. While leveraging external libraries can accelerate feature development, it’s crucial to navigate this path with a strategic approach. By assessing the necessity, quality, and long-term viability of each dependency, ensuring compliance with licensing, and maintaining robust security protocols, you can significantly mitigate the risks of added dependencies. Embracing best practices, setting clear team rules, and utilizing tools for automation and monitoring are key steps to maintaining a healthy dependency ecosystem. Remember, the goal isn’t just to add features rapidly but to build sustainable, secure, and efficient software that stands the test of time.