Microsoft corporate blog
JavaScript
TypeScript
Programming
February 4

Announcing TypeScript 3.3 RC

Today we’re happy to announce the availability of our release candidate (RC) of TypeScript 3.3. Our hope is to collect feedback and early issues to ensure our final release is simple to pick up and use right away.

Original in blog

To get started using the RC, you can get it through NuGet, or use npm with the following command:


npm install -g typescript@rc

You can also get editor support by



TypeScript 3.3 should be a smooth release to adopt, and contains no breaking changes. Let’s explore what’s new in 3.3.


Improved behavior for calling union types


When TypeScript has a union type A | B, it allows you to access all the properties common to both A and B (i.e. the intersection of members).


interface A {
    aProp: string;
    commonProp: string;
}

interface B {
    bProp: number;
    commonProp: number
}

type Union = A | B;

declare let x: Union;

x.aProp; // error - 'B' doesn't have the property 'aProp'
x.bProp; // error - 'A' doesn't have the property 'bProp'
x.commonProp; // okay! Both 'A' and 'B' have a property named `commonProp`.

This behavior should feel intuitive – you can only get a property off of a union type if it’s known to be in every type of the union.


What about, instead of accessing properties, we’re dealing with calling types? Well, when every type has exactly one signature with identical parameters, things just work and you can call these types.


type CallableA = (x: boolean) => string;
type CallableB = (x: boolean) => number;

type CallableUnion = CallableA | CallableB;

declare let f: CallableUnion;

let x = f(true); // Okay! Returns a 'string | number'.

However, this restriction was sometimes, well, overly restrictive.


type Fruit = "apple" | "orange";
type Color = "red" | "orange";

type FruitEater = (fruit: Fruit) => number;     // eats and ranks the fruit
type ColorConsumer = (color: Color) => string;  // consumes and describes the colors

declare let f: FruitEater | ColorConsumer;

// Cannot invoke an expression whose type lacks a call signature.
//   Type 'FruitEater | ColorConsumer' has no compatible call signatures.ts(2349)
f("orange");

Silly example and poor error message aside, both FruitEaters and ColorConsumers should be able to take the string "orange", and return either a number or a string.


In TypeScript 3.3, this is no longer an error.


type Fruit = "apple" | "orange";
type Color = "red" | "orange";

type FruitEater = (fruit: Fruit) => number;     // eats and ranks the fruit
type ColorConsumer = (color: Color) => string;  // consumes and describes the colors

declare let f: FruitEater | ColorConsumer;

f("orange"); // It works! Returns a 'number | string'.

f("apple");  // error - Argument of type '"apple"' is not assignable to parameter of type '"orange"'.

f("red");    // error - Argument of type '"red"' is not assignable to parameter of type '"orange"'.

In TypeScript 3.3, the parameters of these signatures are intersected together to create a new signature. In the example above, the parameters fruit and color are intersected together to a new parameter of type Fruit & Color. Fruit & Color is really the same as ("apple" | "orange") & ("red" | "orange") which is equivalent to ("apple" & "red") | ("apple" & "orange") | ("orange" & "red") | ("orange" & "orange"). Each of those impossible intersections evaporates, and we’re left with "orange" & "orange" which is just "orange".


There are still some restrictions though. This new behavior only kicks in when at most one type in the union has multiple overloads, and at most one type in the union has a generic signature. That means methods on number[] | string[] like map (which is generic) still won’t be callable.


On the other hand, methods like forEach will now be callable, but under noImplicitAny there may be some issues.


interface Dog {
    kind: "pupper"
    dogProp: any;
}
interface Cat {
    kind: "kittyface"
    catProp: any;
}

const catOrDogArray: Dog[] | Cat[] = [];

catOrDogArray.forEach(animal => {
    //                ~~~~~~ error!
    // Parameter 'animal' implicitly has an 'any' type.
});

While we’ll continue to improve the experience here, this is strictly more capable in TypeScript 3.3, and adding an explicit type annotation will work.


interface Dog {
    kind: "pupper"
    dogProp: any;
}
interface Cat {
    kind: "kittyface"
    catProp: any;
}

const catOrDogArray: Dog[] | Cat[] = [];
catOrDogArray.forEach((animal: Dog | Cat) => {
    if (animal.kind === "pupper") {
        animal.dogProp;
        // ...
    }
    else if (animal.kind === "kittyface") {
        animal.catProp;
        // ...
    }
});

Incremental file watching for composite projects in --build --watch


In TypeScript 3.0, we introduced a new feature for structuring builds called “composite projects”. Part of the goal here was to ensure users could break up large projects into smaller parts that build quickly and preserve project structure, without compromising the existing TypeScript experience. Thanks to composite projects, TypeScript can use --build mode to recompile only the set of projects and dependencies. You can think of this as optimizing inter-project builds.


However, around last year our team also shipped optimized --watch mode builds via a new incremental “builder” API. In a similar vein, the entire idea is that this mode only re-checks and re-emits changed files or files whose dependencies might impact type-checking. You can think of this as optimizing intra-project builds.


Perhaps ironically, building composite projects using --build --watch actually didn’t use this infrastructure. An update in one project under --build --watch mode would force a full build of that project, rather than determining which files within that project were affected.


In TypeScript 3.3, --build mode’s --watch flag does leverage incremental file watching as well. That can mean signficantly faster builds under --build --watch. In our testing, this functionality has resulted in a reduction of 50% to 75% in build times of the original --build --watch times. You can read more on the original pull request for the change to see specific numbers, but we believe most composite project users will see significant wins here.


What’s next?


Beyond 3.3, you can keep an eye on our Roadmap page for any upcoming work.


But right now we’re looking forward to hearing about your experience with the RC, so give it a shot now and let us know your thoughts!


– Daniel Rosenwasser and the TypeScript team

+14
873 3
Comments 1