Debugging a case of slow HMR in Vite

Monday, April 29, 2024

Recently at work, we were faced with increasingly long HMR times in our React app after a file change. It was pretty frustrating and was affecting team productivity. I took it up because it was likely a performance bug somewhere, and I was desperate to find it out.

Investigations & vite dive

First, I ran vite in debug mode (via --debug) to get a sense check of what was actually happening when we saved the file.

In (very) short, vite constructs a module graph of your code, and each module has a list of importees. Whenever a module's code is changed, vite figures outs the modules related to the changed module, what may require an update, and constructs an array of modules that need to be updated & invalidated.

After that, it creates an array of updates (with the changed code, and any updates to a module) that needs to be sent, which is accepted at a particular module (called HMR boundary).

Back to the problem - in the debug logs, I saw that the index.css HMR update (which needs to run on every file change, since we use tailwind and it needs to scan for classes) at the place where it is imported, was taking ~17s (!!!!)

Profiling the dev server

That already gave me a hint to what was going wrong - the content key in tailwind config was probably scanning way too many files - but just to be sure, I ran vite in --profile mode, took a CPU profile (p+enter) and threw it on speedscope.

There, I saw the glob matching & directory scanning were taking way too long, which validated my assumption about it being a too-many-files-and-folders-being-scanned problem.

I forgot to take a screenshot of the flamegraph. My apologies.

We also have a monorepo with pnpm workspaces, which likely amplified the problem - as the glob was scanning internal packages inside node_modules, it was also scanning every dependent package of our internal packages.

F.

The fix? Changed the content glob to not scan node_modules (and .js?(x) files, which are basically 90% of the files inside) and instead scan the packages directory via relative paths, which will only scan source files.

fix for the slow HMR perf

The result? (before vs after)

before vs. after of applying fix