Skip to content

feat(animations): Provide a way to lazy load the animations#50738

Closed
JeanMeche wants to merge 3 commits intoangular:mainfrom
JeanMeche:feat/animations-wip
Closed

feat(animations): Provide a way to lazy load the animations#50738
JeanMeche wants to merge 3 commits intoangular:mainfrom
JeanMeche:feat/animations-wip

Conversation

@JeanMeche
Copy link
Copy Markdown
Member

@JeanMeche JeanMeche commented Jun 15, 2023

As part of #50399, this work here is a proposition to allow the lazy loading of animations code that can shave off up to 16KB gzipped of the main bundle !

The PR introduces a new entry point in the platform-browser package : lazy-animations. This has been necessary because the eager providers (BROWSER_ANIMATIONS_PROVIDERS and BROWSER_NOOP_ANIMATIONS_PROVIDERS) were retaining symbols from @angular/animation/browser making the code splitting & lazy loading impossible.

In this entry point, you will find the new provideLazyLoadingAnimations() function which will provide a new RendererFactory2 : AsyncAnimationRendererFactory.

AsyncAnimationRendererFactory will create a new DynamicDelegationRenderer which will delegate to the default renderer by default.
Aside from the renderer, the new factory will lazily import the @angular/animations/browser. Once the package is loaded, the factory will switch the delegate of DynamicDelegationRenderer to an AnimationRenderer.

The side effect of this implementation is that the rendering might not include the styles from the animations defined in components until the module is loaded. This should an acceptable drawback.

Also, I'd like to thank @AndrewKushnir for his help on this PR.

PR Type

  • Feature

Does this PR introduce a breaking change?

  • Nos

@angular-robot angular-robot bot added the detected: feature PR contains a feature commit label Jun 15, 2023
@JeanMeche JeanMeche force-pushed the feat/animations-wip branch 2 times, most recently from 1e600bb to 9eb4b94 Compare June 15, 2023 23:08
@angular-robot angular-robot bot removed the detected: feature PR contains a feature commit label Jun 15, 2023
@JeanMeche JeanMeche force-pushed the feat/animations-wip branch from 9eb4b94 to bc81f84 Compare June 15, 2023 23:37
@ngbot ngbot bot added this to the Backlog milestone Jun 16, 2023
@JeanMeche JeanMeche force-pushed the feat/animations-wip branch 3 times, most recently from 22e43cc to a3b6b41 Compare June 17, 2023 23:38
@angular-robot angular-robot bot added the detected: feature PR contains a feature commit label Jun 17, 2023
@JeanMeche JeanMeche force-pushed the feat/animations-wip branch 5 times, most recently from 7456abb to c773b6c Compare June 19, 2023 15:34
@AndrewKushnir AndrewKushnir self-assigned this Jun 19, 2023
@JeanMeche JeanMeche force-pushed the feat/animations-wip branch 3 times, most recently from 7766964 to 52424c2 Compare June 20, 2023 08:32
@angular-robot angular-robot bot removed the detected: feature PR contains a feature commit label Jun 20, 2023
@JeanMeche JeanMeche force-pushed the feat/animations-wip branch 2 times, most recently from a76ee9c to 5b8836b Compare June 20, 2023 15:24
@angular-robot angular-robot bot added the detected: feature PR contains a feature commit label Jun 20, 2023
@JeanMeche JeanMeche force-pushed the feat/animations-wip branch 5 times, most recently from 7acd70a to 62da920 Compare June 22, 2023 16:46
Copy link
Copy Markdown
Contributor

@AndrewKushnir AndrewKushnir left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@JeanMeche thanks for making progress on this PR, leaving some comments after the initial review with @jessicajaniuk.

Comment on lines 16 to 18
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In this scenario the NoopAnimationDriver and NoopAnimationStyleNormalizer would become non-tree-shakable (as they are eagerly referenced). Can we refactor the code to avoid this?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@JeanMeche as a possible solution, we can mimic the provideNoopAnimations() function and call it provideNoopAnimationsAsync(), so that we create one of the engines depending on the function used.

Copy link
Copy Markdown
Member Author

@JeanMeche JeanMeche Sep 19, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

About that, I thought about your suggestion. I doesn't seem applicable to me.

createEngine(), the drivers and the styleNormalizers are part of the lazy loaded bundle so we cannot use providers.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you please elaborate a bit on this? My assumption is that having the provideNoopAnimationsAsync() function would allow us to reference the noop-related code there, so we don't have to do it in the provideAnimationsAsync() function. As an option, we can have a callback there that we define there:

      useFactory: (doc: Document, renderer: DomRendererFactory2, zone: NgZone) => {
        // in `provideAnimationsAsync`:
        const getEngine = (m) => m.getEngine();
        // in `provideNoopAnimationsAsync`:
        const getEngine = (m) => m.getNoopEngine();
        return new AsyncAnimationRendererFactory(doc, renderer, zone, type, getEngine);
      },

Copy link
Copy Markdown
Member Author

@JeanMeche JeanMeche Sep 20, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure,

NoopAnimationDriver/NoopAnimationStyleNormalizer are part of the animation module, the module we want to lazy load. By referencing anything of that module outside of it, we will break the code splitting/lazy loading, so we can't define them in the providers.

