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
Member

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 angular/fw-compiler as code owners Oct 22, 2019
@googlebot

This comment has been minimized.

Copy link

googlebot commented Oct 22, 2019

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

This comment has been minimized.

Copy link

googlebot commented Oct 22, 2019

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

This comment has been minimized.

Copy link
Member Author

IgorMinar commented Oct 22, 2019

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

This comment has been minimized.

Copy link

mary-poppins commented Oct 23, 2019

@IgorMinar

This comment has been minimized.

Copy link
Member Author

IgorMinar commented Oct 23, 2019

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

@gkalpak

This comment has been minimized.

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

This comment has been minimized.

Copy link
Member

filipesilva commented Oct 23, 2019

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

This comment has been minimized.

Copy link
Member Author

IgorMinar commented Oct 23, 2019

@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.

@petebacondarwin

This comment has been minimized.

Copy link
Member

petebacondarwin commented Nov 6, 2019

This should help #33630

@mary-poppins

This comment has been minimized.

Copy link

mary-poppins commented Nov 20, 2019

@alxhub alxhub added cla: yes and removed cla: no labels Nov 20, 2019
@googlebot

This comment has been minimized.

Copy link

googlebot commented Nov 20, 2019

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.

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 IgorMinar:compiler/setClassMetadataPure branch from 561ffee to 559b341 Nov 20, 2019
@IgorMinar IgorMinar requested a review from angular/docs-infra as a code owner Nov 20, 2019
@googlebot

This comment has been minimized.

Copy link

googlebot commented Nov 20, 2019

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 googlebot added cla: no and removed cla: yes labels Nov 20, 2019
@alxhub alxhub added cla: yes and removed cla: no labels Nov 20, 2019
@googlebot

This comment has been minimized.

Copy link

googlebot commented Nov 20, 2019

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

This comment has been minimized.

Copy link
Contributor

alxhub commented Nov 20, 2019

@mary-poppins

This comment has been minimized.

Copy link

mary-poppins commented Nov 20, 2019

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,

This comment has been minimized.

Copy link
@gkalpak

gkalpak Nov 20, 2019

Member

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 added a commit to filipesilva/angular-cli that referenced this pull request Nov 20, 2019
vikerman added a commit to angular/angular-cli that referenced this pull request Nov 21, 2019
vikerman added 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 filipesilva mentioned this pull request Nov 21, 2019
3 of 14 tasks complete
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 added a commit that referenced this pull request Nov 21, 2019
alxhub added a commit that referenced this pull request Nov 21, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
9 participants
You can’t perform that action at this time.