DPDM: Dependency Analyzer
dpdm is a robust static dependency analyzer designed for JavaScript and TypeScript projects. It efficiently identifies circular dependencies, detects unused files, and generates comprehensive dependency trees. Currently at stable version 4.0.1, the library maintains a relatively frequent release cadence, often aligning minor versions with updates to TypeScript and Node.js to ensure compatibility with modern language features. Key differentiators include its complete support for both CommonJS and ESM module systems, comprehensive TypeScript features such as path mapping resolution and the ability to ignore type-only dependencies, and a lightweight architecture that leverages the official TypeScript compiler for parsing. It aims to provide stable and consistent output, addressing inconsistencies found in alternative tools like `madge` when analyzing complex TypeScript projects.
Common errors
-
Error: dpdm requires Node.js v20.x or higher. You are using v18.x.x.
cause The installed version of Node.js is older than the minimum required by dpdm v4.x.fixUpgrade Node.js to version 20 or newer using `nvm install 20 && nvm use 20` or by updating your Node.js distribution. -
ENOENT: no such file or directory, stat '/path/to/your/project/src/index.ts'
cause The entry file(s) or glob pattern provided to dpdm do not exist or are incorrect.fixDouble-check the file paths and glob patterns specified in your dpdm command or API call. Ensure they are relative to the `--context` or `context` option, or are absolute paths. -
TypeError: Cannot read properties of undefined (reading 'resolvedModules') at getDependenciesOfSourceFile
cause This often indicates a TypeScript version mismatch where dpdm's internal parser struggles with the project's TypeScript syntax or configuration, or an issue with path mapping resolution.fixUpdate dpdm to its latest version, or ensure your project's `tsconfig.json` `compilerOptions.target` and `moduleResolution` settings are standard and well-formed. Verify that the TypeScript version your project uses is compatible with the dpdm version's tested TS version. -
Error: Unable to resolve module: 'src/components/button' in '/path/to/project/src/pages/home.ts'
cause dpdm could not resolve an import path, possibly due to missing `baseUrl` or `paths` configuration in `tsconfig.json`, or an incorrect `--extensions` setting.fixVerify your `tsconfig.json` includes correct `compilerOptions.baseUrl` and `compilerOptions.paths` if you are using path aliases. Ensure `--extensions` (or `extensions` in API) includes all relevant file types for resolution (e.g., `.ts,.tsx,.js,.jsx`).
Warnings
- breaking Version 4.0.1 and subsequent versions require Node.js version 20 or greater to run. Older Node.js environments will not be supported.
- gotcha dpdm closely tracks TypeScript versions due to its internal parsing mechanism. Using dpdm with a significantly older or newer TypeScript version in your project than what dpdm was built against (e.g., TS 5.6 for dpdm v3.15.0) can lead to parsing errors or incorrect analysis results.
- gotcha The `--skip-dynamic-imports` CLI option (or `skipDynamicImports` API option) has two distinct behaviors: 'circular' only ignores dynamic imports when detecting circular references, while 'tree' ignores them during the initial source file parsing for the full dependency tree. Misunderstanding this can lead to incomplete analysis or unexpected results.
- gotcha When using `dpdm` in a CI/CD pipeline, relying solely on its output to `stdout` might not integrate well with build failure conditions. The `--exit-code` option is crucial for making the pipeline fail if circular dependencies are found.
Install
-
npm install dpdm -
yarn add dpdm -
pnpm add dpdm
Imports
- analyze
const { analyze } = require('dpdm');import { analyze } from 'dpdm'; - AnalyzeOptions
import type { AnalyzeOptions } from 'dpdm'; - DpdmResult
import type { DpdmResult } from 'dpdm';
Quickstart
import { analyze } from 'dpdm';
import * as fs from 'node:fs';
import * as path from 'node:path';
const projectRoot = process.cwd();
const srcDir = path.join(projectRoot, 'src');
// Create dummy files for demonstration
fs.mkdirSync(srcDir, { recursive: true });
fs.writeFileSync(path.join(srcDir, 'a.ts'), 'import { b } from "./b"; export const a = b + 1;');
fs.writeFileSync(path.join(srcDir, 'b.ts'), 'import { c } from "./c"; export const b = c + 1;');
fs.writeFileSync(path.join(srcDir, 'c.ts'), 'import { a } from "./a"; export const c = a + 1;'); // Circular dependency
fs.writeFileSync(path.join(srcDir, 'd.ts'), 'export const d = 4;'); // Unused file
async function runAnalysis() {
try {
const result = await analyze([path.join(srcDir, 'a.ts')], {
context: projectRoot,
exclude: 'node_modules',
circular: true,
tree: true,
detectUnusedFilesFrom: path.join(srcDir, '**/*.*')
});
console.log('Analysis completed:');
if (result.circular.length > 0) {
console.log('Circular dependencies found:', result.circular);
} else {
console.log('No circular dependencies found.');
}
if (result.unused.length > 0) {
console.log('Unused files found:', result.unused);
}
console.log('Full dependency tree (truncated for brevity):', Object.keys(result.tree).slice(0, 5));
} catch (error) {
console.error('Analysis failed:', error);
} finally {
// Clean up dummy files
fs.rmSync(srcDir, { recursive: true, force: true });
}
}
runAnalysis();