How to Improve TypeScript Compilation and Build Times with tsconfig.json Optimizations

Photo by Andres Siimon on UnsplashTypeScript’s compilation time can become a bottleneck in large-scale projects, leading to slower development cycles and longer CI/CD pipelines. As projects grow, the complexity of type-checking, module resolution, and …


This content originally appeared on Level Up Coding - Medium and was authored by Robin Viktorsson

Photo by Andres Siimon on Unsplash

TypeScript’s compilation time can become a bottleneck in large-scale projects, leading to slower development cycles and longer CI/CD pipelines. As projects grow, the complexity of type-checking, module resolution, and incremental builds increases, making it essential to optimize TypeScript’s configuration for better performance.

By fine-tuning tsconfig.json, developers can significantly reduce compilation time without sacrificing type safety and code quality. This article explores various strategies to optimize TypeScript's compilation process, from enabling incremental builds to adjusting module resolution and leveraging project references. Whether you're working on a small project or a monorepo with multiple packages, these optimizations will help improve efficiency and streamline your development workflow.

Sit back, grab some popcorn 🍿, and enjoy the read!

Understanding the TypeScript Compilation Process 💡

Before we start to optimize, it's crucial to understand how TypeScript compiles your code. The process consists of three main phases: Parsing, Type Checking, and Emission. Each phase impacts performance differently, depending on factors like codebase size, type complexity, and module structure.

Parsing

The parsing phase is the first step in the compilation process. During this phase, TypeScript reads your source code and converts it into a data structure known as an Abstract Syntax Tree (AST). The AST is a hierarchical representation of the structure of your code, where each node in the tree corresponds to a construct in your code, such as a variable, function, or expression.

The parsing phase can become slow when working with large codebases or complex code structures. If you have large files with many classes, functions, or complex expressions, parsing these files can take longer. The number of files being parsed also plays a crucial role — more files mean more time spent parsing.

Performance Considerations:

  • Large files or deeply nested code can slow down parsing.
  • Syntax errors or complex patterns (e.g., generics, decorators) can increase parsing time.

Type Checking

Once TypeScript has parsed the code and created the AST, the type-checking phase begins. During this phase, TypeScript verifies that your code adheres to the types defined in your codebase. It checks for type errors, infers types for variables and expressions, and ensures that operations on variables are valid according to their types.

TypeScript’s static type checking helps catch errors early in the development process, but it can also be computationally expensive, especially in large applications with complex types.

The type-checking phase is the most computationally expensive part of the compilation process, especially in projects with complex type definitions, many interfaces, or deep generics. The more types TypeScript needs to check, the longer this phase will take.

Performance Considerations:

  • A large number of types or interfaces can slow down type checking.
  • Complex type definitions, especially when using generics or third-party libraries with complex types, can add overhead.
  • Type inference for large codebases (e.g., inferring types for function return values) can consume significant time and resources.
  • Circular type dependencies or deeply nested types can create bottlenecks during type checking.

Emission

The emission phase is the final step of the compilation process, where TypeScript generates the output files. This includes JavaScript files (which will be executed in the browser or Node.js environment) and, optionally, declaration files (.d.ts).

For projects using modules, TypeScript also handles module resolution and generates the corresponding output for each module. If your tsconfig.json is configured to use ES modules or CommonJS, this phase will also involve the conversion of module imports and exports.

The emission phase typically doesn’t take as long as parsing and type-checking. However, it can still introduce delays, especially if TypeScript has to emit a large number of files or if there is a complex module structure. The complexity of the target output (e.g., source maps, minification, or additional assets) also affects the emission time.

Performance Considerations:

  • Emitting multiple files can slow down the emission phase, especially when generating declaration files alongside JavaScript files.
  • Large projects with hundreds of files and intricate module dependencies may cause significant delays in the emission phase.
  • If you’re using source maps or other complex configurations (like outDir and rootDir), this can add extra overhead during the emission.

What Influences TypeScript Compilation Time? 🤔

