Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.

like image 639
John Weisz Avatar asked Dec 17 '15 22:12

John Weisz


People also ask

What does TSC do in TypeScript?

Tsc stands for `TypeScript compiler` and is a simple tool included in Typescript itself, allowing you to compile any ts files into js.

What is Tsconfig json in TypeScript?

The tsconfig.json file specifies the root files and the compiler options required to compile the project. JavaScript projects can use a jsconfig.json file instead, which acts almost the same but has some JavaScript-related compiler flags enabled by default.

What is the TSC command?

The tsc command envokes the TypeScript compiler. When no command-line options are present, this command looks for the tsconfig. json file. If no tsconfig. json is found, it returns by dumping the usage of tsc command.


1 Answers

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.

like image 196
Ryan Cavanaugh Avatar answered Sep 28 '22 07:09

Ryan Cavanaugh