How to deal with the TSLint error "A project without tags cannot depend on any libraries" in an Nx monorepo
Have you ever faced the following lint error in your Nx monorepo:
A project without tags cannot depend on any libraries
while your project clearly has tags defined in nx.json
?
Then this article is just for you!
In just a few minutes you will understand what causes the error and how you can resolve it.
Scenario
For the sake of demonstration, let's assume that we have an Nx monorepo with an nx.json
file that contains the following projects:
"projects": {
"shared-feature-users": {
"tags": ["scope:shared", "type:feature"]
},
"shared-data-access-users": {
"tags": ["scope:shared", "type:data-access"]
}
}
and that a component in the shared-feature-users
library imports
a service from the shared-data-access-users
library.
If you are not familiar with Nx scopes and types, you can learn all about them here.
By default, the nx-enforce-module-boundaries
TSLint rule in nx.json
comes with the following boundaries:
"nx-enforce-module-boundaries": [
true,
{
"allow": [],
"depConstraints": [
{
"sourceTag": "*",
"onlyDependOnLibsWithTags": ["*"]
}
}
]
]
In essence, this allows every application and library to import any other library.
If you are not familiar with the nx-enforce-module-boundaries
TSLint rule, you can learn more about it here.
When you run:
$ ng lint
no boundary violations are reported because of the wildcard rule that allows all libraries to import items from any other libraries.
Great! That's expected behaviour!
Now let's replace the wildcard rule with a new rule that only allows util libraries to import from other util libraries:
"nx-enforce-module-boundaries": [
true,
{
"allow": [],
"depConstraints": [
{
"sourceTag": "type:util",
"onlyDependOnLibsWithTags": ["type:util"]
}
}
]
]
If you now run:
$ ng lint
then the following error is reported:
libs/shared/feature-users/src/lib/some.component.ts
- A project without tags cannot depend on any libraries
But the shared-feature-users
project clearly does have tags defined in nx.json
!
So why does TSLint throw this error?
Here is what happens behind the scenes:
- the TSLint rule makes an inventory of all
sourceTag
tags listed indepConstraints
- next, the project's tags are matched against the
sourceTag
tags fromdepConstraints
- if the linter reaches the end of the
depConstraints
and none of thesourceTag
tags matched against any of the project's tags, the “A project without tags cannot depend on any libraries” error is thrown
So the error is not thrown because the project does not have any tags in nx.json
. Instead, the error is thrown because none of the project’s tags matched a sourceTag
tag in depConstraints
.
So how do we resolve this issue?
A brute solution
A brute way to solve the issue is to add a wildcard rule at the end of the of depConstraints
in nx-enforce-module-boundaries
:
"nx-enforce-module-boundaries": [
true,
{
"allow": [],
"depConstraints": [
{
"sourceTag": "type:util",
"onlyDependOnLibsWithTags": ["type:util"]
},
{
"sourceTag": "*",
"onlyDependOnLibsWithTags": ["*"]
}
}
]
]
This ensures that every application or library that does not match any of the rules above, is allowed access to any other library.
The danger, however, is that you introduce a wildcard that accidentally also matches other imports and thus allows more than you intended.
In addition, the rules are processed from top to bottom, so it is imperative that the rule is added at the end.
Adding the rule at the top:
"nx-enforce-module-boundaries": [
true,
{
"allow": [],
"depConstraints": [
{
"sourceTag": "*",
"onlyDependOnLibsWithTags": ["*"]
},
{
"sourceTag": "type:util",
"onlyDependOnLibsWithTags": ["type:util"]
}
}
]
]
would make the wildcard match any library immediately and prevent the second rule from having any effect at all.
An elegant solution
A more elegant way to solve the issue is to add a rule that matches at least one of your project tags:
"nx-enforce-module-boundaries": [
true,
{
"allow": [],
"depConstraints": [
{
"sourceTag": "type:util",
"onlyDependOnLibsWithTags": ["type:util"]
},
{
"sourceTag": "scope:shared",
"onlyDependOnLibsWithTags": ["scope:shared"]
}
}
]
]
Here, we added a rule with "sourceTag": "scope:shared"
.
Because the TSLint rule now matches the scope:shared
tag when linting the shared-feature-users
project, it is happy because it found a match before the end of the depConstraints
rules was reached.
As a result, the “A project without tags cannot depend on any libraries” error is no longer thrown.
The benefit of the elegant way is two-fold:
- the rule is explicit in your intention
- the rule does not introduce a wildcard that accidentally matches unintended imports
So if you ever face the “A project without tags cannot depend on any libraries” error, you now know how to resolve it gracefully.
Have a great one and happy linting!