Documentation / v0.2.2

Take the graph apart.

unstave analyzes TypeScript and React module graphs, finds expensive barrel imports, exposes cycles and dead exports, and produces byte-preserving rewrites when a direct import can be proven safe.

On this page
Scope

unstave is a graph analyzer and codemod. It is not a bundler, type checker, or bundle-size profiler.

02 / Installation

Choose your entry point.

The CLI ships through crates.io and Homebrew. The Node API and the Vite plugin ship on npm under the @unstave scope.

CLI

Cargo builds the binary from the published crate; the Homebrew tap does the same behind a formula.

Cargo and Homebrew
cargo install unstave-cli

brew tap eddiesr93/unstave https://github.com/eddiesr93/unstave
brew install unstave

Node and Vite

The Vite plugin depends on the native API, so installing @unstave/vite-plugin pulls @unstave/node with it. Install @unstave/node on its own only when you call analyze directly.

npm install --save-dev @unstave/vite-plugin
npm install @unstave/node

03 / Quick start

Measure before you rewrite.

Run the full analysis first, inspect the ranked barrel report, then preview a codemod without changing source files.

Terminal
cd your-typescript-workspace

unstave analyze --format terminal --format html
unstave barrels --min-amplification 2
unstave fix --dry-run
  1. The terminal report summarizes the workspace and ranks structural cost.
  2. The portable report is written to .unstave/unstave-report.html.
  3. fix --dry-run prints the exact unified diff and leaves every file untouched.

04 / Commands

Six focused operations.

Each command shares the global workspace, configuration, cache, and verbosity options.

unstave analyze

Build the complete graph and render one or more terminal, JSON, DOT, Mermaid, or HTML reports.

unstave barrels

Rank barrel files by amplification, excess reachable modules, import sites, and rewritable symbols.

unstave cycles

Report import cycles and a closed shortest path for each cycle.

unstave dead-exports

Find exported definitions with no inbound references while excluding configured and package entrypoints.

unstave fix

Plan or apply safe direct-import rewrites. The default behavior is a dry run.

unstave cache clear

Remove the workspace analysis cache at .unstave/cache-v1.rkyv.

05 / CLI options

Every switch, in one place.

Global options can appear with any subcommand. Command-specific options affect only the operation shown.

Global options

OptionDefaultBehavior
--root <PATH>.Workspace root to analyze.
--config <PATH><root>/unstave.tomlUse an explicit configuration file.
--no-cacheoffBypass reads and writes for the content-addressed cache.
-v, --verbose0Increase verbosity. Repeat as -vv for more detail.

analyze options

OptionDefaultBehavior
--format <FORMAT>terminalChoose terminal, json, dot, mermaid, or html. Repeat to generate multiple formats.
--out <DIR>.unstaveOutput directory for every non-terminal report.
--include-type-edgesoffInclude type-only edges in runtime-cost analyses.
--max-nodes <N>150Collapse HTML, DOT, and Mermaid graphs by directory above this node count.

barrels options

OptionDefaultBehavior
--min-amplification <F>allShow only barrel findings at or above the requested amplification.

fix options

OptionDefaultBehavior
--dry-runonPrint a unified diff without changing files.
--writeoffApply the exact safe rewrite plan to source files.
--checkoffLeave files untouched and exit with status 1 when rewrites are available.
--barrel <PATH>allLimit rewrites to imports from one barrel.
--only <GLOB>allLimit importing files using a workspace-relative glob.
--import-style <STYLE>configUse alias, relative, or preserve for rewritten import specifiers.

06 / Configuration

Defaults first. Overrides when needed.

An unstave.toml file at the workspace root is optional. Unknown fields are rejected, and CLI flags override file values.

unstave.toml
entrypoints = ["src/main.tsx"]
include = ["**/*.{ts,tsx}"]
exclude = ["**/*.test.ts", "**/*.stories.tsx"]

[barrel]
reexport_ratio = 0.8
max_own_decls = 2

[thresholds]
max_amplification = 5.0
max_cycles = 0