Each of these phases — parsing, type checking, and emission — can be impacted by various factors:

  1. Project Size: As the number of files in your TypeScript project increases, the time spent parsing, type-checking, and emitting files will naturally increase.
  2. File Dependencies: Files with large or complex dependencies on other files will require more time to resolve during parsing and type-checking.
  3. Module Resolution Complexity: Projects using a lot of external modules or custom module resolutions (e.g., through baseUrl, paths, or alias configurations) can experience slower performance in module resolution and type-checking.
  4. Complex Type Definitions: Extensive use of complex types (e.g., generics, union types, intersection types) can make type-checking more computationally expensive.
  5. Circular Dependencies: Circular dependencies between modules can increase the time TypeScript needs to spend resolving imports during both parsing and type-checking.
  6. Configuration Complexity: Certain tsconfig.json settings, like source maps, declaration files, or module resolution paths, can add additional complexity to the emission phase.

Compile time vs Runtime ⚙️

In TypeScript development it’s crucial to understand the distinction between compile time and runtime because each has different implications for performance, debugging, and optimization. Both are important to consider when diagnosing performance bottlenecks and understanding how the TypeScript compiler interacts with your code. Here’s a breakdown of the two concepts and how they impact your application.

What is Compile time?

Compile time refers to the phase when the TypeScript compiler processes your code, checking for type correctness, resolving dependencies, and transforming it into JavaScript. The TypeScript compilation process can be triggered by executing the npx tsc command. Key tasks performed during the compilation include:

  • Type Checking: TypeScript ensures that the types in your code are valid according to your type definitions. This is the most time-consuming part of the compilation process.
  • Type Inference: TypeScript automatically infers types in parts of your code where you haven’t explicitly defined types.
  • Module Resolution: TypeScript resolves and loads modules based on your project structure and imports, checking for dependencies and path correctness.
  • Code Transformation: TypeScript converts TypeScript-specific syntax (like interface, enum, etc.) into standard JavaScript.
  • Emission: TypeScript generates output JavaScript files, along with declaration files (.d.ts), based on your settings in tsconfig.json.

Implications of Compile Time:

  • Slower Builds in Large Projects: More files, complex types, and dependencies increase compilation time, slowing the development cycle.
  • Complex Type Checking: Deeply nested interfaces, generics, and unions require more computational effort from the compiler.
  • Configuration Impact: Certain tsconfig.json settings (e.g., strict type checking, source maps) can further affect compile times.

In this article, we focus on improving compilation time.

What is Runtime?

Runtime is the phase when the compiled JavaScript code executes in a JavaScript engine, either in a browser or Node.js. At this stage, TypeScript no longer enforces type checks, and the application performs actual operations such as handling user input, interacting with databases, and processing data. Key aspects of runtime include:

  • Execution of JavaScript: Once the TypeScript code has been compiled to JavaScript, the resulting JavaScript is what runs in the browser or server.
  • Dynamic Behavior: Runtime performance is affected by things like event handling, DOM manipulation (for front-end), and database calls or API requests (for back-end).
  • Memory Management: Memory usage at runtime depends on how your code handles objects, variables, and garbage collection.
  • External APIs/Services: Network requests to external services or databases can introduce latency, which affects runtime performance.

Implications of Runtime Performance:

  • Asynchronous Operations: The performance of asynchronous tasks (e.g., API requests, file I/O) can be a key factor affecting runtime performance, as it can lead to bottlenecks in data flow or concurrency.
  • Data Handling: Poorly optimized algorithms or data structures at runtime can cause delays, such as inefficient sorting algorithms or unoptimized database queries.
  • Memory Leaks: If objects and resources are not released properly, the application may consume an excessive amount of memory at runtime.

In this article, we do not focus on improving runtime, only compilation time.

Utilizing Compiler Options to Speed Up Compilation Time 🛠️

In this section, we will explore key compiler options that can be leveraged to reduce compile times without sacrificing essential type safety or project integrity. By making strategic adjustments to TypeScript’s settings, we can balance performance with the need for type checking, ensuring that builds are both fast and reliable. These optimizations will help your project to compile faster.

1. Enable Incremental Compilation

TypeScript 3.4 introduced incremental compilation, which speeds up rebuilds by caching project information. Enabling incremental allows TypeScript to store metadata in .tsbuildinfo, ensuring that only changed files are recompiled instead of recompiling the entire project.

{
"compilerOptions": {
"incremental": true,
"tsBuildInfoFile": ".tsbuildinfo"
}
}

