Announcing TypeScript 2.0 Beta

Today we’re excited to roll out our beta release of TypeScript 2.0. If you’re not familiar with TypeScript yet, you can start learning it today on our website.

To get your hands on the beta, you can download TypeScript 2.0 Beta for Visual Studio 2015 (which will require VS 2015 Update 3), or just run

npm install -g typescript@beta

This release includes plenty of new features, such as our new workflow for getting .d.ts files, but here’s a couple more features just to get an idea of what else is in store.

Non-nullable Types

null and undefined are two of the most common sources of bugs in JavaScript. Before TypeScript 2.0, null and undefined were in the domain of every type. That meant that if you had a function that took a string, you couldn’t be sure from the type alone of whether you actually had a string – you might actually have null.

In TypeScript 2.0, the new --strictNullChecks flag changes that. string just means string and number means number.

let foo: string = null; // Error!

What if you wanted to make something nullable? Well we’ve brought two new types to the scene: null and undefined. As you might expect, null can only contain null, and undefined only contains undefined. They’re not totally useful on their own, but you can use them in a union type to describe whether something could be null/undefined.

let foo: string | null = null; // Okay!

Because you might often know better than the type system, we’ve also introduced a postfix ! operator that takes null and undefined out of the type of any expression.

declare let strs: string[] | undefined;

// Error! 'strs' is possibly undefined.
let upperCased = strs.map(s => s.toUpperCase());

// 'strs!' means we're sure it can't be 'undefined', so we can call 'map' on it.
let lowerCased = strs!.map(s => s.toLowerCase());

Control Flow Analysis for Types

TypeScript’s support for handling nullable types is possible thanks to changes in how types are tracked throughout the program. In 2.0, we’ve started using control flow analysis to better understand what a type has to be at a given location. For instance, consider this function.

/**
 * @param recipients  An array of recipients, or a comma-separated list of recipients.
 * @param body        Primary content of the message.
 */
function sendMessage(recipients: string | string[], body: string) {
    if (typeof recipients === "string") {
        recipients = recipients.split(",");
    }

    // TypeScript knows that 'recipients' is a 'string[]' here.
    recipients = recipients.filter(isValidAddress);
    for (let r of recipients) {
        // ...
    }
}

Notice that after the assignment within the if block, TypeScript understood that it had to be dealing with an array of strings.
This sort of thing can catch issues early on and save you from spending time on debugging.

let bestItem: Item;
for (let item of items) {
    if (item.id === 42) bestItem = item;
}

// Error! 'bestItem' might not have been initialized if 'items' was empty.
let itemName = bestItem.name;

We owe a major thanks to Ivo Gabe de Wolff for his work and involvement in implementing this feature, which started out with his thesis project and grew into part of TypeScript itself.

Easier Module Declarations

Sometimes you want to just tell TypeScript that a module exists, and you might not care what its shape is. It used to be that you’d have to write something like this:

declare module "foo" {
    var x: any;
    export = x;
}

But that’s a hassle, so we made it easier and got rid of the boilerplate. In TypeScript 2.0 you can just write

declare module "foo";
declare module "bar";

When you’re ready to finally outline the shape of a module, you can come back to these declarations and define the structure you need.

What if you you depend on a package with lots of modules? Writing those out for each module might be a pain, but TypeScript 2.0 makes that easy too by allowing wildcards in these declarations!

declare module "foo/*";

Now you can import any path that starts with foo/ and TypeScript will assume it exists. You can take advantage of this if your module loader understands how to import based on a certain patterns too. For example:

declare module "*!text" {
    const content: string;
    export = content;
}

Now whenever you import a path ending with !text, TypeScript will understand that the import should be typed as a string.

import text = require("./hello.txt!text");
text.toLowerCase();

Next Steps

One feature you might be wondering about is support for async functions in ES3 & ES5. Originally, this was slated for the 2.0 release; however, to reasonably implement async/await, we needed to rewrite TypeScript’s emitter as a series of tree transformations. Doing so, while keeping TypeScript fast, has required a lot of work and attention to detail. While we feel confident in today’s implementation, confidence is no match for thorough testing, and more time is needed for async/awaitto stabilize. You can expect it in TypeScript 2.1, and if you’d like to track the progress, the pull request is currently open on GitHub.

TypeScript 2.0 is still packed with many useful new features, and we’ll be coming out with more details as time goes on. If you’re curious to hear more about what’s new, you can take a look at our wiki. In the coming weeks, a more stable release candidate will be coming out, with the final product landing not too far after.

We’d love to hear any feedback you have, either in the comments below or on GitHub. Happy hacking!