Transition Angular from the whole-program compilation to localized compilation
See original GitHub issueNote 1: This proposal has its origin in Out-of-band type-checking for TypeScript and Template compilation which mentions an “extreme” implementation option — this issue provides more details about this option.
Note 2: 📺 Check out the video recording of a community discussion about this proposal @ angularnation.net
The problem
As described in the Out-of-band type-checking for TypeScript and Template compilation proposal, making Angular’s compilation model highly parallelizable would result in major benefits for Angular developers by significantly lowering the interactive workflow latency (making the “edit & refresh” cycle faster).
This change would additionally improve the efficiency (in time, and CPU cost) of building Angular at scale in monorepos and on CI systems.
The current cost of compilation is significant, and becoming a hurdle affecting DX, and our ability to scale massive code bases.
Background info
The unit of parallelization of Angular’s compilation is the “program” as defined by the TypeScript compiler — the program is the smallest unit of code (a group of .ts
, .d.ts
, and .js
files declared and configured by a single tsconfig.json file) that the TypeScript compiler can process independently of other code.
A typical Angular compilation unit is a program that is composed of several (often many) components, directives, pipes, services, NgModules, and generic TypeScript files.
A small application is commonly built as a single program, that often depends on 3rd party Angular libraries (that themselves were compiled as individual programs). Larger code bases, split up their app into multiple programs using Angular CLI workspaces, Bazel’s rules_nodejs (or the ng_module
rule), or Nx.
These approaches to scaling help, but don’t solve the ultimate problem, which is that it is currently not possible to process a single Angular Component and transform it to runnable JS without processing a bigger chunk of the code base as well (the whole program). This prevents us from taking advantage of massive parallelization that modern hardware in combination with highly parallelizable transpilation engines like esbuild, (and Vite built on top of it) and Bazel offer. This results in wasted human and CPU time and ultimately in loss of focus, creativity, and high CI bills.
Proposed solution
Let’s transition the processing model of Angular code from the “whole world” compilation model (global optimization), to localized compilation (and as close to transpilation as possible). In an extreme example, this would mean that a single .ts
file with an Angular component could be converted to .js
on its own without having to read or process any other files.
In practice, it’s more likely that we’ll need to read and process several files around this single .ts
file because of references to external templates and style sheets, and we’ll need the imported NgModules and (upcoming “standalone components, directives, and pipes” — check out the RFC) to be at least read, if not compiled as well, because they represent transitive dependencies of the component being processed.
Aiming for purely syntactic transpilation of template and metadata to JavaScript code would most likely be a “bridge too far” (see “Alternatives considered” below), but getting as close as possible to that ideal is at the core of this proposal.
We don’t need purely syntactic transpilation to still reap most of the benefits of parallelization, localized compilation would result in a huge improvement already.
Implementation details
- Out-of-band type-checking for TypeScript and Template compilation is a most likely a prerequisite for this project, because we need to decouple type-checking from code generation.
- Moving away from NgModules, to module-less, a.k.a. “standalone” components, directives, and pipes would provide us with focused understand the context within which a component or a directive are being compiled — fortunately we want to execute on this project regardless of this proposal in order to reduce verbosity and complexity of Angular, so this prerequisite is well aligned with our ongoing work.
- We’d need to rethink our compiler architecture, and investigate if building it on top of the TypeScript AST is the right approach or if we should consider more lightweight alternatives. A lot of research in this area needs to be done to make the right call, but considering that we absolutely want to keep TypeScript for type-checking, IDE/Language Service support, etc, it might make a lot of sense to stick with it and find ways to make it fast enough.
- Once the compiler supports localized compilation, we’d need to build plugins for esbuild or its alternative like swc.
- Angular CLI’s build pipeline would have to be revamped to support this processing model. We could even consider adopting Vite since they’ve done a lot of awesome work already 👍🏻.
- The impact on this change on other parts of Angular, like the language-service, google3 integration, and others is less clear at the moment, and will need to be researched.
Trade offs
- Switching to transpilation represents a radical shift for the design of both the Angular compiler and runtime, and has an impact on how type-checking should work. All of this represents a significant effort, which would take time.
Web ecosystem compatibility & Prior art
All of the following use transpilation or localized compilation approach:
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
-
We could switch over to an existing templating solution that is transpilable (JSX, lit), but these systems have fundamentally different component composition & change detection models, which would result in a major breaking change and regressions in runtime performance in some scenarios (due to our inability to perform certain optimizations). This change would not be worth the trouble.
-
We could modify the template syntax to make it more suitable for purely syntactic transpilation (for example by removing selector matching)— while this might be worth doing in the future (pending more exploration of costs vs benefits), we can achieve most of the goals of this proposal by changing how Angular works internally while preserving backwards compatibility for most of the code out there and the investment into these code bases.
Issue Analytics
- State:
- Created 2 years ago
- Reactions:218
- Comments:11 (9 by maintainers)
Top GitHub Comments
Wow. This is amazing! I love it. 👍👍 Two thumbs up indeed! Compilers are not my forte so I don’t have a lot to contribute but I wholeheartedly support this improvement in the framework.
This is pretty much how the compiler’s incremental build system (aka “watch mode”) which powers
ng serve
currently functions. The main source of complexity here is that changes in Angular code are frequently non-local - editing an@NgModule
for example can affect the compilation of components in many other files, depending on how that@NgModule
is imported/re-exported across the program. So to accurately and efficiently react to a change in its input, the compiler has not only an in-memory “database” of all of the metadata for each class, but also understands the dependencies between these artifacts and how a change to one will propagate through the others. This is extremely nontrivial, but is necessary for reasonable incremental performance.