When enabled, TypeScript stores build metadata in a .tsbuildinfo file. During subsequent compilations, it reads this metadata to determine which files have changed and only recompiles those files along with their dependencies. This drastically reduces build times by avoiding unnecessary reprocessing of unchanged files, resulting in a more efficient development workflow.

By reducing the overall compilation time, incremental builds enhance the developer experience, making the workflow more responsive and improving productivity. This is one of the few compiler options that can make a difference even in smaller projects, and a massive difference in large projects.

2. Use composite for Project References

The composite: true compiler option enables project references in TypeScript, allowing your project to be treated as part of a larger system of interconnected projects. This feature is especially valuable for managing large codebases with multiple subprojects, where each subproject has its own tsconfig.json and can be compiled independently.

When composite: true is enabled, TypeScript tracks dependencies between projects, ensuring that only modified projects and their dependent projects are recompiled. Additionally, TypeScript generates .tsbuildinfo files for each project, which optimize the build process by enabling faster incremental builds and minimizing unnecessary recompilation.

{
"compilerOptions": {
"composite": true
}
}

With this option enabled, TypeScript also enforces stricter type-checking across project boundaries, ensuring that all references between projects are type-safe. Type errors across project boundaries are caught, providing an added layer of reliability in your codebase.

The composite: true option is required to use TypeScript's build mode, which can be triggered with tsc --build. This mode is particularly useful for compiling multiple projects as part of a larger system, as it automatically builds projects in the correct order based on their dependencies.

This setup is particularly beneficial for large-scale applications, where multiple teams may be working on separate modules. By allowing independent builds, composite reduces redundant recompilation, improving both development and CI/CD pipelines' efficiency.

Using project references with composite promotes modularization by allowing subprojects to be built separately. This enhances code organization and maintainability, as unchanged subprojects do not need to be recompiled. The reduced computational overhead leads to faster builds, and integration with incremental builds further optimizes performance in large-scale TypeScript applications.

Practical example

For instance, you could have two projects, projectA and projectB. Each project has its own tsconfig.json file and depends on each other. Enable composite: true in both tsconfig.json files.

// projectA/tsconfig.json
{
"compilerOptions": {
"composite": true,
"declaration": true,
"outDir": "../dist/projectA"
}
}
// projectB/tsconfig.json
{
"compilerOptions": {
"composite": true,
"declaration": true,
"outDir": "../dist/projectB"
},
"references": [
{
"path": "../projectA"
}
]
}

Once you have set up the references, you can use tsc --build to compile all the projects in the correct order based on their dependencies. This will build projectA first (since projectB depends on it), then projectB.

3. Adjust Module Resolution

Module resolution in TypeScript refers to the process of locating the modules that are imported in the code. If misconfigured, it can lead to excessive filesystem lookups, slowing down compilation time. Fine-tuning the moduleResolution option can help improve performance by reducing unnecessary searches.

{
"compilerOptions": {
"moduleResolution": "nodenext",
"baseUrl": "./src",
"paths": {
"@app/*": ["./src/app/*"]
}
}
}

To understand the benefits of optimizing moduleResolution, it's important to first understand the module compiler option. The module option in TypeScript defines how modules are emitted in the compiled JavaScript output, specifying how import and export statements are transformed. The available module options are:

  • none: Disables module support, treating all files as global scripts.
  • node16: Reflects Node.js v16+ module system, supporting both ES modules and CommonJS with specific interoperability and detection rules.
  • node18: Reflects Node.js v18+ module system, adding support for import attributes.
  • nodenext: Available since TypeScript 4.7, and behavior changes with each latest stable version of Node.js. As of TypeScript 5.8, nodenext supports require of ECMAScript modules.
  • es2015: Corresponds to the ES2015 language specification, introducing import/export to JavaScript.
  • es2020: Builds on es2015 with support for import.meta and export * as ns from "mod".
  • es2022: Extends es2020 with support for top-level await.
  • esnext: Similar to es2022, but reflects the latest ECMAScript specifications and Stage 3+ proposals.
  • commonjs: Defines how modules are loaded and exported using require() and module.exports.
  • system: A dynamic loader supporting multiple module formats (e.g., ES modules, CommonJS), typically used in older browsers or universal module loaders.
  • amd: Designed for browsers, AMD (Asynchronous Module Definition) enables asynchronous module loading using define() for modules and dependencies.
  • umd: The UMD (Universal Module Definition) combines AMD and CommonJS, making modules compatible with both Node.js and browsers, supporting require() (Node.js) or define() (AMD).
  • preserve: Keeps the original ECMAScript (import/export) and CommonJS (require/module.exports) syntax without conversion, useful for modern bundlers and the Bun runtime.

