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

Commit 1c57ee6

Browse files
committed
feat(optimization): enable manual tree shaking by default
enable manual tree shaking by default
1 parent fb8b69a commit 1c57ee6

File tree

8 files changed

+186
-226
lines changed

8 files changed

+186
-226
lines changed

README.md

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -134,8 +134,8 @@ npm run build --rollup ./config/rollup.config.js
134134
| print modified dependency tree | `ionic_print_modified_dependency_tree` | `--printModifiedDependencyTree` | `null` | Set to `true` to print out the modified dependency tree after purging unused modules |
135135
| print webpack dependency tree | `ionic_print_webpack_dependency_tree` | `--printWebpackDependencyTree` | `null` | Set to `true` to print out a dependency tree after running Webpack |
136136
| parse deeplink config | `ionic_parse_deeplinks` | `--parseDeepLinks` | `true` | Parses and extracts data from the `@IonicPage` decorator |
137-
| experimental manual tree shaking | `ionic_experimental_manual_treeshaking` | `--experimentalManualTreeshaking` | `null` | Set to `true` to purge unused Ionic components/code (Experimental) |
138-
| experimental purge decorators | `ionic_experimental_purge_decorators` | `--experimentalPurgeDecorators` | `null` | Set to `true` to purge unneeded decorators to improve tree shakeability of code (Experimental) |
137+
| manual tree shaking | `ionic_manual_treeshaking` | `--manualTreeshaking` | `true` | Set to `true` to purge unused Ionic components/code |
138+
| purge decorators | `ionic_purge_decorators` | `--purgeDecorators` | `true` | Set to `true` to purge unneeded decorators to improve tree shakeability of code |
139139
| experimental closure compiler | `ionic_use_experimental_closure` | `--useExperimentalClosure` | `null` | Set to `true` to use closure compiler to minify the final bundle |
140140
| experimental babili | `ionic_use_experimental_babili` | `--useExperimentalBabili` | `null` | Set to `true` to use babili to minify es2015 code |
141141
| convert bundle to ES5 | `ionic_build_to_es5` | `--buildToEs5` | `true` | Convert bundle to ES5 for for production deployments |
@@ -179,8 +179,8 @@ These environment variables are automatically set to [Node's `process.env`](http
179179
| `IONIC_PRINT_MODIFIED_DEPENDENCY_TREE` | boolean to print out the modified dependency tree after purging unused modules |
180180
| `IONIC_PRINT_WEBPACK_DEPENDENCY_TREE` | boolean to print out a dependency tree after running Webpack |
181181
| `IONIC_PARSE_DEEPLINKS` | boolean to enable parsing the Ionic 3.x deep links API for lazy loading |
182-
| `IONIC_EXPERIMENTAL_MANUAL_TREESHAKING` | boolean to enable purging unused Ionic components/code (Experimental) |
183-
| `IONIC_EXPERIMENTAL_PURGE_DECORATORS` | boolean to enable purging unneeded decorators from source code (Experimental) |
182+
| `IONIC_MANUAL_TREESHAKING` | boolean to enable purging unused Ionic components/code |
183+
| `IONIC_PURGE_DECORATORS` | boolean to enable purging unneeded decorators from source code |
184184
| `IONIC_USE_EXPERIMENTAL_CLOSURE` | boolean to enable use of closure compiler to minify the final bundle |
185185
| `IONIC_USE_EXPERIMENTAL_BABILI` | boolean to enable use of babili to minify es2015 code |
186186
| `IONIC_BUILD_TO_ES5` | boolean to enable converting bundle to ES5 for for production deployments |
@@ -230,7 +230,6 @@ Example NPM Script:
230230
```
231231
npm run lint --bailOnLintError true
232232
```
233-
3. Set `ionic_experimental_manual_treeshaking` and `ionic_experimental_purge_decorators` to true in the `package.json` to potentially dramatically shrink an app's bundle size.
234233

235234
## The Stack
236235

src/optimization.spec.ts

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { join } from 'path';
33
import * as optimization from './optimization';
44
import * as decorators from './optimization/decorators';
55
import * as treeshake from './optimization/treeshake';
6+
import * as Constants from './util/constants';
67
import * as helpers from './util/helpers';
78

89
import { FileCache } from './util/file-cache';
@@ -65,4 +66,68 @@ describe('optimization task', () => {
6566
expect(context.fileCache.get(filePathTwo)).toBeFalsy();
6667
});
6768
});
69+
70+
describe('doOptimizations', () => {
71+
it('should not manual tree shaking unless the module.js file is in the cache', () => {
72+
const context = {
73+
fileCache: new FileCache(),
74+
};
75+
76+
const mockIndexPath = join('some', 'path', 'myApp', 'node_modules', 'ionic-angular', 'index.js');
77+
78+
spyOn(treeshake, treeshake.calculateUnusedComponents.name);
79+
spyOn(treeshake, treeshake.purgeUnusedImportsAndExportsFromIndex.name);
80+
spyOn(treeshake, treeshake.purgeComponentNgFactoryImportAndUsage.name);
81+
spyOn(treeshake, treeshake.purgeProviderControllerImportAndUsage.name);
82+
spyOn(treeshake, treeshake.purgeProviderClassNameFromIonicModuleForRoot.name);
83+
spyOn(helpers, helpers.getStringPropertyValue.name).and.returnValue(mockIndexPath);
84+
85+
spyOn(helpers, helpers.getBooleanPropertyValue.name).and.callFake((propertyName: string) => {
86+
if (propertyName === Constants.ENV_MANUAL_TREESHAKING) {
87+
return true;
88+
}
89+
return false;
90+
});
91+
92+
optimization.doOptimizations(context, new Map());
93+
94+
expect(treeshake.calculateUnusedComponents).not.toHaveBeenCalled();
95+
expect(treeshake.purgeUnusedImportsAndExportsFromIndex).not.toHaveBeenCalled();
96+
expect(treeshake.purgeComponentNgFactoryImportAndUsage).not.toHaveBeenCalled();
97+
expect(treeshake.purgeProviderControllerImportAndUsage).not.toHaveBeenCalled();
98+
expect(treeshake.purgeProviderClassNameFromIonicModuleForRoot).not.toHaveBeenCalled();
99+
100+
});
101+
102+
it('should run manual tree shaking when there is a module.js file in the cache', () => {
103+
const context = {
104+
fileCache: new FileCache(),
105+
};
106+
107+
const mockIndexPath = join('some', 'path', 'myApp', 'node_modules', 'ionic-angular', 'index.js');
108+
109+
spyOn(treeshake, treeshake.getAppModuleNgFactoryPath.name);
110+
spyOn(treeshake, treeshake.calculateUnusedComponents.name).and.returnValue({ purgedModules: new Map()});
111+
spyOn(treeshake, treeshake.purgeUnusedImportsAndExportsFromIndex.name);
112+
113+
spyOn(helpers, helpers.getStringPropertyValue.name).and.callFake((propertyName: string) => {
114+
return mockIndexPath;
115+
});
116+
117+
spyOn(helpers, helpers.getBooleanPropertyValue.name).and.callFake((propertyName: string) => {
118+
if (propertyName === Constants.ENV_MANUAL_TREESHAKING) {
119+
return true;
120+
}
121+
return false;
122+
});
123+
124+
context.fileCache.set(mockIndexPath, { path: mockIndexPath, content: 'indexContent'});
125+
context.fileCache.set(treeshake.getIonicModuleFilePath(), { path: treeshake.getIonicModuleFilePath(), content: 'moduleContent'});
126+
127+
optimization.doOptimizations(context, new Map());
128+
129+
expect(treeshake.calculateUnusedComponents).toHaveBeenCalled();
130+
expect(treeshake.purgeUnusedImportsAndExportsFromIndex).toHaveBeenCalled();
131+
});
132+
});
68133
});

