Skip to main content
Jakub Mazurek6 min read

Rev-dep 3.0 is here!

This version brings performance improvements, duplicate code detection, and glob matcher behavior parity with gitignore.

Make sure to check Short Story of Rev-dep at the end of this post!

Useful links:

Performance Improvements​

On the codebase I use for benchmarking, which has around 600k LoC, Rev-dep config run was executing for ~300ms prior to the performance improvements; now it executes for ~150ms.

It got 50% faster and now there is likely no more room for improvements.

The gap was mostly caused by inefficient data-structure locks, which effectively made the resolution phase single-threaded. There were also some improvements in the parser.

This was also delivered to version 2.x in release 2.9.1.

Duplicate Code Detection​

Duplicate code detection is a new feature that I was excited to build. I've tried to do it a few years ago in my other tool CodeQue, but due to JavaScript threading limitations, it was not feasible to implement it in a performant way.

I took lessons from that approach and implemented it within Rev-dep, utilizing the full ownership of the parser and Go's concurrency model. The result is that you can add detection on top of existing Rev-dep checks without sacrificing performance.

What's different in Rev-dep's duplicate code identification approach is that it focuses on code chunks that can be refactored right away. It captures duplicated code blocks and JSX expressions, while other tools focus on detecting chunks of duplicated tokens. But those tokens can start in the middle of some function and end in the middle of another, which makes the findings harder to work with.

Read more about duplicate code detection in duplicated code detection docs.

Glob Matchers Behavior Parity With Gitignore​

Anyone who has built a static analysis tool knows how tricky it is to implement glob matchers that behave the same way as gitignore. The 2.x approach had some gaps in the implementation, and they couldn't be fixed without breaking changes.

I believe choosing to be compliant with gitignore is a good choice because you have to support it anyway. Otherwise, projects might need to adjust their gitignore files, and it has become an industry standard due to Git's adoption.

All glob matchers in Rev-dep now follow gitignore behavior. Matching is still very fast thanks to the gobwas/glob library, which can parse glob expressions into data structures that enable very efficient matching.

Short Story of Rev-dep​

This release is a great opportunity to look back at the origin of Rev-dep.

It started as a CLI with just the rev-dep resolve command, serving the purpose of finding where a given React component was used in a server-side React application.

Back then I was working on a project where we migrated a CSS solution in a multi-page React product, and we needed a way to shortlist all pages affected by a given batch of changes.

The initial release happened in November 2020.

The 0.x version was working, but was not robust enough to work on very large codebases with complex dependency graphs.

It was based on dependency-cruiser, which delivered the dependency graph. Later it also used dpdm, which added support for TS codebases (yes, at that time people still worked on JS-only codebases 😀).

Now Rev-dep aims to replace these tools.

1.x Version (2022-2025)​

Version 1.0 included a performance improvement to the poorly designed internal graph structure that was actually a tree with a lot of duplicate nodes instead of a graph that connects unique nodes. Before the fix, the tool was allocating a lot of memory and was not able to finish a task on more complex codebases.

This release also brought more commands, such as getting project entry points and listing files reachable from a given entry point.

As part of Rev-dep, I've also written a Babel-based codemod, purpose-built to refactor imports from barrel files (export * from './x') into direct imports. That was built to solve dev server performance issues in another project I was working on.

At that time, Rev-dep still had a small scope and the CLI tool was not fast enough to use conveniently.

2.x Version - Revamp in Go (2025-2026)​

In 2025, Microsoft announced that they were porting the TypeScript compiler to Go and had achieved around a 10x performance improvement.

At that time, I was learning Go and built a side project with it. The TypeScript story inspired me to try to port Rev-dep to Go and see if I could achieve similar performance improvements.

Initially, I thought that I would be able to use their brand new TypeScript parser written in Go, but it turned out they hadn't exposed a public API for it yet. In fact, even now with the TS 7.0 release, there is no public parser API. Instead of forking their WIP parser, I looked for some alternatives and reminded myself about ESbuild, which is a fast JS/TS bundler written in Go. I found a way to get the dependency graph from esbuild compilation, and I implemented a prototype of Rev-dep resolve based on it. It was about 10 times faster than the previous JS version, and I was impressed.

Building on top of ESbuild was not feasible for all features, as it only exposes runtime dependencies - all type-only imports have been dropped. Ultimately, it is a bundler, and I used bundling metadata for my PoC. But I already knew that reimplementing Rev-dep in Go would make it much faster and pleasant to use day-to-day. With the help of Gen AI, I was able to implement a parser from scratch. It was the best possible parser for the job - it was purely focused on extracting imports from source code files and did not even build the AST. After the resolver implementation and parallelization of the processing, the end result was a CLI tool that was 40 times faster than the previous JS version.

Fast forward to today, Rev-dep 3.0 is now a fully featured tool that can be used to analyze and enforce dependency hygiene in large codebases. It has a rich set of checks, including circular dependencies, orphan files, unused exports, and more. The tool is also highly configurable, allowing users to define their own rules and checks. Even with Gen AI, it took a lot of time and effort to get there, but I'm really happy with the result.


That's it for this update, see you in the next one 👋

Jakub Mazurek
Software Engineer