The "nodenext" module option is recommended for modern Node.js projects as it supports both ESM and CJS, correctly resolving modules based on file extensions and package.json settings. Unlike outdated options like "commonjs" and "node10", it fully supports Node.js’s evolving module system, including the "exports" and "imports" fields. As a future-proof choice, "nodenext" ensures compatibility with new Node.js features while aligning with modern bundlers like Webpack and ESBuild. It’s ideal for projects that require both module types but may not be necessary for strictly CommonJS-based setups.

Now, back to module resolution — by default, "moduleResolution" is commented out when a TypeScript project is created. If it’s not explicitly set in tsconfig.json, TypeScript determines it automatically based on the module option:

  • If you’re using modern Node.js (node16, node18, nodenext), you get "moduleResolution": "nodenext" automatically.
  • If you’re using commonjs or an ES module setting (esnext, es2020, etc.), TypeScript defaults to "moduleResolution": "node10".
  • "classic" is only the default for legacy module formats like amd, system, or umd.

Optimizing module resolution in TypeScript enhances compilation speed by minimizing unnecessary filesystem lookups and reducing dependency resolution overhead. By default, TypeScript searches multiple locations for modules, which can slow down builds — especially with older resolution strategies like "classic" or "node10", which perform extensive fallback checks. Switching to modern options like "nodenext" or "bundler" streamlines ESM/CJS resolution, eliminating redundant lookups.

Additionally, setting baseUrl and paths helps TypeScript locate modules more efficiently, preventing deep dependency traversal and excessive node_modules scanning. These optimizations also improve caching, reducing repeated resolution work and accelerating incremental builds. Fine-tuning module resolution settings can significantly boost TypeScript performance, especially in large-scale projects.

4. Exclude Unnecessary Files

By default, TypeScript scans all files within the project folder. Excluding non-essential files (such as test outputs, build directories, and coverage reports) can speed up compilation and reduce memory usage.

You can tell TypeScript which files or directories to ignore by using the "exclude" option in your tsconfig.json. This allows you to specify an array of paths or glob patterns for the files or directories you want to exclude from the compilation process.

{
"exclude": [
"node_modules",
"dist",
"build",
"coverage"
]
}

Commonly excluded directories in TypeScript projects include:

  • node_modules: Contains dependencies that are already compiled, so TypeScript doesn’t need to process them, improving performance, especially in large projects.
  • dist/build: These directories store compiled output, which doesn’t need recompilation, avoiding unnecessary processing.
  • coverage: Holds code coverage reports that aren’t part of the TypeScript source and should be ignored by the compiler.

Excluding these files boosts TypeScript’s performance by speeding up compilation, reducing memory usage, and minimizing disk I/O operations. This is especially helpful in large projects. Additionally, excluding unnecessary files keeps the development environment uncluttered, allowing TypeScript to focus on the essential source code.

5. Optimize lib and Type Checking

The lib compiler option in TypeScript allows you to specify which built-in JavaScript APIs (such as DOM and ESNext) to include in your project. These libraries define available global types and definitions for your code. For example, if you need support for array.findLast, introduced in ES2023, you can include the appropriate library as follows:

{
"compilerOptions": {
"lib": ["esnext"]
}
}

Similarly, you can exclude unnecessary declarations by not including them in the lib array. For instance, if your application is browser-based, you can exclude DOM declarations by omitting them from the list.

By limiting the lib array to only the libraries relevant to your project, you can significantly improve build times and reduce memory usage. This focused approach narrows the scope of TypeScript’s analysis, speeding up the compilation process.

You can further optimize type checking by using the skipLibCheck and skipDefaultLibCheck compiler options:

{
"compilerOptions": {
"lib": ["ES2021", "DOM"],
"skipLibCheck": true,
"skipDefaultLibCheck": true
}
}

