Rust / oxc Module graph analyzer

See what one import drags in.

unstave maps every TypeScript and React import, measures barrel amplification, and exposes cycles and dead exports. It rewrites an import only after proving where the symbol is declared.

npx @unstave/cli analyze
Then unstave analyze --format terminal --format html
01

Latest release
v0.2.2

Built for
large TS graphs

Live product model / nested-barrels fixture

One symbol. Six modules loaded.

Dependency graph for the nested-barrels fixture Seven modules connected by six import and re-export edges. The selected src/a/index.ts barrel reaches six modules while the importer uses one symbol. src/main.tsx src/a/index.ts 6.0x AMPLIFICATION src/a/b/index.ts src/a/b/c/index.ts .../c/one.ts .../c/two.ts .../c/three.ts

The hidden tax

Barrels make imports look cheap.

Your dev server pays the full graph.

A tidy import { Foo } from '@/clients' can traverse every re-export behind that index file. unstave measures the transitive cost from the source graph itself—no bundler, no TypeScript type-check, no guesswork.

01

Map

The whole graph, not a file at a time.

Resolve relative paths, tsconfig aliases, package exports, workspace boundaries, built-ins, and five edge kinds across a monorepo.

  • Per-package resolver context
  • Cycles and shortest paths
  • Dead export confidence
02

Measure

Turn “this feels slow” into a ranked list.

Score every imported barrel by actual reachable cost, excess modules, worst amplification, import sites, and rewritable symbols.

  • Terminal, JSON, DOT, Mermaid
  • Portable interactive HTML
  • Versioned report schema
03

Rewrite

Cut the barrel out without mangling source.

Plan span-based, byte-preserving rewrites. Ambiguity, namespace imports, cycles, side effects, and external chains are reported—not guessed through.

  • Dry-run by default
  • CI check mode
  • Alias or relative style

Diagnosis / proof / change

The diff is the product.

unstave never rewrites an import because it looks suspicious. It follows the symbol through the re-export chain, proves a unique declaration, preserves aliases and type modifiers, then shows the exact patch before touching disk.

Read the codemod safety rules
unstave fix --dry-run src/main.tsx
@@ -1,4 +1,4 @@
- import { AlphaClient } from './clients'
+ import { AlphaClient } from './clients/alpha'

  export const client = new AlphaClient()
1 safe rewrite0 bytes outside import spans changed

Release benchmark / Apple M4 Pro

Large graph.
Short wait.

$ cargo bench -p unstave-core --bench pipeline

generating 6,000 TypeScript modules…

cold234.0 ms

warm117.4 ms

median of 3 / cache verified / release profile

Method, machine, and budgets

Validated on a real codebase

False positives do not earn rewrites.

unstave was run against pinned commits of Vite, TanStack Query, and Astro. One Astro import reached 143 modules. Every rewrite was then verified by each project's own build, typecheck, and tests.

Read what barrel files actually cost
Repositorywithastro/astro
src/core/config/index.ts
16765reported excess
dev-toolbar/ui-library/index.ts
130false positive removed

Observed during development. No customer code or synthetic claim.

vite.config.tsasync / non-blocking
import unstave from '@unstave/vite-plugin'

export default {
  plugins: [unstave({
    warnAmplification: 5,
    serveReport: true,
  })],
}

Inside the dev loop

Vite starts first. Analysis follows.

The native binding runs through libuv without blocking startup or HMR. Open /__unstave for the live report; production analysis stays opt-in.

TerminalJSONHTMLDOTMermaid

Open source / MIT

Put your graph on the table.

Run unstave on a real TypeScript workspace. If it surfaces a bottleneck worth fixing, star the repository so the next team can find the instrument.

Command copied