Build import dependency trees for TypeScript and JavaScript files. Fast, zero-dependency static analysis for dependency detection and cache invalidation.
When a file changes, you need to know what else is affected. importree builds the full import dependency tree for any TypeScript or JavaScript entry point — with zero dependencies and zero AST overhead.
Built for CI pipelines, build tools, monorepo task runners, and test selectors.
- Zero dependencies — Built entirely on Node.js built-ins. No native binaries, no WASM.
- Fast scanning — Regex-based import extraction with concurrent async file traversal. No AST parsing overhead.
- Path alias support — Resolve
@/components,~/utils, or any custom alias with longest-prefix matching and automatic extension probing. - Cache invalidation — Pre-computed reverse dependency graph answers "what needs rebuilding?" instantly.
- Dual output — Ships both ESM and CJS with full TypeScript declarations.
Measured with Vitest bench on Node.js v22. Results vary by hardware.
Each tool brings different strengths — dependency-tree offers robust AST-based analysis via detective, madge supports multiple languages and provides circular dependency detection with visualization. importree trades those features for raw speed through regex-based extraction.
| Scenario | importree | dependency-tree | madge | Manual glob+regex | ts.createProgram |
|---|---|---|---|---|---|
| Small (10 files) | 0.4 ms | 3.1 ms | 3.7 ms | 0.6 ms | 49.9 ms |
| Medium (100 files) | 2.1 ms | 14.3 ms | 15.1 ms | 5.2 ms | 48.4 ms |
| Large (500 files) | 12.7 ms | 44.4 ms | 43.3 ms | 26.5 ms | 50.9 ms |
| Project size | Mean time | Throughput |
|---|---|---|
| 10 files | 0.4 ms | ~2,548 ops/s |
| 100 files | 2.5 ms | ~406 ops/s |
| 500 files | 12.1 ms | ~83 ops/s |
| 1,000 files | 26.4 ms | ~38 ops/s |
| Operation | Throughput |
|---|---|
scanImports (3 imports) |
~661K ops/s |
scanImports (50 imports) |
~41K ops/s |
stripComments (1,000 lines) |
~2,497 ops/s |
Run
pnpm bench:runto reproduce locally.
npm install importree
# or
pnpm add importree
# or
yarn add importree
# or
bun add importreeRequires Node.js >= 18.
import { importree } from "importree";
const tree = await importree("./src/index.ts", {
aliases: { "@": "./src" },
});
console.log(tree.files);
// ['/abs/src/index.ts', '/abs/src/app.ts', ...]
console.log(tree.externals);
// ['react', 'lodash', 'node:path']
console.log(tree.graph);
// { '/abs/src/index.ts': ['/abs/src/app.ts', ...] }import { importree, getAffectedFiles } from "importree";
const tree = await importree("./src/index.ts");
// When utils.ts changes, what needs rebuilding?
const affected = getAffectedFiles(tree, "./src/utils.ts");
console.log(affected);
// ['/abs/src/app.ts', '/abs/src/index.ts']
// ^ every file that transitively depends on utils.tsRecursively resolves all static imports, dynamic imports, require() calls, and re-exports starting from the entry file. Returns the full dependency graph.
importree(entry: string, options?: ImportreeOptions): Promise<ImportTree>| Parameter | Type | Required | Description |
|---|---|---|---|
entry |
string |
Yes | Path to the entry file (resolved against cwd) |
options |
ImportreeOptions |
No | Configuration for resolution behavior |
| Option | Type | Default | Description |
|---|---|---|---|
rootDir |
string |
process.cwd() |
Root directory for resolving relative alias paths |
aliases |
Record<string, string> |
{} |
Path alias mappings (e.g., { '@': './src' }) |
extensions |
string[] |
['.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs'] |
File extensions to try when resolving extensionless imports |
Promise<ImportTree> — the resolved dependency tree.
BFS traversal of the reverse dependency graph. Returns all files that transitively depend on the changed file — sorted, deterministic, and without the changed file itself.
getAffectedFiles(tree: ImportTree, changedFile: string): string[]| Parameter | Type | Required | Description |
|---|---|---|---|
tree |
ImportTree |
Yes | A tree previously returned by importree() |
changedFile |
string |
Yes | Path to the file that changed (resolved to absolute) |
string[] — sorted absolute paths of all files that transitively depend on the changed file. The changed file itself is excluded. Returns an empty array if the file is not in the graph.
The result object returned by importree().
| Field | Type | Description |
|---|---|---|
entrypoint |
string |
Absolute path of the entry file |
files |
string[] |
Sorted absolute paths of all local files in the dependency tree |
externals |
string[] |
Sorted unique bare import specifiers — packages like react, lodash, node:fs |
graph |
Record<string, string[]> |
Forward adjacency list. Each file maps to its direct local imports. |
reverseGraph |
Record<string, string[]> |
Reverse adjacency list. Each file maps to files that import it. |
importree extracts specifiers from all standard import patterns:
- Static imports —
import { foo } from './bar' - Default imports —
import foo from './bar' - Namespace imports —
import * as foo from './bar' - Side-effect imports —
import './bar' - Type imports —
import type { Foo } from './bar' - Dynamic imports —
import('./bar') - CommonJS require —
require('./bar') - Re-exports —
export { foo } from './bar',export * from './bar'
Imports inside comments and string literals are ignored.
Circular dependencies are handled — each file is visited once.
- Relative imports (
./or../) resolve against the importing file's directory. - Alias imports match against the configured
aliasesusing longest-prefix matching, then resolve as relative paths fromrootDir. - Bare specifiers (e.g.,
react,@scope/pkg,node:fs) are classified as external and collected inexternals.
For each resolved path, importree probes in order:
- Exact path
- Path + each extension (
.ts,.tsx,.js,.jsx,.mjs,.cjs) - Path as directory +
index+ each extension
ISC — Alex Grozav