Library / SDK
dependents/node-dependency-tree avatar
dependents/node-dependency-tree

dependency-tree: a JavaScript dependency graph for ESM, CommonJS, AMD and TypeScript

Get the dependency tree of a module

776 stars83 forksJavaScriptMIT

At a glance

What is it?
dependency-tree walks a module and returns the tree of files it depends on, using precinct to find declarations and filing-cabinet to resolve them to real paths. It suits bundler and editor tooling that needs an accurate file-level graph, and it inherits every resolution quirk of the config you hand it.
Who is it for?
dependency-tree is the right tool for bundler plugins, editor features and build scripts that need a file-level graph of a module across several module formats, and it is the wrong tool for dependency version audits, license compliance or anything that needs the resolved version of a package.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 46 days ago.
What is it written in?
Mainly JavaScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

A file-level graph, not a package-level one

dependency-tree answers one question: given an entry file, which files does it pull in, and in what shape. The output is keyed by absolute file path, and every key holds the subtree of that file, so the answer is a graph of files rather than a list of packages with versions.

That distinction is the whole point of the project. A package manager already knows the dependency graph at the package level, including versions. What is hard to get elsewhere is the file-level view: which specific file inside node_modules ends up in the bundle, which module a webpack alias actually points at, which TypeScript declaration file an import resolves to. The nested output format was designed for visual representation in the Dependents plugin for Sublime Text, which tells you the intended consumer.

The consumer here is developer tooling: a bundler plugin deciding concatenation order, an editor that highlights usages across files, a build script that needs to know what a module touches. The package description is exactly that short, get the dependency tree of a module.

The three-step mechanism: precinct finds, filing-cabinet resolves

The pipeline is documented in the How it works section, and it explains both the power and the limits of the tool.

The entry file is passed to precinct to extract raw dependency strings. Precinct generates an AST via node-source-walk, detects the module format (CommonJS, AMD or ES6), and delegates to the appropriate detective to extract the dependency declarations. So the detection of what a file requires happens in precinct, not here.

Each raw string is then passed to filing-cabinet to resolve it to a real filesystem path, and dependency-tree recurses into the resolved path until the tree is complete. Filing-cabinet reuses the AST to detect the format again, then delegates to the right resolver. The reason it has to detect twice is stated plainly: AMD supports path aliasing via a RequireJS config, while CommonJS has its own resolution algorithm, so a single resolution strategy would be wrong for one of them.

The resolver returns an absolute path, and that path is what becomes a key in the tree. The result is that correctness of the graph depends on the correctness of the config you supply, since a wrong webpackConfig gives you confidently wrong paths.

Installing it and getting a first tree

The package is published on npm as dependency-tree, and installation is the usual one line:

sh
npm install dependency-tree

The package is an ES module. The type field in package.json is module, and the export map points the main entry at index.js with types at ./types/index.d.ts. The engines field requires node >=20.19.0 || >=22.12.0, so an older Node will not be supported.

The library is imported differently depending on your module system:

js
// ESM
import dependencyTree from 'dependency-tree';
// CommonJS
const { default: dependencyTree } = require('dependency-tree');

A first call needs an entry file and a root directory. The nested tree comes back as an object of absolute paths:

js
const tree = dependencyTree({
  filename: 'path/to/a/file',
  directory: 'path/to/all/files'
});

Both options are required, and root is available as an alias for directory. The relative paths in the example are placeholders, so a real call needs an absolute path for filename, since the output keys are absolute paths too.

toList gives the post-order list that bundlers actually want

The second entry point matters more than it looks. dependencyTree.toList returns a post-order flat list of absolute paths, dependencies before dependents, and the documentation says it is useful as a concatenation order for bundling.

That is a different operation from the tree, not just a different shape. The tree tells you the structure, which is what you want for visualisation and for analysis. The flat list tells you an order, which is what you want when the consumer has to emit files in a sequence where a file appears only after everything it depends on. The same order is available from the main function through the isListForm option, which returns the flat post-order list instead of the nested tree, and the two are documented as equivalent.

For a bundler integration, the list form is usually what you want, and the tree is what you want for a diagnostic view. Neither is a bundle itself, and the package does not claim to produce one. Its scope is the graph and an order derived from it, and the last step of concatenation stays with the caller.

Options that decide whether the graph is right

The option table is long, and a few entries change the answer rather than just tuning it.

The config paths are the important ones. requireConfig points at a RequireJS config and resolves aliased paths, with config as an alias. webpackConfig points at a webpack config for aliased modules. tsConfig accepts a path to a TypeScript config or a preloaded config object, and tsConfigPath is the virtual path for that object, which is required for Path Mapping and ignored when tsConfig is a string path. nodeModulesConfig is a config for resolving node_modules entry files, for example { entry: 'module' }.

Two options shape coverage. detectiveConfig is passed through to precinct, so options like { amd: { skipLazyLoaded: true } } or { ts: { skipTypeImports: true } } change what gets extracted, with detective as an alias. noTypeDefinitions, false by default, resolves TypeScript imports to *.js instead of *.d.ts, which is what you want when the graph should reflect runtime files rather than type files.

Two more affect the shape. filter is a predicate taking dependencyPath and parentPath, returning true to include a dependency and its subtree, and the example filters out anything whose path includes node_modules. visited is a memoization cache mapping filename to subtree, defaulting to an empty object, and it exists to skip files that were already processed.