In this configuration:

  • lib: ["ES2023", "DOM"]: Only includes the ES2023 and DOM libraries, ensuring TypeScript is aware of modern ECMAScript features and the browser’s DOM while avoiding unnecessary libraries like ES6, ES2015, or WebWorker.
  • skipLibCheck: true: Skips type checking for declaration files (.d.ts) in third-party libraries, which can reduce processing time significantly.
  • skipDefaultLibCheck: true: Skips type checking for default library files (e.g., lib.d.ts). Since these files are standard and typically error-free, skipping their validation further boosts compilation speed.

Optimizing the lib and type checking settings in TypeScript offers several benefits. Excluding unnecessary libraries and skipping type checks for third-party declarations helps avoid redundant processing, which can significantly reduce build times. Limiting the lib array ensures that TypeScript only loads the type definitions necessary for your project, conserving memory. Additionally, focusing type inference on the relevant libraries speeds up validation, while skipping checks for external library types helps avoid errors caused by mismatched definitions, allowing you to focus on your own code. Fine-tuning these settings can greatly improve both TypeScript’s performance and build efficiency, particularly in large projects with many dependencies.

6. Use noEmit for Type-Only Checking

The noEmit option prevents TypeScript from generating compiled output, including JavaScript files, source maps, and declaration files. It is primarily useful for development, CI/CD pipelines, and staging environments where type checking is required but JavaScript output is unnecessary. However, it should be avoided in production since it prevents TypeScript from generating .js files unless the responsibility for compilation has been delegated to another tool, such as Babel or the Speedy Web Compiler (SWC).

By skipping JavaScript file generation, noEmit speeds up compilation while ensuring type correctness. During development, it is particularly helpful when type checking is needed but JavaScript compilation is handled separately, eliminating redundant steps. To enable noEmit, configure your tsconfig.json as follows:

{
"compilerOptions": {
"noEmit": true
}
}

Using noEmit improves efficiency by reducing unnecessary file generation and ensuring type safety without compiling to JavaScript. This reduces overhead in workflows where JavaScript output is not needed and allows other tools to handle transpilation. As a result, it streamlines the development process, leading to faster, type-safe builds while eliminating redundant compilation steps.

Another related compiler option is noEmitOnError. This option prevents TypeScript from generating output files only if there are errors in the code. If there are no errors, TypeScript will generate the output files as usual. In contrast, noEmit disables all output files, regardless of errors. While noEmitOnError allows output when the code is error-free, noEmit is stricter and prevents any output, making noEmitOnError more flexible when you still want to generate code without errors.

7. Use strict Mode Selectively

TypeScript’s strict mode enhances type safety by enforcing rigorous type-checking rules, helping catch potential errors early. However, enabling it fully can slow down compilation, especially in large projects, due to the increased number of checks. To balance performance and type safety, consider selectively enabling only the strict rules that are most relevant to your project instead of using strict mode in its entirety.

By default, enabling strict mode activates multiple type-checking options that improve code quality. However, this can lead to longer compilation times. Instead, you can disable strict mode and manually enable specific rules that provide the most value to your codebase. For example, this configuration maintains essential strict checks while optimizing performance:

{
"compilerOptions": {
"strict": false, // Disables all strict type-checking options
"noImplicitAny": true, // Ensures that variables with an unknown type are explicitly defined, avoiding the default any type
"strictNullChecks": true // Prevents null and undefined from being assigned to any type unless explicitly allowed, enhancing null safety.
}
}

By selectively enabling key rules like noImplicitAny and strictNullChecks, you ensure robust type safety without unnecessary overhead. This approach speeds up compilation while maintaining essential type checks, making it ideal for large, complex codebases. Ultimately, the right balance depends on your team's priorities—do you favor stronger type safety or faster build times?

8. Use isolatedModules for Faster Builds

Enabling the isolatedModules option in TypeScript optimizes the compilation process by simplifying how files are handled during the build. When this option is enabled, TypeScript treats each file as an independent module, preventing cross-file type checking. This results in TypeScript focusing only on types and dependencies within each individual file, reducing the amount of work it needs to do.

