.NET Core Angular VS2017 Templates (Part 3)

Home / .NET Core Angular VS2017 Templates (Part 3)

In Part 2 of my series on creating Angular templates for Visual Studio 2017, I made a relatively serviceable template for my latest Angular test. But, I knew it could improved.


The major flaw in the previous template was load time. That is to say, typical loading the application into the browser took over 20 seconds (geez!). This was a result of not concatenating / minimizing any of the Angular and rxjs scripts. At the time, though, I really didn’t know how to achieve this with gulp. I found a lot of useful examples of how to use other gulp modules to achieve bundling.

It turns out that the gulp library “systemjs-builder” can concat/minimize all of the dependencies of an Angular application into static bundles. My cursory tests showed load times going from 20s to 4s.

To utilize Builder (systemjs-builder), I had to modify the startup.js I was using. For clarity, I renamed this to “systemjs.config.” The primary change was to the map settings. Rather than loading the Angular bits as packages, I moved them to the maps. I also moved the ES5 shims to the mappings. You can also see that this file no longer imports “app.” That has moved the the HTML index page.

(function (global) {
    var paths = {
        'npm:': 'node_modules/'
    };

    // map tells the System loader where to look for things
    var map = {
        'app': 'scripts',
        'rxjs': 'npm:rxjs',
        
	//shims
	'core-js-shim':'npm:core-js/client/shim.min.js',
	'zone':'npm:zone.js/dist/zone.js',
	'reflect':'npm:reflect-metadata/Reflect.js'
    };

    // packages tells the System loader how to load when no filename and/or no extension
    var packages = {
        'app': { main: 'main.js', defaultExtension: 'js', defaultJSExtensions: true },
        'rxjs': { defaultExtension: 'js' }
    };

    var ngPackageNames = [
      'common',
      'compiler',
      'core',
      'forms',
      'http',
      'platform-browser',
      'platform-browser-dynamic',
      'router',
      'router-deprecated',
      'upgrade',
    ];

    // Individual files (~300 requests):
    function packIndex(pkgName) {
        packages['@angular/' + pkgName] = { main: 'index.js', defaultExtension: 'js' };
    }

    // Bundled (~40 requests):
    function packUmd(pkgName) {
		var pkg = { main: '/bundles/' + pkgName + '.umd.js', defaultExtension: 'js' };
        packages['@angular/' + pkgName] = pkg;
    }
    
    function mapUmd(pkgName) {
		var mapName = '@angular/' + pkgName;
		var mapPath = 'npm:' + mapName +  '/bundles/' + pkgName + '.umd.js';
		map[mapName] = mapPath;
		console.log(mapName + ' ' + mapPath);
	}

    // Most environments should use UMD; some (Karma) need the individual index files
    //var setPackageConfig = System.packageWithIndex ? packIndex : packUmd;
	var setPackageConfig = mapUmd;
	
    // Add package entries for angular packages
    ngPackageNames.forEach(setPackageConfig);

    var config = {
        paths: paths,
        map: map,
        packages: packages,
        defaultJSExtensions: true
    }

    System.config(config);
    //System.import('app').catch(function (err) { console.error(err); });
})(this);

With that configuration change, I also added a vendor.ts file that imports zone.js and a few other things that aren’t typescript files.

The gulp file is then modified to include a few tasks that utilize the system Builder. I have a task that defines the Builder, and two tasks that bundle the app/vendor sources to their respective JavaScript files. Note that I left the TypeScript compilation as it was – with Visual Studio 2017 performing the compilation. I think this may work better with Visual Studio’s Intellisense, but that is purely speculative.

gulp.task('bundle:setup', () => {
    return new Promise((resolve, reject) => {
        builder = new Builder('', paths.webroot + 'scripts/systemjs-config.js');
        resolve();
    });
});

gulp.task('bundle:vendor', function () {
    return builder
        .buildStatic(paths.webroot + 'scripts/vendor.js', paths.webroot + 'scripts/' + vendorBundleName, { minify: true, sourceMaps: true })
        .catch(function (err) {
            console.log('Vendor bundle error');
            console.log(err);
        });
});

gulp.task('bundle:app', function () {
    return builder
        .buildStatic(paths.webroot + 'scripts/main.js', paths.webroot + 'scripts/' + mainBundleName, { minify: true, sourceMaps: true })
        .catch(function (err) {
            console.log('App bundle error');
            console.log(err);
        });
});

gulp.task('bundle', gulp.parallel('bundle:vendor', 'bundle:app'), (cb) => {
    return gulp.src('index.html')
        .pipe(htmlreplace({
            'app': mainBundleName,
            'vendor': vendorBundleName
        }))
        .pipe(gulp.dest(paths.webroot));
});

gulp.task('default', gulp.series(gulp.parallel('copy', 'lib'), 'bundle:setup', 'bundle'), () => {
    return new Promise((resolve, reject) => {
        resolve();
    });
});

You may also notice that I started using gulp 4.0 to take advantage of the series/parallel features. Obviously, the appropriate ‘request’ statements and devDependecies have to be defined for all of this to work. I’ll get this template on github soon for anyone that wants to see the source. I’m still going to give the WebPack alternative to gulp a go (the “universal” template) to see how it compares in terms of performance.

Leave a Reply