Consider not doing breaking changes on Ivy

🚀 feature request

Relevant Package

@angular/compiler-cli

Description

Ivy gained so much for Angular community but the issue that we are facing is often breaking changes on compiler cli and ivy usage.

I would like to be able to use compiled Ivy modules for loading into Angular based app without need to have App compiled for each Angular release because of the breaking changes.

Describe the solution you’d like

Breaking changes to Ivy are only introduced on Major SemVer changes and documented. New optimizations can be maybe included via some Compatibility Level flag which will allow to use all optimizations if set to maximum value. Default for this compatibility flag should be the Start point of Angular version at least.

Describe alternatives you’ve considered

Compiling to Ivy in runtime which is time consuming and should not be part of startup for single page application

Relevant issues:
#41193

1 possible answer(s) on “Consider not doing breaking changes on Ivy

  1. This is something we discuss extensively as a team, as it gets to the heart of what semver means for a compiled framework like Angular.

    Formally, the Angular Compiler is a tool which translates the “Angular Language” (both decorated TypeScript and associated HTML templates) into executable JS code that can run in a browser.

    The Angular Language — as defined by its TypeScript APIs and the compiler’s implementation of the template language — is semantically versioned. Angular code can be written against one version of the framework and compiled with any other semantically compatible version. The compiler must honor this contract, and be able to accept and process code written against a previous semantic version.

    The code generated by the compiler, on the other hand, is not part of the Angular Language, and not considered to be public API (as explicitly stated in our Public API specification). As a tool for building applications, the compiler instead dictates requirements that:

    • The same version of the compiler be used to build all code within an application.
    • When deployed, that code needs to be bundled against the matching version of the Angular runtime.

    That is to say, you can take Angular code and choose a semantically compatible compiler/runtime version to build and deploy it with, but you cannot mix and match compiler/runtime versions within the same application.

    Why is it like this, though?

    As we described in the Ivy Library Distribution RFC, considering generated code to be public API would have two major impacts:

    • It would complicate library compatibility significantly.

    Currently, Angular’s versioning policy goes beyond what’s required by semver, and dictates that the compiler at major version X must be able to consume libraries published using version X – 1. In practice, compatibility is usually even greater, and libraries built with Angular 8 can still work in Angular 11 apps. This compatibility is possible because such libraries published with Angular 8 don’t actually contain compiled code from the version 8 compiler, but rather metadata which the version 11 compiler can later use to produce v11 compiled code.

    If the generated code were considered public API and treated as frozen, such compatibility would no longer be possible. Libraries would be tightly coupled to the major version of Angular used to publish them, and no such forward compatibility would be practical.

    • It would greatly hinder our ability to fix bugs, optimize, and innovate on the framework.

    This is probably the most important reason why we don’t want to do this. On a weekly basis, we make bug fixes, refactorings, and other changes to Angular’s runtime. Often these changes affect the generated code in small ways (such as the change you noticed in #41193 that streamlined generated DI factory functions). Freezing the structure of generated code and applying the same semver mechanics that we do for Angular’s public API would introduce a huge amount of friction for these types of changes, and severely impact our ability to improve the framework over time.

    Unfortunately a compatibility flag would not solve this problem – it would only increase the complexity as a single version of the compiler would have to support multiple compilation targets.

    One scenario that is hurt with current approach is having Microfrontends with Shared dependencies. For example if we use Webpack Module Federation and we want to reuse same Major Angular version between loaded modules (micro frontends) this will not work cause for example 11.2.4 and 11.2.5 modules will not be compatible.

    Yes, as you note, this restriction impacts the microfrontend pattern very directly, as microfrontends built with different versions of Angular will not be able to coexist in the same shell sharing the same runtime.

    As a result we would have to bundle everything in each of the micro frontends which will cause unacceptable size of an application.

    This is one approach – effectively treat them as separate applications. I’m not convinced this will work in all scenarios, since if there is any communication or code sharing between the applications, you run the risk of passing objects created with one version of Angular into the APIs of a second instance of the runtime. There’s also the challenges of having multiple Angular Zones active on the page, or multiple instances of services which are intended to be true singletons.

    An alternative is to enforce the same-version restriction across all deployed microfrontend applications – they must all be built and deployed with the same version of Angular. In such a scenario, upgrading the Angular version requires rebuilding and re-deploying all of the microfrontends. This is likely easiest to achieve in a monorepo setup where all of the applications share a single version of dependencies.

    Microfrontend support in the ecosystem of Javascript tooling is still in its infancy. I would expect, as the technology matures, that the tooling gets better at dealing with both the build time challenges as well as the devops complexity of managing such a complex ecosystem of applications and versioned dependencies.

    To summarize, then, this isn’t something that we can do currently, as we’re counting on the ability to make arbitrary changes to our generated code and to the runtime which supports it. As such, I’m going to close this issue as not actionable for us, but I’m more than happy to keep up the discussion here – I love to engage with our community on topics like this.