
There are easy ways to configure TypeScript to speed up compilation and editing. And the sooner they are implemented, the better. There are also some popular approaches to investigating the causes of slow compilation and editing, some fixes and common ways to help the TypeScript team troubleshoot problems.
1. Writing easy-to-compile code
1.1. Preferring interfaces over intersections (intersection)
More often than not, a simple alias for an object type acts in the same way as an interface.
interface Foo { prop: string }
type Bar = { prop: string };
But if you need a combination of two or more types, you can either extend them using an interface, or create an intersection of types within an alias. The difference between these approaches matters.
Interfaces create a single flatten object type that exposes property conflicts that are usually important to resolve! And intersections just recursively combine properties, and in some cases generate
never
. Also, interfaces are rendered better, while type aliases to intersections cannot be displayed in other intersections. Type relationships between interfaces are cached, unlike intersection types. The final important difference is that when validating against the target intersection type, each component is validated before being validated / flattened.
Therefore, it is recommended to extend types with
interface
/
extends
rather than create type intersections.
- type Foo = Bar & Baz & {
- someProp: string;
- }
+ interface Foo extends Bar, Baz {
+ someProp: string;
+ }
1.2. Using type annotations
Adding type annotations, especially for return values, can save the compiler a lot of work. This is partly because named types are usually more compact than anonymous types (which the compiler can cast), which reduces the time it takes to read and write declaration files (for example, for incremental builds). Casting is very convenient, so there is no need to do it universally. But it can be useful to try when you find slow fragments in your code.
- import { otherFunc } from "other";
+ import { otherFunc, otherType } from "other";
- export function func() {
+ export function func(): otherType {
return otherFunc();
}
1.3. Preference for base types over multiple types
Multiple types are a great tool: they allow you to express the range of possible values for a type.
interface WeekdaySchedule {
day: "Monday" | "Tuesday" | "Wednesday" | "Thursday" | "Friday";
wake: Time;
startWork: Time;
endWork: Time;
sleep: Time;
}
interface WeekendSchedule {
day: "Saturday" | "Sunday";
wake: Time;
familyMeal: Time;
sleep: Time;
}
declare function printSchedule(schedule: WeekdaySchedule | WeekendSchedule);
But everything has a price. Each time an argument is passed to
printSchedule
it, it must be compared with each element of the set. This is not a problem for two elements. But if there are a dozen elements in the set, it can slow down the compilation speed. For example, to remove redundant elements, they all need to be compared in pairs, this is a quadratic function. Such checks can occur when intersecting large sets, when the intersection for each element of the set can lead to the appearance of huge types that need to be reduced. And all this can be avoided by using subtypes rather than sets.
interface Schedule {
day: "Monday" | "Tuesday" | "Wednesday" | "Thursday" | "Friday" | "Saturday" | "Sunday";
wake: Time;
sleep: Time;
}
interface WeekdaySchedule extends Schedule {
day: "Monday" | "Tuesday" | "Wednesday" | "Thursday" | "Friday";
startWork: Time;
endWork: Time;
}
interface WeekendSchedule extends Schedule {
day: "Saturday" | "Sunday";
familyMeal: Time;
}
declare function printSchedule(schedule: Schedule);
A more realistic example is when trying to model all the built-in DOM element types. In this case, it is preferable to create a base type
HtmlElement
with frequent elements, which expands with
DivElement
,
ImgElement
etc., rather than creating a heavy set
DivElement | /*...*/ | ImgElement | /*...*/
.
2. Using project links
When building any large TypeScript codebase, it's useful to organize it into multiple independent projects . Each of them has its own
tsconfig.json
with dependencies in other projects. This can help avoid downloading too many files at one compilation, and it can also make it easier to mix and match different codebase schemes.
There are some very simple ways to split your codebase into projects . For example, a project for a client, a project for a server, and a common project between them.
------------ | | | Shared | ^----------^ / \ / \ ------------ ------------ | | | | | Client | | Server | -----^------ ------^-----
Tests can also be separated into a separate project.
------------ | | | Shared | ^-----^----^ / | \ / | \ ------------ ------------ ------------ | | | Shared | | | | Client | | Tests | | Server | -----^------ ------------ ------^----- | | | | ------------ ------------ | Client | | Server | | Tests | | Tests | ------------ ------------
People often ask: "How big should the project be?" It's like asking, "How big should a function be?" or "How big should the class be?" Much depends on experience. Let's say you can share JS / TS code using folders, and if some components are interconnected enough to be put in one folder, then you can consider them belonging to the same project. Also, avoid large or small projects. If one of them is larger than all the others combined, then this is a bad sign. It is also best not to create dozens of single-file projects because this increases overhead.
You can read about cross-project links here .
3. Configuring tsconfig.json or jsconfig.json
TypeScript and JavaScript users can always customize their compilations using the tsconfig.json file. Jsconfig.json files can also be used to customize JavaScript editing .
3.1. Defining files
Always make sure your config files don't list too many files at once.
You
tsconfig.json
can define project files in two ways:
- list
files
; - lists
include
andexclude
;
The main difference between the two is that it
files
gets a list of source file paths, and
include
/
exclude
uses globbing patterns to identify the corresponding files.
By defining
files
, we allow TypeScript to quickly load files directly. This can be cumbersome if the project has many files and only a few high-level input points. It is also easy to forget to add new files to
tsconfig.json
, and then you run into strange editor behavior.
include
/
exclude
does not require all of these files to be identified, but the system should detect them by going through the added directories. And if there are many of them , compilation may slow down. In addition, sometimes the compilation includes numerous unnecessary
.d.ts
and test files, which can also reduce speed and increase memory consumption. Finally, while
exclude
there are suitable defaults, some configurations, like mono-repositories, have "heavy" folders like
node_modules
that will be added at compile time.
It's best to do this:
- Define only the input folders of your project (for example, the source code from which you want to add during compilation and analysis).
- Do not mix source files from different projects in the same folder.
- If you store tests in the same source folder, name them so that they can be easily excluded.
- Avoid creating large assembly artifacts and dependency folders like
node_modules
.
Note: Without a list, the
exclude
folder
node_modules
will be excluded by default. And if the list is added, it is important to explicitly indicate in it
node_modules
.
Here's an example
tsconfig.json
:
{
"compilerOptions": {
// ...
},
"include": ["src"],
"exclude": ["**/node_modules", "**/.*/"],
}
3.2. Control over adding @types
By default, TypeScript automatically adds all
node_modules
packages found in a folder
@types
, whether you imported them or not. This is to make certain functions “just work” when using Node.js, Jasmine, Mocha, Chai, etc., since these tools / packages are not imported, but loaded into the global environment.
Sometimes this logic can slow down compilation and editing of the program. And even lead to declaration conflicts in numerous global packages that cause errors like this:
Duplicate identifier 'IteratorResult'.
Duplicate identifier 'it'.
Duplicate identifier 'define'.
Duplicate identifier 'require'.
If global packages are not needed, you can define an empty folder in the "types" option in
tsconfig.json
/
jsconfig.json
:
// src/tsconfig.json
{
"compilerOptions": {
// ...
// Don't automatically include anything.
// Only include `@types` packages that we need to import.
"types" : []
},
"files": ["foo.ts"]
}
If you need global packages, add them to the field
types
.
// tests/tsconfig.json
{
"compilerOptions": {
// ...
// Only include `@types/node` and `@types/mocha`.
"types" : ["node", "mocha"]
},
"files": ["foo.test.ts"]
}
3.3. Incremental project generation
The flag
--incremental
allows TypeScript to save the last compiled state to a file
.tsbuildinfo
. It is used to define the minimum set of files that can be rechecked / overwritten since the last run, like the mode
--watch
in TypeScript.
Incremental generation is enabled by default when using the
composite
cross-project reference flag , but it can speed up any other project as well.
3.4. Skipping validation .d.ts
By default, TypeScript will fully re-check all
.d.ts
files in a project to find problems and inconsistencies. But this is usually not needed. More often than not, we already know that these files work: the methods for expanding types have already been checked, but important declarations will still be checked.
TypeScript allows a flag to
skipDefaultLibCheck
skip type checking in supplied
.d.ts
files (for example, in
lib.d.ts
).
You can also enable the flag
skipLibCheck
to skip checking all
.d.ts
files in compilation.
These two options often hide configuration errors and
.d.ts
file conflicts , so it is recommended to use them only to speed up the build.
3.5. Faster variadic checks
List of dogs or animals? Can you lead
List<Dg>
to
List<Animls>
? An easy way to find the answer is through a structural type comparison, element by element. Unfortunately, this solution can be very expensive. But if we know enough about
List<>
, then we can reduce assignment checks to the definition of whether it is permissible to refer
Dog
to
Animal
(that is, without checking each element
List<>
). In particular, we need to know the variability of the parameter type
T
. The compiler can only take full advantage of the optimization if the flag is on
strictFunctionTypes
(otherwise it will use a slower but more lenient structural check). Therefore, it is recommended to build with the flag
--strictFunctionTypes
(which is enabled by default under
--strict
).
4. Setting up other assembly tools
TypeScript is often compiled with other building tools, especially when building a web application that can use bundler. We can only offer a few ideas, but in general this approach can be generalized.
In addition to this part, be sure to read about the performance of your chosen tool, for example:
- The ts-loader part in the Faster Builds article .
- The awesome-typescript-loader part in the Performance Issues article .
4.1. Simultaneous type checking
Type checking usually requires information from other files and is relatively expensive compared to other steps like converting / writing code. Since type checking can be quite time consuming, it can affect the internal development cycle. That is, the edit-compile-run cycle will become longer, which is unpleasant.
Therefore, many build tools can check types in a separate process, without blocking file creation. Although in this case, erroneous code may be run before TypeScript reports the error in your build tool. More often than not, you will first see errors in the editor and will not wait for the code to run.
An example is the fork-ts-checker-webpack-plugin for Webpack, or similarawesome-typescript-loader .
4.2. Isolated file creation
By default, creating files in TypeScript requires semantic information that may not be local to the file. This is necessary to understand how features like
const enum
and are generated
namespace
. But sometimes the generation becomes slower due to the need to check other files to generate the result for an arbitrary file.
We rarely need features that require non-local information. Regulars
enum
can be used instead
const enum
, and modules can be used instead
namespace
. Therefore, TypeScript has a flag
isolatedModules
for throwing errors on features that require non-local information. With this flag, you can safely use tools that use TypeScript APIs like
transpileModule
or alternative compilers like Babel.
This code will not work correctly at runtime with sandboxed file conversion because the values must be inlined
const enum
. Fortunately, he will
isolatedModules
warn us in advance.
// ./src/fileA.ts
export declare const enum E {
A = 0,
B = 1,
}
// ./src/fileB.ts
import { E } from "./fileA";
console.log(E.A);
// ~
// error: Cannot access ambient const enums when the '--isolatedModules' flag is provided.
Remember:
isolatedModules
does not automatically speed up code generation. It only warns about using a feature that may not be supported. You need to generate modules in isolation in different build tools and APIs.
You can create files in isolation using the following tools:
- The ts-loader has a flag transpileOnly , which provides isolated create files using
transpileModule
. - awesome-typescript-loader transpileOnly,
transpileModule
. - API transpileModule TypeScript .
- awesome-typescript-loader useBabel.
- babel-loader ( ).
- gulp-typescript
isolatedModules
. - rollup-plugin-typescript .
- ts-jest [
isolatedModules
true
]. - ts-node can define the "transpileOnly" option in the "ts-node" field of the tsconfig.json file and also has the --transpile-only flag .
5. Problem investigation
There are different ways to figure out why something is going wrong.
5.1. Disable editor plugins
Plugins can affect how the editor works. Try disabling them (especially JavaScript / TypeScript related ones) and see if performance and responsiveness improves.
Some editors have their own recommendations for improving performance, read them. For example, Visual Studio Code has a separate tips page .
5.2. extendedDiagnostics
You can run TypeScript with
--extendedDiagnostics
to see where the compiler's time is spent:
Files: 6
Lines: 24906
Nodes: 112200
Identifiers: 41097
Symbols: 27972
Types: 8298
Memory used: 77984K
Assignability cache size: 33123
Identity cache size: 2
Subtype cache size: 0
I/O Read time: 0.01s
Parse time: 0.44s
Program time: 0.45s
Bind time: 0.21s
Check time: 1.07s
transformTime time: 0.01s
commentTime time: 0.00s
I/O Write time: 0.00s
printTime time: 0.01s
Emit time: 0.01s
Total time: 1.75s
Please note that
Total time
it will not be the sum of all the listed time costs, since some of them overlap, and some are not measured at all.
The most relevant information for most users:
Field | Value |
Files
|
The number of files included in the program (what kind of files you can see using --listFiles
). |
I/O Read time
|
Time spent reading from the file system. This includes reading folders from include
. |
Parse time
|
Time spent scanning and parsing the program. |
Program time
|
The total time for reading from the file system, scanning and parsing the program, as well as other graph calculations. These stages are combined here because they need to be enabled and loaded as soon as they are added via import
and export
. |
Bind time
|
Time spent assembling various semantic information that is local to a specific file. |
Check time
|
The time spent checking the types in the program. |
transformTime time
|
Time spent rewriting TypeScript ASTs (trees representing source files) into forms that work in legacy runtime environments. |
commentTime
|
Time spent evaluating comments in generated files. |
I/O Write time
|
Time spent writing and updating files on disk. |
printTime
|
The time taken to compute the string representation of the generated file and save it to disk. |
Given these inputs, what you might need:
- Does the number of files / lines of code approximately correspond to the number of files in the project? If not, try using
--listFiles
. - Do values
Program time
orI/O Read time
look big? Check if the settings are correctinclude
/exclude
Looks like there is something wrong with other timings? You can fill out a problem report! What will help you diagnose:
- Start with
emitDeclarationOnly
if the value isprintTime
high. - Compiler Performance Issue Reporting Instructions
5.3. showConfig
It is not always clear with what settings the compilation is performed at startup
tsc
, especially considering that
tsconfig.jsons
other configuration files can be extended.
showConfig
can explain what it will calculate
tsc
.
tsc --showConfig # or to select a specific config file... tsc --showConfig -p tsconfig.json
5.4. traceResolution
Running with
traceResolution
will tell you why the file was added to the compilation. The data is quite extensive, so you can save the result to a file:
tsc --traceResolution > resolution.txt
If you find a file that should not be there, you can correct the list
include
/
exclude
in the file
tsconfig.json
, or adjust the settings like
types
,
typeRoots
or
paths
.
5.5. Running one tsc
Users often experience poor performance with third-party build tools like Gulp, Rollup, Webpack, etc. Running
tsc --extendedDiagnostics
to find major discrepancies between TypeScript and a third-party tool can indicate errors in external configurations or inefficiency.
What you need to ask yourself:
- Is there a big difference in build times
tsc
with the TypeScript integrated tool? - If the third party tool has diagnostic tools, is the solution different between TypeScript and the third party tool?
- Does the tool have its own configuration that might be causing poor performance?
- Does the tool have a configuration to integrate it with TypeScript that might be causing poor performance (like options for ts-loader)?
5.6. Updating dependencies
Sometimes computationally complex files can affect type checking in TypeScript
.d.ts
. Rarely, but it happens. This is usually solved by updating to a newer version of TypeScript (more efficiently) or a newer version of the package
@types
(which could reverse the regression).
6. Frequent problems
When faced with difficulties, you will want to learn about solutions to common problems. If the following does not help, you can report the problem .
6.1. Incorrectly configured include and exclude
As mentioned, the
include
/ options
exclude
can be misused.
Problem | Cause | Correction |
node_modules
was accidentally added from a deeper subfolder. |
Was not configured exclude
|
"exclude": ["**/node_modules", "**/.*/"]
|
node_modules
was accidentally added from a deeper subfolder. |
"exclude": ["node_modules"]
|
"exclude": ["**/node_modules", "**/.*/"]
|
Hidden files with a dot are randomly added (for example .git
). |
"exclude": ["**/node_modules"]
|
"exclude": ["**/node_modules", "**/.*/"]
|
Added unexpected files. | Was not configured include
|
"include": ["src"]
|
7. Completing Problem Reports
If your project is already correctly and optimally configured, then you can fill out a problem report .
A good report contains an easy-to-follow description of the problem. That is, it contains a codebase of several files that can be easily cloned via Git. In this case, there is no need for external integration with assembly tools, they can be called through
tsc
, or you can use isolated code that uses the TypeScript API. You cannot prioritize codebases that require complex calls and settings.
Yes, this is not always easy to achieve. Especially because it is difficult to isolate the source of the problem within the codebase, and there are also intellectual property protection considerations. In some cases, you can send us an NDA if you think that the problem is of high importance.
Regardless of the reproducibility of the problem, when completing the report, follow these tips, they will help us in finding a solution.
7.1. Compiler Performance Issues Report
Occasionally, performance issues occur during both build and edit. Then it makes sense to focus on the TypeScript compiler.
First, use the "nightly" version of TypeScript to make sure you're not running into a fixed issue:
npm install --save-dev typescript@next # or yarn add typescript@next --dev
The description of the performance issue should include:
- Installed version of TypeScript (
npx tsc -v
oryarn tsc -v
). - The version of Node that TypeScript (
node -v
) was running on . - Result of running with option
extendedDiagnostics
(tsc --extendedDiagnostics -p tsconfig.json
). - Ideally, the project itself is needed to demonstrate the problem at hand.
- Compiler profiler log (files
isolate-*-*-*.log
and*.cpuprofile
).
Compiler profiling
It is important to provide us with a diagnostic trace by running Node.js v10 + with the flag
--trace-ic
and TypeScript with the flag
--generateCpuProfile
:
node --trace-ic ./node_modules/typescript/lib/tsc.js --generateCpuProfile profile.cpuprofile -p tsconfig.json
This
./node_modules/typescript/lib/tsc.js
can be replaced by whatever path your version of the TypeScript compiler is installed in. And instead it
tsconfig.json
can be any TypeScript config file. Instead
profile.cpuprofile
- the output file of your choice.
Two files will be generated:
--trace-ic
will save the data to a view fileisolate-*-*-*.log
(for exampleisolate-00000176DB2DF130-17676-v8.log
).--generateCpuProfile
will save the data to a file with a name of your choice. In the example above, this isprofile.cpuprofile
.
Note : These files may contain information from your workspace, including paths and source code. Both files are created in plain text, and you can edit them before attaching them to the Github report (for example, by clearing away paths that could reveal sensitive information).
But if you have any doubts about putting them on Github, write to us, and you can send the information in private.
7.2. Report Editor Performance Issues
There are many reasons for poor editing performance. And the TypeScript team can only affect the performance of the JavaScript / TypeScript language service, as well as the integration between the language service and certain editors (for example, Visual Studio, Visual Studio Code, Visual Studio for Mac, and Sublime Text). Make sure all third party plugins are turned off in your editor. This will verify that the issue is related to TypeScript itself.
The editor performance issues are a little more complex, but the same ideas apply: cloned minimal codebases with a reproducible problem are ideal. And in some cases, we can sign an NDA to investigate and isolate issues.
Adding data from
tsc --extendedDiagnostics
, but even better if there is a TSServer trace.
Getting TSServer logs in Visual Studio Code
- Open the command bar, then:
- Set the option
«typescript.tsserver.log»: «verbose»,
. - Restart VS Code and reproduce the issue.
- In VS Code, run the command
TypeScript: Open TS Server log
. - The file should open
tsserver.log
.
Note : The TSServer log may contain information from your workspace, including paths and source code. If you have any doubts about putting it on Github, write to us and you can send the information in private.