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

Commit 60c1e24

Browse files
authored
feature(tree-shaking): purge unused ionic providers
1 parent 967f784 commit 60c1e24

File tree

5 files changed

+193
-14
lines changed

5 files changed

+193
-14
lines changed

config/optimization.config.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,14 @@ module.exports = {
1111
},
1212

1313
resolve: {
14-
extensions: ['.ts', '.js']
14+
extensions: ['.js', '.ts']
1515
},
1616

1717
module: {
1818
loaders: [
1919
{
2020
test: /\.ts$/,
21-
loader: process.env.IONIC_WEBPACK_LOADER
21+
loader: process.env.IONIC_OPTIMIZATION_LOADER
2222
},
2323
{
2424
test: /\.js$/,

src/optimization.spec.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -81,10 +81,10 @@ describe('optimization task', () => {
8181
spyOn(treeshake, treeshake.calculateUnusedComponents.name);
8282

8383
// act
84-
const result = optimization.doOptimizations(context, null);
84+
const result = optimization.doOptimizations(context, new Map());
8585

8686
// assert
87-
expect(result).toEqual(null);
87+
expect(result).toBeTruthy();
8888
expect(decorators.purgeDecorators).not.toHaveBeenCalled();
8989
expect(treeshake.calculateUnusedComponents).not.toHaveBeenCalled();
9090
});

src/optimization.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,17 +31,20 @@ function optimizationWorker(context: BuildContext, configFile: string) {
3131

3232
export function doOptimizations(context: BuildContext, dependencyMap: Map<string, Set<string>>) {
3333
// remove decorators
34+
const modifiedMap = new Map(dependencyMap);
3435
if (getBooleanPropertyValue(Constants.ENV_EXPERIMENTAL_PURGE_DECORATORS)) {
3536
removeDecorators(context);
3637
}
3738

3839
// remove unused component imports
3940
if (getBooleanPropertyValue(Constants.ENV_EXPERIMENTAL_MANUAL_TREESHAKING)) {
40-
const results = calculateUnusedComponents(dependencyMap);
41+
const results = calculateUnusedComponents(modifiedMap);
4142
purgeUnusedImports(context, results.purgedModules);
4243
}
4344

44-
return dependencyMap;
45+
printDependencyMap(modifiedMap);
46+
47+
return modifiedMap;
4548
}
4649

4750
function removeDecorators(context: BuildContext) {

src/optimization/treeshake.spec.ts

Lines changed: 91 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@ import * as Constants from '../util/constants';
44

55
let originalEnv: any = null;
66

7+
const main = '/Users/dan/myApp/app/main.js';
8+
const appModule = '/Users/dan/myApp/app/app.module.js';
9+
710
describe('treeshake', () => {
811
describe('calculateTreeShakeResults', () => {
912

@@ -13,6 +16,8 @@ describe('treeshake', () => {
1316
env[Constants.ENV_VAR_IONIC_ANGULAR_DIR] = '/Users/dan/ionic-angular';
1417
env[Constants.ENV_VAR_IONIC_ANGULAR_ENTRY_POINT] = '/Users/dan/ionic-angular/index.js';
1518
env[Constants.ENV_VAR_SRC_DIR] = '/Users/dan/myApp/';
19+
env[Constants.ENV_APP_ENTRY_POINT] = main;
20+
env[Constants.ENV_APP_NG_MODULE_PATH] = appModule;
1621
process.env = env;
1722
});
1823

@@ -72,9 +77,18 @@ describe('treeshake', () => {
7277
const dependencyFour = '/Users/dan/ionic-angular/components/badge.js';
7378
const dependencyFourNgFactory = '/Users/dan/ionic-angular/components/badge.ngfactory.js';
7479

75-
const appModule = '/Users/dan/myApp/app/app.module.js';
7680
const appModuleNgFactory = '/Users/dan/myApp/app/app.module.ngfactory.js';
7781

82+
const alert = '/Users/dan/ionic-angular/components/alert/alert.js';
83+
const alertController = '/Users/dan/ionic-angular/components/alert/alert-controller.js';
84+
const alertComponent = '/Users/dan/ionic-angular/components/alert/alert-component.js';
85+
const alertComponentNgFactory = '/Users/dan/ionic-angular/components/alert/alert-component.ngfactory.js';
86+
87+
const actionSheet = '/Users/dan/ionic-angular/components/action-sheet/action-sheet.js';
88+
const actionSheetController = '/Users/dan/ionic-angular/components/action-sheet/action-sheet-controller.js';
89+
const actionSheetComponent = '/Users/dan/ionic-angular/components/action-sheet/action-sheet-component.js';
90+
const actionSheetComponentNgFactory = '/Users/dan/ionic-angular/components/action-sheet/action-sheet-component.ngfactory.js';
91+
7892
const home = '/Users/dan/myApp/pages/home.js';
7993
const homeNgFactory = '/Users/dan/myApp/pages/home.ngfactory.js';
8094

@@ -126,6 +140,35 @@ describe('treeshake', () => {
126140
dependencyOneHelperTwoSet.add(dependencyOne);
127141
dependencyOneHelperTwoSet.add(index);
128142

143+
const alertSet = new Set<string>();
144+
alertSet.add(alertController);
145+
146+
const alertControllerSet = new Set<string>();
147+
alertControllerSet.add(index);
148+
alertControllerSet.add(appModuleNgFactory);
149+
150+
const alertComponentSet = new Set<string>();
151+
alertComponentSet.add(index);
152+
alertComponentSet.add(alertComponentNgFactory);
153+
154+
const alertComponentNgFactorySet = new Set<string>();
155+
alertComponentNgFactorySet.add(appModuleNgFactory);
156+
157+
const actionSheetSet = new Set<string>();
158+
actionSheetSet.add(actionSheetController);
159+
160+
const actionSheetControllerSet = new Set<string>();
161+
actionSheetControllerSet.add(index);
162+
actionSheetControllerSet.add(appModuleNgFactory);
163+
actionSheetControllerSet.add(homeNgFactory);
164+
165+
const actionSheetComponentSet = new Set<string>();
166+
actionSheetComponentSet.add(index);
167+
actionSheetComponentSet.add(actionSheetComponentNgFactory);
168+
169+
const actionSheetComponentNgFactorySet = new Set<string>();
170+
actionSheetComponentNgFactorySet.add(appModuleNgFactory);
171+
129172
const dependencyMap = new Map<string, Set<string>>();
130173
dependencyMap.set(appModule, appModuleSet);
131174
dependencyMap.set(appModuleNgFactory, appModuleNgFactorySet);
@@ -140,20 +183,67 @@ describe('treeshake', () => {
140183
dependencyMap.set(dependencyFour, dependencyFourSet);
141184
dependencyMap.set(dependencyFourNgFactory, dependencyFourNgFactorySet);
142185
dependencyMap.set(index, indexSet);
186+
dependencyMap.set(alert, alertSet);
187+
dependencyMap.set(alertController, alertControllerSet);
188+
dependencyMap.set(alertComponent, alertComponentSet);
189+
dependencyMap.set(alertComponentNgFactory, alertComponentNgFactorySet);
190+
dependencyMap.set(actionSheet, actionSheetSet);
191+
dependencyMap.set(actionSheetController, actionSheetControllerSet);
192+
dependencyMap.set(actionSheetComponent, actionSheetComponentSet);
193+
dependencyMap.set(actionSheetComponentNgFactory, actionSheetComponentNgFactorySet);
143194

144195
// act
145196
const results = treeshake.calculateUnusedComponents(dependencyMap);
146197

147198
// assert
199+
expect(results.updatedDependencyMap.get(appModule)).toBeTruthy();
200+
expect(results.updatedDependencyMap.get(appModuleNgFactory)).toBeTruthy();
201+
expect(results.updatedDependencyMap.get(home)).toBeTruthy();
202+
expect(results.updatedDependencyMap.get(homeNgFactory)).toBeTruthy();
148203
expect(results.updatedDependencyMap.get(dependencyOne)).toBeTruthy();
149204
expect(results.updatedDependencyMap.get(dependencyOneNgFactory)).toBeTruthy();
150205
expect(results.updatedDependencyMap.get(dependencyOneHelperOne)).toBeTruthy();
151206
expect(results.updatedDependencyMap.get(dependencyOneHelperTwo)).toBeTruthy();
152207
expect(results.updatedDependencyMap.get(dependencyFour)).toBeTruthy();
153208
expect(results.updatedDependencyMap.get(dependencyFourNgFactory)).toBeTruthy();
209+
expect(results.updatedDependencyMap.get(actionSheet)).toBeTruthy();
210+
expect(results.updatedDependencyMap.get(actionSheetController)).toBeTruthy();
211+
expect(results.updatedDependencyMap.get(actionSheetComponent)).toBeTruthy();
212+
expect(results.updatedDependencyMap.get(actionSheetComponentNgFactory)).toBeTruthy();
213+
154214

155215
expect(results.purgedModules.get(dependencyTwo)).toBeTruthy();
156216
expect(results.purgedModules.get(dependencyThree)).toBeTruthy();
217+
expect(results.purgedModules.get(alert)).toBeTruthy();
218+
expect(results.purgedModules.get(alertController)).toBeTruthy();
219+
expect(results.purgedModules.get(alertComponent)).toBeTruthy();
220+
expect(results.purgedModules.get(alertComponentNgFactory)).toBeTruthy();
221+
222+
223+
/*
224+
Map {
225+
'/Users/dan/ionic-angular/components/action-sheet/action-sheet.js'
226+
=> Set {
227+
'/Users/dan/ionic-angular/components/action-sheet/action-sheet-controller.js' },
228+
'/Users/dan/ionic-angular/components/action-sheet/action-sheet-controller.js'
229+
=> Set {
230+
'/Users/dan/myApp/app/app.module.ngfactory.js',
231+
'/Users/dan/myApp/pages/home.ngfactory.js' },
232+
'/Users/dan/ionic-angular/components/action-sheet/action-sheet-component.js'
233+
=> Set {
234+
'/Users/dan/ionic-angular/components/action-sheet/action-sheet-component.ngfactory.js' },
235+
'/Users/dan/ionic-angular/components/action-sheet/action-sheet-component.ngfactory.js'
236+
=> Set {
237+
'/Users/dan/myApp/app/app.module.ngfactory.js' } },
238+
purgedModules:
239+
Map {
240+
'/Users/dan/ionic-angular/components/radio-button.js' => Set {},
241+
'/Users/dan/ionic-angular/components/check-box.js' => Set {},
242+
'/Users/dan/ionic-angular/components/alert/alert.js' => Set {},
243+
'/Users/dan/ionic-angular/components/alert/alert-controller.js' => Set {},
244+
'/Users/dan/ionic-angular/components/alert/alert-component.js' => Set {},
245+
'/Users/dan/ionic-angular/components/alert/alert-component.ngfactory.js' => Set {} } }
246+
*/
157247
});
158248
});
159249

src/optimization/treeshake.ts

Lines changed: 93 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { dirname, join, relative } from 'path';
1+
import { basename, dirname, join, relative } from 'path';
22
import { Logger } from '../logger/logger';
33
import * as Constants from '../util/constants';
44
import { changeExtension, escapeStringForRegex } from '../util/helpers';
@@ -11,17 +11,18 @@ export function calculateUnusedComponents(dependencyMap: Map<string, Set<string>
1111
export function calculateUnusedComponentsImpl(dependencyMap: Map<string, Set<string>>, importee: string): any {
1212
const filteredMap = filterMap(dependencyMap);
1313
processImportTree(filteredMap, importee);
14+
calculateUnusedIonicProviders(filteredMap);
1415
return generateResults(filteredMap);
1516
}
1617

1718
function generateResults(dependencyMap: Map<string, Set<string>>) {
1819
const toPurgeMap = new Map<string, Set<string>>();
1920
const updatedMap = new Map<string, Set<string>>();
2021
dependencyMap.forEach((importeeSet: Set<string>, modulePath: string) => {
21-
if (importeeSet.size > 0) {
22+
if ((importeeSet && importeeSet.size > 0) || requiredModule(modulePath)) {
2223
updatedMap.set(modulePath, importeeSet);
2324
} else {
24-
toPurgeMap.set(modulePath, importeeSet);
25+
toPurgeMap.set(modulePath, importeeSet);
2526
}
2627
});
2728
return {
@@ -30,10 +31,17 @@ function generateResults(dependencyMap: Map<string, Set<string>>) {
3031
};
3132
}
3233

34+
function requiredModule(modulePath: string) {
35+
const mainJsFile = changeExtension(process.env[Constants.ENV_APP_ENTRY_POINT], '.js');
36+
const appModule = changeExtension(process.env[Constants.ENV_APP_NG_MODULE_PATH], '.js');
37+
const appModuleNgFactory = getAppModuleNgFactoryPath();
38+
return modulePath === mainJsFile || modulePath === appModule || modulePath === appModuleNgFactory;
39+
}
40+
3341
function filterMap(dependencyMap: Map<string, Set<string>>) {
3442
const filteredMap = new Map<string, Set<string>>();
3543
dependencyMap.forEach((importeeSet: Set<string>, modulePath: string) => {
36-
if (isIonicComponent(modulePath)) {
44+
if (isIonicComponentOrAppSource(modulePath)) {
3745
filteredMap.set(modulePath, importeeSet);
3846
}
3947
});
@@ -43,7 +51,7 @@ function filterMap(dependencyMap: Map<string, Set<string>>) {
4351
function processImportTree(dependencyMap: Map<string, Set<string>>, importee: string) {
4452
const importees: string[] = [];
4553
dependencyMap.forEach((importeeSet: Set<string>, modulePath: string) => {
46-
if (importeeSet.has(importee)) {
54+
if (importeeSet && importeeSet.has(importee)) {
4755
importeeSet.delete(importee);
4856
// if it importer by an `ngfactory` file, we probably aren't going to be able to purge it
4957
let ngFactoryImportee = false;
@@ -62,10 +70,88 @@ function processImportTree(dependencyMap: Map<string, Set<string>>, importee: st
6270
importees.forEach(importee => processImportTree(dependencyMap, importee));
6371
}
6472

65-
export function isIonicComponent(modulePath: string) {
73+
function calculateUnusedIonicProviders(dependencyMap: Map<string, Set<string>>) {
74+
const ACTION_SHEET_CONTROLLER = join(process.env[Constants.ENV_VAR_IONIC_ANGULAR_DIR], 'components', 'action-sheet', 'action-sheet-controller.js');
75+
const ACTION_SHEET_COMPONENT_FACTORY = join(process.env[Constants.ENV_VAR_IONIC_ANGULAR_DIR], 'components', 'action-sheet', 'action-sheet-component.ngfactory.js');
76+
77+
const ALERT_CONTROLLER = join(process.env[Constants.ENV_VAR_IONIC_ANGULAR_DIR], 'components', 'alert', 'alert-controller.js');
78+
const ALERT_COMPONENT_FACTORY = join(process.env[Constants.ENV_VAR_IONIC_ANGULAR_DIR], 'components', 'alert', 'alert-component.ngfactory.js');
79+
80+
const LOADING_CONTROLLER = join(process.env[Constants.ENV_VAR_IONIC_ANGULAR_DIR], 'components', 'loading', 'loading-controller.js');
81+
const LOADING_COMPONENT_FACTORY = join(process.env[Constants.ENV_VAR_IONIC_ANGULAR_DIR], 'components', 'loading', 'loading-component.ngfactory.js');
82+
83+
const MODAL_CONTROLLER = join(process.env[Constants.ENV_VAR_IONIC_ANGULAR_DIR], 'components', 'modal', 'modal-controller.js');
84+
const MODAL_COMPONENT_FACTORY = join(process.env[Constants.ENV_VAR_IONIC_ANGULAR_DIR], 'components', 'modal', 'modal-component.ngfactory.js');
85+
86+
const PICKER_CONTROLLER = join(process.env[Constants.ENV_VAR_IONIC_ANGULAR_DIR], 'components', 'picker', 'picker-controller.js');
87+
const PICKER_COMPONENT_FACTORY = join(process.env[Constants.ENV_VAR_IONIC_ANGULAR_DIR], 'components', 'picker', 'picker-component.ngfactory.js');
88+
89+
const POPOVER_CONTROLLER = join(process.env[Constants.ENV_VAR_IONIC_ANGULAR_DIR], 'components', 'popover', 'popover-controller.js');
90+
const POPOVER_COMPONENT_FACTORY = join(process.env[Constants.ENV_VAR_IONIC_ANGULAR_DIR], 'components', 'popover', 'popover-component.ngfactory.js');
91+
92+
const TOAST_CONTROLLER = join(process.env[Constants.ENV_VAR_IONIC_ANGULAR_DIR], 'components', 'toast', 'toast-controller.js');
93+
const TOAST_COMPONENT_FACTORY = join(process.env[Constants.ENV_VAR_IONIC_ANGULAR_DIR], 'components', 'toast', 'toast-component.ngfactory.js');
94+
95+
processIonicProviders(dependencyMap, ACTION_SHEET_CONTROLLER);
96+
processIonicProviders(dependencyMap, ALERT_CONTROLLER);
97+
processIonicProviders(dependencyMap, LOADING_CONTROLLER);
98+
processIonicProviders(dependencyMap, MODAL_CONTROLLER);
99+
processIonicProviders(dependencyMap, PICKER_CONTROLLER);
100+
processIonicProviders(dependencyMap, POPOVER_CONTROLLER);
101+
processIonicProviders(dependencyMap, TOAST_CONTROLLER);
102+
103+
// check if the controllers were deleted, if so, purge the component too
104+
processIonicProviderComponents(dependencyMap, ACTION_SHEET_CONTROLLER, ACTION_SHEET_COMPONENT_FACTORY);
105+
processIonicProviderComponents(dependencyMap, ALERT_CONTROLLER, ALERT_COMPONENT_FACTORY);
106+
processIonicProviderComponents(dependencyMap, LOADING_CONTROLLER, LOADING_COMPONENT_FACTORY);
107+
processIonicProviderComponents(dependencyMap, MODAL_CONTROLLER, MODAL_COMPONENT_FACTORY);
108+
processIonicProviderComponents(dependencyMap, PICKER_CONTROLLER, PICKER_COMPONENT_FACTORY);
109+
processIonicProviderComponents(dependencyMap, POPOVER_CONTROLLER, POPOVER_COMPONENT_FACTORY);
110+
processIonicProviderComponents(dependencyMap, TOAST_CONTROLLER, TOAST_COMPONENT_FACTORY);
111+
}
112+
113+
function processIonicProviderComponents(dependencyMap: Map<string, Set<string>>, providerPath: string, componentPath: string) {
114+
const importeeSet = dependencyMap.get(providerPath);
115+
if (importeeSet && importeeSet.size === 0) {
116+
processIonicProviders(dependencyMap, componentPath);
117+
}
118+
}
119+
120+
function getAppModuleNgFactoryPath() {
121+
const appNgModulePath = process.env[Constants.ENV_APP_NG_MODULE_PATH];
122+
const directory = dirname(appNgModulePath);
123+
const extensionlessFileName = basename(appNgModulePath, '.js');
124+
const ngFactoryFileName = extensionlessFileName + '.ngfactory.js';
125+
return join(directory, ngFactoryFileName);
126+
}
127+
128+
function processIonicProviders(dependencyMap: Map<string, Set<string>>, providerPath: string) {
129+
const importeeSet = dependencyMap.get(providerPath);
130+
const appModuleNgFactoryPath = getAppModuleNgFactoryPath();
131+
// we can only purge an ionic provider if it is imported from one module, which is the AppModuleNgFactory
132+
if (importeeSet && importeeSet.size === 1 && importeeSet.has(appModuleNgFactoryPath)) {
133+
importeeSet.delete(appModuleNgFactoryPath);
134+
// loop over the dependency map and remove this provider from importee sets
135+
processImportTreeForProviders(dependencyMap, providerPath);
136+
}
137+
}
138+
139+
function processImportTreeForProviders(dependencyMap: Map<string, Set<string>>, importee: string) {
140+
const importees: string[] = [];
141+
dependencyMap.forEach((importeeSet: Set<string>, modulePath: string) => {
142+
if (importeeSet.has(importee)) {
143+
importeeSet.delete(importee);
144+
importees.push(modulePath);
145+
}
146+
});
147+
importees.forEach(importee => processImportTreeForProviders(dependencyMap, importee));
148+
}
149+
150+
export function isIonicComponentOrAppSource(modulePath: string) {
66151
// for now, just use a simple filter of if a file is in ionic-angular/components
67152
const ionicAngularComponentDir = join(process.env[Constants.ENV_VAR_IONIC_ANGULAR_DIR], 'components');
68-
return modulePath.indexOf(ionicAngularComponentDir) >= 0;
153+
const srcDir = process.env[Constants.ENV_VAR_SRC_DIR];
154+
return modulePath.indexOf(ionicAngularComponentDir) >= 0 || modulePath.indexOf(srcDir) >= 0;
69155
}
70156

71157
export function isNgFactory(modulePath: string) {

0 commit comments

Comments
 (0)