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
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 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
pnpm add -D @unstave/vite-plugin
pnpm add @unstave/node
yarn add -D @unstave/vite-plugin
yarn add @unstave/node
bun add -d @unstave/vite-plugin
bun add @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.
cd your-typescript-workspace
unstave analyze --format terminal --format html
unstave barrels --min-amplification 2
unstave fix --dry-run
- The terminal report summarizes the workspace and ranks structural cost.
- The portable report is written to
.unstave/unstave-report.html. fix --dry-runprints 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
| Option | Default | Behavior |
|---|---|---|
--root <PATH> | . | Workspace root to analyze. |
--config <PATH> | <root>/unstave.toml | Use an explicit configuration file. |
--no-cache | off | Bypass reads and writes for the content-addressed cache. |
-v, --verbose | 0 | Increase verbosity. Repeat as -vv for more detail. |
analyze options
| Option | Default | Behavior |
|---|---|---|
--format <FORMAT> | terminal | Choose terminal, json, dot, mermaid, or html. Repeat to generate multiple formats. |
--out <DIR> | .unstave | Output directory for every non-terminal report. |
--include-type-edges | off | Include type-only edges in runtime-cost analyses. |
--max-nodes <N> | 150 | Collapse HTML, DOT, and Mermaid graphs by directory above this node count. |
barrels options
| Option | Default | Behavior |
|---|---|---|
--min-amplification <F> | all | Show only barrel findings at or above the requested amplification. |
fix options
| Option | Default | Behavior |
|---|---|---|
--dry-run | on | Print a unified diff without changing files. |
--write | off | Apply the exact safe rewrite plan to source files. |
--check | off | Leave files untouched and exit with status 1 when rewrites are available. |
--barrel <PATH> | all | Limit rewrites to imports from one barrel. |
--only <GLOB> | all | Limit importing files using a workspace-relative glob. |
--import-style <STYLE> | config | Use 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.
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"
| Field | Default | Behavior |
|---|---|---|
entrypoints | empty | Modules used for projected before-and-after reachability and dead-export exclusions. |
include | JS and TS sources | Glob set used to discover source modules. |
exclude | empty | Glob set removed from discovery in addition to built-in directory exclusions. |
barrel.reexport_ratio | 0.8 | Minimum fraction of exports that must be re-exports. |
barrel.max_own_decls | 2 | Maximum local declarations allowed when classifying a barrel. |
thresholds.max_amplification | 5.0 | Informational reporting threshold in v0.1.4. |
thresholds.max_cycles | 0 | Informational cycle threshold in v0.1.4. |
codemod.import_style | preserve | Default 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.
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.
import { defineConfig } from 'vite'
import unstave from '@unstave/vite-plugin'
export default defineConfig({
plugins: [
unstave({
warnAmplification: 5,
serveReport: true,
outDir: '.unstave',
}),
],
})
| Option | Default | Behavior |
|---|---|---|
enabled | dev only | Explicitly enable or disable analysis. Set true for production reports. |
warnAmplification | 5 | Warn when a barrel exceeds this amplification ratio. |
serveReport | true | Serve the live HTML report at /__unstave. |
outDir | .unstave | Production 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.
import { analyze, renderHtml } from '@unstave/node'
const report = await analyze({
root: '.',
includeTypeEdges: false,
noCache: false,
})
const html = await renderHtml(report)
The repository README covers crate boundaries, benchmarks, release packaging, and contributor commands.
Open the repository README09 / 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.
| Project | Graph | Median | Strongest targeted barrel | Safe 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 |
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-upunstave 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