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

Add integration test for side effects, ban top-level property access #29329

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion integration/_payload-limits.json
Expand Up @@ -3,7 +3,7 @@
"master": {
"uncompressed": {
"runtime": 1497,
"main": 164945,
"main": 166739,
Copy link
Contributor

Choose a reason for hiding this comment

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

@filipesilva why this size regression?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think this regression is due to IIFEs being more code, especially when transpiled to ES5. Most of those IIFEs remain because their variable is actually used in a default CLI app.

For instance

// original
const CORE_TOKENS = {
    'ApplicationRef': ApplicationRef,
    'NgZone': NgZone,
};
// transpiled to es5
var CORE_TOKENS = {
    'ApplicationRef': ApplicationRef,
    'NgZone': NgZone,
};

versus

// modified with IIFE
const CORE_TOKENS = (() => ({
  'ApplicationRef': ApplicationRef,
  'NgZone': NgZone,
}))();
// transpiled to es5
var CORE_TOKENS = (function () { return ({
    'ApplicationRef': 'ApplicationRef',
    'NgZone': 'NgZone',
}); })();

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Tried to validate this by making some before/after diffs with prettified output. They are pretty noisy because vars change name.

But you can see it here: filipesilva/compare-toplevel-prop-iife@3f116e9#diff-7a9076d6d94e62c13d641aa71f19ae8eL4109

Before:

    var Os = {
      ApplicationRef: ri,
      NgZone: Fo
    };

After

    var Ou = function () {
      return {
        ApplicationRef: ni,
        NgZone: Ho
      }
    }();

Another example in filipesilva/compare-toplevel-prop-iife@3f116e9#diff-7a9076d6d94e62c13d641aa71f19ae8eL1558

I eyeballed the whole diff and couldn't see anything else out of place. A couple of blocks moved because their var name changed but that was it.

I also found what the concrete before and after numbers are though. It is now 166,558 before and 166,741 after.

So I think what's really happening is that this PR isn't increasing the size by more than 1%. Rather, the payload limited is listed as 164,945, which allows for values between -1% (163,295) and +1%(‭166,594). But the current size was already almost breaking the +1% threshold, and the 183 Bytes just pushed it over the edge.

"polyfills": 43626
}
}
Expand Down
3 changes: 3 additions & 0 deletions integration/side-effects/.gitignore
@@ -0,0 +1,3 @@
# The check-side-effects package generates and deletes this file.
# If the process is killed, it will be left behind.
check-side-effects.tmp-input.js
9 changes: 9 additions & 0 deletions integration/side-effects/README.md
@@ -0,0 +1,9 @@
This test checks if the side effects for loading Angular packages have changed using <https://github.com/filipesilva/check-side-effects>.

Running `yarn test` will check all ES modules listed in `side-effects.json`.

Running `yarn update` will update any changed side effects.

To add a new ES module to this test, add a new entry in `side-effects.json`.