Unresolved paths land in nonExistent instead of throwing

The failure mode of this tool is quiet, and the documentation addresses it directly in the FAQ section.

The nonExistent option is an array, empty by default, that gets populated with the partial paths that could not be resolved. Nothing in the tree tells you a dependency is missing; you have to look at that array. The listed most common causes of missing dependencies are bugs in precinct or an incomplete requireConfig, webpackConfig or tsConfig, and the first thing to do with a suspiciously small tree is check the array rather than the tree.

The CLI gives you a way to see the resolution itself. Setting NODE_DEBUG to a wildcard when using the CLI produces detailed resolution logs:

sh
NODE_DEBUG=* dependency-tree -w path/to/webpack.config.json path/to/a/file

That is the diagnostic path when a dependency is not showing up. It works because the resolution steps are the standard Node debug channel, so you get visibility into filing-cabinet's work without the package having to add a logging mode of its own.

A second quiet behaviour to keep in mind: by default, third-party CommonJS dependencies are included, and core Node built-ins such as assert, path and fs are excluded. That default is sensible for a bundling use case, and wrong for a tool that wants to enumerate everything a module touches.

The CLI, its flags and the global install requirement

The command line interface prints the dependency tree as JSON, and it needs a global install:

sh
npm install -g dependency-tree

The usage line shows the flag set:

code
dependency-tree --directory=path/to/files [--list-form] [--es6-mixed-imports] [-c path/to/require/config] [-w path/to/webpack/config] filename

The entry file is positional and comes last, the root directory is given with --directory, --list-form switches to one path per line instead of JSON, -c takes a RequireJS config and -w takes a webpack config. The --es6-mixed-imports flag is the only one that is not explained in the option table, so its behaviour has to be inferred from the name or checked in the source.

Requiring a global install is the least convenient part of the design. It means the tool is not pinned to your project version, it will not appear in a devDependencies list where a reviewer would look for it, and two projects on the same machine can silently use different versions of the tool. For a script that runs in CI, calling the library API from the project itself avoids the problem entirely.

MIT licensed, version 12, and a test suite that checks its own types

The project is MIT licensed and the current published version is 12.0.1, with v12.0.0 on 2026-05-19 and v11.5.0 on 2026-05-13. The repository is not archived, and the last push was on 2026-09-07.

The toolchain in the repository tells you about the project's engineering standards. The test script runs linting, then a types check, then the test suite:

code
"test": "npm run lint && npm run check:types && vitest run"

The types check is worth a second look, because it is not a runtime test. The generate:types script runs tsc in declaration-only mode over index.js, and check:types regenerates those declarations and fails if the result differs from what is committed:

code
"check:types": "npm run generate:types && git diff HEAD --exit-code types/index.d.ts"

That means the shipped type definitions cannot silently drift from the implementation, which matters for a package whose consumers are bundler plugins written in TypeScript. Linting is handled by xo, and the tests run under vitest.

A note on the dependency surface: the runtime dependencies are small and well chosen, with commander for the CLI and three packages doing the actual work, filing-cabinet, precinct and typescript, plus @discoveryjs/json-ext. The typescript dependency is listed as a runtime dependency rather than a peer, so it is installed whether or not you analyse TypeScript, which adds weight to an install for JavaScript-only users.

Editorial conclusion

dependency-tree is the right tool for bundler plugins, editor features and build scripts that need a file-level graph of a module across several module formats, and it is the wrong tool for dependency version audits, license compliance or anything that needs the resolved version of a package. Before relying on it, feed it the same requireConfig, webpackConfig or tsConfig your build uses, check the nonExistent array for paths that failed to resolve, and note that a missing configuration surfaces as silently absent dependencies rather than as an error.

Frequently asked questions

How do I get the dependency tree of a file with dependency-tree?

Call the default export with a filename and a directory, both required, and you get a nested object of absolute paths where each key holds its own subtree. Use dependencyTree.toList for a flat post-order list of paths instead.

Why are some dependencies missing from the tree?

The most common causes given are bugs in precinct or an incomplete requireConfig, webpackConfig or tsConfig. Any path that could not be resolved is appended to the nonExistent array you pass in, so check that array rather than the tree.

Which module formats and languages does dependency-tree support?

It supports JS in AMD, CommonJS and ES6 flavours, TypeScript, and CSS preprocessors including PostCSS, Sass, Stylus and Less, which is whatever precinct handles. Third-party CommonJS dependencies are included by default and core Node built-ins are excluded.

How do I install the dependency-tree CLI?

The CLI needs a global install with npm install -g dependency-tree. It prints the tree as JSON, accepts --directory, --list-form, --es6-mixed-imports, -c for a RequireJS config and -w for a webpack config, and takes the entry file as the final positional argument.

Which Node version does dependency-tree need?

The package.json engines field requires node >=20.19.0 || >=22.12.0. The package is an ES module with a CommonJS default export path, and it ships type definitions that are checked against the implementation in CI.

Official sources

  1. dependents/node-dependency-tree on GitHub
  2. Issues
  3. License: MIT
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/dependents-node-dependency-tree.svg)](https://hysenlabs.com/projects/dependents-node-dependency-tree)

Was this useful?