Announcing TypeScript 3.4

Today we’re happy to announce the availability of TypeScript 3.4!
If you haven’t yet used TypeScript, it’s a language that builds on JavaScript that adds optional static types. The TypeScript project provides a compiler that checks your programs based on these types to prevent certain classes of errors, and then strips them out of your program so you can get clean readable JavaScript code that will run in any ECMAScript runtime (like your favorite browser, or Node.js). TypeScript also leverages this type information to provide a language server, which can be used for powerful cross-platform editor tooling like code completions, find-all-references, quick fixes, and refactorings.
TypeScript also provides that same tooling for JavaScript users, and can even type-check JavaScript code typed with JSDoc using thecheckJs
flag. If you’ve used editors like Visual Studio or Visual Studio Code on a.js
file, TypeScript is powering that experience, so you might already be using TypeScript in some capacity!
To get started with TypeScript, you can get itthrough NuGet, or through npm with the following command:
npm install -g typescript
You can also get editor support by
- Downloading for Visual Studio 2019 and Visual Studio 2017 (15.2+)
- Using tonight’sVisual Studio Code Insiders (orby manually setting the editor up).
- Sublime Text 3 via PackageControl.
Support for other editors will likely be rolling in in the near future.
Let’s dive in and see what’s new in TypeScript 3.4!
- Faster subsequent builds with the
--incremental
flag - Higher order type inference from generic functions
- Improvements for
ReadonlyArray
andreadonly
tuples const
assertions- Type-checking for
globalThis
- Convert parameters to destructured object
- Breaking changes
Faster subsequent builds with the--incremental
flag
Because TypeScript files are compiled, there is an intermediate step between writing and running your code. One of our goals is to minimize build time given any change to your program. One way to do that is by running TypeScript in--watch
mode. When a file changes under--watch
mode, TypeScript is able to use your project’s previously-constructed dependency graph to determine which files could potentially have been affected and need to be re-checked and potentially re-emitted. This can avoid a full type-check and re-emit which can be costly.
But it’s unrealistic to expectall users to keep atsc --watch
process running overnight just to have faster builds tomorrow morning. What about cold builds? Over the past few months, we’ve been working to see if there’s a way to save the appropriate information from--watch
mode to a file and use it from build to build.
TypeScript 3.4 introduces a new flag called--incremental
which tells TypeScript to save information about the project graph from the last compilation. The next time TypeScript is invoked with--incremental
, it will use that information to detect the least costly way to type-check and emit changes to your project.
// tsconfig.json{"compilerOptions": {"incremental":true,"outDir":"./lib" },"include": ["./src"]}
By default with these settings, when we runtsc
, TypeScript will look for a file called.tsbuildinfo
in the output directory (./lib
). If./lib/.tsbuildinfo
doesn’t exist, it’ll be generated. But if it does,tsc
will try to use that file to incrementally type-check and update our output files.
These.tsbuildinfo
files can be safely deleted and don’t have any impact on our code at runtime – they’re purely used to make compilations faster. We can also name them anything that we want, and place them anywhere we want using the--tsBuildInfoFile
flag.
// front-end.tsconfig.json{"compilerOptions": {"incremental":true,"tsBuildInfoFile":"./buildcache/front-end","outDir":"./lib" },"include": ["./src"]}
As long as nobody else tries writing to the same cache file, we should be able to enjoy faster incremental cold builds.
How fast, you ask? Well, here’s the difference in adding--incremental
to theVisual Studio Code project’stsconfig.json
Step | Compile Time |
---|---|
Compile without--incremental | 47.54s |
First compile with--incremental | 52.77s |
Subsequent compile with--incremental with API surface change | 30.45s |
Subsequent compile with--incremental without API surface change | 11.49s |
For a project the size of Visual Studio Code,TypeScript’s new--incremental
flag was able to reduce subsequent build times down to approximately a fifth of the original.
Composite projects
Part of the intent with composite projects (tsconfig.json
s withcomposite
set totrue
) is that references between different projects can be built incrementally. As such, composite projects willalways produce.tsbuildinfo
files.
outFile
WhenoutFile
is used, the build information file’s name will be based on the output file’s name. As an example, if our output JavaScript file is./output/foo.js
, then under the--incremental
flag, TypeScript will generate the file./output/foo.tsbuildinfo
. As above, this can be controlled with the--tsBuildInfoFile
flag.
The--incremental
file format and versioning
While the file generated by--incremental
is JSON, the file isn’t mean to be consumed by any other tool. We can’t provide any guarantees of stability for its contents, and in fact, our current policy is that any one version of TypeScript will not understand.tsbuildinfo
files generated from another version.
What else?
That’s pretty much it for--incremental
! If you’re interested, check outthe pull request (along withits sibling PR) for more details.
In the future, we’ll be investigating APIs for other tools to leverage these generated build information files, as well as enabling the flag for use directly on the command line (as opposed to just intsconfig.json
files).
Higher order type inference from generic functions
TypeScript 3.4 has several improvements around inference that were inspired by somevery thoughtful feedback from community member Oliver J. Ash on our issue tracker. One of the biggest improvements relates to functions inferring types from other generic functions.
To get more specific, let’s build up some motivation and consider the followingcompose
function:
function compose<A,B,C>(f: (arg:A)=>B,g: (arg:B)=>C): (arg:A)=>C {returnx=>g(f(x));}
compose
takes two other functions:
f
which takes some argument (of typeA
) and returns a value of typeB
g
which takes an argument of typeB
(the typef
returned), and returns a value of typeC
compose
then returns a function which feeds its argument throughf
and theng
.
When calling this function, TypeScript will try to figure out the types ofA
,B
, andC
through a process calledtype argument inference. This inference process usually works pretty well:
interfacePerson { name:string; age:number;}function getDisplayName(p:Person) {returnp.name.toLowerCase();}function getLength(s:string) {returns.length;}// has type '(p: Person) => number'const getDisplayNameLength=compose(getDisplayName,getLength,);// works and returns the type 'number'getDisplayNameLength({ name:"Person McPersonface", age:42 });
The inference process is fairly straightforward here becausegetDisplayName
andgetLength
use types that can easily be referenced. However, in TypeScript 3.3 and earlier, generic functions likecompose
didn’t work so well when passed other generic functions.
interfaceBox<T> { value:T;}function makeArray<T>(x:T):T[] {return [x];}function makeBox<U>(value:U):Box<U> {return {value };}// has type '(arg: {}) => Box<{}[]>'const makeBoxedArray=compose(makeArray,makeBox,)makeBoxedArray("hello!").value[0].toUpperCase();// ~~~~~~~~~~~// error: Property 'toUpperCase' does not exist on type '{}'.
Oof! What’s this{}
type?
Well, traditionally TypeScript would see thatmakeArray
andmakeBox
are generic functions, but it couldn’t just inferT
andU
in the types ofA
,B
, andC
. If it did, it would end up with irreconcilable inference candidatesT[]
andU
forB
, plus it might have the type variablesT
andU
in the resulting function type, which wouldn’t actually be declared by that resulting type. To avoid this, instead of inferring directly fromT
andU
, TypeScript would infer from theconstraints ofT
andU
, which are implicitly the empty object type (that{}
type from above).
As you might notice, this behavior isn’t desirable because type information is lost and Ideally, we would infer a better type than{}
.
TypeScript 3.4 now does that. During type argument inference for a call to a generic function that returns a function type, TypeScriptwill, as appropriate, propagate type parameters from generic function arguments onto the resulting function type.
In other words, instead of producing the type
(arg: {})=>Box<{}[]>
TypeScript 3.4 just “does the right thing” and makes the type
<T>(arg:T)=>Box<T[]>
Notice thatT
has been propagated frommakeArray
into the resulting type’s type parameter list. This means that genericity fromcompose
‘s arguments has been preserved and ourmakeBoxedArray
sample will just work!
interfaceBox<T> { value:T;}function makeArray<T>(x:T):T[] {return [x];}function makeBox<U>(value:U):Box<U> {return {value };}// has type '<T>(arg: T) => Box<T[]>'const makeBoxedArray=compose(makeArray,makeBox,)// works with no problem!makeBoxedArray("hello!").value[0].toUpperCase();
For more details, you canread more at the original change.
Improvements forReadonlyArray
andreadonly
tuples
TypeScript 3.4 makes it a little bit easier to use read-only array-like types.
A new syntax forReadonlyArray
TheReadonlyArray
type describesArray
s that can only be read from. Any variable with a reference to aReadonlyArray
can’t add, remove, or replace any elements of the array.
function foo(arr:ReadonlyArray<string>) {arr.slice();// okayarr.push("hello!");// error!}
While it’s good practice to useReadonlyArray
overArray
when no mutation is intended, it’s often been a pain given that arrays have a nicer syntax. Specifically,number[]
is a shorthand version ofArray<number>
, just asDate[]
is a shorthand forArray<Date>
.
TypeScript 3.4 introduces a new syntax forReadonlyArray
using a newreadonly
modifier for array types.
function foo(arr:readonlystring[]) {arr.slice();// okayarr.push("hello!");// error!}
readonly
tuples
TypeScript 3.4 also introduces new support forreadonly
tuples. We can prefix any tuple type with thereadonly
keyword to make it areadonly
tuple, much like we now can with array shorthand syntax. As you might expect, unlike ordinary tuples whose slots could be written to,readonly
tuples only permit reading from those positions.
function foo(pair:readonly [string,string]) {console.log(pair[0]);// okaypair[1]="hello!";// error}
The same way that ordinary tuples are types that extend fromArray
– a tuple with elements of typeT
1
,T
2
, …T
n
extends fromArray<
T
1
|T
2
| …T
n
>
–readonly
tuples are types that extend fromReadonlyArray
. So areadonly
tuple with elementsT
1
,T
2
, …T
n
extends fromReadonlyArray<
T
1
|T
2
| …T
n
>
.
readonly
mapped type modifiers andreadonly
arrays
In earlier versions of TypeScript, we generalized mapped types to operate differently on array-like types. This meant that a mapped type likeBoxify
could work on arrays and tuples alike.
interfaceBox<T> { value:T }typeBoxify<T>= { [KinkeyofT]:Box<T[K]>}// { a: Box<string>, b: Box<number> }typeA=Boxify<{ a:string, b:number }>;// Array<Box<number>>typeB=Boxify<number[]>;// [Box<string>, Box<number>]typeC=Boxify<[string,boolean]>;
Unfortunately, mapped types like theReadonly
utility type were effectively no-ops on array and tuple types.
// lib.d.tstypeReadonly<T>= {readonly [KinkeyofT]:T[K]}// How code acted *before* TypeScript 3.4// { readonly a: string, readonly b: number }typeA=Readonly<{ a:string, b:number }>;// number[]typeB=Readonly<number[]>;// [string, boolean]typeC=Readonly<[string,boolean]>;
In TypeScript 3.4, thereadonly
modifier in a mapped type will automatically convert array-like types to their correspondingreadonly
counterparts.
// How code acts now *with* TypeScript 3.4// { readonly a: string, readonly b: number }typeA=Readonly<{ a:string, b:number }>;// readonly number[]typeB=Readonly<number[]>;// readonly [string, boolean]typeC=Readonly<[string,boolean]>;
Similarly, you could write a utility type likeWritable
mapped type that strips awayreadonly
-ness, and that would convertreadonly
array containers back to their mutable equivalents.
typeWritable<T>= {-readonly [KinkeyofT]:T[K]}// { a: string, b: number }typeA=Writable<{readonly a:string;readonly b:number}>;// number[]typeB=Writable<readonlynumber[]>;// [string, boolean]typeC=Writable<readonly [string,boolean]>;
Caveats
Despite its appearance, thereadonly
type modifier can only be used for syntax on array types and tuple types. It is not a general-purpose type operator.
let err1:readonlySet<number>;// error!let err2:readonlyArray<boolean>;// error!let okay:readonlyboolean[];// works fine
You cansee more details in the pull request.
const
assertions
When declaring a mutable variable or property, TypeScript oftenwidens values to make sure that we can assign things later on without writing an explicit type.
let x="hello";// hurray! we can assign to 'x' later on!x="world";
Technically, every literal value has a literal type. Above, the type"hello"
got widened to the typestring
before inferring a type forx
.
One alternative view might be to say thatx
has the original literal type"hello"
and that we can’t assign"world"
later on like so:
let x:"hello"="hello";// error!x="world";
In this case, that seems extreme, but it can be useful in other situations. For example, TypeScript users often create objects that are meant to be used in discriminated unions.
typeShape=| { kind:"circle", radius:number }| { kind:"square", sideLength:number }function getShapes():readonlyShape[] {let result= [ { kind:"circle", radius:100, }, { kind:"square", sideLength:50, }, ];// Some terrible error message because TypeScript inferred// 'kind' to have the type 'string' instead of// either '"circle"' or '"square"'.returnresult;}
Mutability is one of the best heuristics of intent which TypeScript can use to determine when to widen (rather than analyzing our entire program).
Unfortunately, as we saw in the last example, in JavaScript properties are mutable by default. This means that the language will often widen types undesirably, requiring explicit types in certain places.
function getShapes():readonlyShape[] {// This explicit annotation gives a hint// to avoid widening in the first place.let result:readonlyShape[]= [ { kind:"circle", radius:100, }, { kind:"square", sideLength:50, }, ];returnresult;}
Up to a certain point this is okay, but as our data structures get more and more complex, this becomes cumbersome.
To solve this, TypeScript 3.4 introduces a new construct for literal values calledconst
assertions. Its syntax is a type assertion withconst
in place of the type name (e.g.123 as const
). When we construct new literal expressions withconst
assertions, we can signal to the language that
- no literal types in that expression should be widened (e.g. no going from
"hello"
tostring
) - object literals get
readonly
properties - array literals become
readonly
tuples
// Type '10'let x=10asconst;// Type 'readonly [10, 20]'let y= [10,20]asconst;// Type '{ readonly text: "hello" }'let z= { text:"hello" }asconst;
Outside of.tsx
files, the angle bracket assertion syntax can also be used.
// Type '10'let x= <const>10;// Type 'readonly [10, 20]'let y= <const>[10,20];// Type '{ readonly text: "hello" }'let z= <const>{ text:"hello" };
This feature means that types that would otherwise be used just to hint immutability to the compiler can often be omitted.
// Works with no types referenced or declared.// We only needed a single const assertion.function getShapes() {let result= [ { kind:"circle", radius:100, }, { kind:"square", sideLength:50, }, ]asconst;returnresult;}for (const shapeofgetShapes()) {// Narrows perfectly!if (shape.kind==="circle") {console.log("Circle radius",shape.radius); }else {console.log("Square side length",shape.sideLength); }}
Notice the above needed no type annotations. Theconst
assertion allowed TypeScript to take the most specific type of the expression.
This can even be used to enableenum
-like patterns in plain JavaScript code if you choose not to use TypeScript’senum
construct.
exportconst Colors= { red:"RED", blue:"BLUE", green:"GREEN",}asconst;// or use an 'export default'exportdefault { red:"RED", blue:"BLUE", green:"GREEN",}asconst;
Caveats
One thing to note is thatconst
assertions can only be applied immediately on simple literal expressions.
// Error! A 'const' assertion can only be applied to a// to a string, number, boolean, array, or object literal.let a= (Math.random()<0.5?0:1)asconst;// Works!let b=Math.random()<0.5?0asconst:1asconst;
Another thing to keep in mind is thatconst
contexts don’t immediately convert an expression to be fully immutable.
let arr= [1,2,3,4];let foo= { name:"foo", contents:arr,}asconst;foo.name="bar";// error!foo.contents= [];// error!foo.contents.push(5);// ...works!
For more details, you cancheck out the respective pull request.
Type-checking forglobalThis
It can be surprisingly difficult to access or declare values in the global scope, perhaps because we’re writing our code in modules (whose local declarations don’t leak by default), or because we might have a local variable that shadows the name of a global value. In different environments, there are different ways to access what’s effectively the global scope –global
in Node,window
,self
, orframes
in the browser, orthis
in certain locations outside of strict mode. None of this is obvious, and often leaves users feeling unsure of whether they’re writing correct code.
TypeScript 3.4 introduces support for type-checking ECMAScript’s newglobalThis
– a global variable that, well, refers to the global scope. Unlike the above solutions,globalThis
provides a standard way for accessing the global scope which can be used across different environments.
// in a global file:var abc=100;// Refers to 'abc' from above.globalThis.abc=200;
Note that global variables declared withlet
andconst
don’t show up onglobalThis
.
let answer=42;// error! Property 'answer' does not exist on 'typeof globalThis'.globalThis.answer=333333;
It’s also important to note that TypeScript doesn’t transform references toglobalThis
when compiling to older versions of ECMAScript. As such, unless you’re targeting evergreen browsers (which already supportglobalThis
), you may want touse an appropriate polyfill instead.
For more details on the implementation, seethe feature’s pull request.
Convert parameters to destructured object
Sometimes, parameter lists start getting unwieldy.
function updateOptions(hue?:number,saturation?:number,brightness?:number,positionX?:number,positionY?:number,positionZ?:number,) {// ....}
In the above example, it’s way too easy for a caller to mix up the order of arguments given. A common JavaScript pattern is to instead use an “options object”, so that each option is explicitly named and order doesn’t ever matter. This emulates a feature that other languages have called “named parameters”.
interfaceOptions { hue?:number, saturation?:number, brightness?:number, positionX?:number, positionY?:number, positionZ?:number,}function updateOptions(options:Options= {}) {// ....}
In TypeScript 3.4, our internGabriela Britto has implemented a new refactoring to convert existing functions to use this “named parameters” pattern.
In the presence of multiple parameters, TypeScript will provide a refactoring to convert the parameter list into a single destructured object. Accordingly, each site where a function is called will also be updated. Features like optionality and defaults are also tracked, and this feature also works on constructors as well.
Currently the refactoring doesn’t generate a name for the type, but we’re interested in hearing feedback as to whether that’s desirable, or whether providing it separately throughan upcoming refactoring would be better.
For more details on this refactoring,check out the pull request.
Breaking changes
While it’s never ideal, TypeScript 3.4 does introduce some breaking changes – some simply due to improvements in inference. You can see slightly more detailed explanations onour Breaking Changes page.
Propagated generic type arguments
In certain cases, TypeScript 3.4’s improved inference might produce functions that are generic, rather than ones that take and return their constraints (usually{}
).
declarefunction compose<T,U,V>(f: (arg:T)=>U,g: (arg:U)=>V): (arg:T)=>V;function list<T>(x:T) {return [x]; }function box<T>(value:T) {return {value }; }let f=compose(list,box);let x=f(100)// In TypeScript 3.4, 'x.value' has the type//// number[]//// but it previously had the type//// {}[]//// So it's now an error to push in a string.x.value.push("hello");
An explicit type annotation onx
can get rid of the error.
Contextual return types flow in as contextual argument types
TypeScript now uses types that flow into function calls (likethen
in the below example) to contextually type function arguments (like the arrow function in the below example).
function isEven(prom:Promise<number>):Promise<{ success:boolean }> {returnprom.then((x)=> {returnx%2===0? { success:true }:Promise.resolve({ success:false }); });}
This is generally an improvement, but in the above example it causestrue
andfalse
to acquire literal types which is undesirable.
The appropriate workaround is to add type arguments to the appropriate call – thethen
method call in this example.
function isEven(prom:Promise<number>):Promise<{ success:boolean }> {// vvvvvvvvvvvvvvvvvvvvreturnprom.then<{ success:boolean }>((x)=> {returnx%2===0? { success:true }:Promise.resolve({ success:false }); });}
Consistent inference priorities outside ofstrictFunctionTypes
In TypeScript 3.3 with--strictFunctionTypes
off, generic types declared withinterface
were assumed to always be covariant with respect to their type parameter. For function types, this behavior was generally not observable. However, for genericinterface
types that used their type parameters withkeyof
positions – a contravariant use – these types behaved incorrectly.
In TypeScript 3.4, variance of types declared withinterface
is now correctly measured in all cases. This causes an observable breaking change for interfaces that used a type parameter only inkeyof
(including places likeRecord<K, T>
which is an alias for a type involvingkeyof K
). The example above is one such possible break.
interfaceHasX { x:any }interfaceHasY { y:any }declareconst source:HasX|HasY;declareconst properties:KeyContainer<HasX>;interfaceKeyContainer<T> { key:keyofT;}function readKey<T>(source:T,prop:KeyContainer<T>) {console.log(source[prop.key])}// This call should have been rejected, because we might// incorrectly be reading 'x' from 'HasY'. It now appropriately errors.readKey(source,properties);
This error is likely indicative of an issue with the original code.
Top-levelthis
is now typed
The type of top-levelthis
is now typed astypeof globalThis
instead ofany
. As a consequence, you may receive errors for accessing unknown values onthis
undernoImplicitAny
.
// previously okay in noImplicitAny, now an errorthis.whargarbl=10;
Note that code compiled undernoImplicitThis
will not experience any changes here.
What’s next?
The TypeScript team has recently started to publish our iteration plans – write-ups of features considered, committed work items, and targeted release dates for a given release. To get an idea of what’s next, you can check out the3.5 iteration plan document, as well as therolling feature roadmap page. Based on our planning, some key highlights of 3.5 might include.d.ts
file emit from JavaScript projects, and several editor productivity features.
We hope that TypeScript continues to make coding a joy. If you’re happy with this release, let us knowon Twitter, and if you’ve got any suggestions on what we can improve, feel free tofile an issue on GitHub.
Happy hacking!
– Daniel Rosenwasser and the TypeScript team
Author

Daniel Rosenwasser is the product manager of the TypeScript team. He has a passion for programming languages, compilers, and great developer tooling.
6 comments
Discussion is closed.Login to edit/delete existing comments.
Balasubramanian Ramanathan When we have compileOnSave set to true whether the incremental flag will have its effect?. I tried modifying the tsconfig.json and added
“incremental”: true,”tsBuildInfoFile”: “./buildcache/scripts”
but it is not generating the buildinfo file.Bert Cielen Can you also please update the download link for Visual Studio 2017 on https://www.typescriptlang.org/ ? It still points to 3.3.1.
Dimitri Mitropoulos is the `-` before `-readonly` in the example line“`
-readonly [K in keyof T]: T[K]
“`a typo?
thanks for the great stuff. the more features that support immutability and functional programming (like the higher order type inference from generic functions) the better.Daniel Rosenwasser Hi Dimitri, thanks, we’re definitely interested in improving expressiveness there.
And nope, mapped types allow you to strip off modifiers from the original types. The mapped type here is saying “this is an object whose properties are identical to those of
T
, except none of them isreadonly
.
Angus Fenying Nice job. I like the `as const` feature.