The Hidden Cost of Code Dependencies

10-13-21 Nate Jacobs

Relying on third-party code can be a dangerous balancing act. Let’s explore the pros and cons of dependencies in a modern frontend ecosystem.

As a tech lead on projects at Sparkbox, it is my job to balance the health of the project, current working features, and future tech debt when making decisions. Over the years, I have learned that relying on third-party code can be a dangerous balancing act.

Let’s explore the pros and cons of dependencies in the context of a modern frontend ecosystem.

The Cost of Dependencies

The next time you type npm install…stop. Take a minute to think. Consider the commitment you are about to make. Consider the cost.

The harsh truth is that every dependency you install will begin to eat away at the life expectancy of your project. It does not matter how popular or stable the dependency is. Your maintenance burden will grow and an expiration date will be imposed upon you and some slice of your web project. Can you predict that expiration date?

Cognitive Hurdles

Inasmuch as they provide prepackaged solutions, dependencies often create additional work and complexity for you and your team—including more to learn. From a developer’s perspective, software can be a maze of mental hurdles. Our job when writing code is to organize and simplify problems, and our dependencies should help us achieve that. But their effectiveness is subjective. Considering the volatile nature of project requirements and an ever-changing platform, today’s investment in any dependency is likely to suffer collateral damages down the line.

Vendor Lock-in

It’s not easy to gauge the quality and sustainability of third-party solutions. When a package becomes outdated, you will likely want to replace it. But when your project requirements outgrow the scope of a package, you will need to replace it.

If you choose tools that have a pervasive presence in your codebase, then you are making a big commitment. They have a way of weaving their way through your code, limiting your ability to pivot. Vendor lock-in is real. If your code is tightly coupled to non-standard, library-specific patterns, then you might as well put a ring on it. Congrats, you’re married!

You can definitely take steps to break free from vendor lock-in. Centralizing code that consumes third-party libraries is a common pattern. That is to say, make a “wrapper.” You determine the interface the rest of your code touches, and this helps to create isolation. When this is done correctly, it should allow you to introduce changes to the dependency in a less disruptive manner. Adapter and Facade patterns are a good place to start when considering the need for this kind of gateway abstraction.

Nightmare on Upgrade Street

But what about simple upgrades to your existing dependencies? In a perfect world, SemVer works flawlessly and you can avoid the disruption of a minor or patch version bump. The reality is that people decide how packages get versioned. Even if those people use tooling to help manage versioning (e.g. npm, Lerna, conventional commits) it is still imperfect people who control the next version of your dependency. Mistakes happen. Breaking changes creep in. Security vulnerabilities creep in.

Upgrading dependencies can be a nightmare. If you have a package.json in your project, go run npm ls --all right now. What do you see? Most real-world projects will have a massive list of production, development, peer, and transitive dependencies. It’s no wonder that security updates are a rolling burden. Security is a big topic and it’s incredibly important to keep track of what code makes its way into your JavaScript bundles. The easiest way to mitigate potential security issues related to dependencies is by simply not installing them in the first place!

Performance Woes

If you’re writing frontend code, do you know the cost your users pay for your build artifacts? Performance is a critical aspect of high-quality web applications. More dependencies mean larger bundles with a higher potential to ship unused code. The more you pile on, the slower your pages will be. Period.

Build Complexity

Some dependencies require extra work to build and optimize your code. That usually means even more dependencies and time spent wrestling with a complex toolchain. Of course, this isn’t necessary if you make the right choices.

Before you commit to the next dependency, ask yourself a couple of questions.

  1. What problem is it solving for you?
  2. How far away from web standards does this put you?

Then, carefully consider whether you really need to npm install.

The Value of Dependencies

Third-party code will cost you in the long run. There is no doubt about it (and if you disagree, go back and read again.) But let’s not downplay the benefits of open source software. Historically, libraries that employ non-standard code have helped influence some of the standardization process. jQuery, CoffeeScript, and Sass come to mind.

It would be unrealistic to produce any large-scale web product without tapping into the open source community. And frankly, from the perspective of a consultant, it would be irresponsible to hand off work to a client that is 100% custom-built. It takes more time to do everything from the ground up—and it takes a lot more money. Certainly, some dependencies are warranted.

UI frameworks like React, Vue, and others provide some huge benefits upfront. Since learning your way around a codebase takes time, large “core” dependencies like these provide consistency, documentation, and community that spans all projects that use them. These are portable benefits that don’t come with creating your own one-off solutions. But I’m not just talking about UI frameworks. There are a myriad of reasons that any given dependency might be warranted in your codebase.

So How Do I Know When to Use a Dependency?

Here are some things I typically consider when choosing third-party packages.

Features and Flexibility

Obviously, you need it to solve some problems for you. Does it do that? More specifically, does it do so in a way that applies to all or most use cases while accounting for best practices?

Learning Curve

With little or no exposure to the dependency, how much time does it take to get up to speed and be productive?

Testing

How does this affect your testing strategies? Does it integrate into your test suite or does it require you to mock more things? Are extra testing utilities needed?

Performance

Does it add too much weight to your bundle? See bundlephobia for a quick peek at package sizes.

Accessibility

For off-the-shelf solutions, accessibility is more often than not a sad story. Nevertheless, if you are installing a UI dependency, you should be asking whether it accounts for good accessibility practices or if it limits your ability to control them.

Industry Movement and Community

ThoughtWorks Technology Radar is a great resource to help you understand whether a package is moving in the right direction to have been vetted by the community. Other questions to ask include: Is it actively maintained? Are there learning resources? Is general support available in any way?

After considering all of these questions, you may still need to compromise in one or more categories to make the best choice for your project. Above all, refer to web standards to help guide your decision-making. Whenever possible, skip the third-party solution and use platform features. Add polyfills as needed to write code that meets those standards, and you will be rewarded with better portability and sustainability.

Moving Forward

Generally speaking, knowledge of web standards can help us make better decisions about our dependencies. But to do this, we need to first pay the cost of learning the platform.

ECMAScript, CSS, and all the other web standards provide many tools that can be used to our advantage.

The web is still growing and the future is bright. New technologies are emerging with a focus on web standards and first-class tooling. Projects like Deno, Astro, and Rome give me hope that we can simplify our toolchains and lean on web standards across more of our runtime and development environments. This will help reduce our need for external dependencies and expose platform features to more developers.

In practice, all developers need to make tradeoffs. You shouldn’t reinvent wheels for every aspect of your software projects, but you should ask yourself if you know the full cost of the wheels you choose. You may find that you don’t need them at all.