|
| 1 | +There are easy ways to configure TypeScript to ensure faster compilations and editing experiences. |
| 2 | +The earlier on these practices are adopted, the better. |
| 3 | +Beyond best-practices, there are some common techniques for investigating slow compilations/editing experiences, some common fixes, and some common ways of helping the TypeScript team investigate the issues as a last resort. |
| 4 | + |
| 5 | +# Using Project References |
| 6 | + |
| 7 | +When building up any codebase of a non-trivial size with TypeScript, it is helpful to organize the codebase into several independent *projects*. |
| 8 | +Each project has its own `tsconfig.json` that has dependencies on other projects. |
| 9 | +This can be helpful to avoid loading too many files in a single compilation, and also makes certain codebase layout strategies easier to put together. |
| 10 | + |
| 11 | +There are some very basic ways of breaking up a codebase into projects. |
| 12 | +As an example, one might be a program with a project for the client, a project for the server, and a project that's shared between the two. |
| 13 | + |
| 14 | +``` |
| 15 | + ------------ |
| 16 | + | | |
| 17 | + | Shared | |
| 18 | + ^----------^ |
| 19 | + / \ |
| 20 | + / \ |
| 21 | +------------ ------------ |
| 22 | +| | | | |
| 23 | +| Client | | Server | |
| 24 | +-----^------ ------^----- |
| 25 | +``` |
| 26 | + |
| 27 | +Tests can also be broken into their own project. |
| 28 | + |
| 29 | +``` |
| 30 | + ------------ |
| 31 | + | | |
| 32 | + | Shared | |
| 33 | + ^-----^----^ |
| 34 | + / | \ |
| 35 | + / | \ |
| 36 | +------------ ------------ ------------ |
| 37 | +| | | Shared | | | |
| 38 | +| Client | | Tests | | Server | |
| 39 | +-----^------ ------------ ------^----- |
| 40 | + | | |
| 41 | + | | |
| 42 | +------------ ------------ |
| 43 | +| Client | | Server | |
| 44 | +| Tests | | Tests | |
| 45 | +------------ ------------ |
| 46 | +``` |
| 47 | + |
| 48 | +You can read up more about project references here. <!-- TODO --> |
| 49 | + |
| 50 | +# Configuring `tsconfig.json` |
| 51 | + |
| 52 | +Within a `tsconfig.json`, there are two ways to specify files in a project: |
| 53 | + |
| 54 | +* the `files` list |
| 55 | +* the `include` and `exclude` lists |
| 56 | + |
| 57 | +The primary difference between the two is that `files` expects a list of file paths to source files, and `include/`exclude` use globbing patterns to match against files. |
| 58 | + |
| 59 | +While specifying `files` will allow TypeScript to quickly load up files up directly, it can be cumbersome if you have many files in your project without just a few top-level entry-points. |
| 60 | +Additionally, it's easy to forget to add new files to your `tsconfig.json`, which means that you might end up with strange editor behavior where those new files are incorrectly analyzed. |
| 61 | +All this can be cumbersome. |
| 62 | + |
| 63 | +`include`/`exclude` help avoid needing to specify these files, but at a cost: files must be discovered by walking through included directories. |
| 64 | +When running through a *lot* of folders, this can slow compilations down. |
| 65 | +Additionally, sometimes a compilation will include lots of unnecessary `.d.ts` files and test files, which can increase compilation time and memory overhead. |
| 66 | +Finally, while `exclude` has some reasonable defaults, certain configurations like mono-repos mean that a "heavy" folders like `node_modules` can still end up being included. |
| 67 | + |
| 68 | +For best practices, we recommend the following: |
| 69 | + |
| 70 | +* Specify only source folders in your project. |
| 71 | +* Don't mix source files from other projects in the same folder. |
| 72 | +* If keeping tests in the same folder as other source files, give them a distinct name so they can easily be excluded. |
| 73 | +* Avoid large build artifacts and dependency folders like `node_modules` in source directories |
| 74 | + |
| 75 | +Here is a reasonable `tsconfig.json` that demonstrates this in action. |
| 76 | + |
| 77 | +```json5 |
| 78 | +{ |
| 79 | + "compilerOptions": { |
| 80 | + // ... |
| 81 | + }, |
| 82 | + "include": ["src"], |
| 83 | + "exclude": ["**/node_modules", "**/.*/"], |
| 84 | +} |
| 85 | +``` |
| 86 | + |
| 87 | +## Incremental Project Emit |
| 88 | + |
| 89 | +The `--incremental` flag allows TypeScript to save state from the last compilation to a `.tsbuildinfo` file. |
| 90 | +This file is used to figure out the smallest set of files that might to be re-checked/re-emitted since it last ran, much like how TypeScript's `--watch` mode works. |
| 91 | + |
| 92 | +Incremental compiles are enabled by default when using the `composite` flag for project references, but can bring the same speed-ups for any project that opts in. |
| 93 | + |
| 94 | +## Skipping `.d.ts` Checking |
| 95 | + |
| 96 | +By default, TypeScript performs a full re-check of all `.d.ts` files in a project to find issue and inconsistencies; however, this is typically unnecessary. |
| 97 | +Most of the time, the `.d.ts` files are known to already work - the way that types extend each other was already verified once, and declarations that matter will be checked anyway. |
| 98 | + |
| 99 | +TypeScript provides the option to skip type-checking of the `.d.ts` files that it ships with (e.g. `lib.d.ts`) using the `skipDefaultLibCheck` flag. |
| 100 | + |
| 101 | +Alternatively, you can also enable the `skipLibCheck` flag to skip checking *all* `.d.ts` files in a compilation. |
| 102 | + |
| 103 | +These two options can often hide misconfiguration and conflicts in `.d.ts` files, so we suggest using them sparingly. |
| 104 | + |
| 105 | +# Configuring Other Build Tools |
| 106 | + |
| 107 | +TypeScript compilation is often performed with other build tools in mind - especially when writing web apps that might involve a bundler. |
| 108 | +While we can only make suggestions for a few build tools, ideally these techniques can be generalized. |
| 109 | + |
| 110 | +## Concurrent Type-Checking |
| 111 | + |
| 112 | +Type-checking typically requires semantic information from other files, and can be relatively expensive compared to other steps like emitting. |
| 113 | +Because type-checking can take a little bit longer, it can impact the inner development loop - in other words, you might experience a longer edit/compile/retry cycle, and this might be frustrating. |
| 114 | + |
| 115 | +For this reason, some build tools can run type-checking in a concurrent process without blocking emit. |
| 116 | +While this means that invalid code can run before TypeScript reports an error in the tool, you'll often see errors in your editor first, and you won't be blocked for as long from running working code. |
| 117 | + |
| 118 | +An example of this in action is Webpack's `fork-ts-checker` plugin. |
| 119 | + |
| 120 | +## Isolated File Emit |
| 121 | + |
| 122 | +By default, TypeScript's emit requires semantic information that might not be local to a file. |
| 123 | +This is to understand how to emit features like `const enum`s and `namespace`s. |
| 124 | +But needing to check other files to emit of an arbitrary file can make emit slower. |
| 125 | + |
| 126 | +The need for features that need non-local information is somewhat rare - regular `enum`s can be used in place of `const enum`s, and modules can be used instead of `namespace`s. |
| 127 | +For that reason, TypeScript provides the `isolatedModules` flag to warn when using them. |
| 128 | +Using `isolatedModules` means that your codebase is safe for tools to use TypeScript APIs like `transpileModule` or alternative compilers like Babel. |
| 129 | + |
| 130 | +As an example of this in action, ts-loader provides |
| 131 | + |
| 132 | +* the `transpileOnly` flag to use `transpileModule`. |
| 133 | +* the `useBabel` flag |
| 134 | + |
| 135 | +<!-- TODO --> |
| 136 | + |
| 137 | +# Investigating Issues |
| 138 | + |
| 139 | +There are certain ways to get hints of what might be going wrong. |
| 140 | + |
| 141 | +## `extendedDiagnostics` |
| 142 | + |
| 143 | +You can run TypeScript with `--extendedDiagnostics` to get a printout of where the compiler is spending its time. |
| 144 | + |
| 145 | +``` |
| 146 | +Files: 6 |
| 147 | +Lines: 24906 |
| 148 | +Nodes: 112200 |
| 149 | +Identifiers: 41097 |
| 150 | +Symbols: 27972 |
| 151 | +Types: 8298 |
| 152 | +Memory used: 77984K |
| 153 | +Assignability cache size: 33123 |
| 154 | +Identity cache size: 2 |
| 155 | +Subtype cache size: 0 |
| 156 | +I/O Read time: 0.01s |
| 157 | +Parse time: 0.44s |
| 158 | +Program time: 0.45s |
| 159 | +Bind time: 0.21s |
| 160 | +Check time: 1.07s |
| 161 | +transformTime time: 0.01s |
| 162 | +commentTime time: 0.00s |
| 163 | +I/O Write time: 0.00s |
| 164 | +printTime time: 0.01s |
| 165 | +Emit time: 0.01s |
| 166 | +Total time: 1.75s |
| 167 | +``` |
| 168 | + |
| 169 | +The most relevant information for most users is: |
| 170 | + |
| 171 | +Field | Meaning |
| 172 | +--------|--------- |
| 173 | +`Files` | the number of files that the program is including (use `listFiles` to see what they are). |
| 174 | +`I/O Read time` | time spent reading from the file system - this includes traversing `include`'d folders. |
| 175 | +`Parse time` | time spent scanning and parsing the program |
| 176 | +`Program time` | combined time spent performing reading from the file system, scanning and parsing the program, and other calculation of the program graph. These steps are intermingled and combined here because files need to be resolved and loaded once they're included via `import`s and `export`s. |
| 177 | +`Bind time` | Time spent building up various semantic information that is local to a single file. |
| 178 | +`Check time` | Time spent type-checking the program. |
| 179 | +`transformTime time` | Time spent rewriting TypeScript ASTs (trees that represent source files) into forms that work in older runtimes. |
| 180 | +`commentTime` | Time spent calculating comments in output files. |
| 181 | +`I/O Write time` | Time spent writing/updating files on disk. |
| 182 | +`printTime` | Time spent calculating the string representation of an output file. |
| 183 | + |
| 184 | +<!-- TODO: is this important? Looks identical to printTime |
| 185 | +
|
| 186 | +`Emit time` | ... |
| 187 | +
|
| 188 | +--> |
| 189 | + |
| 190 | +## `showConfig` |
| 191 | + |
| 192 | +It's not always obvious what settings a compilation is being run with when running `tsc`, especially given that `tsconfig.json`s can extend other configuration files. |
| 193 | +`showConfig` can explain what `tsc` will calculate for an invocation. |
| 194 | + |
| 195 | +```sh |
| 196 | +tsc --showConfig |
| 197 | + |
| 198 | +# or to select a specific config file... |
| 199 | + |
| 200 | +tsc --showConfig -p tsconfig.json |
| 201 | +``` |
| 202 | + |
| 203 | +## `traceResolution` |
| 204 | + |
| 205 | +Running with `traceResolution` can help explain *why* a file was included in a compilation. |
| 206 | +The emit is somewhat verbose, so you might want to redirect output to a file. |
| 207 | + |
| 208 | +```sh |
| 209 | +tsc -p tsconfig.json > resolution.txt |
| 210 | +``` |
| 211 | + |
| 212 | +## Running `tsc` Alone |
| 213 | + |
| 214 | +Much of the time, users run into slow performance using 3rd party build tools like Gulp, Rollup, Webpack, etc. |
| 215 | +Running with `tsc --extendedDiagnostics` to find major discrepancies between using TypeScript and the tool can indicate external misconfiguration or inefficiencies. |
| 216 | + |
| 217 | +Some questions to keep in mind: |
| 218 | + |
| 219 | +* Is there a major difference in build times between `tsc` and the build tool with TypeScript integration? |
| 220 | +* If the build tool provides diagnostics, is there a difference between TypeScript's resolution and the build tool's? |
| 221 | +* Does the build tool have *its own configuration* that could be the cause? |
| 222 | +* Does the build tool have configuration *for its TypeScript integration* that could be the cause? (e.g. options for ts-loader?) |
| 223 | + |
| 224 | +## Disabling Editor Plugins |
| 225 | + |
| 226 | +Editor experiences can be impacted by plugins. |
| 227 | +Try disabling plugins (namely, JavaScript/TypeScript-related plugins) to see if that fixes any issues in performance and responsiveness. |
| 228 | + |
| 229 | +Certain editors also have their own troubleshooting guides for performance, so consider reading up on them. |
| 230 | +For example, Visual Studio Code has its own page for [Performance Issues](https://github.com/microsoft/vscode/wiki/Performance-Issues) as well. |
| 231 | + |
| 232 | +## Upgrading Dependencies |
| 233 | + |
| 234 | +Sometimes TypeScript's type-checking can be impacted by computationally intensive `.d.ts` files. |
| 235 | +This is rare, but can happen. |
| 236 | +Upgrading to a newer version of TypeScript (which can be more efficient) or to a newer version of an `@types` package (which may have reverted a regression) can often solve the issue. |
| 237 | + |
| 238 | +# Common Issues |
| 239 | + |
| 240 | +Once you've trouble-shooted, you might want to explore some fixes to common issues. |
| 241 | +If the following solutions don't work, it may be worth [filing an issue](https://github.com/microsoft/TypeScript/issues/new/choose). |
| 242 | + |
| 243 | +## Misconfigured `include` and `exclude` |
| 244 | + |
| 245 | +As mentioned above, the `include`/`exclude` options can be misused in several ways. |
| 246 | + |
| 247 | +Problem | Cause | Fix |
| 248 | +-----------------|--------|------- |
| 249 | +`node_modules` was accidentally included from deeper folder | *`exclude` was not set* | `"exclude": ["**/node_modules", "**/.*/"]` |
| 250 | +`node_modules` was accidentally included from deeper folder | `"exclude": ["node_modules"]` | `"exclude": ["**/node_modules", "**/.*/"]` |
| 251 | +Hidden dot files (e.g. `.git`) were accidentally included | `"exclude": ["**/node_modules"]` | `"exclude": ["**/node_modules", "**/.*/"]` |
| 252 | +Unexpected files are being included. | *`include` was not set* | `"include": ["src"]` |
| 253 | + |
| 254 | +<!-- |
| 255 | +
|
| 256 | +# Explicit File Lists |
| 257 | +
|
| 258 | +Sometimes a `tsconfig.json` can be found by TypeScript, but the command line experience doesn't appear to reflect it. |
| 259 | +This is often because users specify explicit files to `tsc`, which will stop TypeScript from seeking out a tsconfig. |
| 260 | +
|
| 261 | +```sh |
| 262 | +# doesn't look for a tsconfig.json! |
| 263 | +tsc main.ts helper.ts |
| 264 | +``` |
| 265 | +
|
| 266 | +As a result, users often run into using the default options with warnings like |
| 267 | +
|
| 268 | +``` |
| 269 | +Cannot find module '{0}'. |
| 270 | +Accessors are only available when targeting ECMAScript 5 and higher. |
| 271 | +``` |
| 272 | +
|
| 273 | +--> |
| 274 | + |
| 275 | +# Filing an Issue |
| 276 | + |
| 277 | +The best reports of performance issues contain *easily obtainable* and *minimal* reproductions of the problem. |
| 278 | +In other words, a codebase that can easily be cloned over git that contains only a few files. |
| 279 | +They require either no external integration with build tools - they can either be invoked via `tsc` or use isolated code which consumes the TypeScript API. |
| 280 | +Codebases that require complex invocations and setups cannot be prioritized. |
| 281 | + |
| 282 | +We understand that this is not always easy to achieve - specifically, because it is hard to isolate the source of a problem within a codebase, and because sharing intellectual property may be an issue. |
| 283 | +In some cases, the team will be willing to send a non-disclosure agreement if we believe the issue is highly impactful. |
| 284 | + |
| 285 | +Aside from that, here are some other things a performance issue report should provide. |
| 286 | + |
| 287 | +* `extendedDiagnostics` output |
| 288 | +* |
| 289 | + |
| 290 | +<!-- TODO --> |
0 commit comments