Usually the ESM and FESM should have the same output, but retained objects that were renamed during the flattening step will leave behind a different name.
19 changes: 19 additions & 0 deletions integration/side-effects/package.json
@@ -0,0 +1,19 @@
{
"name": "angular-side-effects",
"version": "0.0.0",
"license": "MIT",
"scripts": {
"test": "check-side-effects --test side-effects.json --pure-getters",
"update": "yarn test --update"
},
"dependencies": {
"@angular/animations": "file:../../dist/packages-dist/animations",
"@angular/common": "file:../../dist/packages-dist/common",
"@angular/core": "file:../../dist/packages-dist/core",
"@angular/elements": "file:../../dist/packages-dist/elements",
"@angular/forms": "file:../../dist/packages-dist/forms",
"@angular/platform-browser": "file:../../dist/packages-dist/platform-browser",
"@angular/router": "file:../../dist/packages-dist/router",
"check-side-effects": "file:../../node_modules/check-side-effects"
}
}
132 changes: 132 additions & 0 deletions integration/side-effects/side-effects.json
@@ -0,0 +1,132 @@
{
"tests": [
{
"esModules": "./node_modules/@angular/animations/esm5/animations.js",
"expectedOutput": "./snapshots/animations/esm5.js"
},
{
"esModules": "./node_modules/@angular/animations/fesm5/animations.js",
"expectedOutput": "./snapshots/animations/esm5.js"
},
{
"esModules": "./node_modules/@angular/animations/esm2015/animations.js",
"expectedOutput": "./snapshots/animations/esm2015.js"
},
{
"esModules": "./node_modules/@angular/animations/fesm2015/animations.js",
"expectedOutput": "./snapshots/animations/esm2015.js"
},
{
"esModules": "./node_modules/@angular/animations/esm5/browser/browser.js",
"expectedOutput": "./snapshots/animations-browser/esm5.js"
},
{
"esModules": "./node_modules/@angular/animations/fesm5/browser.js",
"expectedOutput": "./snapshots/animations-browser/esm5.js"
},
{
"esModules": "./node_modules/@angular/animations/esm2015/browser/browser.js",
"expectedOutput": "./snapshots/animations-browser/esm2015.js"
},
{
"esModules": "./node_modules/@angular/animations/fesm2015/browser.js",
"expectedOutput": "./snapshots/animations-browser/esm2015.js"
},
{
"esModules": "./node_modules/@angular/common/esm5/common.js",
"expectedOutput": "./snapshots/common/esm5.js"
},
{
"esModules": "./node_modules/@angular/common/fesm5/common.js",
"expectedOutput": "./snapshots/common/esm5.js"
},
{
"esModules": "./node_modules/@angular/common/esm2015/common.js",
"expectedOutput": "./snapshots/common/esm2015.js"
},
{
"esModules": "./node_modules/@angular/common/fesm2015/common.js",
"expectedOutput": "./snapshots/common/esm2015.js"
},
{
"esModules": "./node_modules/@angular/core/esm5/core.js",
"expectedOutput": "./snapshots/core/esm5.js"
},
{
"esModules": "./node_modules/@angular/core/fesm5/core.js",
"expectedOutput": "./snapshots/core/esm5.js"
},
{
"esModules": "./node_modules/@angular/core/esm2015/core.js",
"expectedOutput": "./snapshots/core/esm2015.js"
},
{
"esModules": "./node_modules/@angular/core/fesm2015/core.js",
"expectedOutput": "./snapshots/core/esm2015.js"
},
{
"esModules": "./node_modules/@angular/elements/esm5/elements.js",
"expectedOutput": "./snapshots/elements/esm5.js"
},
{
"esModules": "./node_modules/@angular/elements/fesm5/elements.js",
"expectedOutput": "./snapshots/elements/esm5.js"
},
{
"esModules": "./node_modules/@angular/elements/esm2015/elements.js",
"expectedOutput": "./snapshots/elements/esm2015.js"
},
{
"esModules": "./node_modules/@angular/elements/fesm2015/elements.js",
"expectedOutput": "./snapshots/elements/esm2015.js"
},
{
"esModules": "./node_modules/@angular/forms/esm5/forms.js",
"expectedOutput": "./snapshots/forms/esm5.js"
},
{
"esModules": "./node_modules/@angular/forms/fesm5/forms.js",
"expectedOutput": "./snapshots/forms/esm5.js"
},
{
"esModules": "./node_modules/@angular/forms/esm2015/forms.js",
"expectedOutput": "./snapshots/forms/esm2015.js"
},
{
"esModules": "./node_modules/@angular/forms/fesm2015/forms.js",
"expectedOutput": "./snapshots/forms/esm2015.js"
},
{
"esModules": "./node_modules/@angular/platform-browser/esm5/platform-browser.js",
"expectedOutput": "./snapshots/platform-browser/esm5.js"
},
{
"esModules": "./node_modules/@angular/platform-browser/fesm5/platform-browser.js",
"expectedOutput": "./snapshots/platform-browser/esm5.js"
},
{
"esModules": "./node_modules/@angular/platform-browser/esm2015/platform-browser.js",
"expectedOutput": "./snapshots/platform-browser/esm2015.js"
},
{
"esModules": "./node_modules/@angular/platform-browser/fesm2015/platform-browser.js",
"expectedOutput": "./snapshots/platform-browser/esm2015.js"
},
{
"esModules": "./node_modules/@angular/router/esm5/router.js",
"expectedOutput": "./snapshots/router/esm5.js"
},
{
"esModules": "./node_modules/@angular/router/fesm5/router.js",
"expectedOutput": "./snapshots/router/esm5.js"
},
{
"esModules": "./node_modules/@angular/router/esm2015/router.js",
"expectedOutput": "./snapshots/router/esm2015.js"
},
{
"esModules": "./node_modules/@angular/router/fesm2015/router.js",
"expectedOutput": "./snapshots/router/esm2015.js"
}
]
}
@@ -0,0 +1,3 @@
import "@angular/animations";