By treating files independently, isolatedModules accelerates incremental builds, especially in large codebases. Since only the file being compiled is checked rather than the entire project, the build process becomes faster. This approach is particularly advantageous when using bundlers like Webpack or Rollup, which compile TypeScript files separately. By aligning TypeScript’s behavior with how these bundlers work, unnecessary operations are avoided, streamlining the build process and improving overall resource efficiency.

{
"compilerOptions": {
"isolatedModules": true
}
}

Enabling the isolatedModules option brings several key benefits to your TypeScript project. It significantly improves incremental build times by recompiling only the files that have changed, which reduces the overall build time. This feature is especially valuable in large codebases where frequent changes are made. Additionally, isolatedModules is optimized for use with bundlers like Webpack, Rollup, and similar tools, ensuring compatibility with module-based workflows.

Moreover, enabling isolatedModules enhances the efficiency of continuous integration and deployment (CI/CD) pipelines. By speeding up the build process, it ensures faster, more efficient deployments. Overall, using isolatedModules streamlines TypeScript's compilation, making it faster, more scalable, and particularly beneficial in modularized or bundler-based workflows.

The isolatedModules option is less ideal when your project relies on cross-file type checking, such as when using TypeScript features like mixins, decorators, or complex interdependencies across modules. It can also be problematic if you need full, comprehensive type checking across your entire codebase to ensure type safety, as it disables checks that span multiple files.

9. Skip generating declaration files

The declaration option instructs TypeScript to generate .d.ts files, which provide type information about your modules. If you skip this option, TypeScript doesn't need to generate these files, saving time during the compilation process. Similarly, the declarationMap option generates a map file (.d.ts.map) for each .d.ts file, helping to map the declaration files back to the original TypeScript source. By skipping these options, TypeScript avoids the overhead of creating and writing the declaration and map files.

{
"compilerOptions": {
"declaration": true,
"declarationMap": true
}
}

However, skipping these options has its consequences. If you’re working on a library or a project intended for others to import, not generating declaration files (.d.ts) means consumers won’t have access to the type information when importing your code. This can make it harder for others to leverage TypeScript’s static type-checking capabilities effectively. Additionally, skipping declarationMap means the generated declaration files won't have source maps, which complicates tracing errors or understanding the original TypeScript source during debugging. This can be especially challenging in large codebases or when your code is used as a dependency by others.

In summary, while skipping the declaration and declarationMap options can speed up compilation by reducing TypeScript’s workload, it comes at the cost of losing type information and source maps for declaration files, which may reduce the usefulness of your code for others and complicate debugging.

10. Optimize Third-Party Libraries

Although not directly related to a tsconfig.json compiler option, optimizing third-party libraries is a crucial aspect of improving TypeScript's compile time. Large libraries or those with complex type definitions can significantly slow down the build process. To improve performance, consider using lighter, faster alternatives to heavy libraries that include unnecessary features or dependencies. By evaluating and replacing current libraries with more efficient options, you can reduce both the time spent on type-checking and the size of your compiled code.

Another important step is regularly auditing your project to remove unused or unnecessary dependencies. Over time, projects tend to accumulate libraries that are no longer in use. By removing these, you reduce the number of files TypeScript needs to analyze, ultimately speeding up the compilation process.

Additionally, many third-party libraries lack TypeScript declaration files by default, which can slow down type resolution. Where available, use the corresponding @types package to ensure proper type checking. By leveraging precompiled declaration files, TypeScript can resolve types more efficiently, speeding up the type-checking process.

Optimizing third-party libraries results in faster builds by reducing the amount of code TypeScript needs to process. Regularly removing unused dependencies keeps your project lean and maintainable, while using properly defined declaration files improves type resolution, making the type-checking process faster and more accurate. These optimizations help streamline the build process, enhance performance, and improve the overall maintainability of your project.

How to Diagnose and Measure Compile Time? 🕵️

Understanding how TypeScript allocates resources during the compilation process is essential for optimizing build performance, especially in large projects. By leveraging TypeScript’s diagnostic options, developers can gain valuable insights into how the compiler is spending its time and resources. This information can guide performance optimizations and help pinpoint areas that may require attention, such as inefficient type-checking or excessive file processing.

Measure Compilation Time with diagnostics Option

The diagnostics compiler option provides basic metrics on the time spent during various phases of the TypeScript compilation process. This includes time spent parsing, type-checking, and emitting files, as well as memory usage and the number of files compiled.

