Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(ivy): move setClassMetadata calls into a pure iife #33337

Closed

Conversation

IgorMinar
Copy link
Contributor

@IgorMinar IgorMinar commented Oct 22, 2019

This commit transforms the setClassMetadata calls generated by ngtsc from:

/*@__PURE__*/ setClassMetadata(...);

to:

/*@__PURE__*/ (function() {
  setClassMetadata(...);
})();

Without the IIFE, terser won't remove these function calls because the
function calls have arguments that themselves are function calls or other
impure expressions. In order to make the whole block be DCE-ed by terser,
we wrap it into IIFE and mark the IIFE as pure.

It should be noted that this change doesn't have any impact on CLI* with
build-optimizer, which removes the whole setClassMetadata block within
the webpack loader, so terser or webpack itself don't get to see it at
all. This is done to prevent cross-chunk retention issues caused by
webpack's internal module registry.

* actually we do expect a short-term size regression while angular/angular-cli#16228
is merged and released in the next rc of the CLI. But long term this
change does nothing to CLI + build-optimizer configuration and is done
primarly to correct the seeminly correct but non-function PURE annotation
that builds not using build-optimizer could rely on.

@IgorMinar IgorMinar requested review from a team as code owners October 22, 2019 23:56
@googlebot
Copy link

All (the pull request submitter and all commit authors) CLAs are signed, but one or more commits were authored or co-authored by someone other than the pull request submitter.

We need to confirm that all authors are ok with their commits being contributed to this project. Please have them confirm that by leaving a comment that contains only @googlebot I consent. in this pull request.

Note to project maintainer: There may be cases where the author cannot leave a comment, or the comment is not properly detected as consent. In those cases, you can manually confirm consent of the commit author(s), and set the cla label to yes (if enabled on your project).

ℹ️ Googlers: Go here for more info.

@IgorMinar IgorMinar force-pushed the compiler/setClassMetadataPure branch from 56644fe to f379dfa Compare October 22, 2019 23:56
@IgorMinar IgorMinar added cla: yes action: merge The PR is ready for merge by the caretaker merge: caretaker note Alert the caretaker performing the merge to check the PR for an out of normal action needed or note target: major This PR is targeted for the next major release and removed cla: no labels Oct 22, 2019
@googlebot
Copy link

A Googler has manually verified that the CLAs look good.

(Googler, please make sure the reason for overriding the CLA status is clearly documented in these comments.)

ℹ️ Googlers: Go here for more info.

@IgorMinar
Copy link
Contributor Author

merge-assistance: this is a cherry-pick of a commit from #31939 that has been already reviewed and approved. I'm splitting up that PR to land stuff that is good to go.

@mary-poppins
Copy link

You can preview f379dfa at https://pr33337-f379dfa.ngbuilds.io/.

@IgorMinar IgorMinar removed the merge: caretaker note Alert the caretaker performing the merge to check the PR for an out of normal action needed or note label Oct 23, 2019
@IgorMinar
Copy link
Contributor Author

I don't quite get why this is causing size regressions. The generated output before terser pass looks legit: https://hackmd.io/3mGNjS7nThmdq6qcAZQ_uw

And oddly enough the original PR from @alxhub is not failing with this issue: #31939

@AndrewKushnir AndrewKushnir added the action: cleanup The PR is in need of cleanup, either due to needing a rebase or in response to comments from reviews label Oct 23, 2019
@gkalpak
Copy link
Member

gkalpak commented Oct 23, 2019

And oddly enough the original PR from @alxhub is not failing with this issue: #31939

Even more oddly, the original PR #31939 is failing the size test for aio but not for integration projects 😕

@filipesilva
Copy link
Contributor

I've looked into this and think that I have identified the problem. It's related to how this PR changes the pattern that allows Build Optimizer (BO) to identify static properties for classes.

In what BO calls "class fold", it tries to "fold" all static properties accesses after the class into a class declaration. We know these are static properties because it is how TS emits them.

// input
export class TemplateRef { }
TemplateRef.__NG_ELEMENT_ID__ = () => SWITCH_TEMPLATE_REF_FACTORY(TemplateRef, ElementRef);

// output
export const TemplateRef = /*@__PURE__*/ function () {
  class TemplateRef { }
  TemplateRef.__NG_ELEMENT_ID__ = () => SWITCH_TEMPLATE_REF_FACTORY(TemplateRef, ElementRef);
  return TemplateRef;
}();

These property assignments can reference variables, causing them to be retained even if the base class isn't used. Having these property assignment inside the class allows everything to be removed if the class is removed.

Inside @angular/core there's a very important class called ApplicationRef that has many references. It looks like this after being processed by ngcc:

