Setup TypeScript is installed by default in VS 2015. However, for non-ASP. NET projects configuration is NOT automated and various elements need to be added to the .csproj file for correct build and debugging behaviour. Integration with ASP. NET projects is easy, and project configuration takes place automatically following the inclusion of the first .ts file.
Using Library Code Declaration files (typings) are required to consume libraries such as JQuery from TypeScript code (acting like C header files), and these are available for popular libraries via NuGet (DefinitelyTyped is a popular repository). Dependent on configuration, declaration files in a project will be located automatically by IntelliSense.
Unanticipated Runtime/Compile-time behaviour Despite the appearance of a statically, strongly typed language like C#, there are still limitations to the compile time warnings that TypeScript provides. The initial appearance of more safety than you are actually getting can result in unanticipated runtime behaviour.
Steven Fentons blog post provides useful information for setting up TypeScript support in a VS project.
If you are using Visual Studio 2013 and you have the TypeScript Visual Studio Extension installed, youll get the following message as soon as you add a TypeScript file to your project ‘Your project has been configured to support TypeScript…’
However, this only seems to be the case in ASP. NET projects. For non-ASP. NET projects, at least in VS 2013+, the following import clause should be included in the .csproj file manually, as the last child of the Project element.
This will make a TypeScript Build section accessible through the project properties pane in VS
Important: If a .tsconfig file (recommend in the TypeScript documentation for configuring the transcompiler) is detected somewhere in the project then the TypeScript Build pane becomes disabled. Using a .tsconfig file seems to be buggy in Visual Studio (changes to the .tsconfig do not always seem to be applied and library scripts are not automatically detected - these issues could have been caused by conflicts with the .csproj file, or incorrect .tsconfig formatting), so it seems best to only use the TypeScript Build pane provided by VS.
Following this manual addition to the .csproj file, navigate to the TypeScript Build pane in the project properties, and perform the following steps.
- Ensure the Configuration dropdown list is set to Debug
- Now set the Configuration dropdown list to Release
Note: In the TypeScript Build pane, “allow implicit ‘any’ types” is ticked by default. It seems to be generally more useful to disallow implicit ‘any’ types to take full advantage of compile time type checking.
To enable TypeScript debugging from the browser the TypeScript Build option ‘Generate source maps’ must be ticked. For ASP. NET projects this seems to be all that is required.
If not using ASP. NET, the .ts files should also be explicitly included in the output directory (copy if newer). Also, as the build action for .ts files is automatically and necessarily set to ‘TypeScriptCompile’ (as opposed to ‘Content’) an MSBuild task must be added to csproj to copy the .ts files to the output directory (see below).
1 2 3 4 5
@(TypeScriptCompile) refers to a collection of .ts files that will be automatically defined elsewhere in the .csconfig file. $(TypeScriptSourceMap) is a variable that should be auto generated after making changes in the TypeScript Build project property section, which specifies if source maps are generated (if not TypeScript debugging can’t take place anyway, hence the condition in the above MSBuild task).
Edge appears to not work very well with .ts debugging (it sometimes points to an entirely different location in the TypeScript code), but Chrome seems to do a great job, with far more informative error messages.
Using Library Code (pretty simple)
DefinitelyTyped is a popular declaration file repository, providing JQuery and other popular library typings. These can be installed via NuGet.
So long as a .tsconfig file is not being used, IntelliSense seems to locate TypeScript declaration files automatically without needing to specify a folder.
Unanticipated Runtime/Compile-time Behaviour
TypeScript Class Definitions are Not Hoisted In TypeScript it seems valid at compile time to declare/define a class at the bottom of a file, and instantiate and use this object somewhere else in that file (as is the case in C#, Java, etc.).
Moving class declarations above where the classes are instantiated in TypeScript solves this problem (TypeScript does not warn or prompt about this).
Also be wary of introducing a race condition, through use of the $(document).ready shorthand, as below.
1 2 3 4 5
The above code will almost always work, unless the JQuery document.ready callback runs before the MyClass definition. It therefore seems best practice to place TypeScript entry points at the bottom of the file, or at least below all type declarations/definitions.
‘this’ Keyword Confusion Non-static class members must always be dereferenced with an instance qualifier (instance name, or this, i.e this.property), however care must be taken when using callback or local functions inside TypeScript classes. See the behaviour commented in the code below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
Instance Dereferencing Inconsistencies There are at least three different kinds of syntax for defining the methods of a class, see methods print1, 2, and 3 in the class definition below.
1 2 3 4 5 6 7 8 9 10 11 12 13
The syntax used with print3 is used by Microsoft in the official TypeScript documentation (in the Classes section), but the other ways appear to be just as valid (no compile time issues). Instantiating MyClass and calling the print methods directly results in no unexpected behaviour.
1 2 3 4 5 6
print1, this.property = Hello print2, this.property = Hello print3, this.property = Hello
However, I encountered an issue when wanting to pass a class' method as an argument to another function. As a simplified example see the code and output below:
1 2 3 4 5 6 7 8 9 10 11 12 13
print1, this.property = Hello print2, this.property = undefined print3, this.property = undefined
While print1 (the method that was defined in MyClass as an ‘expression bodied member’) works as expected, the other two syntaxes result in what appears to be buggy behaviour.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
The problem is again related to use of the keyword ‘this’, which obviously varies based on the calling context.
You can see that the TypeScript developers have got around the issue by storing a ‘_this’ variable to capture the MyClass context. However ‘_this’ is only dereferenced by print1, while print2 and print3 utilize the ‘this’ keyword directly.
When called normally ‘this’ refers to MyClass. However, when assigned to and invoked via a variable, ‘this’ ends up referring to the global Window object. This was the context at the time of lambda initialisation. Nevertheless, this inconsistency is actually recognised as proper TypeScript behaviour. The fat arrow syntax/semantics is newer, and is recognised as the means of correctly keeping a class this context (see this TypeScript wiki article).
I looked into the use of TSLint, hoping that it would provide compile time warnings for some of these issues that are not picked up by the TypeScript compiler. Despite available NuGet packages it appears to be difficult to setup in VS 2015 (at least for non-ASP. NET projects). The easiest way seems to be to install Mads Kristensen’s Web Analyzer extension.
TSLint picked up on a missing colon in my code and a call to parseInt() without using a radix argument. Other than that, by default TSLint is very strict about always using type definitions, even when the TypeScript compiler and IntelliSense can easily infer the type or when the type is blatantly obvious to developers, i.e.
let i = 0;
needs to become
let i : number = 0;
Despite this, unfortunately TSLint doesn’t provide warnings for any of the runtime issues described in this post.