Prevent circular dependency issues when using type imports (enforce-module-boundarie)
See original GitHub issue- I’d be willing to implement this feature (contributing guide)
Description
It would be a lot simpler if type imports were not calculated into de project graphs.
import type { MailData, MailAdapter } from "utils-emails"
Error ESLint: Circular dependency between “xxxxxxxx” and “xxxxxxxx” detected (@nrwl/nx/enforce-module-boundaries)
Motivation
Circular dependencies in EsLint can be really annoying and type imports should simply not be considered as dependencies anyway. It’s also making it difficult to use some common patterns like facades, adapters, etc…
Suggested
Omit type imports when generating the project graph
Issue Analytics
- State:
- Created 2 years ago
- Reactions:4
- Comments:7 (2 by maintainers)
Top Results From Across the Web
Resolve Circular Dependencies - Nx
First, identify the import statements causing the dependency. Search in the source folder of projectA for references to @myorg/projectB and search in the...
Read more >avoiding module circular dependency (organization issue?)
Here's a (relatively simple) feasible solution: module internalemailer sends emails and throws errors, module errorhandler imports ...
Read more >Taming Code Organization with Module Boundaries in Nx
The self circular dependency (when lib imports from a named alias of itself), while not recommended, can be overridden by setting the flag ......
Read more >How to avoid circular dependencies in NestJS - LogRocket Blog
Avoiding circular dependencies also ensures that our code is easier to understand and modify, because a circular dependency means there is tight ...
Read more >How to solve this basic ES6-module circular dependency ...
The reason why I'm after a solution for the circular dependency is because in my real-world case I need to use instanceof A...
Read more >
Top Related Medium Post
No results found
Top Related StackOverflow Question
No results found
Troubleshoot Live Code
Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free
Top Related Reddit Thread
No results found
Top Related Hackernoon Post
No results found
Top Related Tweet
No results found
Top Related Dev.to Post
No results found
Top Related Hashnode Post
No results found
I’m genuinely sorry my answer and our linting rule got you so frustrated, @g3tr1ght.
Some of the statements you mentioned are false (e.g. scaling), some are outdated (e.g. change in test triggering dependant projects) and some are simply against the best practices (e.g. deep imports - which might not be bad if you know what you are doing, we do it ourselves in the Nx repo).
Nx and
@nrwl/nx/enforce-module-boundaries
follow a certain philosophy and were battle-tested on applications of various sizes and companies ranging from small startups to large Fortune 100 companies. Nx works for popular open-source projects and large enterprises with 1000s of projects. But that doesn’t mean it should work for you as well. No tool is ever perfect, nor it can be ever built to suit everyone’s needs.If you have proposals on improving it, we welcome you to submit a PR. Otherwise, you might have a specific use case for which our tools simply do not work or work against.
@meeroslav I would disagree with the statement:
Have you published a project whose only circular dependencies in the source code are types? Aren’t types extracted into .d.ts modules and all type imports removed from the meaningful bundle so it’s free of circular references? Why does ECMAScript modules specification support circular dependencies?
I do agree that circular dependency is usually a code smell. The key word here is usually. Not always. What’s more important is code smell is a subjective (not objective) characteristic. NX philosophy is barrel library exports, which means you can’t use precise import specifiers when going cross-libraries. However, do you always
import lodash from 'lodash';
or ratherimport merge from 'lodash/merge';
? As you are familiar with npm packages, you should as well be familiar with variability in import specifiers usage within each library (import foo from 'foo';
orimport bar from 'foo/bar';
orimport fiz from 'foo/bar/fiz';
), unless it’s packaged as a single module… oh wait! NX makes us use own libraries as if they consist of a single module but they aren’t which actually creates circular dependencies. If instead (breaking NX philosophy) we were to use precise import specifiers (which a sane person would usually do if one doesn’t use NX), it eliminates most (if not all!) circular dependencies, and if i have two modules cross-referencing each other, I would split one of them into two (absolutely no concern here). And we would have awesome dev experience, everything now becomes much faster, speed-of-light type check, linting, jest (or whatever it is for unit tests). Fun fact but these tools spend most of their time on module resolution and loading vs doing the actual work. There is absolutely no added value inimport { bar } from '@my-awesome-org/foo';
vsimport { bar } from '@my-awesome-org/foo/bar';
but extra slowness and harm.Why would we do it? Nobody wants to create meaningless sub-libraries (which don’t make sense on their own) just to extract some shared code out of multiple logically created libraries. Simple projects become littered with a hundred libraries some of which consist of only couple modules. Say, I have users, comments, scheduler libraries encapsulating logically relevant code, which could depend on each other. To avoid circular dependencies (especially in auxiliary modules like tests it’s unavoidable, or results in massive code duplication), I now need to create the following libraries: users, comments, scheduler, users-comments-shared, users-scheduler-shared, comments-scheduler-shared, users-comments-scheduler-shared, users-comments-shared-types, users-comments-shared-constants, comments-scheduler-shared-types, how-many-more?? This design decision limiting the structure flexibility has a polynomial complexity in terms of scalability and is harmful in real large-scale development environment. I created a workaround to allow precise imports from libraries (so that I don’t have circular dependencies and all tooling is much faster) but I can’t enable
@nrwl/nx/enforce-module-boundaries
with it, it simply reports error.As code smell is subjective, every official eslint rule has various configurations available. In my opinion,
@nrwl/nx/enforce-module-boundaries
is not mature enough to be useful, it’s trying to do too much, why not splitting it into multiple rules? Transitive circular dependencies (via something like test modules or Storybook stories) we can’t work around. I tried to configure NX eslint rule for"files": ["*.ts", "*.tsx", "*.js", "*.jsx"]
and added an override for"files": ["*.stories.tsx", "*.test.tsx", "*.test.ts"]
, but this only works if one doesn’t have transitive circular dependencies. In my opinion it is a bug in the rule itself rather than devs structural design mistakes. Why overriding every other eslint rule works as expected, but NX eslint rule - doesn’t? It clearly isn’t following basic eslint conventions.I presume that “circular dependency” NX team opinion is actually less of an opinion but rather NX design limitation as everything is built around “caching” mechanism, and instead of making the tool providing basic dev tooling and making caching optional, NX decided to go cache-first at an expense of loosing basic features (e.g. multiple configurations which Angular CLI supports). I understand that caching is hard. But why would one want to loose so much needed features just because of caching? Most of the fundamental web development tools support file-based caching. Yeah, not project-based, but why would I need to re-check the entire project and all its dependents if I changed one line in a test module? This obviously goes against NX goals of providing blazing fast tasks execution and dev scalability. As funny as it sounds, you can’t scale up if adding an empty line in a test module of shared across a thousand projects library invalidates not only the library’s cache but also caches of thousand dependent projects. At the end of the day, most of the tasks can be completely parallelized, I doubt that “caching” is what everyone here started using NX for. It looks good on paper but other than small personal projects face harsh reality. I think we yet to see real enterprise NX monorepo consisting of hundreds real beefy projects and not of a legion of meaningless libraries created to work around circular dependencies problem.