src/optimization.ts

Lines changed: 37 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,14 @@ import { getBooleanPropertyValue, getStringPropertyValue, webpackStatsToDependen
1010
import { BuildContext, TaskInfo } from './util/interfaces';
1111
import { runWebpackFullBuild, WebpackConfig } from './webpack';
1212
import { addPureAnnotation, purgeStaticCtorFields, purgeStaticFieldDecorators, purgeTranspiledDecorators } from './optimization/decorators';
13-
import { getAppModuleNgFactoryPath, calculateUnusedComponents, purgeUnusedImportsAndExportsFromIndex, purgeComponentNgFactoryImportAndUsage, purgeProviderControllerImportAndUsage, purgeProviderClassNameFromIonicModuleForRoot } from './optimization/treeshake';
13+
import { getAppModuleNgFactoryPath,
14+
calculateUnusedComponents,
15+
getIonicModuleFilePath,
16+
purgeUnusedImportsAndExportsFromIndex,
17+
purgeComponentNgFactoryImportAndUsage,
18+
purgeProviderControllerImportAndUsage,
19+
purgeProviderClassNameFromIonicModuleForRoot
20+
} from './optimization/treeshake';
1421

1522
export function optimization(context: BuildContext, configFile: string) {
1623
const logger = new Logger(`optimization`);
@@ -58,9 +65,15 @@ export function doOptimizations(context: BuildContext, dependencyMap: Map<string
5865
}
5966

6067
// remove unused component imports
61-
if (getBooleanPropertyValue(Constants.ENV_EXPERIMENTAL_MANUAL_TREESHAKING)) {
62-
const results = calculateUnusedComponents(modifiedMap);
63-
purgeUnusedImports(context, results.purgedModules);
68+
if (getBooleanPropertyValue(Constants.ENV_MANUAL_TREESHAKING)) {
69+
// TODO remove this in a couple versions
70+
// only run manual tree shaking if the module file is found
71+
// since there is a breaking change here
72+
const ionicModulePath = getIonicModuleFilePath();
73+
if (context.fileCache.get(ionicModulePath)) {
74+
const results = calculateUnusedComponents(modifiedMap);
75+
purgeUnusedImports(context, results.purgedModules);
76+
}
6477
}
6578

6679
if (getBooleanPropertyValue(Constants.ENV_PRINT_MODIFIED_DEPENDENCY_TREE)) {
@@ -74,7 +87,7 @@ export function doOptimizations(context: BuildContext, dependencyMap: Map<string
7487

7588
function optimizationEnabled() {
7689
const purgeDecorators = getBooleanPropertyValue(Constants.ENV_PURGE_DECORATORS);
77-
const manualTreeshaking = getBooleanPropertyValue(Constants.ENV_EXPERIMENTAL_MANUAL_TREESHAKING);
90+
const manualTreeshaking = getBooleanPropertyValue(Constants.ENV_MANUAL_TREESHAKING);
7891
return purgeDecorators || manualTreeshaking;
7992
}
8093

@@ -99,26 +112,31 @@ function removeDecorators(context: BuildContext) {
99112

100113
function purgeUnusedImports(context: BuildContext, purgeDependencyMap: Map<string, Set<string>>) {
101114
// for now, restrict this to components in the ionic-angular/index.js file
102-
const indexFilePath = process.env[Constants.ENV_VAR_IONIC_ANGULAR_ENTRY_POINT];
115+
const indexFilePath = getStringPropertyValue(Constants.ENV_VAR_IONIC_ANGULAR_ENTRY_POINT);
116+
const moduleFilePath = getIonicModuleFilePath();
103117
const file = context.fileCache.get(indexFilePath);
104118
if (!file) {
105119
throw new Error(`Could not find ionic-angular index file ${indexFilePath}`);
106120
}
121+
const moduleFile = context.fileCache.get(moduleFilePath);
122+
if (!moduleFile) {
123+
throw new Error(`Could not find ionic-angular module file ${moduleFilePath}`);
124+
}
107125
const modulesToPurge: string[] = [];
108126
purgeDependencyMap.forEach((set: Set<string>, moduleToPurge: string) => {
109127
modulesToPurge.push(moduleToPurge);
110128
});
111129

112-
const updatedFileContent = purgeUnusedImportsAndExportsFromIndex(indexFilePath, file.content, modulesToPurge);
113-
context.fileCache.set(indexFilePath, { path: indexFilePath, content: updatedFileContent });
130+
const updatedFileContent = purgeUnusedImportsAndExportsFromIndex(moduleFilePath, moduleFile.content, modulesToPurge);
131+
context.fileCache.set(moduleFilePath, { path: moduleFilePath, content: updatedFileContent });
114132

115-
attemptToPurgeUnusedProvider(context, purgeDependencyMap, process.env[Constants.ENV_ACTION_SHEET_CONTROLLER_PATH], process.env[Constants.ENV_ACTION_SHEET_VIEW_CONTROLLER_PATH], process.env[Constants.ENV_ACTION_SHEET_COMPONENT_FACTORY_PATH], process.env[Constants.ENV_ACTION_SHEET_CONTROLLER_CLASSNAME]);
116-
attemptToPurgeUnusedProvider(context, purgeDependencyMap, process.env[Constants.ENV_ALERT_CONTROLLER_PATH], process.env[Constants.ENV_ALERT_VIEW_CONTROLLER_PATH], process.env[Constants.ENV_ALERT_COMPONENT_FACTORY_PATH], process.env[Constants.ENV_ALERT_CONTROLLER_CLASSNAME]);
117-
attemptToPurgeUnusedProvider(context, purgeDependencyMap, process.env[Constants.ENV_LOADING_CONTROLLER_PATH], process.env[Constants.ENV_LOADING_VIEW_CONTROLLER_PATH], process.env[Constants.ENV_LOADING_COMPONENT_FACTORY_PATH], process.env[Constants.ENV_LOADING_CONTROLLER_CLASSNAME]);
118-
attemptToPurgeUnusedProvider(context, purgeDependencyMap, process.env[Constants.ENV_MODAL_CONTROLLER_PATH], process.env[Constants.ENV_MODAL_VIEW_CONTROLLER_PATH], process.env[Constants.ENV_MODAL_COMPONENT_FACTORY_PATH], process.env[Constants.ENV_MODAL_CONTROLLER_CLASSNAME]);
119-
attemptToPurgeUnusedProvider(context, purgeDependencyMap, process.env[Constants.ENV_PICKER_CONTROLLER_PATH], process.env[Constants.ENV_PICKER_VIEW_CONTROLLER_PATH], process.env[Constants.ENV_PICKER_COMPONENT_FACTORY_PATH], process.env[Constants.ENV_PICKER_CONTROLLER_CLASSNAME]);
120-
attemptToPurgeUnusedProvider(context, purgeDependencyMap, process.env[Constants.ENV_POPOVER_CONTROLLER_PATH], process.env[Constants.ENV_POPOVER_VIEW_CONTROLLER_PATH], process.env[Constants.ENV_POPOVER_COMPONENT_FACTORY_PATH], process.env[Constants.ENV_POPOVER_CONTROLLER_CLASSNAME]);
121-
attemptToPurgeUnusedProvider(context, purgeDependencyMap, process.env[Constants.ENV_TOAST_CONTROLLER_PATH], process.env[Constants.ENV_TOAST_VIEW_CONTROLLER_PATH], process.env[Constants.ENV_TOAST_COMPONENT_FACTORY_PATH], process.env[Constants.ENV_TOAST_CONTROLLER_CLASSNAME]);
133+
attemptToPurgeUnusedProvider(context, purgeDependencyMap, getStringPropertyValue(Constants.ENV_ACTION_SHEET_CONTROLLER_PATH), getStringPropertyValue(Constants.ENV_ACTION_SHEET_VIEW_CONTROLLER_PATH), getStringPropertyValue(Constants.ENV_ACTION_SHEET_COMPONENT_FACTORY_PATH), getStringPropertyValue(Constants.ENV_ACTION_SHEET_CONTROLLER_CLASSNAME));
134+
attemptToPurgeUnusedProvider(context, purgeDependencyMap, getStringPropertyValue(Constants.ENV_ALERT_CONTROLLER_PATH), getStringPropertyValue(Constants.ENV_ALERT_VIEW_CONTROLLER_PATH), getStringPropertyValue(Constants.ENV_ALERT_COMPONENT_FACTORY_PATH), getStringPropertyValue(Constants.ENV_ALERT_CONTROLLER_CLASSNAME));
135+
attemptToPurgeUnusedProvider(context, purgeDependencyMap, getStringPropertyValue(Constants.ENV_LOADING_CONTROLLER_PATH), getStringPropertyValue(Constants.ENV_LOADING_VIEW_CONTROLLER_PATH), getStringPropertyValue(Constants.ENV_LOADING_COMPONENT_FACTORY_PATH), getStringPropertyValue(Constants.ENV_LOADING_CONTROLLER_CLASSNAME));
136+
attemptToPurgeUnusedProvider(context, purgeDependencyMap, getStringPropertyValue(Constants.ENV_MODAL_CONTROLLER_PATH), getStringPropertyValue(Constants.ENV_MODAL_VIEW_CONTROLLER_PATH), getStringPropertyValue(Constants.ENV_MODAL_COMPONENT_FACTORY_PATH), getStringPropertyValue(Constants.ENV_MODAL_CONTROLLER_CLASSNAME));
137+
attemptToPurgeUnusedProvider(context, purgeDependencyMap, getStringPropertyValue(Constants.ENV_PICKER_CONTROLLER_PATH), getStringPropertyValue(Constants.ENV_PICKER_VIEW_CONTROLLER_PATH), getStringPropertyValue(Constants.ENV_PICKER_COMPONENT_FACTORY_PATH), getStringPropertyValue(Constants.ENV_PICKER_CONTROLLER_CLASSNAME));
138+
attemptToPurgeUnusedProvider(context, purgeDependencyMap, getStringPropertyValue(Constants.ENV_POPOVER_CONTROLLER_PATH), getStringPropertyValue(Constants.ENV_POPOVER_VIEW_CONTROLLER_PATH), getStringPropertyValue(Constants.ENV_POPOVER_COMPONENT_FACTORY_PATH), getStringPropertyValue(Constants.ENV_POPOVER_CONTROLLER_CLASSNAME));
139+
attemptToPurgeUnusedProvider(context, purgeDependencyMap, getStringPropertyValue(Constants.ENV_TOAST_CONTROLLER_PATH), getStringPropertyValue(Constants.ENV_TOAST_VIEW_CONTROLLER_PATH), getStringPropertyValue(Constants.ENV_TOAST_COMPONENT_FACTORY_PATH), getStringPropertyValue(Constants.ENV_TOAST_CONTROLLER_CLASSNAME));
122140
}
123141

124142
function attemptToPurgeUnusedProvider(context: BuildContext, dependencyMap: Map<string, Set<string>>, providerPath: string, providerComponentPath: string, providerComponentFactoryPath: string, providerClassName: string) {
@@ -137,12 +155,12 @@ function attemptToPurgeUnusedProvider(context: BuildContext, dependencyMap: Map<
137155
context.fileCache.set(appModuleNgFactoryPath, { path: appModuleNgFactoryPath, content: updatedContent});
138156

139157
// purge the provider name from the forRoot method providers list
140-
const indexFilePath = process.env[Constants.ENV_VAR_IONIC_ANGULAR_ENTRY_POINT];
141-
const ionicIndexFile = context.fileCache.get(indexFilePath);
142-
let newIndexFileContent = purgeProviderClassNameFromIonicModuleForRoot(ionicIndexFile.content, providerClassName);
158+
const moduleFilePath = getIonicModuleFilePath();
159+
const ionicModuleFile = context.fileCache.get(moduleFilePath);
160+
let newModuleFileContent = purgeProviderClassNameFromIonicModuleForRoot(ionicModuleFile.content, providerClassName);
143161

144162
// purge the component from the index file
145-
context.fileCache.set(indexFilePath, { path: indexFilePath, content: newIndexFileContent});
163+
context.fileCache.set(moduleFilePath, { path: moduleFilePath, content: newModuleFileContent});
146164
}
147165
}
148166

@@ -163,5 +181,3 @@ const taskInfo: TaskInfo = {
163181
packageConfig: 'ionic_dependency_tree',
164182
defaultConfigFile: 'optimization.config'
165183
};
166-
167-

0 commit comments

Comments
 (0)