class ApplicationRef {
  // lots of code
}
ApplicationRef.ɵfac = function ApplicationRef_Factory(t) { return new (t || ApplicationRef)(ɵɵinject(NgZone), ɵɵinject(Console), ɵɵinject(Injector), ɵɵinject(ErrorHandler), ɵɵinject(ComponentFactoryResolver), ɵɵinject(ApplicationInitStatus)); };
ApplicationRef.ɵprov = ɵɵdefineInjectable({ token: ApplicationRef, factory: function (t) { return ApplicationRef.ɵfac(t); }, providedIn: null });
/*@__PURE__*/ setClassMetadata(ApplicationRef, [{
        type: Injectable
    }], function () { return [{ type: NgZone }, { type: Console }, { type: Injector }, { type: ErrorHandler }, { type: ComponentFactoryResolver }, { type: ApplicationInitStatus }]; }, { constructor: [], _zone: [], _console: [], _injector: [], _exceptionHandler: [], _componentFactoryResolver: [], _initStatus: [], _bootstrapListeners: [], _views: [], _runningTick: [], _enforceNoNewChanges: [], _stable: [], componentTypes: [], components: [], bootstrap: [], tick: [], attachView: [], detachView: [], _loadComponent: [], _unloadComponent: [], ngOnDestroy: [], viewCount: [] });
/**
 * \@internal
 */
ApplicationRef._tickScope = wtfCreateScope('ApplicationRef#tick()');
/** @nocollapse */
ApplicationRef.ctorParameters = () => [
    { type: NgZone },
    { type: Console },
    { type: Injector },
    { type: ErrorHandler },
    { type: ComponentFactoryResolver },
    { type: ApplicationInitStatus }
];

