Many existing Angular 1 projects are looking to NgUpgrade to help with the transition to post 1.x versions of Angular. In this post I will show how to create an optimized Angular bundle for the NgUpgrade scenario.
The demo application for this article is deployed here.
NgUpgrade bridges the gap between Angular 1.x and newer versions of Angular by allowing code from both frameworks to co-exist in the same application.
This is good news for large Angular 1.x projects since it allows the transition to happen gradually over time.
NgUpgrade integrates new Angular code with legacy Angular 1 code very well, but requires us to download both versions of the framework. This could be undesirable from a performance perspective since the combined footprint of both Angular versions makes our application bigger.
Luckily there are ways to shrink the footprint of Angular and reduce the impact of running two versions of Angular in the same application.
AoT compilation and Tree Shaking are two very effective optimization techniques.
In the following sample I will show how to create an AoT compiled and “Tree Shaken” NgUpgrade bundle.
Using NgUpgrade I will downgrade a few of my new Angular components and use them in my Angular 1.x demo application.
In order to use a downgraded Angular component in Angular 1, we have to map the new Angular component to an Angular 1 directive. The directive registration follows familiar Angular 1 directive syntax.
I have created a standard NgModule containing the components I want to downgrade. It's important to register the components as entryComponents. Otherwise the application will error at runtime because Angular isn't able to find the component factories for our components.
import {Component, NgModule} from '@angular/core';
import {BrowserModule} from '@angular/platform-browser';
import {ReactiveFormsModule} from '@angular/forms';
import {UpgradeModule} from '@angular/upgrade/static';
import {TextEditor} from './components/text-editor/text-editor';
import {Algorithms} from './components/algorithms/algorithms';
import {SurveyDemo} from './components/survey/survey-demo';
import {Survey} from './components/survey/survey';
import {InsertionSort} from './components/insertion-sort/insertion-sort';
declare var angular:any;
@NgModule({
declarations: [Algorithms, InsertionSort, SurveyDemo, Survey, TextEditor],
entryComponents: [Algorithms, InsertionSort, SurveyDemo, Survey, TextEditor],
imports: [BrowserModule, UpgradeModule, ReactiveFormsModule]
})
export class AngularModule {
ngDoBootstrap() {}
}
Next I will AoT compile my NgModule and bootstrap the application using the generated NgModuleFactory
import {Component, NgModule} from '@angular/core';
import {BrowserModule} from '@angular/platform-browser';
import {ReactiveFormsModule} from '@angular/forms';
import {platformBrowser} from '@angular/platform-browser';
import {UpgradeModule} from '@angular/upgrade/static';
import {downgradeComponent} from '@angular/upgrade/static';
import {TextEditor} from './components/text-editor/text-editor';
import {Algorithms} from './components/algorithms/algorithms';
import {SurveyDemo} from './components/survey/survey-demo';
import {Survey} from './components/survey/survey';
import {InsertionSort} from './components/insertion-sort/insertion-sort';
import {AngularModuleNgFactory} from './upgrade-aot/upgrade-module.ngfactory';
declare var angular:any;
angular.module('angular-legacy').directive('algorithms', downgradeComponent({component: Algorithms}));
angular.module('angular-legacy').directive('survey', downgradeComponent({component: SurveyDemo}));
angular.module('angular-legacy').directive('editor', downgradeComponent({component: TextEditor}));
platformBrowser().bootstrapModuleFactory(AngularModuleNgFactory).then((ref) => {
const adapter = ref.injector.get(UpgradeModule) as UpgradeModule;
console.log('bootstrapping');
adapter.bootstrap(document.body, ['angular-legacy']);
});
The next step is to enable Tree Shaking. In this example I've decided to go with Rollup as the “Tree Shaker”. My sample assumes the following Rollup configuration:
import rollup from 'rollup'
import nodeResolve from 'rollup-plugin-node-resolve'
import commonjs from 'rollup-plugin-commonjs';
import uglify from 'rollup-plugin-uglify';
import closure from 'google-closure-compiler-js';
export default {
entry: 'upgrade.js',
dest: './dist/upgrade-build.js',
sourceMap: true,
sourceMapFile: './dist/upgrade-build.js.map',
format: 'iife',
plugins: [
nodeResolve({jsnext: true, module: true}),
commonjs({
include: 'node_modules/rxjs/**',
}),
uglify()
]
}
AoT and Tree Shaking can be triggered from the command line by running the following command:
ngc -p tsconfig-upgrade.json && rollup -c rollup-ng-upgrade.js
rollup-ng-upgrade.js is the Rollup config file described above.
tsconfig-upgrade.json is a standard typescript config file, but notice that I am generating es2015 modules to be benefit from Tree Shaking.
{
"compilerOptions": {
"target": "es5",
"module": "es2015",
"moduleResolution": "node",
"removeComments": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"allowUnreachableCode": false,
"noImplicitAny": false
},
"files": [
"upgrade.ts",
"typings/index.d.ts"
]
}
If you are interested in more of the details you can find the full source code on Github. There is also a runnable demo here.
If you are interested in seeing the final bundle you can check out the deployed demo and look for the upgrade-build.js file in the network tab.
I purposely wanted to make this a little bit more interesting than another “Hello World” app, so I included a few “semi” advanced sample components. The NgUpgraded components utilize both Reactive Forms and RxJs.