- Improving TypeScript build performance through parallel processing.
- Challenges and strategies for optimizing monorepo builds.
- The role of type checking, declaration emit, and JavaScript emit in builds.
- Introduction of isolated declarations to enhance parallelism.
- Practical gains and future potential of these approaches.
TypeScript is a powerful tool for developers, but as projects grow in size, build times can become a significant bottleneck. This is especially true in monorepos, where multiple projects are housed under a single repository. To tackle this challenge, we explored ways to speed up TypeScript builds, particularly focusing on harnessing the capabilities of modern multi-core processors.
One of the main issues with TypeScript builds is the time-consuming nature of type checking, especially when dealing with large codebases. While optimizations to the TypeScript compiler have been ongoing, the gains from these improvements are often incremental. Therefore, the idea of running the compiler in parallel emerged as a promising solution to leverage the full potential of available hardware.
In a typical monorepo, projects have complex interdependencies. This means that while it might seem ideal to run all projects in parallel, dependencies require a more nuanced approach. We experimented with parallel builds by running separate instances of the compiler for each project, combined with shared file and syntax tree caches. This approach aimed to maximize parallelism while respecting the dependency graph.
However, the inherent dependencies between projects restricted the level of achievable parallelism. On a system with multiple cores, only a fraction of the CPU power was utilized, limiting performance improvements to around 10-20%. To unlock further parallelism, we needed to understand the TypeScript build process more deeply.
TypeScript builds involve three main processes: type checking, declaration emit, and JavaScript emit. The declaration emit phase is crucial as it generates the necessary information for dependent projects to begin their type checking. By isolating declaration emit from type checking, we could potentially increase parallelism by enabling projects to start sooner.
This led us to the concept of "isolated declarations," a new feature that simplifies declaration emit by removing its dependency on type checking. The idea is to ensure that all necessary type annotations are present in the code, making declaration emit a purely syntactic transformation. This change allows for more efficient builds by front-loading declaration emit and enabling projects to proceed without waiting for full type checking of dependencies.
Implementing isolated declarations requires writing explicit type annotations for all exported values. While this might seem burdensome, the benefits are substantial. Tools like Visual Studio Code can assist developers by automatically inserting type annotations where needed, making the transition smoother.
Our experiments with isolated declarations in a sample monorepo showed promising results. By converting the monorepo to use isolated declarations, we achieved significant performance gains, with build times reduced to single-digit seconds in some cases. The improvements were visually apparent, with a threefold increase in build speed.
Looking forward, the introduction of isolated declarations holds the potential to further enhance TypeScript's build performance. While the initial focus has been on monorepos, there is interest in exploring how these techniques could benefit single-project setups. Additionally, the development of a robust ecosystem of TypeScript declaration emitters could lead to even faster builds.
In summary, by rethinking the build process and introducing isolated declarations, we have made significant strides in improving TypeScript build performance. These innovations not only speed up builds but also improve compatibility with other tools and enhance the overall developer experience.