vite-optimize-persist
v1.0.0
Published
Vite plugin that prevents split optimization batches by persisting the discovered dependency list across dev server restarts
Maintainers
Readme
vite-optimize-persist
Vite plugin that prevents split optimization batches by persisting the discovered dependency list across dev server restarts.
The Problem
Vite's dependency optimizer can discover deps across multiple rounds during the first page load, each producing a different browserHash. When React and ReactDOM land in different rounds, the browser loads two separate React instances and hooks break:
TypeError: Cannot read properties of null (reading 'useContext')
Invalid hook call. Hooks can only be called inside of the body of a function component.This is especially common with react-router v7's dev server, which triggers dep discovery after Vite's initial crawl completes. Vite's built-in holdUntilCrawlEnd doesn't help because the late discovery happens during SSR rendering, not the static import scan.
optimizeDeps.include with a manual list doesn't help either — when Vite discovers deps not in the list, it re-optimizes everything with a new hash, and modules already loaded in the browser still reference the old hash.
The Fix
On startup, read the dep list Vite wrote to _metadata.json (from the previous run or the build step that precedes dev) and feed it back via optimizeDeps.include + noDiscovery: true. Everything ends up in one batch with one hash.
Install
npm install vite-optimize-persist --save-devUsage
// vite.config.ts
import { defineConfig } from 'vite';
import { optimizePersist } from 'vite-optimize-persist';
export default defineConfig({
plugins: [optimizePersist()],
});Only runs during vite dev (apply: 'serve').
How It Works
- Vite writes dependency metadata to
node_modules/.vite/deps/_metadata.jsonduring builds and dev runs. - On the next dev server startup, this plugin reads that file and extracts the full dependency list.
- It sets
optimizeDeps.noDiscovery: true+optimizeDeps.includewith all known deps, so Vite pre-bundles everything in a single pass. - If the metadata file is missing or unreadable, the plugin does nothing and Vite discovers deps normally.
Prior Art
vite-plugin-optimize-persist by antfu solved the same problem for Vite 2 by writing discovered deps to package.json. It was abandoned when Vite 2.9.1 improved dep discovery with holdUntilCrawlEnd, which the Vite team considered a complete fix. It wasn't — react-router v7 re-exposed the issue.
This plugin takes a simpler approach: read from Vite's own cache file instead of maintaining a separate dep list.
