Hi everyone! Today I want to talk about fast TS compilation. There are a lot of tips and tricks about TypeScript code style. But not about compiler performance. I will tell you about the five most efficient ways (in my opinion) to improve the compilation speed!
Type Annotations, such as parameters, variables and return types help the compiler to work faster. Named types are more compact than anonymous ones. They decrease the amount of work compiler needs to read and write declaration files.
Do NOT:
import { baz } from "baz";
export function foo() {
return baz();
}
DO:
import { baz, BazType } from "baz";
export function foo(): BazType {
return baz();
}
Intersections merge all properties and may produce never
type for some of them.
Interfaces are single flat object types that detects conflicts between properties. Relationships between interfaces are also cached. Every added part of interface is checked against target interface before checking against the result interface. That's why interfaces are faster!
Do NOT:
type Foo = Bar & Baz & {
prop: string;
}
DO:
interface Foo extends Bar, Baz {
prop: string;
}
Every passed argument must be compared to every union value. Especially with big (10+) unions. For elements elimination they need to be compared in pairs, that leads to quadratic complexity. That's why types are better choice.
Do NOT:
interface Cats {
colour: "Black" | "White";
}
interface Dogs {
colour: "Brown" | "White";
size: number;
}
declare function getAnimal(animal: Cats | Dogs);
DO:
interface Animals {
colour: "Brown" | "Black" | "White";
}
interface Cats extends Animals {
colour: "Black" | "White";
}
interface Dogs extends Animals {
colour: "Brown" | "White";
size: number;
}
declare function getAnimal(animal: Animal);
TypeScript need to recompile conditional type every time when complex type is called. Also any two instances of a type that use them requires recompiling the structure of it.
Do NOT:
interface Foo<T> {
foo<U>(x: U):
U extends Type1<T> ? Type2<U, T> :
U;
}
DO:
type FooResult<U, T> =
U extends Type1<T> ? Type2<U, T> :
U;
interface Foo<T> {
foo<U>(x: U): FooResult<U, T>;
}
Return type can be extracted to a type alias and can be cached by the compiler
tsconfig.json
--incremental
- this flag allows TypeScript to save the state from the last compilation to a .tsbuildinfo
file. This is used to recompile only the smallest amount of files since last compilation. This is enabled by default with composite
flag for project references.--skipDefaultLibCheck/skipLibCheck
- this flag give an option to skip type check for .d.ts
files. Without the flag TypeScript will do a full check of all .d.ts
files in your project. But mostly they have been already verified. If you want to increase the performance even more, you can enable skipLibCheck
to skip all .d.ts
files.--strictFunctionTypes
- this flag allows to reduce assignability check between types. For example if we know that Dog
extends Animal
we can skip such checking for List<Dog>
and List<Animals>
. This flag is enabled by default with --strict
.
You can read more about TS performance here - https://github.com/microsoft/TypeScript/wiki/Performance
P.S. Thanks for reading!
More articles about frontend development: