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

Commit ba5e0cd

Browse files
committed
feat(optimizations): purge transpiled decorators
purge transpiled decorators
1 parent 1117837 commit ba5e0cd

File tree

4 files changed

+337
-2
lines changed

4 files changed

+337
-2
lines changed

src/optimization.spec.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ describe('optimization task', () => {
1919

2020
spyOn(helpers, helpers.getBooleanPropertyValue.name).and.returnValue(false);
2121
spyOn(decorators, decorators.purgeStaticFieldDecorators.name);
22+
spyOn(decorators, decorators.purgeTranspiledDecorators.name);
2223
spyOn(treeshake, treeshake.calculateUnusedComponents.name);
2324

2425
// act
@@ -27,6 +28,7 @@ describe('optimization task', () => {
2728
// assert
2829
expect(result).toBeTruthy();
2930
expect(decorators.purgeStaticFieldDecorators).not.toHaveBeenCalled();
31+
expect(decorators.purgeTranspiledDecorators).not.toHaveBeenCalled();
3032
expect(treeshake.calculateUnusedComponents).not.toHaveBeenCalled();
3133
});
3234
});

src/optimization.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import { BuildError } from './util/errors';
99
import { getBooleanPropertyValue, getStringPropertyValue, webpackStatsToDependencyMap, printDependencyMap } from './util/helpers';
1010
import { BuildContext, TaskInfo } from './util/interfaces';
1111
import { runWebpackFullBuild, WebpackConfig } from './webpack';
12-
import { purgeStaticFieldDecorators } from './optimization/decorators';
12+
import { purgeStaticFieldDecorators, purgeTranspiledDecorators } from './optimization/decorators';
1313
import { getAppModuleNgFactoryPath, calculateUnusedComponents, purgeUnusedImportsAndExportsFromIndex, purgeComponentNgFactoryImportAndUsage, purgeProviderControllerImportAndUsage, purgeProviderClassNameFromIonicModuleForRoot } from './optimization/treeshake';
1414

1515
export function optimization(context: BuildContext, configFile: string) {
@@ -83,6 +83,7 @@ function removeDecorators(context: BuildContext) {
8383
jsFiles.forEach(jsFile => {
8484
let magicString = new MagicString(jsFile.content);
8585
magicString = purgeStaticFieldDecorators(jsFile.path, jsFile.content, getStringPropertyValue(Constants.ENV_VAR_IONIC_ANGULAR_DIR), getStringPropertyValue(Constants.ENV_VAR_AT_ANGULAR_DIR), context.srcDir, magicString);
86+
magicString = purgeTranspiledDecorators(jsFile.path, jsFile.content, getStringPropertyValue(Constants.ENV_VAR_IONIC_ANGULAR_DIR), getStringPropertyValue(Constants.ENV_VAR_AT_ANGULAR_DIR), context.srcDir, magicString);
8687
jsFile.content = magicString.toString();
8788
const sourceMap = magicString.generateMap({
8889
source: basename(jsFile.path),

src/optimization/decorators.spec.ts

Lines changed: 281 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -999,4 +999,285 @@ let actionSheetIds = -1;
999999
expect(result.indexOf(propDecorators)).toEqual(-1);
10001000
});
10011001
});
1002+
1003+
describe('purgeTranspiledDecorators', () => {
1004+
it('should purge out transpiled decorators', () => {
1005+
1006+
const inputDecorator = `
1007+
__decorate([
1008+
Input(),
1009+
__metadata("design:type", String)
1010+
], AboutPage.prototype, "someVariable", void 0);
1011+
`;
1012+
1013+
const outputDecorator = `
1014+
__decorate([
1015+
Output(),
1016+
__metadata("design:type", typeof (_a = typeof EventEmitter !== "undefined" && EventEmitter) === "function" && _a || Object)
1017+
], AboutPage.prototype, "emitter", void 0);
1018+
`;
1019+
1020+
const viewChildDecorator = `
1021+
__decorate([
1022+
ViewChild('test', { read: ElementRef }),
1023+
__metadata("design:type", Object)
1024+
], AboutPage.prototype, "test", void 0);
1025+
`;
1026+
1027+
const viewChildrenDecorator = `
1028+
__decorate([
1029+
ViewChildren('test'),
1030+
__metadata("design:type", Object)
1031+
], AboutPage.prototype, "tests", void 0);
1032+
`;
1033+
1034+
const hostBindingDecorator = `
1035+
__decorate([
1036+
HostBinding('class.searchbar-has-focus'),
1037+
__metadata("design:type", Boolean)
1038+
], AboutPage.prototype, "_sbHasFocus", void 0);
1039+
`;
1040+
1041+
const hostListenerDecorator = `
1042+
__decorate([
1043+
HostListener('click', ['$event']),
1044+
__metadata("design:type", Function),
1045+
__metadata("design:paramtypes", [Object]),
1046+
__metadata("design:returntype", void 0)
1047+
], AboutPage.prototype, "someFunction", null);
1048+
`;
1049+
1050+
const classDecorators = `
1051+
AboutPage = __decorate([
1052+
IonicPage(),
1053+
Component({
1054+
selector: 'page-about',
1055+
templateUrl: 'about.html'
1056+
}),
1057+
__metadata("design:paramtypes", [typeof (_b = typeof NavController !== "undefined" && NavController) === "function" && _b || Object, typeof (_c = typeof PopoverController !== "undefined" && PopoverController) === "function" && _c || Object])
1058+
], AboutPage);
1059+
`;
1060+
1061+
const knownContent = `
1062+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
1063+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
1064+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
1065+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
1066+
return c > 3 && r && Object.defineProperty(target, key, r), r;
1067+
};
1068+
var __metadata = (this && this.__metadata) || function (k, v) {
1069+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
1070+
};
1071+
import { Component, ElementRef, EventEmitter, HostBinding, HostListener, Input, Output, ViewChild, ViewChildren } from '@angular/core';
1072+
import { IonicPage, NavController, PopoverController } from 'ionic-angular';
1073+
var AboutPage = (function () {
1074+
function AboutPage(navCtrl, popoverCtrl) {
1075+
this.navCtrl = navCtrl;
1076+
this.popoverCtrl = popoverCtrl;
1077+
this.conferenceDate = '2047-05-18';
1078+
this.someVariable = '';
1079+
this.emitter = new EventEmitter();
1080+
}
1081+
AboutPage.prototype.presentPopover = function (event) {
1082+
var popover = this.popoverCtrl.create('PopoverPage');
1083+
popover.present({ ev: event });
1084+
};
1085+
AboutPage.prototype.someFunction = function (event) {
1086+
};
1087+
return AboutPage;
1088+
}());
1089+
${inputDecorator}
1090+
${outputDecorator}
1091+
${viewChildDecorator}
1092+
${viewChildrenDecorator}
1093+
${hostBindingDecorator}
1094+
${hostListenerDecorator}
1095+
${classDecorators}
1096+
export { AboutPage };
1097+
var _a, _b, _c;
1098+
//# sourceMappingURL=about.js.map
1099+
`;
1100+
1101+
let magicString = new MagicString(knownContent);
1102+
const filePath = join(ionicAngular, 'components', 'action-sheet', 'action-sheet-component.js');
1103+
magicString = decorators.purgeTranspiledDecorators(filePath, knownContent, ionicAngular, angularDir, srcDir, magicString);
1104+
const result: string = magicString.toString();
1105+
expect(result.indexOf(inputDecorator)).toEqual(-1);
1106+
expect(result.indexOf(outputDecorator)).toEqual(-1);
1107+
expect(result.indexOf(viewChildDecorator)).toEqual(-1);
1108+
expect(result.indexOf(viewChildrenDecorator)).toEqual(-1);
1109+
expect(result.indexOf(hostBindingDecorator)).toEqual(-1);
1110+
expect(result.indexOf(hostListenerDecorator)).toEqual(-1);
1111+
expect(result.indexOf(classDecorators)).toEqual(-1);
1112+
});
1113+
1114+
it('should not purge any injectable decorators', () => {
1115+
1116+
const injectableDecorator = `
1117+
ConferenceData = __decorate([
1118+
Injectable(),
1119+
__metadata("design:paramtypes", [typeof (_a = typeof Http !== "undefined" && Http) === "function" && _a || Object, typeof (_b = typeof UserData !== "undefined" && UserData) === "function" && _b || Object])
1120+
], ConferenceData);
1121+
`;
1122+
1123+
const knownContent = `
1124+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
1125+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
1126+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
1127+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
1128+
return c > 3 && r && Object.defineProperty(target, key, r), r;
1129+
};
1130+
var __metadata = (this && this.__metadata) || function (k, v) {
1131+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
1132+
};
1133+
import { Injectable } from '@angular/core';
1134+
import { Http } from '@angular/http';
1135+
import { UserData } from './user-data';
1136+
import { Observable } from 'rxjs/Observable';
1137+
import 'rxjs/add/operator/map';
1138+
import 'rxjs/add/observable/of';
1139+
var ConferenceData = (function () {
1140+
function ConferenceData(http, user) {
1141+
this.http = http;
1142+
this.user = user;
1143+
}
1144+
ConferenceData.prototype.load = function () {
1145+
if (this.data) {
1146+
return Observable.of(this.data);
1147+
}
1148+
else {
1149+
return this.http.get('assets/data/data.json')
1150+
.map(this.processData, this);
1151+
}
1152+
};
1153+
ConferenceData.prototype.processData = function (data) {
1154+
var _this = this;
1155+
// just some good 'ol JS fun with objects and arrays
1156+
// build up the data by linking speakers to sessions
1157+
this.data = data.json();
1158+
this.data.tracks = [];
1159+
// loop through each day in the schedule
1160+
this.data.schedule.forEach(function (day) {
1161+
// loop through each timeline group in the day
1162+
day.groups.forEach(function (group) {
1163+
// loop through each session in the timeline group
1164+
group.sessions.forEach(function (session) {
1165+
session.speakers = [];
1166+
if (session.speakerNames) {
1167+
session.speakerNames.forEach(function (speakerName) {
1168+
var speaker = _this.data.speakers.find(function (s) { return s.name === speakerName; });
1169+
if (speaker) {
1170+
session.speakers.push(speaker);
1171+
speaker.sessions = speaker.sessions || [];
1172+
speaker.sessions.push(session);
1173+
}
1174+
});
1175+
}
1176+
if (session.tracks) {
1177+
session.tracks.forEach(function (track) {
1178+
if (_this.data.tracks.indexOf(track) < 0) {
1179+
_this.data.tracks.push(track);
1180+
}
1181+
});
1182+
}
1183+
});
1184+
});
1185+
});
1186+
return this.data;
1187+
};
1188+
ConferenceData.prototype.getTimeline = function (dayIndex, queryText, excludeTracks, segment) {
1189+
var _this = this;
1190+
if (queryText === void 0) { queryText = ''; }
1191+
if (excludeTracks === void 0) { excludeTracks = []; }
1192+
if (segment === void 0) { segment = 'all'; }
1193+
return this.load().map(function (data) {
1194+
var day = data.schedule[dayIndex];
1195+
day.shownSessions = 0;
1196+
queryText = queryText.toLowerCase().replace(/,|\.|-/g, ' ');
1197+
var queryWords = queryText.split(' ').filter(function (w) { return !!w.trim().length; });
1198+
day.groups.forEach(function (group) {
1199+
group.hide = true;
1200+
group.sessions.forEach(function (session) {
1201+
// check if this session should show or not
1202+
_this.filterSession(session, queryWords, excludeTracks, segment);
1203+
if (!session.hide) {
1204+
// if this session is not hidden then this group should show
1205+
group.hide = false;
1206+
day.shownSessions++;
1207+
}
1208+
});
1209+
});
1210+
return day;
1211+
});
1212+
};
1213+
ConferenceData.prototype.filterSession = function (session, queryWords, excludeTracks, segment) {
1214+
var matchesQueryText = false;
1215+
if (queryWords.length) {
1216+
// of any query word is in the session name than it passes the query test
1217+
queryWords.forEach(function (queryWord) {
1218+
if (session.name.toLowerCase().indexOf(queryWord) > -1) {
1219+
matchesQueryText = true;
1220+
}
1221+
});
1222+
}
1223+
else {
1224+
// if there are no query words then this session passes the query test
1225+
matchesQueryText = true;
1226+
}
1227+
// if any of the sessions tracks are not in the
1228+
// exclude tracks then this session passes the track test
1229+
var matchesTracks = false;
1230+
session.tracks.forEach(function (trackName) {
1231+
if (excludeTracks.indexOf(trackName) === -1) {
1232+
matchesTracks = true;
1233+
}
1234+
});
1235+
// if the segement is 'favorites', but session is not a user favorite
1236+
// then this session does not pass the segment test
1237+
var matchesSegment = false;
1238+
if (segment === 'favorites') {
1239+
if (this.user.hasFavorite(session.name)) {
1240+
matchesSegment = true;
1241+
}
1242+
}
1243+
else {
1244+
matchesSegment = true;
1245+
}
1246+
// all tests must be true if it should not be hidden
1247+
session.hide = !(matchesQueryText && matchesTracks && matchesSegment);
1248+
};
1249+
ConferenceData.prototype.getSpeakers = function () {
1250+
return this.load().map(function (data) {
1251+
return data.speakers.sort(function (a, b) {
1252+
var aName = a.name.split(' ').pop();
1253+
var bName = b.name.split(' ').pop();
1254+
return aName.localeCompare(bName);
1255+
});
1256+
});
1257+
};
1258+
ConferenceData.prototype.getTracks = function () {
1259+
return this.load().map(function (data) {
1260+
return data.tracks.sort();
1261+
});
1262+
};
1263+
ConferenceData.prototype.getMap = function () {
1264+
return this.load().map(function (data) {
1265+
return data.map;
1266+
});
1267+
};
1268+
return ConferenceData;
1269+
}());
1270+
${injectableDecorator}
1271+
export { ConferenceData };
1272+
var _a, _b;
1273+
//# sourceMappingURL=conference-data.js.map
1274+
`;
1275+
1276+
let magicString = new MagicString(knownContent);
1277+
const filePath = join(ionicAngular, 'components', 'action-sheet', 'action-sheet-component.js');
1278+
magicString = decorators.purgeTranspiledDecorators(filePath, knownContent, ionicAngular, angularDir, srcDir, magicString);
1279+
const result: string = magicString.toString();
1280+
expect(result.indexOf(injectableDecorator)).toBeGreaterThan(1);
1281+
});
1282+
});
10021283
});

