Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Investigating long TypeScript compile times

I'm investigating why the compilation time for my Angular 2.0 TypeScript project went from around 4 seconds to around 15 seconds in a relatively short time.

I came across the very useful, but seemingly undocumented --diagnostics switch.

For example, here's what I get when running tsc --noEmit --diagnostics on my project now:

Files:             231
Lines:           50872
Nodes:          170067
Identifiers:     65994
Symbols:       7712123
Types:          407677
Memory used:   600554K
I/O read:        0.43s
I/O write:       0.00s
Parse time:      1.13s
Bind time:       0.34s
Check time:     10.17s
Emit time:       0.00s
Total time:     11.64s

Here's what I get when I run the same command on an earlier version of the project.

Files:             197
Lines:           30882
Nodes:          124208
Identifiers:     46201
Symbols:       5856945
Types:           10989
Memory used:    80412K
I/O read:        0.03s
I/O write:       0.00s
Parse time:      0.60s
Bind time:       0.27s
Check time:      0.93s
Emit time:       0.00s
Total time:      1.79s

The number of Types has gone way up, and so has the Check time.

Is it possible to get more detailed/verbose output from --diagnostics?

NodeJS v4.4.3, TypeScript v1.8.10. This is my tsconfig.json

{
  "compilerOptions": {
    "target": "es5",
    "module": "system",
    "moduleResolution": "node",

    "noImplicitAny": false,
    "noEmitOnError": false,

    "experimentalDecorators": true,

    "emitDecoratorMetadata": true,
    "removeComments": false
  },
  "exclude": [
    "node_modules",
    "wwwroot",
    "typings/main.d.ts",
    "typings/main"
  ]
}
like image 779
Ronald Zarīts Avatar asked Apr 14 '16 13:04

Ronald Zarīts


People also ask

How long does TypeScript take to compile?

Using the built-in TypeScript compiler (tsc), it takes an average of 13.37 seconds to compile the project.

Does TypeScript improve performance?

Situations Where TypeScript Takes the Cake At the time of deployment, TypeScript can point out the compilation errors, thereby minimizing the errors during run-time. On the other hand, JavaScript is an interpreted language and can point out the errors only during the run-time.


3 Answers

Seems I have found the culprit in my case. I did it the hard way; my process:

  1. Find the commit that made the compilation slow. Go through the history commit-by-commit and check the compile times.
  2. Comment out the changed code until the offending lines are found.

Before the offending commit, I consistently got compile times of around 2-4 seconds, after the commit - 13-17 seconds.

In my case, I have a class, with a accessTokenGetter field, which was initialized in the constructor:

export class JwtConfig {
    //...
    accessTokenGetter: () => Observable<string>;
    //...
      constructor(private config?: IJwtConfig) {
          // ...
          this.accessTokenGetter = this.config.accessTokenGetter || (() => Observable.of(null));
      }
}

The second part of the initialization || (() => Observable.of(null)); was causing the slowness. Commenting it out or adding a type annotation got the compile time back down. Since Observable is generic, it seems like the TypeScript compiler needs a hint to narrow down some type checks it needs to do. My initialization now reads as:

//...
this.accessTokenGetter = this.config.accessTokenGetter || (() => Observable.of(<string>null));
//...

Observable.of(null as string)) also seems to do the job. There were a couple of other places where adding a type annotation sped compilation up.

Hope this helps someone.

Still, if there's a facility in the compiler to the answer faster - I'd be happy to hear it.

like image 69
Ronald Zarīts Avatar answered Oct 06 '22 21:10

Ronald Zarīts


I could speed up the compile process from 15 sec. to 6-7 sec. by changing this single line of code:

// slow:
// ...
.flatMap((receivedObj: MyType) => {
    let nextObservable: Observable<MySecondType> = this.dependingPut(receivedObj);
    return nextObservable || new Observable((observer) => {
            observer.next(undefined);
        });
});


// fast:
.flatMap((receivedObj: MyType) => {
    let nextObservable: Observable<MySecondType> = this.dependingPut(receivedObj);  
    return nextObservable || new Observable<MySecondType>((observer) => { // <--- use the generics!
            observer.next(undefined);
        });
});

From the Typescript handbook (https://www.typescriptlang.org/docs/handbook/generics.html):

function identity<T>(arg: T): T {
    return arg;
}

// ...

let output = identity("myString");  // type of output will be 'string'

"Notice that we didn’t have to explicitly pass the type in the angle brackets (<>), the compiler just looked at the value "myString", and set T to its type. While type argument inference can be a helpful tool to keep code shorter and more readable, you may need to explicitly pass in the type arguments as we did in the previous example when the compiler fails to infer the type, as may happen in more complex examples."

In my case it didn't fail; the type inference just took a very long time (by the way, it consumed plenty of memory as well). Before you start to build workarounds, go back in your revision history and try to identify the bad revision. To be sure that the compiler is the culprit, use the --diagnostics option. If the returned statistics returns a high "Check time" value, then inspect your code for missing types.

like image 27
KGolbang Avatar answered Oct 06 '22 21:10

KGolbang


For me, slowdowns were due to imports like import "./file.ts";. Removing the .ts extension makes things 90% faster: import "./file";

like image 1
Gavin Haynes Avatar answered Oct 06 '22 22:10

Gavin Haynes