- This is a small reference guide to the elements you can use to write rules for dependency-cruiser. If you want a step-by-step introduction check the rules tutorial.
- There is a json schema that describes the output format. Dependency-cruiser checks rule sets against it.
- Some examples:
- dependency-cruiser's own configuration
- the configuration State Machine cat uses for validation and the one it uses for generating a visual graph.
- mscgen.js's .dependency-cruiser.js
- Tip: run
depcruise --init
to create a .dependency-cruiser.js with some rules that make sense in most projects.
- The structure of a dependency-cruiser configuration
- The structure of an individual rule
- Conditions
- The
options
doNotFollow
: don't cruise modules any furtherincludeOnly
: only include modules satisfying a patternexclude
: exclude dependencies from being cruisedmaxDepth
prefix
: prefix links in reportsmoduleSystems
tsPreCompilationDeps
tsConfig
: use a typscript configuration file ('project')- Yarn Plug'n'Play support -
externalModuleResolutionStrategy
webackConfig
: use (the resolution options of) a webpack configurationreporterOptions
- Some more esoteric options
- Configurations in javascript
The typical dependency-cruiser config is json file (although you can use javascript -
see below)). The three most important sections
are forbidden
, allowed
and options
, so a skeleton config could look something like this:
{
"forbidden": [],
"allowed": [],
"options": {}
}
The following paragraphs explain these three and the other sections.
A list of rules that describe dependencies that are not allowed. dependency-cruiser will emit a separate error (warning/ informational) message for each violated rule.
A list of rules that describe dependencies that are allowed. dependency-cruiser
will emit a 'not-in-allowed' message for each dependency that does not
satisfy at least one of them. The severity of the message is warn by
default, but you can override it with allowedSeverity
The severity to use in reports when a dependency is not in the allowed
list of rules. It takes the same values as other severity
fields and
also defaults to warn
.
This takes one or more file path to other dependency-cruiser-configs. When
dependency-cruiser reads your config, it takes the contents of the
extends
and merges them with the contents of your config.
dependency-cruiser resolves the extends
relative to the file name with the
same algorithm node uses, which means a.o.
- names starting with
./
are local - you can use external node_modules to reference rule sets (e.g.
dependency-cruiser/configs/recommended
) - there's no need to specify the extension for javascript files, but for json it's mandatory.
allowed
rules
Dependency-cruiser concatsallowed
rules from the extends, and de-duplicates them.forbidden
rules
Forforbidden
rules it uses the same approach, except when the rules have a name, in which case the rule with the same name in the current file gets merged into the one from extends, where attributes from the current file win. This allows you to override only one attribute, e.g. the severityallowedSeverity
If there's anallowedSeverity
in the current file, it wins. If neither file has anallowedSeverity
dependency-cruiser uses warn as a defaultoptions
options
get the Object.assign treatment - where the option in the current file wins.- If there's more than one path in extends, they get merged into the current file one by one, running through the array left to right.
To use a local base config:
{
"extends": "./configs/dependency-cruiser-base.json"
}
To use a base config from an npm package:
{
"extends": "@ourcompany/dependency-cruiser-configs/frontend-rules-base.json"
}
module.exports = {
extends: "dependency-cruiser/configs/recommended",
forbidden: [
{
// because we still use a deprecated core module, still let
// the no-deprecated-core rule from recommended fire,
// but at least temporarily don't let it break our build
// by setting the severity to "warn" here
name: "no-deprecated-core",
severity: "warn",
// no need to specify the from and to, because they're already
// defined in 'recommended'
},
],
};
Options that influence what is cruised, and how it is cruised. See The options below for an exhaustive list.
An individual rule consists at least of a from
and a to
attribute that contain one or more conditions that trigger the rule, so
a minimal rule will look like this:
{
"from": {},
"to": {}
}
A rule within the 'allowed' section can also have a comment
attribute
which you can use to describe the rule.
Rules within the 'forbidden' section can have a name
and a severity
.
{
"name": "kebab-cased-name",
"comment": "(optional) description of the rule",
"severity": "warn",
"from": {},
"to": {}
}
Conditions an end of a dependency should match to be caught by this rule. Leave it empty if you want any module to be matched.
The conditions section below describes them all.
You can use this field to document why the rule is there. It's not used in any rule logic.
(only available in the
forbidden
section )
A short name for the rule - will appear in reporters to enable customers to quickly identify a violated rule. Try to keep them short, eslint style. E.g. 'not-to-core' for a rule forbidding dependencies on core modules, or 'not-to-unresolvable' for one that prevents dependencies on modules that probably don't exist.
If you do not provide a name, dependency-cruiser will default it
to unnamed
.
(only available in the
forbidden
section )
How severe a violation of a rule is. The 'error' severity will make
some reporters (at least the err
one) return a non-zero exit
code, so if you want e.g. a build to stop when there's a rule
violated: use that.
The other values you can use are info
, warn
and ignore
. If you
leave it out dependency-cruiser will assume it to be warn
.
With the severity set to ignore
dependency-cruiser will not check
the rule at all. This can be useful if you want to temporarily
disable a rule or disable a rule you inherited from a rule set you
extended.
A regular expression an end of a dependency should match to be catched by this rule.
In from
, this is the path from the current working directory (typically your
project root) to the file containing a dependency. In to
, this is the path from
the current working directory to the file the dependency resolves to.
When path is in a to
part of a rule it accepts the regular expression
'group matching' special variables $0
, $1
, $2
, ... as well. See
'group matching' below for an explanation & example.
A regular expression an end of a dependency should NOT match to be catched by this rule.
When pathNot is in a to
part of a rule it accepts the regular expression
'group matching' special variables $0
, $1
, $2
, ... just like the path
attribute. See 'group matching' below for an explanation & example.
I chose regular expressions for matching paths over the more traditional glob because they're more expressive - which makes it easier to specify rules. Some common patterns
glob | regular expression | this expresses: |
---|---|---|
*.js |
[^/]+\.js$ |
files in the current folder with the extension .js |
src/**/* |
^src |
all files in the src folder |
not possible | ^src/([^/]+)/.+ |
everything in the src tree - remember the matched folder name directly under src for later reference. |
To make sure rules you specify run on all platforms, dependency-cruiser
internally represents paths with forward slashes as path separators
(src/alez/houpe
).
Sometimes you'll want to use a part of the path the 'from' part of your rule matched and use it in the 'to' part. E.g. when you want to prevent stuff in the same folder to be matched.
To achieve this you'll need to do two things:
- In the
from
of your rule:
Make sure the part of thepath
you want to be matched is between brackets. Like so:"^src/([^/]+)/.+"
- In the
to
part of your rule:
You can reference the part matched between brackets by using$1
inpath
andpathNot
rules. Like so:"pathNot": "^src/$1/.+".
- It is possible to use more than one group per rule as well. E.g. this
expression
"^src/([^/]+)/[^\.]\.(.+)$"
has two groups; one for the folder directly under src, and one for the extension. The first is available in theto
part of your rule with$1
, the second with$2
. - The special variable
$0
contains the whole matched string. I haven't seen a practical use for it in the context of depedendency-cruiser, but I'll be glad to be surprised.
Say you have the following folder structure
src
└── business-components
├── search
├── upsell
├── check-out
├── view-trip
└── check-in
Business components should be completely independent of each other. So typically you'd specify a rule like this to prevent accidents in the "forbidden" section:
{
"name": "no-inter-ubc",
"comment": "Don't allow relations between code in business components",
"severity": "error",
"from": { "path": "^src/business-components/([^/]+)/.+" },
"to": {
"path": "^src/business-components/([^/]+)/.+"
}
}
This will correctly flag relations from one folder to another, but also
relations within folders. It's possible to get around that by specifying it
for each folder explicitly, leaving the current 'from' folder from the to
list e.g.
from: search, to: upsell|check-out|view-trip|check-in,
from: upsell, to: search|check-out|view-trip|check-in,
...
That'll be heavy maintenance though; especially when your business components breed like a litter of rabbits. In stead, you can use group matching:
{
"name": "no-inter-ubc",
"comment": "Don't allow relations between business components",
"severity": "error",
"from": { "path": "^src/business-components/([^/]+)/.+" },
"to": {
"path": "^src/business-components/([^/]+)/.+",
"pathNot": "^src/business-components/$1/.+"
}
}
... which makes sure dependency-cruiser does not match stuff in the from folder currently being matched.
A boolean indicating whether or not to match modules that have no incoming
or outgoing dependencies. Orphans might need special attention because
they're unused leftovers from a refactoring. Or the start of some feature
that never got finished but which was merged anyway. Leaving the orphan
attribute out means you don't care about orphans in your code.
Detecting orphans will have an impact on performance. You will probably only notice it when you have a larger code base (thousands of modules in your dependency graph), but it is something to keep in mind.
To detect orphan guys you can add e.g. this snippet to your
.dependency-cruiser.json's forbidden
section:
{
"name": "no-orphans",
"severity": "warn",
"from": { "orphan": true },
"to": {}
}
- dependency-cruiser will typically not find orphans when you give it
only one module to start with. Any module it finds, it finds by
following its dependencies, so each module will have at least one
dependency incoming or outgoing. Specify one or more folder, several
files or a glob. E.g.
will find orphans if they exist, whereas
depcruise -v -- src lib test
probably won't (unless index.ts is an orphan itself).depcruise -v -- src/index.ts
- by definition orphan modules have no dependencies. So when
orphan
is part of a rule, theto
part won't make sense. This is why dependency-cruiser will ignore theto
part of these rules. - For similar reasons
orphan
is not allowed in theto
part of rules.
reachable
is a boolean indicating whether or not modules matching the to
part
of the rule are reachable (either directly or via other moduels) from modules
matching the from
part of the rule. This can be useful for two use cases:
For instance, in this dependency-graph several modules are not reachable from
the root index.js
. If index.js
is the only (legal) entry to this package,
those unreachable modules are likely candidates for removal:
Here's a rule snippet that will detect these for you:
{
"forbidden": [
{
"name": "no-unreachable-from-root",
"severity": "error",
"from": {
"path": "src/index\\.js$"
},
"to": {
"path": "src",
/*
spec files shouldn't be reachable from regular code anyway, so you
might typically want to exclude these from reachability rules.
The same goes for typescript definition files:
*/
"pathNot": "\\.spec\\.(js|ts)$|\\.d\\.ts$"
/*
for each file matching path and pathNot, check if it's reachable from the
modules matching the criteria mentioned in "from"
*/
"reachable": false
}
}
]
}
With this rule enabled, the unreachable rules jump out immediately. Both in the output of the err
reporter
error no-unreachable-from-root: src/other-stuff/index.js
error no-unreachable-from-root: src/other-stuff/untouched-one.js
error no-unreachable-from-root: src/other-stuff/untouched-two.js
error no-unreachable-from-root: src/relevant/to-untouched.js
✖ 4 dependency violations (4 errors, 0 warnings). 8 modules cruised.
... and in the output of the dot
one:
You can use the same reachable
attribute to find transient dependencies (fancy
way to say via via). Let's say you have a bunch of javascript files that define
static schema's. It's ok if they import stuff, but they should never touch
database implementation code (which happens to live in src/lib/database
).
With a rule like this in the forbidden
section you can make sure that never
happens:
{
"name": "implementation-not-reachable-from-info-ts",
"comment": "Don't allow importing database implementation files for schema declaration files",
"severity": "error",
"from": {
"path": "\\.schema\\.ts$",
},
"to": {
"path": "^src/libs/database/",
"reachable": true,
},
};
- You can set up multiple rules with a
reachable
attribute in theto
section. If you do so, make sure you give aname
to each rule. It's not only the only way dependency-cruiser can keep reachable rules apart - it will be for you as well :-). - Different from other rules, rules with a
reachable
attribute can only havepath
andpathNot
in thefrom
part of the rulepath
andpathNot
alongside thereachable
in theto
part of the rule
(these limitations might get lifted somewhere in the future)
Whether or not to match modules dependency-cruiser could not resolve (and probably aren't on disk). For this one too: leave out if you don't care either way.
To get an error for each unresolvable dependency, put this in your "forbidden" section:
{
"name": "not-to-unresolvable",
"severity": "error",
"from": {},
"to": { "couldNotResolve": true }
}
A boolean indicating whether or not to match module dependencies that end up where you started (a.k.a. circular dependencies). Leaving this out => you don't care either way.
For example, adding this rule to the "forbiddden" section in your .dependency-cruiser.json will issue a warning for each dependency that ends up at itself.
{
"name": "no-circular",
"severity": "warn",
"from": { "pathNot": "^(node_modules)" },
"to": { "circular": true }
}
You can flag dependent modules that have licenses that are e.g. not
compatible with your own license or with the policies within your company with
license
and licenseNot
. Both take a regular expression that matches
against the license string that goes with the dependency.
E.g. to forbid GPL and APL licenses (which require you to publish your source code - which will not always be what you want):
{
"name": "no-gpl-apl-licenses",
"severity": "error",
"from": {},
"to": { "license": "GPL|APL" }
}
This raise an error when you use a dependency that has a string with GPL or
APL in the "license" attribute of its package.json (e.g.
SPDX compatible expressions like GPL-3.0
, APL-1.0
and
MIT OR GPL-3.0
but also on non SPDX compatible)
To only allow licenses from an approved list (e.g. a whitelist provided by your legal department):
{
"name": "only-licenses-approved-by-legal",
"severity": "warn",
"from": {},
"to": { "licenseNot": "MIT|ISC" }
}
Note: dependency-cruiser can help out a bit here, but you remain responsible for managing your own legal stuff. To re-iterate what is in the LICENSE to dependency-cruiser:
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
You might have spent some time wondering why something works on your machine, but not on other's. Only to discover you did install a dependency, but did not save it to package.json. Or you already had it in your devDependencies and started using it in a production source.
To save you from embarassing moments like this, you can make rules with the
dependencyTypes
verb. E.g. to prevent you accidentally depend on a
devDependency
from anything in src
add this to your
.dependency-cruiser.json's "forbidden" section:
{
"name": "not-to-dev-dep",
"severity": "error",
"comment": "because an npm i --production will otherwise deliver an unreliably running package",
"from": { "path": "^src" },
"to": { "dependencyTypes": ["npm-dev"] }
}
Or to detect stuff you npm i'd without putting it in your package.json:
{
"name": "no-non-package-json",
"severity": "error",
"comment": "because an npm i --production will otherwise deliver an unreliably running package",
"from": { "pathNot": "^(node_modules)" },
"to": {
"dependencyTypes": ["unknown", "undetermined", "npm-no-pkg", "npm-unknown"]
}
}
If you don't specify dependencyTypes in a rule, dependency-cruiser will ignore them in the evaluation of that rule.
This is a list of dependency types dependency-cruiser currently detects.
dependency type | meaning | example |
---|---|---|
local | a module in your own ('local') package | "./klont" |
localmodule | a module in your own ('local') package, but which was in the resolve.modules attribute of the webpack config you passed |
"shared/stuff.ts" |
npm | it's a module in package.json's dependencies |
"lodash" |
npm-dev | it's a module in package.json's devDependencies |
"chai" |
npm-optional | it's a module in package.json's optionalDependencies |
"livescript" |
npm-peer | it's a module in package.json's peerDependencies - note: deprecated in npm 3 |
"thing-i-am-a-plugin-for" |
npm-bundled | it's a module that occurs in package.json's bundle(d)Dependencies array |
"iwillgetbundled" |
npm-no-pkg | it's an npm module - but it's nowhere in your package.json | "forgetmenot" |
npm-unknown | it's an npm module - but there is no (parseable/ valid) package.json in your package | |
deprecated | it's an npm module, but the version you're using or the module itself is officially deprecated | "some-deprecated-package" |
core | it's a core module | "fs" |
aliased | it's a module that's linked through an aliased (webpack) | "~/hello.ts" |
unknown | it's unknown what kind of dependency type this is - probably because the module could not be resolved in the first place | "loodash" |
undetermined | the dependency fell through all detection holes. This could happen with amd dependencies - which have a whole jurasic park of ways to define where to resolve modules to | "veloci!./raptor" |
A boolean that tells you whether the dependency is a dynamic one (i.e.
it uses the async ES import statement a la import('othermodule').then(pMod => pMod.doStuff())
).
You can use this e.g. to restrict the usage of dynamic dependencies:
{
"forbidden": [
{
"name": "no-non-dynamic-dependencies",
"severity": "error",
"from": {},
"to": { "dynamic": true }
}
]
}
... or to enforce the use of dynamic dependencies for certain dependencies
{
"forbidden": [
{
"name": "only-dyn-deps-to-otherside",
"comment": "only dynamically depend on 'otherside' modules",
"severity": "error",
"from": {},
"to": { "path": "@theotherside/", "dynamic": false }
}
]
}
With the flexible character of package.json it's totally possible to specify
a package more than once - e.g. both in the peerDependencies
and in the
dependencies
. Sometimes this is intentional (e.g. to make sure a plugin
type package works with both npm 2 and 3), but it can be a typo as well.
Anyway, it's useful to be conscious about it. You can check
for it with a moreThanOneDependencyType
attribute - which matches these
when set to true:
{
"name": "no-duplicate-dep-types",
"severity": "warn",
"from": {},
"to": { "moreThanOneDependencyType": true }
}
When left out it doesn't matter how many dependency types a dependency has.
(If you're more of an 'allowed' user: it matches the 0 and 1 cases when set to false).
For exotic requires/ require wrappers you might want to have different rules a.c.t. normal requires. E.g.
- When you use a require wrapper to include a dependency that might not be there and handle it elegantly, it's not an error if the module-to-be-there doesn't actually exist - or is e.g. in your optionalDependencies.
- You might want to only allow the use of certain dependencies through an exotic require:
{
"name": "not-to-optional-deps",
"severity": "error",
"from": {},
"to": {
"dependencyTypes": ["npm-optional"],
"exoticallyRequired": false
}
}
- ... or ban exotic requires altogether:
{
"name": "ban-all-exotic-requires",
"severity": "error",
"from": {},
"to": {
"exoticallyRequired": true
}
}
- Or allow only one specific:
{
"name": "only-window-require-exotic",
"severity": "error",
"comment": "The only 'exotic' require allowed is window.require",
"from": {},
"to": {
"exoticRequireNot": "^window\\.require$",
"exoticallyRequired": true
}
}
If you want to set restrictions on dependencies that only exist before
compilation from TypeScript to JavaScript, you can use the preCompilation
only attribute.
E.g. to make sure to only import stuff from the react-native stuff that doesn't make it beyond the pre-compilation step:
{
"forbidden": [
{
"name": "only-types-from-react-native",
"description": "make sure to only import stuff that's portable over platforms",
"severity": "error",
"from": {
"path": "^src/platform-independent-stuff"
},
"to": {
"path": "^src/lib/react-native-stuff",
"preCompilationOnly": false
}
}
],
"options": {
"tsPreCompilationDeps": "specify"
}
}
This attribute only works for typescript sources, and only when
tsPreCompilationDeps
option is set to "specify"
.
command line option equivalent:
--do-not-follow
(string values passed to 'path' only)
If you do want to see certain modules in your reports, but are not interested
in these modules' dependencies, you'd pass the regular expression for those
modules to the doNotFollow
option. A typical pattern you'd
use with this is "node_modules":
"options": {
"doNotFollow": {
"path": "node_modules"
}
}
It's not possible to use this on the command line
It is possible to specify a regular expression for files that dependency-cruiser should cruise, but not follow any further. In the options section you can restrict what gets cruised by specifying dependency types. So if e.g. you don't want dependency-cruiser to follow external dependencies, instead of specifying the "node_modules" path:
"options": {
"doNotFollow": {
// "path": "node_modules",
"dependencyTypes": [
"npm",
"npm-dev",
"npm-optional",
"npm-peer",
"npm-bundled"
]
}
}
There's a few steps dependency-cruiser takes when you fire it of:
- gather all files specified as an argument, filtering out the stuff in
exclude
anddoNotFollow
and that which is not inincludeOnly
.- starting from the gathered files: crawl all dependencies it can find. Crawling stops when a module matches
doNotFollow
rule or a modules' dependency matches eitherexclude
or does not matchincludeOnly
.- apply any rules over the result & report it.
So in the first step
doNotFollow
behaves itself exactly likeexclude
would. Only in the second step it allows files matching its pattern to be visited (but not followed any further).This means dependency-cruise will encounter files matching
doNotFollow
but only when they are dependencies of other modules. This a.o. prevents unexpected behavior where specifying node modules asdoNotFollow
pattern would still traverse all node_modules when the node_modules were part of the arguments e.g. indepcruise --do-not-follow node_modules --validate -- src test node_modules
or, more subtly withdepcruise --don-not-follow node_modules -- validate -- .
.
command line option equivalent:
--include-only
In the includeOnly
option you can pass a regular expression of all file paths
dependency-cruiser should include in a cruise. It will discard all files
not matching the includeOnly
pattern.
This can be handy if you want to make an overview of only your internal application
structure. E.g. to only take modules into account that are in the src
tree (and
exclude all node_modules, core modules and modules otherwise outside it):
"options": {
"includeOnly": "^src/"
}
If you specify both an includeOnly and an exclude (see below), dependency-cruiser takes them both into account.
command line option equivalent:
--exclude
(string values passed to 'path' only)
If you don't want to see certain modules in your report (or not have them
validated), you can exclude them by passing a regular expression to the
exclude
. E.g. to exclude node_modules
from being scanned altogether:
"options": {
"exclude": {
"path": "node_modules"
}
}
Because it's regular expressions, you can do more interesting stuff here as well. To exclude all modules with a file path starting with coverage, test or node_modules, you could do this:
"options": {
"exclude": {
"path": "^(coverage|test|node_modules)"
}
}
It's also possible to exclude dependencies on other properties than the (resolved) paths
at either end of them. To exclude all dependencies that result of an (ECMAScript)
dynamic import from being included in a cruise, you can use the dynamic
attribute:
"options": {
"exclude": {
"dynamic": true
}
}
Other attributes might come in future releases
command line option equivalent:
--max-depth
Only cruise the specified depth, counting from the specified root-module(s). This command is mostly useful in combination with visualisation output like dot to keep the generated output to a manageable size.
If you use this to get a high level overview of your dependencies, be sure to check out the archi reporter. That's more flexible, while still taking into account all your rules and dependencies
This will cruise the dependencies of each file directly in the src folder, up to a depth of 1:
...
"maxDepth": 1
...
With "maxDepth": 2
it'll look like this:
And with "maxDepth": 3
like this:
The maxDepth
option is there to help with visualizing. If your goal is to validate
this option is best left alone as you'll miss a dependency or two otherwise.
command line option equivalent:
--prefix
If you want the links in the svg output to have a prefix (say,
https://github.com/you/yourrepo/tree/master/
) so when you click them you'll
open the link on github instead of the local file - pass that in the
prefix
option, e.g.:
...
"prefix": "https://github.com/sverweij/dependency-cruiser/tree/develop/"
...
Typically you want the prefix to end on a /
.
command line option equivalent:
--module-systems
Here you can pass a list of module systems dependency-cruiser should use
to detect dependencies. It defaults to ["amd", "cjs", "es6"]
The 'module systems'
dependency-cruiser supports:
System | Meaning |
---|---|
amd |
Asynchronous Module Defintion as used by a.o. RequireJS |
cjs |
Common js as popularized by node.js which uses the require function to include other modules |
es6 |
modules as defined for ECMAScript 6 in 2015 in ecma-262, with proper import and export statements |
tsd |
TypeScript 'tripple slash directives' |
command line option equivalent:
--ts-pre-compilation-deps
By default dependency-cruiser does not take dependencies between typescript modules that don't exist after compilation to javascript.
Switch this option to true
if you do want to take them into account
(as a bonus this will make cruising typescript code bases faster).
If you want to define rules on whether dependencies exist only
before compilation or also after (with preCompilationOnly
)
you can pass the value "specify"
to this option. Only do this if
you really need it as it will impact cruise speed. You can only use the
"specify"
value within dependency-cruiser configurations (not from the
command line).
Pre-compilation dependencies example: only importing a type
As the javascript doesn't really know about types, dependencies on types only exist before, but not after compile time.a.ts
exports an interface ...
import { B } from "./b";
export interface A {
foo: string;
}
const b = new B();
... and b.ts
uses that interface:
import { A } from "./a";
export class B {}
const a: A = { foo: "foo" };
After compilation b.js
looks like this:
// import omitted as it only contained a reference to a type
export class B {}
const a = { foo: "foo" }; // no type refer
Normally, without tsPreCompilationDeps
the output will
look like this:
With tsPreCompilationDeps
the dependency graph does include the
dependency-on-a-type-only from b.ts
to a.ts
:
Pre-compilation dependencies example: import without use
Similarly, if you import something, but don't use it, the dependency only exists before compilation. Take for example these two typescript modules:
a.ts
:
import { B } from "./b";
export class A {}
b.ts
:
export class B {}
As a.ts
uses none of the imports from b, the typescript
compiler will omit them when compiling and yield this for a.js
:
// no imports here anymore...
export class A {}
Hence, without tsPreCompilationDeps
dependency-cruiser's
output will look like this:
... and with tsPreCompilationDeps
like this:
command line option equivalent: --ts-config
If dependency-cruiser encounters typescript, it compiles it to understand what it
is looking at. If you have compilerOptions
in your tsconfig.json
you think
it should take into account, you can use this option to make it do that.
You might want to do this e.g. if you have baseDir
/ paths
keys in your
tsconfig
, or are using jsx/ tsx outside of a react context.
Dependency-cruiser understands the extends
configuration in tsconfig's so
if you have a hierarchy of configs, you just need to pass the relevant one.
Sample
"options": {
"tsConfig": {
"fileName": "tsconfig.json"
}
}
You can do it even more minimalistically like so (in which case dependency-cruiser will
assume the fileName to be tsconfig.json
)
"options": {
"tsConfig": {}
}
### use the `tsconfig.json` in the current directory into account when looking
### at typescript sources:
depcruise --ts-config --validate -- src
### use `tsconfig.prod.json for the same purpose:
depcruise --ts-config tsconfig.prod.json --validate -- src
- The configuration file you can pass as an argument to this option is relative to the current working directory.
- dependency-cruiser currently only looks at the
compilerOptions
key in the tsconfig.json and not at other keys (e.g.files
,include
andexclude
).
there is no command line equivalent for this
If you're using yarn's Plug'n'Play to have external modules resolved and want
dependency-cruiser to take that into account, set the
externalModuleResolutionStrategy
attribute to yarn-pnp
. The default for this
attribute is node_modules
which is the default strategy in the node ecosystem
as well.
command line option equivalent:
--webpack-config
passingenv
andarguments
is only available in the configuration file's options
Dependency-cruiser will pluck the resolve
key from the webpack configuration
you pass here and will use that information to resolve files on disk.
"options": {
"webpackConfig": {
"fileName": "webpack.config.js"
}
}
Or, shorter, to let dependency-cruiser pick the default webpack.config.js all by itself:
"options": {
"webpackConfig": {}
}
If your webpack configuration exports a function that takes parameters, you can provide the parameters like so:
"options": {
"webpackConfig": {
"fileName": "webpack.config.js",
"env": { "production": true },
"arguments": { "mode": "production" }
}
}
- The configuration file you can pass as an argument to this option is relative to the current working directory.
- If your webpack config exports an array of configurations, dependency-cruiser will only use the resolve options of the first configuration in that array.
- For more information check out the the webpack resolve documentation.
In the reporterOptions
attribute you can pass things to reporters to influence
their behavior - for reporters that support this.
Most representational aspects of the 'dot' reporter are customizable:
- On a global level, affecting all rendered modules and dependencies with
graph
,node
andedge
. - Conditional - only affecting modules (or dependencies) that meet the criteria
you specify with
modules
anddependencies
.- You can use any module attribute and any dependency attribute for dependencies.
- For attributes you can use anything GraphViz dot can understand as an attribute (see their attributes documentation for a complete overview).
The criteria are evaluated top to bottom:
- Criteria higher up get precedence over the ones lower down.
- Criteria in the configuration file take precedence over the default ones.
For an extensive example you can have a look at the default theme dependency-cruiser ships with - default-theme.json.
As a base, take this part of dependency-cruisers code:
base.config.js
module.exports = {
options: {
includeOnly: "^src/main",
exclude: "/filesAndDirs/",
},
};
The default template, with tweaks to get an 'engineering' like look (and all
file names ending on .json
a cylindrical look with a soft gradient):
engineering.config.js
module.exports = {
extends: "./base.config.js",
options: {
reporterOptions: {
dot: {
theme: {
replace: false,
graph: {
bgcolor: "dodgerblue",
color: "white",
fontcolor: "white",
fillcolor: "transparent",
splines: "ortho",
},
node: {
color: "white",
fillcolor: "#ffffff33",
fontcolor: "white",
},
edge: {
arrowhead: "vee",
arrowsize: "0.5",
penwidth: "1.0",
color: "white",
fontcolor: "white",
},
modules: [
{
criteria: { source: "\\.json$" },
attributes: {
shape: "cylinder",
fillcolor: "#ffffff33:#ffffff88",
},
},
{
criteria: { coreModule: true },
attributes: {
color: "white",
fillcolor: "#ffffff33",
fontcolor: "white",
},
},
],
dependencies: [
{
criteria: { resolved: "\\.json$" },
attributes: { arrowhead: "obox" },
},
],
},
},
},
},
};
To shift the dependency graph from a horizontal orientation to a vertical one, set
the global graph attribute rankdir
to TD
(top down):
vertical.config.js
module.exports = {
extends: "./base.config.js",
options: {
reporterOptions: {
dot: {
theme: {
graph: { rankdir: "TD" },
},
},
},
},
};
To get output without any attributes and no conditional coloring you can order
the default theme to be replaced by flipping the replace
attribute to true
.
bare
module.exports = {
extends: "./base.config.js",
options: {
reporterOptions: {
dot: {
theme: {
replace: true,
},
},
},
},
};
The dot reporter also supports the collapsePattern
option originally created
for the archi reporter.
The 'customizable dot' (cdot
) or 'archi' reporter exists to make high level
dependency overviews. Out of the box it recognizes structures that summarize
to folders directly under packages, src, lib, and node_modules. You can
adapt this behavior by passing a collapsePattern to the archi reporterOptions
in your dependency-cruiser configurations e.g. like so:
module.exports = {
options: {
reporterOptions: {
archi: {
collapsePattern: "^(src/[^/]+|bin)",
},
},
},
};
It also accepts the same theme
option dot
does. If you don't specify a theme,
it'll use the one specified for dot
or the default one if that one isn't
specified either.
With the above collapsePattern and a custom dot scheme, the archi report for dependency-cruiser looks like this:
Modules collapsed in this fashion get the special attribute
consolidated
so they're easy to distinguish in a.o. themes. The default theme makes them abox3d
shape (as you can see above) but if you want to use other attributes you can use the power of the theme mechanism to use your own e.g.// ... reporterOptions: { archi: { // ... theme: { modules: [ { criteria: { collapsed: true }, attributes: { shape: "tab" } } ] } } // ...
The directory (ddot
) reporter aggregates your dependencies on folder level. Just
like the archi one, it accepts a theme
option, with the same fallback to
the theme specified for dot
or to the default one.
E.g. with a custom scheme the internals dependencies on folder level for dependency-cruiser itself look like this:
The anonymous reporter has a wordlist
option to pass it a list of words to use
to replace path elements with before it starts to generate random names. If you
use the anonymous report a lot it can be beneficial to use a list of words so the
output is repeatable (and easier to read).
{
"options": {
"reporterOptions": {
"anon": {
"wordlist": [
"foo",
"bar",
"baz",
"qux",
"grault",
"garply",
"waldo",
"fred"
]
}
}
}
}
You're likely to need a lot of words to cover all your path elements if you want to prevent random names as much as possible. There's word lists in the wild that work exceptionally well - in the past I have used Sindre Sorhus' mnemonic-words list for this. If you use javascript as the configuration file format you can simply require it:
const mnemonicWords = require('mnemonic-words');
module.exports = {
// ...
options: {
reporterOptions:
anon: {
wordlist: mnemonicWords
}
}
}
command line option equivalent:
--preserve-symlinks
Whether to leave symlinks as is or resolve them to their realpath. This option
defaults to false
(which is also nodejs' default behavior since release 6).
If combinedDependencies
is on false
(the default) dependency-cruiser will
search for a package.json
closest up from the source file it investigates.
This is the behavior you expect in a regular repo and in mono repos with
independent packages. When in doubt keep this switch out of your config or
set it to false
.
Example
- monodash/
- package.json
- packages/
- begindash/
- package.json <- only look in this one
- src/
- index.ts
- begindash/
With combinedDependencies
on true
dependency-cruiser will merge dependencies
from package.json
s from closest up from the source file until the place you
started the cruise (typically the root of your monorepo). It 'll give
precedence to the dependencies declared in the package.json closest to
the file it investigates:
- monodash/
- package.json <- look in this one as well; merge it into the one down the tree
- packages/
- begindash/
- package.json <- look in this one
- src/
- index.ts
In some situations you might not be able to use the require
function
directly or at all. E.g. when you're not sure a module is present and
want to have a fallback (semver-try-require).
Or because require in your environment is used for something else and
you needed to redefine require (const want = require; const whoa = want('whoadash')
).
Or because you're in AMD and you named the require parameter something
else because of a company wide standard to do so.
In each of these cases you can still infer dependencies with the exoticRequireStrings option by adding an exoticRequireStrings array to the options in your dependency cruiser config.
E.g.:
"options": {
"exoticRequireStrings": ["want", "tryRequire", "window.require"]
}
From version 4.7.0 you can pass a javascript module to --validate
.
It'll work as long as it exports a valid configuration object
and node can understand it.
This allows you to do all sorts of nifty stuff, like composing rule sets or using function predicates in rules. For example:
const subNotAllowed = require("rules/sub-not-allowed.json");
const noInterComponents = require("rules/sub-no-inter-components.json");
module.exports = {
forbidden: [subNotAllowed, noInterComponents],
options: {
tsConfig: {
fileName: "./tsconfig.json",
},
},
};