import "@angular/core";
5 changes: 5 additions & 0 deletions integration/side-effects/snapshots/animations-browser/esm5.js
@@ -0,0 +1,5 @@
import "tslib";

import "@angular/animations";

import "@angular/core";
1 change: 1 addition & 0 deletions integration/side-effects/snapshots/animations/esm2015.js
@@ -0,0 +1 @@

1 change: 1 addition & 0 deletions integration/side-effects/snapshots/animations/esm5.js
@@ -0,0 +1 @@

1 change: 1 addition & 0 deletions integration/side-effects/snapshots/common/esm2015.js
@@ -0,0 +1 @@
import "@angular/core";
3 changes: 3 additions & 0 deletions integration/side-effects/snapshots/common/esm5.js
@@ -0,0 +1,3 @@
import "@angular/core";

import "tslib";
151 changes: 151 additions & 0 deletions integration/side-effects/snapshots/core/esm2015.js
@@ -0,0 +1,151 @@
import { Subject, Subscription } from "rxjs";

import "rxjs/operators";

function getGlobal() {
const __globalThis = "undefined" !== typeof globalThis && globalThis;
const __window = "undefined" !== typeof window && window;
const __self = "undefined" !== typeof self && "undefined" !== typeof WorkerGlobalScope && self instanceof WorkerGlobalScope && self;
const __global = "undefined" !== typeof global && global;
return __globalThis || __global || __window || __self;
}

const _global = getGlobal();

let _symbolIterator = null;

function getSymbolIterator() {
if (!_symbolIterator) {
const Symbol = _global["Symbol"];
if (Symbol && Symbol.iterator) _symbolIterator = Symbol.iterator; else {
const keys = Object.getOwnPropertyNames(Map.prototype);
for (let i = 0; i < keys.length; ++i) {
const key = keys[i];
if ("entries" !== key && "size" !== key && Map.prototype[key] === Map.prototype["entries"]) _symbolIterator = key;
}
}
}
return _symbolIterator;
}

if ("undefined" === typeof ngI18nClosureMode) _global["ngI18nClosureMode"] = "undefined" !== typeof goog && "function" === typeof goog.getMsg;

function flatten(list, mapFn) {
const result = [];
let i = 0;
while (i < list.length) {
const item = list[i];
if (Array.isArray(item)) if (item.length > 0) {
list = item.concat(list.slice(i + 1));
i = 0;
} else i++; else {
result.push(mapFn ? mapFn(item) : item);
i++;
}
}
return result;
}

class EventEmitter extends Subject {
constructor(isAsync = false) {
super();
this.__isAsync = isAsync;
}
emit(value) {
super.next(value);
}
subscribe(generatorOrNext, error, complete) {
let schedulerFn;
let errorFn = err => null;
let completeFn = () => null;
if (generatorOrNext && "object" === typeof generatorOrNext) {
schedulerFn = this.__isAsync ? value => {
setTimeout(() => generatorOrNext.next(value));
} : value => {
generatorOrNext.next(value);
};
if (generatorOrNext.error) errorFn = this.__isAsync ? err => {
setTimeout(() => generatorOrNext.error(err));
} : err => {
generatorOrNext.error(err);
};
if (generatorOrNext.complete) completeFn = this.__isAsync ? () => {
setTimeout(() => generatorOrNext.complete());
} : () => {
generatorOrNext.complete();
};
} else {
schedulerFn = this.__isAsync ? value => {
setTimeout(() => generatorOrNext(value));
} : value => {
generatorOrNext(value);
};
if (error) errorFn = this.__isAsync ? err => {
setTimeout(() => error(err));
} : err => {
error(err);
};
if (complete) completeFn = this.__isAsync ? () => {
setTimeout(() => complete());
} : () => {
complete();
};
}
const sink = super.subscribe(schedulerFn, errorFn, completeFn);
if (generatorOrNext instanceof Subscription) generatorOrNext.add(sink);
return sink;
}
}

class QueryList {
constructor() {
this.dirty = true;
this._results = [];
this.changes = new EventEmitter();
this.length = 0;
}
map(fn) {
return this._results.map(fn);
}
filter(fn) {
return this._results.filter(fn);
}
find(fn) {
return this._results.find(fn);
}
reduce(fn, init) {
return this._results.reduce(fn, init);
}
forEach(fn) {
this._results.forEach(fn);
}
some(fn) {
return this._results.some(fn);
}
toArray() {
return this._results.slice();
}
[getSymbolIterator()]() {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This computed method name seems to be causing all the retained code here.

Without it, the result is just

import "rxjs";

import "rxjs/operators";

Copy link
Contributor

Choose a reason for hiding this comment

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

The iterator symbol should probably just be polyfilled like any other polyfill (from core-js or manually)

return this._results[getSymbolIterator()]();
}
toString() {
return this._results.toString();
}
reset(resultsTree) {
this._results = flatten(resultsTree);
this.dirty = false;
this.length = this._results.length;
this.last = this._results[this.length - 1];
this.first = this._results[0];
}
notifyOnChanges() {
this.changes.emit(this);
}
setDirty() {
this.dirty = true;
}
destroy() {
this.changes.complete();
this.changes.unsubscribe();
}
}
17 changes: 17 additions & 0 deletions integration/side-effects/snapshots/core/esm5.js
@@ -0,0 +1,17 @@
import "tslib";

import "rxjs";

import "rxjs/operators";

function getGlobal() {
var __globalThis = "undefined" !== typeof globalThis && globalThis;
var __window = "undefined" !== typeof window && window;
var __self = "undefined" !== typeof self && "undefined" !== typeof WorkerGlobalScope && self instanceof WorkerGlobalScope && self;
var __global = "undefined" !== typeof global && global;
return __globalThis || __global || __window || __self;
}

var _global = getGlobal();

if ("undefined" === typeof ngI18nClosureMode) _global["ngI18nClosureMode"] = "undefined" !== typeof goog && "function" === typeof goog.getMsg;
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think what causes this to be retained is the typeof comparison.

5 changes: 5 additions & 0 deletions integration/side-effects/snapshots/elements/esm2015.js
@@ -0,0 +1,5 @@
import "@angular/core";

import "rxjs";

import "rxjs/operators";
7 changes: 7 additions & 0 deletions integration/side-effects/snapshots/elements/esm5.js
@@ -0,0 +1,7 @@
import "tslib";

import "@angular/core";

import "rxjs";

import "rxjs/operators";
7 changes: 7 additions & 0 deletions integration/side-effects/snapshots/forms/esm2015.js
@@ -0,0 +1,7 @@
import "@angular/core";

import "@angular/platform-browser";

import "rxjs";

import "rxjs/operators";
9 changes: 9 additions & 0 deletions integration/side-effects/snapshots/forms/esm5.js
@@ -0,0 +1,9 @@
import "tslib";

import "@angular/core";

import "@angular/platform-browser";

import "rxjs";

import "rxjs/operators";