[codemod]
import_style = "preserve"
FieldDefaultBehavior
entrypointsemptyModules used for projected before-and-after reachability and dead-export exclusions.
includeJS and TS sourcesGlob set used to discover source modules.
excludeemptyGlob set removed from discovery in addition to built-in directory exclusions.
barrel.reexport_ratio0.8Minimum fraction of exports that must be re-exports.
barrel.max_own_decls2Maximum local declarations allowed when classifying a barrel.
thresholds.max_amplification5.0Informational reporting threshold in v0.1.4.
thresholds.max_cycles0Informational cycle threshold in v0.1.4.
codemod.import_stylepreserveDefault alias, relative, or preserve strategy for rewritten imports.

07 / Safe rewrites

A rewrite must be proven.

unstave follows each imported symbol through the re-export graph and changes source only when the declaration target is unique and the transformation is structurally safe.

Rewrites preserve

  • Source bytes outside the affected import spans.
  • Imported aliases, default bindings, and type modifiers.
  • The configured or predominant alias-versus-relative path style.
  • Explicit NodeNext runtime extensions and the file's semicolon convention.
  • Existing compatible imports from the same direct module.

Rewrites are skipped when

  • A symbol resolves to multiple possible declarations.
  • The import is a namespace import.
  • The re-export chain is cyclic or external.
  • The barrel has observed top-level side effects.
  • An existing namespace import cannot accept named bindings.
Recommended workflow
unstave fix --root . --dry-run
unstave fix --root . --barrel src/clients/index.ts --only 'src/app/**' --write
unstave fix --root . --check

08 / Integrations

Use the engine where work happens.

The native Node API runs analysis away from the main thread. The Vite plugin schedules the same analysis without blocking dev-server startup or HMR.

Vite plugin

Development analysis is enabled by default. Production builds remain opt-in.

vite.config.ts
import { defineConfig } from 'vite'
import unstave from '@unstave/vite-plugin'

export default defineConfig({
  plugins: [
    unstave({
      warnAmplification: 5,
      serveReport: true,
      outDir: '.unstave',
    }),
  ],
})
OptionDefaultBehavior
enableddev onlyExplicitly enable or disable analysis. Set true for production reports.
warnAmplification5Warn when a barrel exceeds this amplification ratio.
serveReporttrueServe the live HTML report at /__unstave.
outDir.unstaveProduction JSON and HTML report directory relative to the Vite root.

Native Node API

analyze returns the versioned report object. renderHtml turns that report into one portable HTML string.

analysis.mjs
import { analyze, renderHtml } from '@unstave/node'

const report = await analyze({
  root: '.',
  includeTypeEdges: false,
  noCache: false,
})

const html = await renderHtml(report)
Need implementation detail?

The repository README covers crate boundaries, benchmarks, release packaging, and contributor commands.

Open the repository README

09 / Real projects

Pinned code. Reproducible evidence.

Three active TypeScript repositories were analyzed at exact commits with the release binary. Each cache figure is the median of three runs; each rewrite was applied only inside a disposable clone and analyzed again.

ProjectGraphMedianStrongest targeted barrelSafe rewrite
Vite 57fea00 1,546 modules20.9 MiB peak RSS 68 ms miss38 ms hit 5x peak18 total excess 4 files5 imports
TanStack Query 46d7f02 1,081 modules21.0 MiB peak RSS 41 ms miss26 ms hit 19x peak189 total excess 14 files14 imports
Astro fba468c 2,858 modules44.7 MiB peak RSS 154 ms miss93 ms hit 143x peak574 total excess 5 files5 imports
Rewrites verified by upstream toolchains

Filtered lockfile installs let us verify the changes with each repository's own tools: Vite build, typecheck, and 10 tests; TanStack Query typecheck and 168 tests; Astro filtered build and 84 targeted tests. These remain source-graph measurements, not claims of equivalent build-time savings.

Open methodology and findings Read the write-up
Reproduce on a clean clone
unstave cache clear --root /path/to/project
unstave analyze --root /path/to/project --format json --out /tmp/report
unstave fix --root /path/to/project --barrel path/to/index.ts --dry-run
Command copied