Repository: angular
Issue: Out-of-band type-checking for TypeScript and Template compilation
Labels: feature,action: discuss,area: compiler,needs: discussion,compiler: template type-checking,feature: under consideration

*Note 1: the original title of this issue was "Remove type-checking from the critical path of Angular builds (TypeScript and Template compilation)", but through community discussion we found "Out-of-band type-checking for TypeScript and Template compilation" more fitting).*

*Note 2: This proposal has its origin in [RFC: Exploration of use-cases for Angular JIT compilation mode](https://github.com/angular/angular/issues/43133) and aims to address the concerns around AOT builds being too slow compared to the JIT alternative.*

*Note 3: 📺 Check out the [video recording of a community discussion about this proposal](https://youtu.be/FbJLC7GxAAs?t=3714) @ [angularnation.net](https://www.angularnation.net/)*

## The problem

Currently, precious hundreds of milliseconds, but often more commonly many seconds, are being spent on type-checking code of Angular applications as developers edit the code using interactive workflows (like `ng serve`, `ng test --watch`, etc). Not only does this increase the latency, and decrease the number of iterations per minute, it also makes it impossible to see results of a change in the browser if the change doesn't pass the type-checking step. This makes it hard to experiment, and explore, which impacts creativity, and forces developers to either iterate less, or take the time to make the compiler happy, just to find out that they are heading down the wrong direction.

But it doesn't have to be this way... we can have both a faster iteration cycle, as well as all the benefits of the type system and type checking.


## Background info

Developer productivity, velocity, and happiness (no, really!), as well as higher quality of code, and better UX of the final product are directly correlated with how quickly developers can iterate on the code — make a code change and see the result locally.

**The lower the edit & refresh latency (the faster the feedback loop) the better** — this is especially relevant for  interactive workflows (local dev server, unit tests, etc).

**Lower latency means that developers can afford more iterations on solving a problem in a given period of time**, thus enabling them to try various approaches, **be more creative, and to pick the best solution**. If the iteration latency is low, developers often end up shipping code that (barely) meets the functional criteria, and due to a lack of time can't focus on UX or code base quality.

*side note: Latency of committing code and deploying it to production also plays a big role in overall success, but this proposal is specifically focused on the interactive feedback loop.*

Angular has always tried to strike the balance between flexibility that enables creativity and providing guide rails for developers to support them on the path of creating correct, performant, maintainable, accessible, localizable code. One of the means to provide these guide rails has been the use of TypeScript and type-checking to ensure that the code is sound.

Type-checking and the use of type systems are a great way to increase code quality, prevent bugs, and enable automated refactoring of the code base at scale. However, **type-checking also comes at a cost**.


## Proposed solution


The current (simplified) processing model of a typical Angular build system is as follows:

```
parse code -> analyze Angular specific metadata (e.g. decorators) -> generate JS code ->
generate TS code needed for type checking (type check blocks) -> ask TypeScript perform type-checking ->
TypeScript to downlevel `.ts` files to `.js` files -> bundle code ->
finally do something with the code (run tests, update dev server, etc)
```

Note that these tasks are performed in a sequence, and type-checking is in the middle of this critical path.

*side note: The Angular CLI currently uses TypeScript’s incremental compilation to minimize the amount of type checking required per rebuild, which however still blocks the final steps of the bundling process, resulting in DX similar to (but slightly faster than) fully sequential build pipeline.* 

*side note (2): We can, and do utilize parallelization in order to improve build times, especially when using build systems like [Bazel](https://bazel.build/) and friends. With this approach we achieve parallelization by breaking down bigger apps into smaller compilation units and build those in parallel as much as possible, while still honoring the dependency graph of these compilation units. This improves edit & refresh latency in some cases but with limitations and increased complexity.*

If we removed the type-checking from this critical path we unlock two benefits:

faster builds and rebuilds — type-checking is an expensive operation
enable prototypization and exploration — we'd allow unit tests and dev servers to refresh even if the code doesn't pass all of the type-checks

**The mental model shift proposed by this change is to turn type-checking into a very sophisticated lint check.**

## Implementation details

Much is TBD still and there are many options, ranging from:

- *simple but inefficient:* disable type-checking in the critical path, and use a separate process to run a duplicate TS program that would type-check the code (but not block the dev server refresh / unit test run).
  - We could also support a mode in which the type-checking is disabled during builds, but would still work when running `ng lint` or using [Angular Language Service](https://angular.io/guide/language-service) plugin in IDEs.
- *more evolved, but more efficient*: one possible option would be to embrace a highly parallelizable transpiler/build engine like [esbuild](https://esbuild.github.io/) or [swc](https://swc.rs/) with `ngc` (the Angular compiler) as a plugin. We could even consider using [Vite](https://vitejs.dev/) (which uses esbuild). In this setup, `ngc` would need to be improved to support a "type-check-less" mode for efficiently converting Angular metadata and templates into JS code without bothering to analyze and generate code that supports type-checking.
  - Type-checking would still happen out of band, in a way similar to the "simple" solution.
- *extreme*: [Transition Angular from the program-based compilation to localized compilation](https://github.com/angular/angular/issues/43165) (where each `.ts` file can be converted to `.js` file without global program analysis or global optimization). Then use this localized compiler as a highly parallelizable plugin to a build engine like [esbuild](https://esbuild.github.io/) or [swc](https://swc.rs/). This approach would also enable Vite-style, on-demand transpilation of parts of the code base, something that is very difficult with the current Angular compiler architecture. 
  - Type-checking would still happen out of band, in a way similar to the "simple" solution.


## Trade offs

- *developers could write code with bugs* that could be easily caught by type-checking, and waste time debugging the issue in their dev server / unit test. This *could be mitigated with*:
  - IDEs already offer excellent support for low latency type-checking for TypeScript, and with the recent release of Ivy-based Angular Language Service (in v12), we now have the same capability for Angular templates and decorators as well.
  - We can and should still perform type-checking in the background, and **embrace the "eventual consistency" mindset to provide developers with type-checking feedback** in the terminal, and dev server UI (via injected scripts eventually displaying warnings about type issues in the browser)
- *increased complexity of the build pipeline* in order to support parallelization and eventual delivery of the type-checking results to the terminal (for `ng build` and `ng test` or browser (for `ng serve`).
- *duplication of some work* — depending on the implementation we might end up performing a limited amount of work twice, for example source code parsing.
- *increased memory usage of the build pipeline* — similarly to the trade off above, depending on how this is implemented, the build pipeline might end up consuming more memory.

## Web ecosystem compatibility & Prior art

With the rise of popularity of TypeScript in the web ecosystem, we also observed that a part of the community has embraced the approach of using TypeScript as a glorified linter already. Some examples:

- [babel](https://babeljs.io/) - the TypeScript team @ Microsoft have worked closely to enable syntax-level support (type-stripping) in babel ([blog post](https://devblogs.microsoft.com/typescript/typescript-and-babel-7/))
- [deno](https://deno.land/) - supports TypeScript as the input language without performing type-checking (which is the responsibility of the TypeScript compiler that the developers might choose to run as a "linter").
- [swc](https://swc.rs/) - TypeScript / JavaScript transpiler & bundler (used by deno).
- [esbuild](https://esbuild.github.io/) - TypeScript / JavaScript transpiler & bundler (used by Angular CLI (in a limited way) and [Vite](https://vitejs.dev/))
- [Vite](https://vitejs.dev/) - JavaScript dev server (and build pipeline) with [TypeScript support](https://vitejs.dev/guide/features.html#typescript)
- [bazel](https://bazel.build/) - limited number of recently created projects have started to embrace the use of TypeScript type-checking via [`testonly`](https://docs.bazel.build/versions/main/be/common-definitions.html) targets (according to [aspect.dev](https://www.aspect.dev/))
- [ts-node](https://github.com/TypeStrong/ts-node) - TypeScript execution engine for Node.js with **optional** support for type-checking.

The trend in the community seems to be only accelerating in the direction of using type-checking as an aid, rather than as a hard block in the developer workflows.

## What's the goal of this issue?

The goal is to write down some of the ideas that have been made possible thanks to Ivy and start collecting early feedback that could be used as an input for future RFC.

## Alternatives considered

Keep on improving the type-checking speed — something we've been focused on for years, and we have reached the point of diminishing returns.