From b479ed9407ed529ca568667871b4eb8642fd7c74 Mon Sep 17 00:00:00 2001 From: Jason Aden Date: Sat, 1 Jul 2017 10:29:56 -0700 Subject: [PATCH] perf(core): refactor NgZone, decrease size by 1.2Kb (#17773) - Remove getters - Hide private methods for better property renaming ``` 497893 May 31 11:26 core.umd.js 718073 May 31 11:26 core.umd.js.map 217108 May 31 11:26 core.umd.min.js 575092 May 31 11:26 core.umd.min.js.map ``` ``` 495594 May 31 11:28 core.umd.js 716943 May 31 11:28 core.umd.js.map 215826 May 31 11:28 core.umd.min.js 574401 May 31 11:28 core.umd.min.js.map ``` diff: 1,282 --- packages/core/src/zone/ng_zone.ts | 265 +++++++++--------- .../core/test/testability/testability_spec.ts | 14 +- packages/core/testing/src/ng_zone_mock.ts | 4 +- 3 files changed, 135 insertions(+), 148 deletions(-) diff --git a/packages/core/src/zone/ng_zone.ts b/packages/core/src/zone/ng_zone.ts index decbe1b17f..1b32aafea5 100644 --- a/packages/core/src/zone/ng_zone.ts +++ b/packages/core/src/zone/ng_zone.ts @@ -84,18 +84,37 @@ import {EventEmitter} from '../event_emitter'; * @experimental */ export class NgZone { - private outer: Zone; - private inner: Zone; + readonly hasPendingMicrotasks: boolean = false; + readonly hasPendingMacrotasks: boolean = false; - private _hasPendingMicrotasks: boolean = false; - private _hasPendingMacrotasks: boolean = false; + /** + * Whether there are no outstanding microtasks or macrotasks. + */ + readonly isStable: boolean = true; - private _isStable = true; - private _nesting: number = 0; - private _onUnstable: EventEmitter = new EventEmitter(false); - private _onMicrotaskEmpty: EventEmitter = new EventEmitter(false); - private _onStable: EventEmitter = new EventEmitter(false); - private _onErrorEvents: EventEmitter = new EventEmitter(false); + /** + * Notifies when code enters Angular Zone. This gets fired first on VM Turn. + */ + readonly onUnstable: EventEmitter = new EventEmitter(false); + + /** + * Notifies when there is no more microtasks enqueue in the current VM Turn. + * This is a hint for Angular to do change detection, which may enqueue more microtasks. + * For this reason this event can fire multiple times per VM Turn. + */ + readonly onMicrotaskEmpty: EventEmitter = new EventEmitter(false); + + /** + * Notifies when the last `onMicrotaskEmpty` has run and there are no more microtasks, which + * implies we are about to relinquish VM turn. + * This event gets called just once. + */ + readonly onStable: EventEmitter = new EventEmitter(false); + + /** + * Notifies that an error has been delivered. + */ + readonly onError: EventEmitter = new EventEmitter(false); constructor({enableLongStackTrace = false}) { if (typeof Zone == 'undefined') { @@ -103,18 +122,20 @@ export class NgZone { } Zone.assertZonePatched(); + const self = this as any as NgZonePrivate; + self._nesting = 0; - this.outer = this.inner = Zone.current; + self._outer = self._inner = Zone.current; if ((Zone as any)['wtfZoneSpec']) { - this.inner = this.inner.fork((Zone as any)['wtfZoneSpec']); + self._inner = self._inner.fork((Zone as any)['wtfZoneSpec']); } if (enableLongStackTrace && (Zone as any)['longStackTraceZoneSpec']) { - this.inner = this.inner.fork((Zone as any)['longStackTraceZoneSpec']); + self._inner = self._inner.fork((Zone as any)['longStackTraceZoneSpec']); } - this.forkInnerZoneWithAngularBehavior(); + forkInnerZoneWithAngularBehavior(self); } static isInAngularZone(): boolean { return Zone.current.get('isAngularZone') === true; } @@ -124,6 +145,7 @@ export class NgZone { throw new Error('Expected to be in Angular Zone, but it is not!'); } } + static assertNotInAngularZone(): void { if (NgZone.isInAngularZone()) { throw new Error('Expected to not be in Angular Zone, but it is!'); @@ -142,13 +164,13 @@ export class NgZone { * * If a synchronous error happens it will be rethrown and not reported via `onError`. */ - run(fn: () => any): any { return this.inner.run(fn); } + run(fn: () => any): any { return (this as any as NgZonePrivate)._inner.run(fn); } /** * Same as `run`, except that synchronous errors are caught and forwarded via `onError` and not * rethrown. */ - runGuarded(fn: () => any): any { return this.inner.runGuarded(fn); } + runGuarded(fn: () => any): any { return (this as any as NgZonePrivate)._inner.runGuarded(fn); } /** * Executes the `fn` function synchronously in Angular's parent zone and returns value returned by @@ -163,125 +185,94 @@ export class NgZone { * * Use {@link #run} to reenter the Angular zone and do work that updates the application model. */ - runOutsideAngular(fn: () => any): any { return this.outer.run(fn); } - - /** - * Notifies when code enters Angular Zone. This gets fired first on VM Turn. - */ - get onUnstable(): EventEmitter { return this._onUnstable; } - - /** - * Notifies when there is no more microtasks enqueue in the current VM Turn. - * This is a hint for Angular to do change detection, which may enqueue more microtasks. - * For this reason this event can fire multiple times per VM Turn. - */ - get onMicrotaskEmpty(): EventEmitter { return this._onMicrotaskEmpty; } - - /** - * Notifies when the last `onMicrotaskEmpty` has run and there are no more microtasks, which - * implies we are about to relinquish VM turn. - * This event gets called just once. - */ - get onStable(): EventEmitter { return this._onStable; } - - /** - * Notify that an error has been delivered. - */ - get onError(): EventEmitter { return this._onErrorEvents; } - - /** - * Whether there are no outstanding microtasks or macrotasks. - */ - get isStable(): boolean { return this._isStable; } - - get hasPendingMicrotasks(): boolean { return this._hasPendingMicrotasks; } - - get hasPendingMacrotasks(): boolean { return this._hasPendingMacrotasks; } - - private checkStable() { - if (this._nesting == 0 && !this._hasPendingMicrotasks && !this._isStable) { - try { - this._nesting++; - this._onMicrotaskEmpty.emit(null); - } finally { - this._nesting--; - if (!this._hasPendingMicrotasks) { - try { - this.runOutsideAngular(() => this._onStable.emit(null)); - } finally { - this._isStable = true; - } - } - } - } - } - - private forkInnerZoneWithAngularBehavior() { - this.inner = this.inner.fork({ - name: 'angular', - properties: {'isAngularZone': true}, - onInvokeTask: (delegate: ZoneDelegate, current: Zone, target: Zone, task: Task, - applyThis: any, applyArgs: any): any => { - try { - this.onEnter(); - return delegate.invokeTask(target, task, applyThis, applyArgs); - } finally { - this.onLeave(); - } - }, - - - onInvoke: (delegate: ZoneDelegate, current: Zone, target: Zone, callback: Function, - applyThis: any, applyArgs: any[], source: string): any => { - try { - this.onEnter(); - return delegate.invoke(target, callback, applyThis, applyArgs, source); - } finally { - this.onLeave(); - } - }, - - onHasTask: - (delegate: ZoneDelegate, current: Zone, target: Zone, hasTaskState: HasTaskState) => { - delegate.hasTask(target, hasTaskState); - if (current === target) { - // We are only interested in hasTask events which originate from our zone - // (A child hasTask event is not interesting to us) - if (hasTaskState.change == 'microTask') { - this.setHasMicrotask(hasTaskState.microTask); - } else if (hasTaskState.change == 'macroTask') { - this.setHasMacrotask(hasTaskState.macroTask); - } - } - }, - - onHandleError: (delegate: ZoneDelegate, current: Zone, target: Zone, error: any): boolean => { - delegate.handleError(target, error); - this.triggerError(error); - return false; - } - }); - } - - private onEnter() { - this._nesting++; - if (this._isStable) { - this._isStable = false; - this._onUnstable.emit(null); - } - } - - private onLeave() { - this._nesting--; - this.checkStable(); - } - - private setHasMicrotask(hasMicrotasks: boolean) { - this._hasPendingMicrotasks = hasMicrotasks; - this.checkStable(); - } - - private setHasMacrotask(hasMacrotasks: boolean) { this._hasPendingMacrotasks = hasMacrotasks; } - - private triggerError(error: any) { this._onErrorEvents.emit(error); } + runOutsideAngular(fn: () => any): any { return (this as any as NgZonePrivate)._outer.run(fn); } +} + +interface NgZonePrivate extends NgZone { + _outer: Zone; + _inner: Zone; + _nesting: number; + + hasPendingMicrotasks: boolean; + hasPendingMacrotasks: boolean; + isStable: boolean; +} + +function checkStable(zone: NgZonePrivate) { + if (zone._nesting == 0 && !zone.hasPendingMicrotasks && !zone.isStable) { + try { + zone._nesting++; + zone.onMicrotaskEmpty.emit(null); + } finally { + zone._nesting--; + if (!zone.hasPendingMicrotasks) { + try { + zone.runOutsideAngular(() => zone.onStable.emit(null)); + } finally { + zone.isStable = true; + } + } + } + } +} + +function forkInnerZoneWithAngularBehavior(zone: NgZonePrivate) { + zone._inner = zone._inner.fork({ + name: 'angular', + properties: {'isAngularZone': true}, + onInvokeTask: (delegate: ZoneDelegate, current: Zone, target: Zone, task: Task, applyThis: any, + applyArgs: any): any => { + try { + onEnter(zone); + return delegate.invokeTask(target, task, applyThis, applyArgs); + } finally { + onLeave(zone); + } + }, + + + onInvoke: (delegate: ZoneDelegate, current: Zone, target: Zone, callback: Function, + applyThis: any, applyArgs: any[], source: string): any => { + try { + onEnter(zone); + return delegate.invoke(target, callback, applyThis, applyArgs, source); + } finally { + onLeave(zone); + } + }, + + onHasTask: + (delegate: ZoneDelegate, current: Zone, target: Zone, hasTaskState: HasTaskState) => { + delegate.hasTask(target, hasTaskState); + if (current === target) { + // We are only interested in hasTask events which originate from our zone + // (A child hasTask event is not interesting to us) + if (hasTaskState.change == 'microTask') { + zone.hasPendingMicrotasks = hasTaskState.microTask; + checkStable(zone); + } else if (hasTaskState.change == 'macroTask') { + zone.hasPendingMacrotasks = hasTaskState.macroTask; + } + } + }, + + onHandleError: (delegate: ZoneDelegate, current: Zone, target: Zone, error: any): boolean => { + delegate.handleError(target, error); + zone.onError.emit(error); + return false; + } + }); +} + +function onEnter(zone: NgZonePrivate) { + zone._nesting++; + if (zone.isStable) { + zone.isStable = false; + zone.onUnstable.emit(null); + } +} + +function onLeave(zone: NgZonePrivate) { + zone._nesting--; + checkStable(zone); } diff --git a/packages/core/test/testability/testability_spec.ts b/packages/core/test/testability/testability_spec.ts index 17fe365830..95af50ffaf 100644 --- a/packages/core/test/testability/testability_spec.ts +++ b/packages/core/test/testability/testability_spec.ts @@ -28,22 +28,20 @@ function microTask(fn: Function): void { @Injectable() class MockNgZone extends NgZone { /** @internal */ - _onUnstableStream: EventEmitter; - get onUnstable() { return this._onUnstableStream; } + onUnstable: EventEmitter; /** @internal */ - _onStableStream: EventEmitter; - get onStable() { return this._onStableStream; } + onStable: EventEmitter; constructor() { super({enableLongStackTrace: false}); - this._onUnstableStream = new EventEmitter(false); - this._onStableStream = new EventEmitter(false); + this.onUnstable = new EventEmitter(false); + this.onStable = new EventEmitter(false); } - unstable(): void { this._onUnstableStream.emit(null); } + unstable(): void { this.onUnstable.emit(null); } - stable(): void { this._onStableStream.emit(null); } + stable(): void { this.onStable.emit(null); } } export function main() { diff --git a/packages/core/testing/src/ng_zone_mock.ts b/packages/core/testing/src/ng_zone_mock.ts index 8dae724ad9..a3356d8e6b 100644 --- a/packages/core/testing/src/ng_zone_mock.ts +++ b/packages/core/testing/src/ng_zone_mock.ts @@ -14,12 +14,10 @@ import {EventEmitter, Injectable, NgZone} from '@angular/core'; */ @Injectable() export class MockNgZone extends NgZone { - private _mockOnStable: EventEmitter = new EventEmitter(false); + onStable: EventEmitter = new EventEmitter(false); constructor() { super({enableLongStackTrace: false}); } - get onStable() { return this._mockOnStable; } - run(fn: Function): any { return fn(); } runOutsideAngular(fn: Function): any { return fn(); }