question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Out-of-band type-checking for TypeScript and Template compilation

See original GitHub issue

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 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 @ 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 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 plugin in IDEs.
  • more evolved, but more efficient: one possible option would be to embrace a highly parallelizable transpiler/build engine like esbuild or swc with ngc (the Angular compiler) as a plugin. We could even consider using Vite (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 (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 or swc. 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 - the TypeScript team @ Microsoft have worked closely to enable syntax-level support (type-stripping) in babel (blog post)
  • deno - 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 - TypeScript / JavaScript transpiler & bundler (used by deno).
  • esbuild - TypeScript / JavaScript transpiler & bundler (used by Angular CLI (in a limited way) and Vite)
  • Vite - JavaScript dev server (and build pipeline) with TypeScript support
  • bazel - limited number of recently created projects have started to embrace the use of TypeScript type-checking via testonly targets (according to aspect.dev)
  • 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.

Issue Analytics

  • State:open
  • Created 2 years ago
  • Reactions:145
  • Comments:25 (16 by maintainers)

github_iconTop GitHub Comments

18reactions
jpike88commented, Jan 19, 2022

Angular’s core tools have always been greatly lacking in development cycle speed, helping give rise to a whole ecosystem of bundlers that do it multiple times faster. I jumped ship a long time ago and have contributed to several bundlers, which has allowed me to retain my sanity, and have migrated back to angular CLI due to the breaking changes related to 13. It feels like I’ve permanently moved out of the fast lane, to put it nicely.

The demand for performant development has always been there, it shaves precious hours off everyone’s time and lets people focus on quality, not quantity. The last few major versions of Angular have done little to help with this. Throwing more stuff on the pile needs to stop and performance needs to take the front seat. Hopefully the upper management will reconsider what the priorities should be, I don’t understand how such a slow cycle (10, 20, even 30 seconds a cycle) was ever acceptable. Tools like esbuild represent the pinnacle of this performance goal and all efforts should be made to heavily integrate with these kinds of tools. The only thing in the way is making that the focus.

I think the ‘extreme’ pathway is ideal, as it gives the most room for a clean slate and matches what bundlers out there already do with their HMR functionality. Of course, what would be truly ideal is porting the whole Angular build system to Go itself, treating esbuild as a first-party bundler and shifting away from Webpack would bring maximum performance gains, cutting a 2 minute build down to even just a few seconds. If the current alternatives out there can solve for that, surely so can the Angular team.

15reactions
IgorMinarcommented, Aug 12, 2021

@Airblader absolutely! the goal is not to make type-checking insignificant - the errors would be very prominent.

The goal is to give developers an option to ignore the type errors temporarily during the interactive development.

We could even provide an option to make the type-checking errors blocking if developer or team prefers that (controlled via a command line flag, or angular.json config).

Read more comments on GitHub >

github_iconTop Results From Across the Web

تويتر \ Igor Minar على تويتر: "I ❤️ TypeScript. We've had a ...
Type checking should not get in the way of exploration, prototyping, ... Out-of-band type-checking for TypeScript and Template compilation · Issue #43131 ...
Read more >
Methods for TypeScript runtime type checking - LogRocket Blog
Explore five methods of performing TypeScript type checks at runtime in this post and learn each of their advantages and disadvantages.
Read more >
To Typescript Or Not To? - DEV Community ‍ ‍
Exactly because of that. TypeScript will only check types at compile time and only types that are available. Any network calls, system libraries ......
Read more >
TypeScript in a Weekend - The acting developer
TypeScript helps you avoid unwanted runtime errors by type-checking during development. TypeScript files are compiled into JavaScript files ...
Read more >
TypeScript Type Inference Guide - Tomasz Ducin -blog
Additionally, an interesting question is if TypeScript is going to type check the result in the runtime, by injecting additional code into ...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found