Skip to content
This repository has been archived by the owner on May 1, 2020. It is now read-only.

Commit

Permalink
feature(optimization): purge ctor parameters static field from all no…
Browse files Browse the repository at this point in the history
…n-entry components in ionic and angular
  • Loading branch information
danbucholtz committed Apr 5, 2017
1 parent 254bd8a commit 8f49908
Show file tree
Hide file tree
Showing 7 changed files with 202 additions and 23 deletions.
2 changes: 2 additions & 0 deletions src/optimization.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ describe('optimization task', () => {

spyOn(helpers, helpers.getBooleanPropertyValue.name).and.returnValue(false);
spyOn(decorators, decorators.purgeStaticFieldDecorators.name);
spyOn(decorators, decorators.purgeStaticCtorFields.name);
spyOn(decorators, decorators.purgeTranspiledDecorators.name);
spyOn(treeshake, treeshake.calculateUnusedComponents.name);

Expand All @@ -28,6 +29,7 @@ describe('optimization task', () => {
// assert
expect(result).toBeTruthy();
expect(decorators.purgeStaticFieldDecorators).not.toHaveBeenCalled();
expect(decorators.purgeStaticCtorFields).not.toHaveBeenCalled();
expect(decorators.purgeTranspiledDecorators).not.toHaveBeenCalled();
expect(treeshake.calculateUnusedComponents).not.toHaveBeenCalled();
});
Expand Down
3 changes: 2 additions & 1 deletion src/optimization.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { BuildError } from './util/errors';
import { getBooleanPropertyValue, getStringPropertyValue, webpackStatsToDependencyMap, printDependencyMap } from './util/helpers';
import { BuildContext, TaskInfo } from './util/interfaces';
import { runWebpackFullBuild, WebpackConfig } from './webpack';
import { addPureAnnotation, purgeStaticFieldDecorators, purgeTranspiledDecorators } from './optimization/decorators';
import { addPureAnnotation, purgeStaticCtorFields, purgeStaticFieldDecorators, purgeTranspiledDecorators } from './optimization/decorators';
import { getAppModuleNgFactoryPath, calculateUnusedComponents, purgeUnusedImportsAndExportsFromIndex, purgeComponentNgFactoryImportAndUsage, purgeProviderControllerImportAndUsage, purgeProviderClassNameFromIonicModuleForRoot } from './optimization/treeshake';

export function optimization(context: BuildContext, configFile: string) {
Expand Down Expand Up @@ -83,6 +83,7 @@ function removeDecorators(context: BuildContext) {
jsFiles.forEach(jsFile => {
let magicString = new MagicString(jsFile.content);
magicString = purgeStaticFieldDecorators(jsFile.path, jsFile.content, getStringPropertyValue(Constants.ENV_VAR_IONIC_ANGULAR_DIR), getStringPropertyValue(Constants.ENV_VAR_AT_ANGULAR_DIR), context.srcDir, magicString);
magicString = purgeStaticCtorFields(jsFile.path, jsFile.content, getStringPropertyValue(Constants.ENV_VAR_IONIC_ANGULAR_DIR), getStringPropertyValue(Constants.ENV_VAR_AT_ANGULAR_DIR), context.srcDir, magicString);
magicString = purgeTranspiledDecorators(jsFile.path, jsFile.content, getStringPropertyValue(Constants.ENV_VAR_IONIC_ANGULAR_DIR), getStringPropertyValue(Constants.ENV_VAR_AT_ANGULAR_DIR), context.srcDir, magicString);
magicString = addPureAnnotation(jsFile.path, jsFile.content, getStringPropertyValue(Constants.ENV_VAR_IONIC_ANGULAR_DIR), getStringPropertyValue(Constants.ENV_VAR_AT_ANGULAR_DIR), context.srcDir, magicString);
jsFile.content = magicString.toString();
Expand Down
134 changes: 114 additions & 20 deletions src/optimization/decorators.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ const srcDir = join(baseDir, 'src');
describe('optimization', () => {
describe('purgeStaticFieldDecorators', () => {

it('should remove the static decorator', () => {
it('should remove the static decorators', () => {
// arrange
const decoratorStatement = `
import { Taco } from 'blah';
Expand Down Expand Up @@ -252,7 +252,7 @@ some more content
});

it('should not remove decorators when it has an injectable statement in it', () => {
const knownContent = `
const knownContent = `
var ActionSheetController = (function () {
/**
* @param {?} _app
Expand Down Expand Up @@ -294,7 +294,7 @@ ActionSheetController.ctorParameters = function () { return [
});

it('should work with the ionic-angular index file', () => {
const ionicModuleDecorator = `
const ionicModuleDecorator = `
IonicModule.decorators = [
{ type: NgModule, args: [{
imports: [
Expand Down Expand Up @@ -408,7 +408,7 @@ IonicModule.decorators = [
},] },
];
`;
const knownContent = `
const knownContent = `
import { ANALYZE_FOR_ENTRY_COMPONENTS, APP_INITIALIZER, ComponentFactoryResolver, Inject, Injector, NgModule, NgZone, Optional } from '@angular/core';
import { APP_BASE_HREF, Location, LocationStrategy, HashLocationStrategy, PathLocationStrategy, PlatformLocation } from '@angular/common';
import { DOCUMENT } from '@angular/platform-browser';
Expand Down Expand Up @@ -782,14 +782,14 @@ export function provideLocationStrategy(platformLocationStrategy, baseHref, conf
expect(result.indexOf(ionicModuleDecorator)).toEqual(-1);
});

it('shoud process component file correctly', () => {
it('should process component file correctly', () => {

const propDecorators = `
const propDecorators = `
ActionSheetCmp.propDecorators = {
'keyUp': [{ type: HostListener, args: ['body:keyup', ['$event'],] },],
};
`;
const decoratorContent = `
const decoratorContent = `
ActionSheetCmp.decorators = [
{ type: Component, args: [{
selector: 'ion-action-sheet',
Expand Down Expand Up @@ -821,7 +821,7 @@ ActionSheetCmp.decorators = [
},] },
];
`;
const knownContent = `
const knownContent = `
import { Component, ElementRef, HostListener, Renderer, ViewEncapsulation } from '@angular/core';
import { GestureController, BLOCK_ALL } from '../../gestures/gesture-controller';
import { Config } from '../../config/config';
Expand Down Expand Up @@ -1003,42 +1003,42 @@ let actionSheetIds = -1;
describe('purgeTranspiledDecorators', () => {
it('should purge out transpiled decorators', () => {

const inputDecorator = `
const inputDecorator = `
__decorate([
Input(),
__metadata("design:type", String)
], AboutPage.prototype, "someVariable", void 0);
`;

const outputDecorator = `
const outputDecorator = `
__decorate([
Output(),
__metadata("design:type", typeof (_a = typeof EventEmitter !== "undefined" && EventEmitter) === "function" && _a || Object)
], AboutPage.prototype, "emitter", void 0);
`;

const viewChildDecorator = `
const viewChildDecorator = `
__decorate([
ViewChild('test', { read: ElementRef }),
__metadata("design:type", Object)
], AboutPage.prototype, "test", void 0);
`;

const viewChildrenDecorator = `
const viewChildrenDecorator = `
__decorate([
ViewChildren('test'),
__metadata("design:type", Object)
], AboutPage.prototype, "tests", void 0);
`;

const hostBindingDecorator = `
const hostBindingDecorator = `
__decorate([
HostBinding('class.searchbar-has-focus'),
__metadata("design:type", Boolean)
], AboutPage.prototype, "_sbHasFocus", void 0);
`;

const hostListenerDecorator = `
const hostListenerDecorator = `
__decorate([
HostListener('click', ['$event']),
__metadata("design:type", Function),
Expand All @@ -1047,7 +1047,7 @@ __decorate([
], AboutPage.prototype, "someFunction", null);
`;

const classDecorators = `
const classDecorators = `
AboutPage = __decorate([
IonicPage(),
Component({
Expand All @@ -1058,7 +1058,7 @@ AboutPage = __decorate([
], AboutPage);
`;

const knownContent = `
const knownContent = `
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
Expand Down Expand Up @@ -1113,14 +1113,14 @@ var _a, _b, _c;

it('should not purge any injectable decorators', () => {

const injectableDecorator = `
const injectableDecorator = `
ConferenceData = __decorate([
Injectable(),
__metadata("design:paramtypes", [typeof (_a = typeof Http !== "undefined" && Http) === "function" && _a || Object, typeof (_b = typeof UserData !== "undefined" && UserData) === "function" && _b || Object])
], ConferenceData);
`;

const knownContent = `
const knownContent = `
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
Expand Down Expand Up @@ -1283,7 +1283,7 @@ var _a, _b;

describe('addPureAnnotation', () => {
it('should add the pure annotation to a transpiled class', () => {
const knownContent = `
const knownContent = `
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
Expand Down Expand Up @@ -1407,7 +1407,7 @@ function CardContent_tsickle_Closure_declarations() {
//# sourceMappingURL=card-content.js.map
`;

const expectedContent = `
const expectedContent = `
var __extends = (this && this.__extends) || (function () {
var extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
Expand Down Expand Up @@ -1464,4 +1464,98 @@ function CardContent_tsickle_Closure_declarations() {
expect(result).toEqual(expectedContent);
});
});

describe('purgeStaticCtorFields', () => {
it('should purge the ctor field', () => {

const ctorParams = `
Badge.ctorParameters = function () { return [
{ type: Config, },
{ type: ElementRef, },
{ type: Renderer, },
]; };
`;
const knownContent = `
var __extends = (this && this.__extends) || (function () {
var extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
return function (d, b) {
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
})();
import { Directive, ElementRef, Renderer } from '@angular/core';
import { Config } from '../../config/config';
import { Ion } from '../ion';
var Badge = (function (_super) {
__extends(Badge, _super);
function Badge(config, elementRef, renderer) {
return _super.call(this, config, elementRef, renderer, 'badge') || this;
}
return Badge;
}(Ion));
export { Badge };
Badge.decorators = [
{ type: Directive, args: [{
selector: 'ion-badge'
},] },
];
${ctorParams}
function Badge_tsickle_Closure_declarations() {
Badge.decorators;
Badge.ctorParameters;
}
//# sourceMappingURL=badge.js.map
`;



let magicString = new MagicString(knownContent);
const filePath = join(ionicAngular, 'components', 'badge', 'badge.js');
magicString = decorators.purgeStaticCtorFields(filePath, knownContent, ionicAngular, angularDir, srcDir, magicString);
const result: string = magicString.toString();
expect(result.indexOf(ctorParams)).toEqual(-1);
});

it('should purge an empty ctor field', () => {
const ctorParams = `
Avatar.ctorParameters = function () { return []; };
`;
const knownContent = `
var Avatar = (function () {
function Avatar() {
}
return Avatar;
}());
export { Avatar };
Avatar.decorators = [
{ type: Directive, args: [{
selector: 'ion-avatar'
},] },
];
${ctorParams}
function Avatar_tsickle_Closure_declarations() {
Avatar.decorators;
Avatar.ctorParameters;
}
//# sourceMappingURL=avatar.js.map
`;

let magicString = new MagicString(knownContent);
const filePath = join(ionicAngular, 'components', 'badge', 'badge.js');
magicString = decorators.purgeStaticCtorFields(filePath, knownContent, ionicAngular, angularDir, srcDir, magicString);
const result: string = magicString.toString();
expect(result.indexOf(ctorParams)).toEqual(-1);
});
});
});
59 changes: 57 additions & 2 deletions src/optimization/decorators.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,10 @@ import {
SyntaxKind } from 'typescript';

import { Logger } from '../logger/logger';
import * as Constants from '../util/constants';
import { getStringPropertyValue } from '../util/helpers';
import { MagicString } from '../util/interfaces';
import { findNodes, getTypescriptSourceFile } from '../util/typescript-utils';
import { getNodeStringContent, findNodes, getTypescriptSourceFile } from '../util/typescript-utils';

export function addPureAnnotation(filePath: string, originalFileContent: string, ionicAngularDir: string, angularDir: string, srcDir: string, magicString: MagicString) {
Logger.debug(`[decorators] addPureAnnotation: processing ${filePath} ...`);
Expand All @@ -41,7 +43,6 @@ export function addPureAnnotation(filePath: string, originalFileContent: string,
magicString.prependLeft(parenthesizedExpression.pos, PURE_ANNOTATION);

}

}
});
return magicString;
Expand Down Expand Up @@ -102,15 +103,69 @@ export function purgeStaticFieldDecorators(filePath: string, originalFileContent
if (filePath.indexOf(angularDir) >= 0 || filePath.indexOf(ionicAngularDir) >= 0 || filePath.indexOf(srcDir) >= 0) {
Logger.debug(`[decorators] purgeStaticFieldDecorators: processing ${filePath} ...`);
const typescriptFile = getTypescriptSourceFile(filePath, originalFileContent);

const decoratorExpressionStatements = getDecoratorsExpressionStatements(typescriptFile);
removeDecorators(decoratorExpressionStatements, magicString);

const propDecoratorsExpressionStatements = getPropDecoratorsExpressionStatements(typescriptFile);
removePropDecorators(propDecoratorsExpressionStatements, magicString);

Logger.debug(`[decorators] purgeStaticFieldDecorators: processing ${filePath} ... DONE`);
}
return magicString;
}

export function purgeStaticCtorFields(filePath: string, originalFileContent: string, ionicAngularDir: string, angularDir: string, srcDir: string, magicString: MagicString) {
// TODO - we could extend this to other libs and stuff too such as material 2, but that doesn't seem
// particularly maintainable
if ((filePath.indexOf(angularDir) >= 0 || filePath.indexOf(ionicAngularDir) >= 0) && !isIonicEntryComponent(filePath)) {
Logger.debug(`[decorators] purgeStaticCtorFields: processing ${filePath} ...`);
const typescriptFile = getTypescriptSourceFile(filePath, originalFileContent);
const expressionStatements = findNodes(typescriptFile, typescriptFile, SyntaxKind.ExpressionStatement, false) as ExpressionStatement[];
const toPurge: ExpressionStatement[] = [];
for (const expressionStatement of expressionStatements) {
if (expressionStatement.expression && expressionStatement.expression.kind === SyntaxKind.BinaryExpression
&& (expressionStatement.expression as BinaryExpression).left
&& (expressionStatement.expression as BinaryExpression).left.kind === SyntaxKind.PropertyAccessExpression
&& ((expressionStatement.expression as BinaryExpression).left as PropertyAccessExpression).name
&& ((expressionStatement.expression as BinaryExpression).left as PropertyAccessExpression).name.text === 'ctorParameters'
) {

toPurge.push(expressionStatement);

}
}

toPurge.forEach(tsNode => {
magicString.overwrite(tsNode.pos, tsNode.end, '');
});

Logger.debug(`[decorators] purgeStaticFieldDecorators: processing ${filePath} ... DONE`);
}
return magicString;
}

function isIonicEntryComponent(filePath: string) {
if (filePath === getStringPropertyValue(Constants.ENV_ACTION_SHEET_COMPONENT_PATH)) {
return true;
} else if (filePath === getStringPropertyValue(Constants.ENV_ALERT_COMPONENT_PATH)) {
return true;
} else if (filePath === getStringPropertyValue(Constants.ENV_APP_ROOT_COMPONENT_PATH)) {
return true;
} else if (filePath === getStringPropertyValue(Constants.ENV_LOADING_COMPONENT_PATH)) {
return true;
} else if (filePath === getStringPropertyValue(Constants.ENV_MODAL_COMPONENT_PATH)) {
return true;
} else if (filePath === getStringPropertyValue(Constants.ENV_PICKER_COMPONENT_PATH)) {
return true;
} else if (filePath === getStringPropertyValue(Constants.ENV_POPOVER_COMPONENT_PATH)) {
return true;
} else if (filePath === getStringPropertyValue(Constants.ENV_TOAST_COMPONENT_PATH)) {
return true;
}
return false;
}

function getDecoratorsExpressionStatements(typescriptFile: SourceFile) {
const expressionStatements = findNodes(typescriptFile, typescriptFile, SyntaxKind.ExpressionStatement, false) as ExpressionStatement[];
const decoratorExpressionStatements: ExpressionStatement[] = [];
Expand Down
Loading

0 comments on commit 8f49908

Please sign in to comment.