This pattern breaks BOs identification of class static properties because /*@__PURE__*/ setClassMetadata(... is in the middle of property accesses. This in turn caused the property accesses after setClassMetadata to stay outside the class and retain code.

In angular/angular-cli#15664 we got around this problem by identifying these calls and removing them before the "class fold" transform is ran. This way all properties went inside the class again.

With this PR the shape changes again:

ApplicationRef.ɵfac = function ApplicationRef_Factory(t) { return new (t || ApplicationRef)(ɵɵinject(NgZone), ɵɵinject(Console), ɵɵinject(Injector), ɵɵinject(ErrorHandler), ɵɵinject(ComponentFactoryResolver), ɵɵinject(ApplicationInitStatus)); };
ApplicationRef.ɵprov = ɵɵdefineInjectable({ token: ApplicationRef, factory: function (t) { return ApplicationRef.ɵfac(t); }, providedIn: null });
/*@__PURE__*/ (function () { setClassMetadata(ApplicationRef, [{
        type: Injectable
    }], function () { return [{ type: NgZone }, { type: Console }, { type: Injector }, { type: ErrorHandler }, { type: ComponentFactoryResolver }, { type: ApplicationInitStatus }]; }, { constructor: [], _zone: [], _console: [], _injector: [], _exceptionHandler: [], _componentFactoryResolver: [], _initStatus: [], _bootstrapListeners: [], _views: [], _runningTick: [], _enforceNoNewChanges: [], _stable: [], componentTypes: [], components: [], bootstrap: [], tick: [], attachView: [], detachView: [], _loadComponent: [], _unloadComponent: [], ngOnDestroy: [], viewCount: [] }); })();
/**
 * \@internal
 */
ApplicationRef._tickScope = wtfCreateScope('ApplicationRef#tick()');
/** @nocollapse */
ApplicationRef.ctorParameters = () => [
    { type: NgZone },
    { type: Console },
    { type: Injector },
    { type: ErrorHandler },
    { type: ComponentFactoryResolver },
    { type: ApplicationInitStatus }
];

/*@__PURE__*/ (function () { setClassMetadata(ApplicationRef, [{ is identified by BO and is not removed, which causes the properties afterwards to be retained by "class fold".

We could change CLI to also identify the new shape. But if we instead move the setClassMetadata call after all static properties, there is no confusion in BO's side. I think that's a better approach because trying to remove certain bits of code in the middle of static props was what caused the problem in the first place.

@IgorMinar
Copy link
Contributor Author

@filipesilva thanks for the info! the whole point of this change is to take away responsibility of the BO to do extra work. It would make no sense to make this change in the emit format and then keep on correcting the emit in BO.

Let's iterate more on this change and get the code emitted correctly. This change can wait and is not RC blocking.

@IgorMinar IgorMinar removed the action: merge The PR is ready for merge by the caretaker label Oct 23, 2019
@AndrewKushnir AndrewKushnir added target: patch This PR is targeted for the next patch release and removed target: major This PR is targeted for the next major release labels Oct 31, 2019
@atscott atscott added the area: compiler Issues related to `ngc`, Angular's template compiler label Nov 1, 2019
@ngbot ngbot bot added this to the needsTriage milestone Nov 1, 2019
@IgorMinar IgorMinar modified the milestones: needsTriage, v9-blockers Nov 4, 2019
petebacondarwin added a commit to petebacondarwin/angular that referenced this pull request Nov 6, 2019
@petebacondarwin
Copy link
Member

This should help #33630

@googlebot
Copy link

A Googler has manually verified that the CLAs look good.

(Googler, please make sure the reason for overriding the CLA status is clearly documented in these comments.)

ℹ️ Googlers: Go here for more info.

@alxhub alxhub added action: merge The PR is ready for merge by the caretaker and removed action: cleanup The PR is in need of cleanup, either due to needing a rebase or in response to comments from reviews labels Nov 20, 2019
This commit transforms the setClassMetadata calls generated by ngtsc from:

```typescript
/*@__PURE__*/ setClassMetadata(...);
```

to:

```typescript
/*@__PURE__*/ (function() {
  setClassMetadata(...);
})();
```

Without the IIFE, terser won't remove these function calls because the
function calls have arguments that themselves are function calls or other
impure expressions. In order to make the whole block be DCE-ed by terser,
we wrap it into IIFE and mark the IIFE as pure.

It should be noted that this change doesn't have any impact on CLI* with
build-optimizer, which removes the whole setClassMetadata block within
the webpack loader, so terser or webpack itself don't get to see it at
all. This is done to prevent cross-chunk retention issues caused by
webpack's internal module registry.

* actually we do expect a short-term size regression while
angular/angular-cli#16228
is merged and released in the next rc of the CLI. But long term this
change does nothing to CLI + build-optimizer configuration and is done
primarly to correct the seemingly correct but non-function PURE annotation
that builds not using build-optimizer could rely on.
@alxhub alxhub force-pushed the compiler/setClassMetadataPure branch from 561ffee to 559b341 Compare November 20, 2019 19:38
@IgorMinar IgorMinar requested a review from a team as a code owner November 20, 2019 19:38
@googlebot
Copy link

All (the pull request submitter and all commit authors) CLAs are signed, but one or more commits were authored or co-authored by someone other than the pull request submitter.

We need to confirm that all authors are ok with their commits being contributed to this project. Please have them confirm that by leaving a comment that contains only @googlebot I consent. in this pull request.

Note to project maintainer: There may be cases where the author cannot leave a comment, or the comment is not properly detected as consent. In those cases, you can manually confirm consent of the commit author(s), and set the cla label to yes (if enabled on your project).

ℹ️ Googlers: Go here for more info.

@googlebot
Copy link

A Googler has manually verified that the CLAs look good.

(Googler, please make sure the reason for overriding the CLA status is clearly documented in these comments.)

ℹ️ Googlers: Go here for more info.

@alxhub
Copy link
Member

alxhub commented Nov 20, 2019

Presubmit
Ivy Presubmit

@mary-poppins
Copy link

You can preview 559b341 at https://pr33337-559b341.ngbuilds.io/.

@@ -12,7 +12,7 @@
"master": {
"uncompressed": {
"runtime-es2015": 2987,
"main-es2015": 461159,
"main-es2015": 506857,
Copy link
Member

Choose a reason for hiding this comment

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

I know this is temporary, but 😱 😱 😱

alxhub added a commit that referenced this pull request Nov 20, 2019
This commit transforms the setClassMetadata calls generated by ngtsc from:

```typescript
/*@__PURE__*/ setClassMetadata(...);
```

to:

```typescript
/*@__PURE__*/ (function() {
  setClassMetadata(...);
})();
```

Without the IIFE, terser won't remove these function calls because the
function calls have arguments that themselves are function calls or other
impure expressions. In order to make the whole block be DCE-ed by terser,
we wrap it into IIFE and mark the IIFE as pure.

It should be noted that this change doesn't have any impact on CLI* with
build-optimizer, which removes the whole setClassMetadata block within
the webpack loader, so terser or webpack itself don't get to see it at
all. This is done to prevent cross-chunk retention issues caused by
webpack's internal module registry.

* actually we do expect a short-term size regression while
angular/angular-cli#16228
is merged and released in the next rc of the CLI. But long term this
change does nothing to CLI + build-optimizer configuration and is done
primarly to correct the seemingly correct but non-function PURE annotation
that builds not using build-optimizer could rely on.

PR Close #33337
@alxhub alxhub closed this in 08a4f10 Nov 20, 2019
vikerman pushed a commit to filipesilva/angular-cli that referenced this pull request Nov 20, 2019
vikerman pushed a commit to angular/angular-cli that referenced this pull request Nov 21, 2019
vikerman pushed a commit to angular/angular-cli that referenced this pull request Nov 21, 2019
filipesilva added a commit to filipesilva/angular that referenced this pull request Nov 21, 2019
filipesilva added a commit to filipesilva/angular that referenced this pull request Nov 21, 2019
filipesilva added a commit to filipesilva/angular that referenced this pull request Nov 21, 2019
alxhub pushed a commit that referenced this pull request Nov 21, 2019
alxhub pushed a commit that referenced this pull request Nov 21, 2019
@angular-automatic-lock-bot
Copy link

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.

@angular-automatic-lock-bot angular-automatic-lock-bot bot locked and limited conversation to collaborators Dec 21, 2019
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: compiler Issues related to `ngc`, Angular's template compiler cla: yes target: patch This PR is targeted for the next patch release
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

9 participants