6

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? :)

Community
  • 1
  • 1
Flion
  • 10,468
  • 13
  • 48
  • 68

1 Answers1

9

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.

Dependencies

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.

Fenton
  • 241,084
  • 71
  • 387
  • 401
  • 5
    thank you Steve, you rock. I'm going to spend some more time trying to remove two way dependencies and am crossing internal modules off the list. The notes on what excactly a module _is_ are also helpful. Because now - if I do end up putting things in one file - it will feel less like a workaround, and more like a natural result of the way the module works. All this really helps me move forward with a bit more certainty – Flion Nov 12 '14 at 14:00
  • @Flion for the refactoring and class-to-interface work you may find useful the [Dependency Structure Matrix](http://stackoverflow.com/questions/1841552/what-are-the-open-source-alternatives-to-lattix) and especially some of the automatic partitioning algorithms offered by some tools, e.g. http://lattix.com/videos – xmojmr Nov 12 '14 at 16:12
  • 1
    I think what was confusing me was trying to model my javascript classes exactly after my database structure. Sure they should resemble each other, but circular dependencies aren't forbidden in database design and that tripped me up. Once I realized they serve different purposes, it was easier to grasp how each should be modeled. – aaaaaa Jan 11 '15 at 05:32
  • 1
    Here is a plugin that really is helping me deal with circular dependencies in my TypeScript code. https://www.npmjs.com/package/circular-dependency-plugin Without a plugin like this you end up finding these problems as runtime which is a disaster. Detecting circular dependencies SHOULD be built into every module loader automatically, because it is a known certainty that things will fail at runtime with modules being inexplicably null at runtime. It's such a shame that it's 2017 and still developers are having to wrangle with this. It's costing billions in lost developer productivity. –  Nov 22 '17 at 17:10