Currently createEngine() is not imported statically but via the dynamic import of @angular/animations/browser. That why it doesn't break the lazy loading.

I have tried using the animationType parameter to determine which of createEngine or createNoopEngine to call. But both builder don't seem able to infer that animationType is readonly in AsyncAnimationRendererFactory (and it doesn't tree shake the unused one). (demo)

Also, I did try your other solution with the callback, It did not work, likely because the lazy loaded module is passed to the callback function, so the minifer doesn't know what to keep / what to tree-shake.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we extract this into a separate PR?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is definitely some work to do on this.
Pulling the BrowserAnimationBuilder breaks the code splitting because it's in the platform-browser/animations (and not in the async secondary entry we had to created).

I'll remove this from the scope of this PR.

@JeanMeche JeanMeche force-pushed the feat/animations-wip branch 5 times, most recently from caa720c to f372012 Compare September 18, 2023 23:49
@JeanMeche
Copy link
Copy Markdown
Member Author

@AndrewKushnir @jessicajaniuk Thank you both for your feedbacks !

I have a great news, for a reason I could not determine, the import of the core module in not an issue anymore when using the webpack builder !

Copy link
Copy Markdown
Contributor

@AndrewKushnir AndrewKushnir left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have a great news, for a reason I could not determine, the import of the core module in not an issue anymore when using the webpack builder !

This is great 🎉

Comment on lines 16 to 18
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@JeanMeche as a possible solution, we can mimic the provideNoopAnimations() function and call it provideNoopAnimationsAsync(), so that we create one of the engines depending on the function used.

Copy link
Copy Markdown
Contributor

@AndrewKushnir AndrewKushnir left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@JeanMeche thanks for addressing the feedback!

I'm also thinking that it'd be great to add a new integration testing app to have a way to capture the bundle size in the size tracking golden files (so that we confirm that everything works as expected and there are no regressions in the future). We can copy the integration/animations app and update it to use the provideAnimationsAsync function there.

Comment on lines 16 to 18
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you please elaborate a bit on this? My assumption is that having the provideNoopAnimationsAsync() function would allow us to reference the noop-related code there, so we don't have to do it in the provideAnimationsAsync() function. As an option, we can have a callback there that we define there:

      useFactory: (doc: Document, renderer: DomRendererFactory2, zone: NgZone) => {
        // in `provideAnimationsAsync`:
        const getEngine = (m) => m.getEngine();
        // in `provideNoopAnimationsAsync`:
        const getEngine = (m) => m.getNoopEngine();
        return new AsyncAnimationRendererFactory(doc, renderer, zone, type, getEngine);
      },

@JeanMeche
Copy link
Copy Markdown
Member Author

@AndrewKushnir I've added the integration tests and checked, they fail when the code split/lazy loading is broken.

Copy link
Copy Markdown
Contributor

@AndrewKushnir AndrewKushnir left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@JeanMeche thanks for addressing the feedback, the change looks great! 👍

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is great! 🎉

Copy link
Copy Markdown
Contributor

@jessicajaniuk jessicajaniuk left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!!!! 🎉

`provideLazyLoadedAnimations()` returns providers which allow the lazy loading of the animation module.

Lazy loading of the animation code can shave off up to 16KB gzipped of the main bundle.
This integration test aims to cover that we do not break the code splitting of the animation module when we use `provideAnimationsAsync()`.
Let's have the same test app for async and eagerly loaded animations.
Copy link
Copy Markdown
Contributor

@jessicajaniuk jessicajaniuk left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The issue was resolved in #51910. Once we have G3 issues resolved, this is good to go.

reviewed-for: fw-animations, fw-core, integration-tests, public-api, size-tracking

Copy link
Copy Markdown
Member

@devversion devversion left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM for Bazel

Copy link
Copy Markdown
Contributor

@dylhunn dylhunn left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

reviewed-for: public-api

@jessicajaniuk
Copy link
Copy Markdown
Contributor

TGP

@jessicajaniuk
Copy link
Copy Markdown
Contributor

TGP is green. This is ready to merge.

@alxhub
Copy link
Copy Markdown
Member

alxhub commented Sep 29, 2023

This PR was merged into the repository by commit 81e7f5b.

@robertIsaac
Copy link
Copy Markdown
Contributor

was it renamed to provideAnimationsAsync?

@jessicajaniuk
Copy link
Copy Markdown
Contributor

@robertIsaac yes, it was.

@robertIsaac
Copy link
Copy Markdown
Contributor

I tried it, but saw size increases instead of decrease
The component that have animation is deferred, can it be related?
I can share a reproduce repo if you wish to investigate further

@jessicajaniuk
Copy link
Copy Markdown
Contributor

@robertIsaac Please file an issue with the repro and we can take a look.

@JeanMeche
Copy link
Copy Markdown
Member Author

@robertIsaac Please open an new issue with a repro repo and we'll happily investigate that 😊

@angular-automatic-lock-bot
Copy link
Copy Markdown

This issue has been automatically locked due to inactivity.
Please file a new issue if you are encountering a similar or related problem.

Read more about our automatic conversation locking policy.

This action has been performed automatically by a bot.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

action: merge The PR is ready for merge by the caretaker area: animations detected: feature PR contains a feature commit target: major This PR is targeted for the next major release

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants