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

Commit 254bd8a

Browse files
committed
feature(optimization): add pure annotation to transpiled classes for the sake of dead code removal
1 parent e364134 commit 254bd8a

File tree

4 files changed

+223
-2
lines changed

4 files changed

+223
-2
lines changed

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, purgeTranspiledDecorators } from './optimization/decorators';
12+
import { addPureAnnotation, 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) {
@@ -84,6 +84,7 @@ function removeDecorators(context: BuildContext) {
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);
8686
magicString = purgeTranspiledDecorators(jsFile.path, jsFile.content, getStringPropertyValue(Constants.ENV_VAR_IONIC_ANGULAR_DIR), getStringPropertyValue(Constants.ENV_VAR_AT_ANGULAR_DIR), context.srcDir, magicString);
87+
magicString = addPureAnnotation(jsFile.path, jsFile.content, getStringPropertyValue(Constants.ENV_VAR_IONIC_ANGULAR_DIR), getStringPropertyValue(Constants.ENV_VAR_AT_ANGULAR_DIR), context.srcDir, magicString);
8788
jsFile.content = magicString.toString();
8889
const sourceMap = magicString.generateMap({
8990
source: basename(jsFile.path),

src/optimization/decorators.spec.ts

Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1280,4 +1280,188 @@ var _a, _b;
12801280
expect(result.indexOf(injectableDecorator)).toBeGreaterThan(1);
12811281
});
12821282
});
1283+
1284+
describe('addPureAnnotation', () => {
1285+
it('should add the pure annotation to a transpiled class', () => {
1286+
const knownContent = `
1287+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
1288+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
1289+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
1290+
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;
1291+
return c > 3 && r && Object.defineProperty(target, key, r), r;
1292+
};
1293+
var __metadata = (this && this.__metadata) || function (k, v) {
1294+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
1295+
};
1296+
import { Component, ElementRef, EventEmitter, HostBinding, HostListener, Input, Output, ViewChild, ViewChildren } from '@angular/core';
1297+
import { IonicPage, NavController, PopoverController } from 'ionic-angular';
1298+
var AboutPage = (function () {
1299+
function AboutPage(navCtrl, popoverCtrl) {
1300+
this.navCtrl = navCtrl;
1301+
this.popoverCtrl = popoverCtrl;
1302+
this.conferenceDate = '2047-05-18';
1303+
this.someVariable = '';
1304+
this.emitter = new EventEmitter();
1305+
}
1306+
AboutPage.prototype.presentPopover = function (event) {
1307+
var popover = this.popoverCtrl.create('PopoverPage');
1308+
popover.present({ ev: event });
1309+
};
1310+
AboutPage.prototype.someFunction = function (event) {
1311+
};
1312+
return AboutPage;
1313+
}());
1314+
export { AboutPage };
1315+
var _a, _b, _c;
1316+
//# sourceMappingURL=about.js.map
1317+
`;
1318+
1319+
const expectedContent = `
1320+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
1321+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
1322+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
1323+
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;
1324+
return c > 3 && r && Object.defineProperty(target, key, r), r;
1325+
};
1326+
var __metadata = (this && this.__metadata) || function (k, v) {
1327+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
1328+
};
1329+
import { Component, ElementRef, EventEmitter, HostBinding, HostListener, Input, Output, ViewChild, ViewChildren } from '@angular/core';
1330+
import { IonicPage, NavController, PopoverController } from 'ionic-angular';
1331+
var AboutPage = /*#__PURE__*/ (function () {
1332+
function AboutPage(navCtrl, popoverCtrl) {
1333+
this.navCtrl = navCtrl;
1334+
this.popoverCtrl = popoverCtrl;
1335+
this.conferenceDate = '2047-05-18';
1336+
this.someVariable = '';
1337+
this.emitter = new EventEmitter();
1338+
}
1339+
AboutPage.prototype.presentPopover = function (event) {
1340+
var popover = this.popoverCtrl.create('PopoverPage');
1341+
popover.present({ ev: event });
1342+
};
1343+
AboutPage.prototype.someFunction = function (event) {
1344+
};
1345+
return AboutPage;
1346+
}());
1347+
export { AboutPage };
1348+
var _a, _b, _c;
1349+
//# sourceMappingURL=about.js.map
1350+
`;
1351+
1352+
let magicString = new MagicString(knownContent);
1353+
const filePath = join(ionicAngular, 'components', 'action-sheet', 'action-sheet-component.js');
1354+
magicString = decorators.addPureAnnotation(filePath, knownContent, ionicAngular, angularDir, srcDir, magicString);
1355+
const result: string = magicString.toString();
1356+
expect(result).toEqual(expectedContent);
1357+
});
1358+
1359+
it('should work with a class that extends another class', () => {
1360+
const knownContent = `
1361+
var __extends = (this && this.__extends) || (function () {
1362+
var extendStatics = Object.setPrototypeOf ||
1363+
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
1364+
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
1365+
return function (d, b) {
1366+
extendStatics(d, b);
1367+
function __() { this.constructor = d; }
1368+
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
1369+
};
1370+
})();
1371+
import { Directive, ElementRef, Renderer } from '@angular/core';
1372+
import { Config } from '../../config/config';
1373+
import { Ion } from '../ion';
1374+
/**
1375+
* @hidden
1376+
*/
1377+
var CardContent = (function (_super) {
1378+
__extends(CardContent, _super);
1379+
/**
1380+
* @param {?} config
1381+
* @param {?} elementRef
1382+
* @param {?} renderer
1383+
*/
1384+
function CardContent(config, elementRef, renderer) {
1385+
return _super.call(this, config, elementRef, renderer, 'card-content') || this;
1386+
}
1387+
return CardContent;
1388+
}(Ion));
1389+
export { CardContent };
1390+
/**
1391+
* @nocollapse
1392+
*/
1393+
CardContent.ctorParameters = function () { return [
1394+
{ type: Config, },
1395+
{ type: ElementRef, },
1396+
{ type: Renderer, },
1397+
]; };
1398+
function CardContent_tsickle_Closure_declarations() {
1399+
/** @type {?} */
1400+
CardContent.decorators;
1401+
/**
1402+
* @nocollapse
1403+
* @type {?}
1404+
*/
1405+
CardContent.ctorParameters;
1406+
}
1407+
//# sourceMappingURL=card-content.js.map
1408+
`;
1409+
1410+
const expectedContent = `
1411+
var __extends = (this && this.__extends) || (function () {
1412+
var extendStatics = Object.setPrototypeOf ||
1413+
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
1414+
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
1415+
return function (d, b) {
1416+
extendStatics(d, b);
1417+
function __() { this.constructor = d; }
1418+
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
1419+
};
1420+
})();
1421+
import { Directive, ElementRef, Renderer } from '@angular/core';
1422+
import { Config } from '../../config/config';
1423+
import { Ion } from '../ion';
1424+
/**
1425+
* @hidden
1426+
*/
1427+
var CardContent = /*#__PURE__*/ (function (_super) {
1428+
__extends(CardContent, _super);
1429+
/**
1430+
* @param {?} config
1431+
* @param {?} elementRef
1432+
* @param {?} renderer
1433+
*/
1434+
function CardContent(config, elementRef, renderer) {
1435+
return _super.call(this, config, elementRef, renderer, 'card-content') || this;
1436+
}
1437+
return CardContent;
1438+
}(Ion));
1439+
export { CardContent };
1440+
/**
1441+
* @nocollapse
1442+
*/
1443+
CardContent.ctorParameters = function () { return [
1444+
{ type: Config, },
1445+
{ type: ElementRef, },
1446+
{ type: Renderer, },
1447+
]; };
1448+
function CardContent_tsickle_Closure_declarations() {
1449+
/** @type {?} */
1450+
CardContent.decorators;
1451+
/**
1452+
* @nocollapse
1453+
* @type {?}
1454+
*/
1455+
CardContent.ctorParameters;
1456+
}
1457+
//# sourceMappingURL=card-content.js.map
1458+
`;
1459+
1460+
let magicString = new MagicString(knownContent);
1461+
const filePath = join(ionicAngular, 'components', 'action-sheet', 'action-sheet-component.js');
1462+
magicString = decorators.addPureAnnotation(filePath, knownContent, ionicAngular, angularDir, srcDir, magicString);
1463+
const result: string = magicString.toString();
1464+
expect(result).toEqual(expectedContent);
1465+
});
1466+
});
12831467
});

src/optimization/decorators.ts

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,49 @@ import {
33
BinaryExpression,
44
CallExpression,
55
ExpressionStatement,
6+
FunctionExpression,
67
Identifier,
78
ObjectLiteralExpression,
9+
ParenthesizedExpression,
810
PropertyAccessExpression,
911
PropertyAssignment,
1012
SourceFile,
1113
SyntaxKind } from 'typescript';
1214

1315
import { Logger } from '../logger/logger';
1416
import { MagicString } from '../util/interfaces';
15-
import { getNodeStringContent, findNodes, getTypescriptSourceFile } from '../util/typescript-utils';
17+
import { findNodes, getTypescriptSourceFile } from '../util/typescript-utils';
18+
19+
export function addPureAnnotation(filePath: string, originalFileContent: string, ionicAngularDir: string, angularDir: string, srcDir: string, magicString: MagicString) {
20+
Logger.debug(`[decorators] addPureAnnotation: processing ${filePath} ...`);
21+
const typescriptFile = getTypescriptSourceFile(filePath, originalFileContent);
22+
const parenthesizedExpressions = findNodes(typescriptFile, typescriptFile, SyntaxKind.ParenthesizedExpression, false) as ParenthesizedExpression[];
23+
parenthesizedExpressions.forEach(parenthesizedExpression => {
24+
25+
if (parenthesizedExpression.expression && parenthesizedExpression.expression.kind === SyntaxKind.CallExpression
26+
&& (parenthesizedExpression.expression as CallExpression).expression
27+
&& (parenthesizedExpression.expression as CallExpression).expression.kind === SyntaxKind.FunctionExpression
28+
&& !((parenthesizedExpression.expression as CallExpression).expression as FunctionExpression).name
29+
&& ((parenthesizedExpression.expression as CallExpression).expression as FunctionExpression).parameters) {
30+
31+
// it's an iffe
32+
if (((parenthesizedExpression.expression as CallExpression).expression as FunctionExpression).parameters.length === 0) {
33+
34+
magicString.prependLeft(parenthesizedExpression.pos, PURE_ANNOTATION);
35+
36+
} else if (((parenthesizedExpression.expression as CallExpression).expression as FunctionExpression).parameters[0]
37+
&& ((parenthesizedExpression.expression as CallExpression).expression as FunctionExpression).parameters[0].name
38+
&& (((parenthesizedExpression.expression as CallExpression).expression as FunctionExpression).parameters[0].name as Identifier).text === '_super') {
39+
40+
41+
magicString.prependLeft(parenthesizedExpression.pos, PURE_ANNOTATION);
42+
43+
}
44+
45+
}
46+
});
47+
return magicString;
48+
}
1649

1750
export function purgeTranspiledDecorators(filePath: string, originalFileContent: string, ionicAngularDir: string, angularDir: string, srcDir: string, magicString: MagicString) {
1851
if (filePath.indexOf(angularDir) >= 0 || filePath.indexOf(ionicAngularDir) >= 0 || filePath.indexOf(srcDir) >= 0) {
@@ -177,3 +210,5 @@ export const OUTPUT_DECORATOR = 'Output';
177210
export const PIPE_DECORATOR = 'Pipe';
178211
export const VIEW_CHILD_DECORATOR = 'ViewChild';
179212
export const VIEW_CHILDREN_DECORATOR = 'ViewChildren';
213+
214+
export const PURE_ANNOTATION = ' /*#__PURE__*/';

src/util/interfaces.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,4 +205,5 @@ export interface WebpackDependency {
205205
export interface MagicString {
206206
overwrite(startIndex: number, endIndex: number, newContent: string): void;
207207
toString(): string;
208+
prependLeft(index: number, contentToPrepend: string): string;
208209
}

0 commit comments

Comments
 (0)