{
"compilerOptions": {
"diagnostics": true
}
}

Once enabled, the TypeScript compiler will output detailed statistics about the compilation process to the console. Key metrics include:

  • Total time spent on parsing, type-checking, and emitting files.
  • Memory usage during compilation.
  • Number of files processed.

For example, you might see output like this:

Files:              191
Lines: 113139
Identifiers: 116703
Symbols: 74509
Types: 1465
Instantiations: 4825
Memory used: 126306K
I/O read: 0.06s
I/O write: 0.01s
Parse time: 0.90s // Time spent on parsing
Bind time: 0.28s
Check time: 0.19s // Time spent on type-checking
Emit time: 0.05s // Time spent on emitting
Total time: 1.41s

This information helps identify performance issues — such as if the type-checking phase is taking too long, which could indicate overly complex type definitions or unnecessary file processing. It can also help to compare the results of tweaking and optimizing compiler options in tsconfig.json.

Measurement Compilation Time with extendedDiagnostics Option

The extendedDiagnostics option extends the basic diagnostics output by adding more detailed information about compilation performance. It includes granular timings and memory usage statistics for each individual step in the TypeScript compilation process.

{
"compilerOptions": {
"extendedDiagnostics": true
}
}

Enabling this option provides a breakdown like the following:

Files:                         191
Lines of Library: 40406
Lines of Definitions: 72662
Lines of TypeScript: 71
Lines of JavaScript: 0
Lines of JSON: 0
Lines of Other: 0
Identifiers: 116703
Symbols: 74509
Types: 1465
Instantiations: 4825
Memory used: 126808K
Assignability cache size: 380
Identity cache size: 0
Subtype cache size: 6
Strict subtype cache size: 0
I/O Read time: 0.07s
Parse time: 0.64s
ResolveModule time: 0.12s
ResolveTypeReference time: 0.02s
ResolveLibrary time: 0.03s
Program time: 0.94s
Bind time: 0.28s
Check time: 0.14s
transformTime time: 0.01s
commentTime time: 0.00s
I/O Write time: 0.00s
printTime time: 0.04s
Emit time: 0.04s
Total time: 1.41s

This detailed output helps pinpoint which stages of the compilation process consume the most time, providing valuable insights into where the bottlenecks lie. By analyzing these time metrics, you can identify what may be slowing down the build.

Analyze CPU Usage of Compilation Process with generateCpuProfile Option

The generateCpuProfile option generates a CPU profile of the TypeScript compilation process, which can be useful in identifying performance bottlenecks in terms of CPU usage.

{
"compilerOptions": {
"generateCpuProfile": "profile.cpuprofile"
}
}

When this option is enabled, TypeScript captures an execution profile of the compiler during its run. The output file (profile.cpuprofile) is generated in the working directory. In Mozilla Firefox, simply visit Firefox Profiler and load the file to start analyzing the file. In most other browsers you need to:

  1. Open the Browser
  2. Navigate to Web Developer Tools (F12)
  3. Click the Performance tab
  4. Click the Load Profile… button and select the profile.cpufile file
  5. Analyse the visuals

Analyze Trace of Compilation Process with generateTrace Option

The generateTrace option generates a trace file that records detailed events and timing information of the entire compilation process. This file is useful for diagnosing performance bottlenecks in TypeScript compilation.

{
"compilerOptions": {
"generateTrace": "trace.json"
}
}

The output file (trace.json) is generated in the working directory. In Mozilla Firefox, simply visit Firefox Profiler and load the file to start analysing the file. In Google Chrome and MSEdge you can navigate to chrome://tracing/or edge://tracing/ to load and analyze the file.

Analyze Trace Module Resolution with traceResolution Option

The traceResolution option helps trace how TypeScript resolves module imports. This is particularly helpful when you have large projects with complex dependency trees or issues with module resolution, which may contribute to build performance bottlenecks.

{
"compilerOptions": {
"traceResolution": true
}
}

This option outputs detailed logs about how TypeScript resolves each module and the steps it takes to locate dependencies. This can help you identify inefficiencies or misconfigurations in your module resolution strategy. For example, if TypeScript is spending excessive time resolving modules across deep directory structures or incorrect paths, this flag will expose those issues. By optimizing module imports (e.g., using absolute imports or restructuring directories), you can reduce resolution time.

