Was this page helpful?

TypeScript 3.4

Faster subsequent builds with the--incremental flag

TypeScript 3.4 introduces a new flag calledincremental which tells TypeScript to save information about the project graph from the last compilation.The next time TypeScript is invoked withincremental, it will use that information to detect the least costly way to type-check and emit changes to your project.

// tsconfig.json
{
"":true,
"":"./lib"
},
"": ["./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 thetsBuildInfoFile option.

// front-end.tsconfig.json
{
"":true,
"":"./buildcache/front-end",
"":"./lib"
},
"": ["./src"]
}

Composite projects

Part of the intent with composite projects (tsconfig.jsons 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 theincremental flag, TypeScript will generate the file./output/foo.tsbuildinfo.As above, this can be controlled with thetsBuildInfoFile option.

Higher order type inference from generic functions

TypeScript 3.4 can now produce generic function types when inference from other generic functions produces free type variables for inferences.This means many function composition patterns now work better in 3.4.

To get more specific, let’s build up some motivation and consider the followingcompose function:

ts
functioncompose<A,B,C>(f: (arg:A)=>B,g: (arg:B)=>C): (arg:A)=>C {
return (x)=>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:

ts
interfacePerson {
name:string;
age:number;
}
functiongetDisplayName(p:Person) {
returnp.name.toLowerCase();
}
functiongetLength(s:string) {
returns.length;
}
// has type '(p: Person) => number'
constgetDisplayNameLength =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.

ts
interfaceBox<T> {
value:T;
}
functionmakeArray<T>(x:T):T[] {
return [x];
}
functionmakeBox<U>(value:U):Box<U> {
return {value };
}
// has type '(arg: {}) => Box<{}[]>'
constmakeBoxedArray =compose(makeArray,makeBox);
makeBoxedArray("hello!").value[0].toUpperCase();
// ~~~~~~~~~~~
// error: Property 'toUpperCase' does not exist on type '{}'.

In older versions, TypeScript would infer the empty object type ({}) when inferring from other type variables likeT andU.

During type argument inference in TypeScript 3.4, 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

ts
(arg: {})=>Box<{}[]>

TypeScript 3.4 produces the type

ts
<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!

ts
interfaceBox<T> {
value:T;
}
functionmakeArray<T>(x:T):T[] {
return [x];
}
functionmakeBox<U>(value:U):Box<U> {
return {value };
}
// has type '<T>(arg: T) => Box<T[]>'
constmakeBoxedArray =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 describesArrays that can only be read from.Any variable with a reference to aReadonlyArray can’t add, remove, or replace any elements of the array.

ts
functionfoo(arr:ReadonlyArray<string>) {
arr.slice();// okay
arr.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.

ts
functionfoo(arr:readonlystring[]) {
arr.slice();// okay
arr.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.

ts
functionfoo(pair:readonly [string,string]) {
console.log(pair[0]);// okay
pair[1] ="hello!";// error
}

The same way that ordinary tuples are types that extend fromArray - a tuple with elements of typeT1,T2, …Tn extends fromArray< T1 | T2 | … Tn > -readonly tuples are types that extend fromReadonlyArray. So areadonly tuple with elementsT1,T2, …Tn extends fromReadonlyArray< T1 | T2 | … Tn.

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.

ts
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.

ts
// lib.d.ts
typeReadonly<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.

ts
// 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.

ts
typeWritable<T> = {
-readonly [KinkeyofT]:T[K];
};
// { a: string, b: number }
typeA =Writable<{
readonlya:string;
readonlyb: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.

ts
leterr1:readonlySet<number>;// error!
leterr2:readonlyArray<boolean>;// error!
letokay:readonlyboolean[];// works fine

You cansee more details in the pull request.

const assertions

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 getreadonly properties
  • array literals becomereadonly tuples
ts
// Type '"hello"'
letx ="hello"asconst;
// Type 'readonly [10, 20]'
lety = [10,20]asconst;
// Type '{ readonly text: "hello" }'
letz = {text:"hello" }asconst;

Outside of.tsx files, the angle bracket assertion syntax can also be used.

ts
// Type '"hello"'
letx = <const>"hello";
// Type 'readonly [10, 20]'
lety = <const>[10,20];
// Type '{ readonly text: "hello" }'
letz = <const>{text:"hello" };

This feature means that types that would otherwise be used just to hint immutability to the compiler can often be omitted.

ts
// Works with no types referenced or declared.
// We only needed a single const assertion.
functiongetShapes() {
letresult = [
{kind:"circle",radius:100 },
{kind:"square",sideLength:50 },
]asconst;
returnresult;
}
for (constshapeofgetShapes()) {
// 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.

ts
exportconstColors = {
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.

ts
// Error! A 'const' assertion can only be applied
// to a string, number, boolean, array, or object literal.
leta = (Math.random() <0.5 ?0 :1)asconst;
letb = (60 *60 *1000)asconst;
// Works!
letc =Math.random() <0.5 ? (0asconst) : (1asconst);
letd =3_600_000asconst;

Another thing to keep in mind is thatconst contexts don’t immediately convert an expression to be fully immutable.

ts
letarr = [1,2,3,4];
letfoo = {
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

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.

ts
// in a global file:
varabc =100;
// Refers to 'abc' from above.
globalThis.abc =200;

Note that global variables declared withlet andconst don’t show up onglobalThis.

ts
letanswer =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.

The TypeScript docs are an open source project. Help us improve these pagesby sending a Pull Request

Contributors to this page:
DRDaniel Rosenwasser  (51)
OTOrta Therox  (14)
S�Shun 🎴  (1)
JBJack Bates  (1)
MUMasato Urai  (1)
4+

Last updated: Dec 16, 2025