Practical TypeScript

Features That Will Matter in the Upcoming TypeScript 1.8

There are lots of enhancements waiting for you in TypeScript 1.8, including features that make it easier to move your project to TypeScript, that protect you from errors and that give you even better control over data types.

The headline for the next version of TypeScript is probably its continued support for and integration with JSX (something I'll discuss in my column next time). But there are several other features in version 1.8 that are worth talking about. This column doesn't cover everything new in TypeScript 1.8, but these are the features that I think will make the biggest difference in your programming life with TypeScript.

Easier Integration with Existing Projects
For example, TypeScript 1.8 reduces the effort required to integrate TypeScript into existing projects. First, TypeScript is now available as a NuGet package, making it considerably easier to add it to a project.

Second, if your project already includes JavaScript files, the TypeScript compiler can now simply include JavaScript files into its compilation process and bundle all of your JavaScript output into a single .js file. If you're using AMD modules to selectively load JavaScript as you need it, you can still generate a single JavaScript file (the compiler will insert module closures around the various modules).

In theory, you've always been able to rename your JavaScript files to .ts to include existing JavaScript files into the TypeScript compilation process. In practice, however, (and especially for projects with lots of JavaScript code) this always generated some TypeScript compile time errors. These errors were always trivial to fix but now that the compiler simply accepts JavaScript files as JavaScript the problem disappears. Your JavaScript is still checked for JavaScript (not TypeScript) syntax errors and you still get a source map to support debugging.

This feature is turned off by default but you can turn it on by adding the --allowJs switch to your compiler's build line. If you're using a tsconfig.json file to control how your TypeScript is compiled, you can add allowJs to the compiler options section, like this:

{
  "compilerOptions": 
  {
    "allowJs": true,
    "module": "commonjs",
    "noImplicitAny": true,
    "removeComments": true,
    "preserveConstEnums": true,
    "out": "../../built/local/tsc.js",
    "sourceMap": true
  }
}

And, with TypeScript 1.8 Visual Studio 2015, the tsconfig.json file is supported in all project types. You can also have multiple tsconfig.json files in separate folders if you need to configure different groups of TypeScript and JavaScript to compile differently.

Compiler Enhancements
First, the TypeScript compiler, wherever possible, is now using ChakraCore (Microsoft's new JavaScript engine) improving compile speeds by 10 percent to 50 percent.

Second, the compiler now does more for you in identifying problems (or potential problems) in your code. The compiler will now, for example, flag any unreachable code in your TypeScript files. This feature can catch some pretty obscure JavaScript errors. This code, for example, looks OK (though oddly formatted), but it won't work, at least not the way you might expect:

function MyFunction(): string
{
  return
    "fred";
}

The problem is that JavaScript automatically puts a semicolon after a return statement that ends with a newline/carriage return. As a result, that string literal "fred" at the end of the function won't be returned from the function. The good news is that the TypeScript 1.8 compiler will now catch that error as "unreachable" code.

Two other new features: The compiler also now flags any functions that don't return values along all of the code paths if you add the noImplicitReturns option to your compiles (otherwise the function returns undefined). And, if you forget to put the break statement in the case portion of your switch block, the compiler will catch that now, too, provided you add the noFallthroughCasesInSwitch option (this will still allow your case statement to fall through if it has nothing in it at all). Personally, I'd want these to be my defaults.

Simple String Enumerations
TypeScript has always understood that some functions expect to be passed specific string values that trigger some functionality. This is especially important when generating HTML where you want to ensure that you're passing acceptable values as tag names and attribute values. Using TypeScript string literals you can now specify the acceptable values for strings. To define a string literal you just declare the variable name and provide the list of acceptable literal values, separated by the vertical bar (the string data type is implicitly assumed).

This code, for example, declares a Vendor class with a creditRating property of type string and a set of three acceptable values:

class Vendor 
{
  id: number;
  name: string;
  creditRating: "good" | "bad" | "excellent";
}

When I set the creditRating property on a Vendor class, IntelliSense will prompt me with the acceptable values and will flag the following code (which misspells "good") as an error:

var vnd: Vendor;
vnd = new Vendor();
vnd.creditRating = "goob";

Effectively, this provides a simple way to set up an enumeration of valid values for any string variable. This code defines a status variable that can only hold one of three acceptable string values:

var status: "new" | "old" | "pending";

User-Defined Type Guards
Starting with TypeScript 1.6, you could define a type guard using the is keyword, to ensure that, within a function, you were only processing objects of a specific type. TypeScript extends type guards to include your functions that use the is keyword.

For example, assume that I have a union type called Partner that includes both Customer and Vendor classes:

type Partner = Vendor | Customer;

I also have a function that only works with Vendor objects and filters Vendors based on their credit rating:

function IsExcellentVendor(vnd: any): vnd is Vendor {
  return vnd.creditRating === 'excellent';
}

The constraint "vnd is Vendor" in the function declaration acts as type guard for the function so, within my IsExcellentVendor function, TypeScript assumes that the vnd parameter only refers to Vendors. I can't, by the way, declare my vnd parameter as Partner. If I do that, even with the "vnd is Vendor" constraint, TypeScript assumes that vnd is either a Customer or a Vendor object. While that's too bad, with TypeScript 1.8, I can now use this IsExcellentVendor function as a type guard in other code.

In the following example, while the variable prtnr is declared as Partner (and could, therefore, be a Customer or a Vendor) my function IsExcellentVendor controls access to the if block. As a result, within the if block, TypeScript assumes that prtnr is a Vendor because IsExcellentVendor has the "vnd is Vendor" constraint:

var prtnr: Partner;
if (IsExcellentVendor(prtnr)) {
    var rating: string;
    rating = prtnr.creditRating;
}

As I said, there's more in TypeScript 1.8 than I've discussed here. However, as I work to convince my clients that they should be moving from JavaScript to TypeScript, these changes (especially the ones around integrating existing JavaScript files) are considerably easier.

About the Author

Peter Vogel is a system architect and principal in PH&V Information Services. PH&V provides full-stack consulting from UX design through object modeling to database design. Peter tweets about his VSM columns with the hashtag #vogelarticles. His blog posts on user experience design can be found at http://blog.learningtree.com/tag/ui/.

comments powered by Disqus
Upcoming Events

.NET Insight

Sign up for our newsletter.

I agree to this site's Privacy Policy.