John White John White - 1 month ago 12
TypeScript Question

TypeScript dependencies in compiled output are not resolved in the correct order

Good evening.

I have a -- seemingly -- nonsense issue arising in the Javascript output compiled from a Typescript project. The issue seems complex, but I'll try to make it short and straightforward.

My Typescript project called "MyApp" (fully object-oriented, being developed in Visual Studio 2013) is set to compile into a single output file (main.js, ECMAScript 5), which is then ran in a browser. No module loading software (like AMD) is used, and I'd wish to keep it that way. All typescript files (.ts) referenced in the project are compiled automatically, there is no need to manually reference them using

/// <reference path>
.

I have this file (Score.ts), one level deep into the namespace structure:

App_Script/Score.ts

namespace MyApp.App_Script {
export class Score {
protected tracks: MyApp.App_Script.Entities.Track[];
...
}
}


The namespace structure reflects the directory layout:

MyApp
|-- App_Script
| |-- Entities
| | |-- Track.ts
| | |-- Note.ts
| | `-- ...
| |-- Loader.ts
| |-- Score.ts
| `-- ...
`-- main.ts


The
Score
class is instantiated in the main.ts file (this file can be considered the singular "entry point" into the application), which contents is enclosed into the MyApp namespace:

main.ts

namespace MyApp {
import Score = MyApp.App_Script.Score;

var score: Score = new Score();

score.init();
...
}


The above code (among with all additional dependencies shown on the above directory layout) compiles without issues, and runs correctly, nice and smooth. However, in Score.ts I'd wish to
import
the Track class to avoid having to type its fully qualified name dozens of times:

App_Script/Score.ts

namespace MyApp.App_Script {
import Track = MyApp.App_Script.Entities.Track;

export class Score {
protected tracks: Track[];
...
}
}


This code compiles as well, and correctly without barfing up any errors, creating the designated output file as expected. Everything seems to be fine in my Typescript source files, as everything Typescript-related works flawlessly: no syntax errors are being reported, and autocompletion works properly on the imported
Track
class, as well as all other code.

But when ran in the browser,


main.js: Cannot read property 'Track' of undefined.


... which is no wonder, since this is what an abomination of a beginning of the designated output Javascript file looks like (these are the very first lines in the output):

main.js

var MyApp;
(function (MyApp) {
var App_Script;
(function (App_Script) {
var Track = MyApp.App_Script.Entities.Track; // PLEASE LEAVE
var Score = (function () {
function Score() {
this.tracks = [];

for (var i = 0; i < 4; i++) {
// shouldn't this be "new MyApp.App_Script.Entities.Track()"?
// if I rewrite it manually, it works
this.tracks.push(new Track());
}
}
return Score;
})();
App_Script.Score = Score;
})(App_Script = MyApp.App_Script || (MyApp.App_Script = {}));
})(MyApp || (MyApp = {}));
...


The definition for
Track
is actually on the bottom of the compiled output (when it should be on top):

main.js

...
var MyApp;
(function (MyApp) {
var App_Script;
(function (App_Script) {
var Entities;
(function (Entities) {
var Track = (function () {
function Track() {
...
}
return Track;
})();
Entities.Track = Track;
})(Entities = App_Script.Entities || (App_Script.Entities = {}));
})(App_Script = MyApp.App_Script || (MyApp.App_Script = {}));
})(MyApp|| (MyApp = {}));
...





Here is what I observed: if you
import
a class, that's fine (Score is imported from main.ts without issues). If you
import
a class in a namespace containing an exported class that is imported elsewhere, the compiler barfs up unusable code without reporting any errors during the process.

Is this a bug in the TypeScript compiler, or is it just something completely trivial I am missing? I do not wish to use a module loading system, as this seems to be an issue with the compiler, and the code will run in a browser.

IMO,
import
should simply replace the affected class names with their fully qualified names during compilation, and not introduce additional instructions into the output Javascript code.

Answer

Is this a bug in the TypeScript compiler

No

or is it just something completely trivial I am missing?

Yes

I do not wish to use a module loading system,

You probably should, since you have file ordering issues

import should simply replace the affected class names with their fully qualified names during compilation, and not introduce additional instructions into the output Javascript code.

That would be the wrong thing to do. Consider some code like this:

namespace Z {
  export var A = 10;
}

namespace X {
  import Y = Z.A;
  namespace Q {
     let Z = { A: 20 };
     // If we replaced 'Y' with 'Z.A', as proposed, 'a' would be 20
     // when it should be 10
     let a = Y;
  }
}

The problem here is that TypeScript doesn't automatically guess what order your files should be concatenated in.

The fix is to either add /// <reference ... tags to files so that the compiler can figure out what you want the file order to be, or manually specify the ordering of the files on the commandline in the order you want them to appear in the output file.

Comments