I'm in the process of moving a fairly large typescript project from internal modules to external modules. I do this because I want to create one core bundle, which, if and when required, can load other bundles. A seccond requirement which I'm keeping in mind is that I'd like to be able to run the bundles (with some modifications if required) on the server with nodeJS aswell.
I first tried using AMD & require.js to build the core bundle, but I came across an issue with circular dependencies. After having reading that this is common with require.js and commonJS is more often adviced for large project I tried that. But now that it's set up together with browserify I have the exact same issue coming up at the exact same place when I run the compiled bundle.
I have something like 10 base classes that havily rely on eachother and form multiple circular dependencies. I don't see any way to remove all of them.
A simplified setup to explain why I don't think I can remove the circular dependencies:
Triples are made of 3 Resources (subject,predicate,object)
Resource has TripleCollections to keep track of which triples its used in
Resource has multiple functions which rely on properties of Triple
Triple has some functions which handle TripleCollections
TripleCollection has multiple functions which uses functions of Triple
TripleCollection.getSubjects() returns a ResourceCollection
ResourceCollection.getTriples() returns a TripleCollection
Resource keeps track of the objects of its triples in ResourceCollections
ResourceCollection uses multiple functions of Resource
I've read multiple related issues here on SO (this one being most helpful), and from what I can gather I only have a few options:
1) Put all the base classes with circular dependencies in 1 file.
This means it will become one hell of a file. And imports will always need aliases:
import core = require('core');
import BaseA = core.BaseA;
2) Use internal modules
The core bundle worked fine (with its circular dependencies) when I used internal typescript modules and reference files. However if I want to create separate bundles and load them at run time, I'm going to have to use shims for all modules with require.js.
Though I don't really like all the aliasing, I think I will try option 1 now, because if it works I can keep going with CommonJS and browserify and later on I can also more easily run everything on the server in node. And I'll have to resort to option 2 if 1 doesn't work out.
Q1: Is there some possible solution I havn't mentioned?
Q2: what is the best setup for a typescript project with 1 core bundle which loads other bundles (which build upon the core) on demand. Which seems to cannot go around circular dependencies. And which preferably can run both on the client and the server.
Or am I just asking for the impossible? :)
There are a couple of options to get rid of circular dependencies. For a longer chain, A -> B -> C -> D -> A , if one of the references is removed (for instance, the D -> A reference), the cyclic reference pattern is broken, as well. For simpler patterns, such as A -> B -> A , refactoring may be necessary.
Avoiding circular dependencies by refactoring Circular dependencies create tight couplings between the classes or modules involved, which means both classes or modules have to be recompiled every time either of them is changed.
A circular dependency occurs when two classes depend on each other. For example, class A needs class B, and class B also needs class A. Circular dependencies can arise in Nest between modules and between providers. While circular dependencies should be avoided where possible, you can't always do so.
That is - since the circular dependency means that other code receives an incomplete module object, the proposed solution is to ensure the incomplete module will have the necessary objects in it. A circular dependency between Node. js modules occurs when the modules require() each other.
Simply put (perhaps simplistically, but I don't think so), if you have ModuleA
and ModuleB
and both rely on each other, they aren't modules. They are in separate files, but they are not acting like modules.
In your situation, the classes are highly interdependent, you can't use any one of those classes without needing them all, so unless you can refactor the program to try an make the dependencies one-way, rather than two-way (for example), I would treat the group of classes as a single module.
If you do put all of these in a single module, you may still be able to break it into modules by moving some of the dependencies, for example (and all of these questions are largely rhetorical as they are the kind of question you would need to ask yourself), what do Triple
and Resource
share on each other? Can that be moved into a class that they could both depend on? Why does a Triple
need to know about a TripleCollection
(probably because this represents some hierarchical data)? There may only be some things you can move, but any dependency removed from this current design will reduce complexity. For example, if the two-way relationship between Triple
and Resource
could be removed.
It may be that you can't substantially change this design right now, in which case you can put it all in one module and that will largely solve the module loading issue and it will also keep code together that is likely to change for the same reason.
The summary of all of this is:
If you have a two way module dependency, make it a one-way module dependency. Do this by moving code to make the dependency one way or by moving it all into a single larger module that more honestly represents the coupling between the modules.
So my view on your options is slightly different to those in your questions...
1) Try refactoring the code to reduce coupling to see if you can keep smaller modules
Failing that...
2) Put all the base classes with circular dependencies in 1 file.
I wouldn't place the internal modules option on the list - I think external modules are a much better way of managing a large program.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With