Josh Goldberg
Zoomed in screenshot of a knip.json file showing 'entry' and 'ignore' string array entries

Speeding Up Centered Part 4: Unused Code Bloat

Aug 21, 202310 minute read

Using Knip to automatically detect unused files and dependencies in the Centered repository.

This post is the fourth in a series of five performance improvements I made to the Centered app. See 81 <iframe> Embeds for more context.

  1. 81 <iframe> Embeds
  2. Hidden Embedded Images
  3. Barrel Exports
  4. 👉 Unused Code Bloat
  5. Emoji Processing

Issue 4: Unused Code Bloat

The changes I’d made in the Centered performance post, Barrel Exports, improved Centered’s tree shaking to defer loading portions of larger shared packages. But in the previous performance post, Hidden Embedded Images, we’d seen how a single unused file significantly increased bundle size despite not being referenced anywhere. I had to wonder whether there were other unused files, and if so, whether they were contributing to load time. Even if their load was deferred, surely removing them would have some positive impact on page performance, right?

1. Tooling

It just so happens that I’d recently started using Knip in many of my projects. Quoting its lovely GitHub repository description:

✂️ Find unused files, dependencies and exports in your JavaScript and TypeScript projects. Knip it before you ship it!

Cute.

Knip looked like just the right tool for the job. It can sniff out unused files and package dependencies in just one command: npx knip.

2. Implementation

I added knip as a devDependency in the Centered website package, then added a knip.json config file to let it know how to deal with the Next.js structure:

{
	// Page files are the app "entry" points that are always considered used
	"entry": ["pages/**/*.tsx"],

	// Ignore public assets that may be referenced by URI
	"ignore": ["public"]
}

I then ran npx knip. The results were pleasing: it found quite a few unused areas of code!

Quoting and summarizing its output:

I uninstalled the unused dependencies, removed the unused files, and deleted the unused types and values in code. Whoohoo! 🥳

Bonus Deletion: Shared Components

Knip is highly configurable and generally supports monorepos. In theory I could have enabled it on the entire Centered monorepo and configured it to understand that Centered’s shared components package was only ever used internally. Doing so would have allowed it to catch unused shared components like the giant GCallSuggestionModal component I’d deleted.

But, in the interest of time, I just ran a manual find-all-references on all components exported by the package. It found 34 unused components. I deleted them all.

3. Confirmation

Deleting files feels satisfying, but needs to be paired with confirmation on whether it changed anything. I ran the Webpack Bundle Analyzer on an updated production build.

The results were not very impressive. The chunk sizes remained just about the same. Only a small handful of chunks changed, and no more than 1-2% of their total size.

A Lighthouse performance audit confirmed that the page’s score wasn’t changed by the changes.

Ah well.

In Conclusion

Sometimes your performance investigations will drastically speed up the page. Sometimes they do virtually nothing for page performance. Such is the nature of speculative performance work.

Still, I think this set of changes was worth the investigation. Pruning away dead code is beneficial for projects in the long run. Dead code leads to slower builds, harder-to-understand codebases, and increased complexity of dependency trees.

I’ve been on teams where introducing unused code detection removed hundreds of files and dozens of unused dependencies. Doing has shaved off multiple percentage points from total file sizes, package manager install times, and build times.

I’d highly recommend anybody reading this give Knip a try. You can see it in action in my create-typescript-app. That package runs it in CI so that individual pull requests are verified to not cause any code or dependencies to become unused.

Coming Soon

The next post in this series will be the final one, but I’ve been saving the best for last. It’ll show how I discovered runtime code in a package wasting multiple seconds on repeated work (!). Look forward to it later this season!

  1. 81 <iframe> Embeds
  2. Hidden Embedded Images
  3. Barrel Exports
  4. 👉 Unused Code Bloat
  5. Emoji Processing

Liked this post? Thanks! Let the world know: