AMD Module Dependency Detective
detective-amd is a JavaScript utility for statically analyzing JavaScript files or Abstract Syntax Trees (ASTs) to identify dependencies declared using various AMD (Asynchronous Module Definition) module syntaxes. It supports the four core AMD forms ('named', 'dependency list', 'factory', 'no dependencies'), as well as 'driver script' (`require([deps], func)`) and CommonJS-like 'REM' forms (`define(function(require, exports, module) {})`). The package also handles dynamically loaded dependencies and JSX code through `node-source-walk`. The current stable version is 6.0.1. While not on a fixed release schedule, it sees updates to maintain compatibility with modern Node.js versions and dependencies, with major versions primarily driven by Node.js LTS support drops. Its key differentiator is its specialized focus on AMD syntax, offering a robust solution for environments still leveraging RequireJS or similar AMD loaders, distinguishing it from general-purpose CommonJS or ESM dependency analysis tools.
Common errors
-
Error [ERR_REQUIRE_ESM]: require() of ES Module ... not supported.
cause Attempting to `require()` this package from an ES module context, or the package itself has become ESM-only (which it has not, but this is a common problem in the ecosystem).fixThis package is CommonJS. If you are in an ES module context (e.g., `"type": "module"` in package.json), you must use dynamic `import()` to load it: `const detectiveModule = await import('detective-amd'); const detective = detectiveModule.default;` -
SyntaxError: Unexpected token 'export' (or 'import')
cause You are using `detective-amd` in a CJS file, but the source code you are passing to it contains ES module syntax (e.g., `import` or `export` statements). While `detective-amd` supports JSX, it is specifically for AMD syntax analysis, not ESM.fixEnsure the source code being passed to `detective-amd` exclusively uses AMD or older JavaScript syntax. If you need to detect ES module dependencies, use a different package designed for ESM analysis. -
TypeError: detective is not a function
cause This typically occurs when `detective-amd` is imported incorrectly in an ESM context, where `import detective from 'detective-amd';` results in `detective` being the entire module object, not the function itself.fixWhen using `await import('detective-amd')` in ESM, the main function is accessed via the `.default` property: `const detectiveModule = await import('detective-amd'); const detective = detectiveModule.default;`
Warnings
- breaking Version 6.0.0 introduced a breaking change by dropping support for Node.js versions older than 18. Users on older Node.js runtimes will encounter errors or unexpected behavior.
- breaking Version 5.0.0 dropped support for Node.js 12. Environments running Node.js 12 or earlier will not be able to use this version.
- gotcha When an AMD `require` call uses an expression instead of a string literal (e.g., `require('./' + variable)`), `detective-amd` will return a string representation of that expression (e.g., `"'./' + variable"`) rather than attempting to resolve it. This means dynamic dependencies will not yield concrete file paths.
- gotcha The package is primarily designed for CommonJS consumption. Attempting to use `import detective from 'detective-amd';` in an ES module context will result in a runtime error because `module.exports` is not directly compatible with default ES module imports.
Install
-
npm install detective-amd -
yarn add detective-amd -
pnpm add detective-amd
Imports
- detective
import detective from 'detective-amd';
const detective = require('detective-amd'); - detective (named import attempt)
import { detective } from 'detective-amd';const detective = require('detective-amd'); - detective (ESM dynamic import)
import detective from 'detective-amd';
const detective = await import('detective-amd');
Quickstart
const fs = require('fs');
const detective = require('detective-amd');
// Create a dummy AMD file for demonstration
const fileContent = `
// a.js
define(['./b', './c'], function (b, c) {
console.log(b, c);
});
// b.js
define({
name: 'foo'
});
// c.js
define(function () {
return 'bar';
});
// main.js - A driver script
require(['./a'], function (a) {
// My app will get booted up from here
});
// another.js - with expression-based require
define(function (require) {
const dynamicDep = 'dynamic_module';
const x = require('./' + dynamicDep);
const y = require('./static_module');
});
`;
fs.writeFileSync('example.js', fileContent, 'utf8');
// Analyze the 'a.js' part of the content
const srcA = `define(['./b', './c'], function (b, c) { console.log(b, c); });`;
console.log('Dependencies for a.js:', detective(srcA));
// Analyze the 'main.js' part of the content
const srcMain = `require(['./a'], function (a) {});`;
console.log('Dependencies for main.js (driver script):', detective(srcMain));
// Analyze the 'another.js' part with expression-based requires
const srcAnother = `
define(function (require) {
const dynamicDep = 'dynamic_module';
const x = require('./' + dynamicDep);
const y = require('./static_module');
});
`;
console.log('Dependencies for another.js (expression-based):', detective(srcAnother));
// Clean up dummy file (optional)
fs.unlinkSync('example.js');