src/optimization/decorators.ts

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import {
22
ArrayLiteralExpression,
33
BinaryExpression,
4+
CallExpression,
45
ExpressionStatement,
56
Identifier,
67
ObjectLiteralExpression,
@@ -11,8 +12,58 @@ import {
1112

1213
import { Logger } from '../logger/logger';
1314
import { MagicString } from '../util/interfaces';
14-
import { findNodes, getTypescriptSourceFile } from '../util/typescript-utils';
15+
import { getNodeStringContent, findNodes, getTypescriptSourceFile } from '../util/typescript-utils';
1516

17+
export function purgeTranspiledDecorators(filePath: string, originalFileContent: string, ionicAngularDir: string, angularDir: string, srcDir: string, magicString: MagicString) {
18+
if (filePath.indexOf(angularDir) >= 0 || filePath.indexOf(ionicAngularDir) >= 0 || filePath.indexOf(srcDir) >= 0) {
19+
Logger.debug(`[decorators] purgeTranspiledDecorators: processing ${filePath} ...`);
20+
const typescriptFile = getTypescriptSourceFile(filePath, originalFileContent);
21+
const expressionsToRemove = getTranspiledDecoratorExpressionStatements(typescriptFile);
22+
expressionsToRemove.forEach(expression => {
23+
magicString.overwrite(expression.pos, expression.end, '');
24+
});
25+
Logger.debug(`[decorators] purgeTranspiledDecorators: processing ${filePath} ...`);
26+
}
27+
return magicString;
28+
}
29+
30+
function getTranspiledDecoratorExpressionStatements(sourceFile: SourceFile) {
31+
const expressionStatements = findNodes(sourceFile, sourceFile, SyntaxKind.ExpressionStatement, false) as ExpressionStatement[];
32+
const toReturn: ExpressionStatement[] = [];
33+
expressionStatements.forEach(expressionStatement => {
34+
if (expressionStatement && expressionStatement.expression
35+
&& expressionStatement.expression.kind === SyntaxKind.CallExpression
36+
&& (expressionStatement.expression as CallExpression).expression
37+
&& ((expressionStatement.expression as CallExpression).expression as Identifier).text === '___decorate') {
38+
39+
toReturn.push(expressionStatement);
40+
41+
} else if (expressionStatement && expressionStatement.expression
42+
&& expressionStatement.expression.kind === SyntaxKind.BinaryExpression
43+
&& (expressionStatement.expression as BinaryExpression).right
44+
&& (expressionStatement.expression as BinaryExpression).right.kind === SyntaxKind.CallExpression
45+
&& ((expressionStatement.expression as BinaryExpression).right as CallExpression).expression
46+
&& (((expressionStatement.expression as BinaryExpression).right as CallExpression).expression as Identifier).text === '___decorate') {
47+
48+
((expressionStatement.expression as BinaryExpression).right as CallExpression).arguments.forEach(argument => {
49+
if (argument.kind === SyntaxKind.ArrayLiteralExpression) {
50+
let injectableFound = false;
51+
for (const element of (argument as ArrayLiteralExpression).elements) {
52+
if (element.kind === SyntaxKind.CallExpression && (element as CallExpression).expression
53+
&& ((element as CallExpression).expression as Identifier).text === 'Injectable' ) {
54+
injectableFound = true;
55+
break;
56+
}
57+
}
58+
if (!injectableFound) {
59+
toReturn.push(expressionStatement);
60+
}
61+
}
62+
});
63+
}
64+
});
65+
return toReturn;
66+
}
1667

1768
export function purgeStaticFieldDecorators(filePath: string, originalFileContent: string, ionicAngularDir: string, angularDir: string, srcDir: string, magicString: MagicString) {
1869
if (filePath.indexOf(angularDir) >= 0 || filePath.indexOf(ionicAngularDir) >= 0 || filePath.indexOf(srcDir) >= 0) {

0 commit comments

Comments
 (0)