I cannot get inheritance to work across CommonJS modules generated using typescript 1.0 (tsc
is run using --module commonjs
)
This fails when two classes inheriting the same base class, call each other "through" the base class.
What seems to hapeen is that the first class imports the Base Class, which imports the second class which also imports the Base Class but the last Base Class import is failing.
An example illustrating this behaviour is provided below.
Is there anything in the Typescript or the CommonJS specifications preventing me from doing this, or is this a bug ?
=== Example ===
This fascinating piece of software is failed by running Lower.test.ts
. What it simply tries to achieve is loading a word into Lower
which keeps it in lower case, then using the inherited toUpper()
method from the Base
class, transforms it in upper case using the Upper
class (which also inherits Base
)
Lower.test.ts
import Lower = require('./Lower')
console.log(new Lower('smallcaps').toUpper())
Base.ts
import Upper = require('./Upper')
class Base {
word: string
toUpper(): string {
return new Upper(this.word).word
}
}
export = Base
Upper.ts
import Base = require('./Base')
class Upper extends Base {
constructor(word:string) {
super()
this.word = word.toUpperCase()
}
}
export = Upper
Lower.ts
import Base = require('./Base')
class Lower extends Base {
constructor(word:string) {
super()
this.word = word.toLowerCase()
}
}
export = Lower
After some research and testing, this ends up being a case of circular dependencies.
The issue is pretty well documented and various solutions have been proposed:
- Exporting before
requiring
: here
- Delaying the
require
or using injection: here
- Moving statements around: a pretty long list here
Unfortunately, there is very little control on where import
and export
statements can be set in Typescript which reduces solutions to injection only. The rewritten Base
class is provided below.
Circular dependencies are a pain in Javascript modular projects. Typescript somehow makes things worse. That is bad news for a language supposed to address large projects
EDIT
I have opened a case and submitted a proposed transpiler fix to the TypeScript project: here
Base.ts
//This import will "fail" by importing an empty object but is needed to trick the compiler
//An alternative is to design and Upper.d.ts definition file and reference it
import Upper = require('./Upper')
class Base {
//Upper is a sub-class of Base and needs to be injected to be instantiated
private _Upper: typeof Upper
word: string
constructor(_Upper: typeof Upper) {
this._Upper = _Upper
}
toUpper(): string {
//This where the injection is used
return new this._Upper(this.word).word
}
getUpperWord(upper: Upper): string {
//The injection is not reauired in this case
return upper.word
}
}
export = Base
Upper.ts
import Base = require('./Base')
class Upper extends Base {
constructor(word:string) {
super(Upper)
this.word = word.toUpperCase();
}
}
export = Upper
It is usually best to depend in just one direction - as per SOLID principles.
However, as you always need both Base
and Upper
(i.e. you cannot have one without the other) you can add them to the same module...
base.ts
export class Base {
word: string
toUpper(): string {
return new Upper(this.word).word
}
}
export class Upper extends Base {
constructor(word: string) {
super()
this.word = word.toUpperCase()
}
}
lower.ts
import b = require('base')
class Lower extends b.Base {
constructor(word: string) {
super()
this.word = word.toLowerCase()
}
}
export = Lower
app.ts
import Lower = require('lower');
console.log(new Lower('smallcaps').toUpper());