Example output:

Resolution for module 'utils' from '/src/components/Button.tsx':
Trying to resolve from '/src/components/utils.ts'
Found module 'utils' at '/src/components/utils.ts'

The trace reveals the exact steps TypeScript is taking to resolve module paths, which can guide you in streamlining your project’s module structure.

Conclusion 📣

Optimizing TypeScript’s compilation time is essential for maintaining a smooth development experience, especially as projects scale. By understanding the intricacies of TypeScript’s compilation process — parsing, type checking, and emission — you can pinpoint areas that contribute to slow compilation times and address them effectively.

Strategic adjustments to tsconfig.json, such as enabling incremental builds, leveraging project references, fine-tuning module resolution, and selectively using strict mode, can dramatically improve performance without sacrificing type safety. Additionally, excluding unnecessary files, optimizing library usage, and diagnosing compilation bottlenecks using TypeScript’s diagnostic tools further contribute to a more efficient workflow.

By implementing these optimizations, developers can significantly reduce build times, enhance CI/CD efficiency, and maintain a responsive development environment. Whether you’re working on a small project or managing a large monorepo, these best practices will help streamline TypeScript’s compilation, leading to faster iterations and improved productivity.

Start refining your tsconfig.json today and experience the benefits of optimized TypeScript builds firsthand! 🥳

🙏 Thanks for reading to the end! If you have any questions, feel free to drop a comment below.

If you enjoyed this article, follow me on social media — I’d love to connect and follow you back:

And don’t forget to follow Level Up Coding for more insightful content! 🚀


How to Improve TypeScript Compilation and Build Times with tsconfig.json Optimizations was originally published in Level Up Coding on Medium, where people are continuing the conversation by highlighting and responding to this story.


This content originally appeared on Level Up Coding - Medium and was authored by Robin Viktorsson


Print Share Comment Cite Upload Translate Updates
APA

Robin Viktorsson | Sciencx (2025-03-24T14:19:51+00:00) How to Improve TypeScript Compilation and Build Times with tsconfig.json Optimizations. Retrieved from https://www.scien.cx/2025/03/24/how-to-improve-typescript-compilation-and-build-times-with-tsconfig-json-optimizations/

MLA
" » How to Improve TypeScript Compilation and Build Times with tsconfig.json Optimizations." Robin Viktorsson | Sciencx - Monday March 24, 2025, https://www.scien.cx/2025/03/24/how-to-improve-typescript-compilation-and-build-times-with-tsconfig-json-optimizations/
HARVARD
Robin Viktorsson | Sciencx Monday March 24, 2025 » How to Improve TypeScript Compilation and Build Times with tsconfig.json Optimizations., viewed ,<https://www.scien.cx/2025/03/24/how-to-improve-typescript-compilation-and-build-times-with-tsconfig-json-optimizations/>
VANCOUVER
Robin Viktorsson | Sciencx - » How to Improve TypeScript Compilation and Build Times with tsconfig.json Optimizations. [Internet]. [Accessed ]. Available from: https://www.scien.cx/2025/03/24/how-to-improve-typescript-compilation-and-build-times-with-tsconfig-json-optimizations/
CHICAGO
" » How to Improve TypeScript Compilation and Build Times with tsconfig.json Optimizations." Robin Viktorsson | Sciencx - Accessed . https://www.scien.cx/2025/03/24/how-to-improve-typescript-compilation-and-build-times-with-tsconfig-json-optimizations/
IEEE
" » How to Improve TypeScript Compilation and Build Times with tsconfig.json Optimizations." Robin Viktorsson | Sciencx [Online]. Available: https://www.scien.cx/2025/03/24/how-to-improve-typescript-compilation-and-build-times-with-tsconfig-json-optimizations/. [Accessed: ]
rf:citation
» How to Improve TypeScript Compilation and Build Times with tsconfig.json Optimizations | Robin Viktorsson | Sciencx | https://www.scien.cx/2025/03/24/how-to-improve-typescript-compilation-and-build-times-with-tsconfig-json-optimizations/ |

Please log in to upload a file.




There are no updates yet.
Click the Upload button above to add an update.

You must be logged in to translate posts. Please log in or register.