Bundling And Minifying In ASP.NET Core Applications

No one likes an application or site that loads slowly.

Thankfully, there are things that we can do, as developers, to help mitigate load times and often turn seconds into milliseconds. Web performance is such a large subject, but this post is going to focus on bundling and minifying resources, particularly CSS and JavaScript files within ASP.NET Core applications.

What's changed in Bundling in .NET Core?

If you have been working in the ASP.NET ecosystem for the past few years, you know that you have been able to rely on the built-in bundling and minification that it offers. While it may not have been the friendliest thing in the world, it generally worked as follows within a BundleConfig.cs file.

  1. public static void RegisterBundles(BundleCollection bundles)    
  2. {  
  3.      // Bundling jQuery   
  4.      bundles.Add(new ScriptBundle("~/bundles/jquery")  
  5.             .Include("~/Scripts/jquery-{version}.js"));  
  6.      bundles.Add(new ScriptBundle("~/bundles/jqueryval")  
  7.             .Include("~/Scripts/jquery.validate*"));  
  8.      // Bundling CSS files  
  9.      bundles.Add(new StyleBundle("~/Content/css")  
  10.             .Include("~/Content/site.css"));  
  11. }   

This would handle smashing all of your files together as a single, cachable resource that your users could download once, as opposed to making a request for every single file and dependency in your application; which saves them time.

However, in this new ASP.NET Core world, long gone is the BundleConfig.cs file.

Enter Grunt and Gulp

As ASP.NET Core became more lean and modular, many of the built-in features, like bundling, were removed and those tasks were delegated to other tools and utilities, like Grunt and Gulp.

Grunt and Gulp are both JavaScript based tools for running tasks and building respectively. These tools are easily integrated into ASP.NET Core projects, and are incredibly useful for performing any type of file manipulation, automation, and other tasks like bundling and minifying files as mentioned in the documentation.

The problem here is that it often isn't pretty or easy to look at, especially for folks not familiar with JavaScript.

  1. // Defining dependencies  
  2. var gulp = require("gulp"),    
  3.     rimraf = require("rimraf"),  
  4.     concat = require("gulp-concat"),  
  5.     cssmin = require("gulp-cssmin"),  
  6.     uglify = require("gulp-uglify");  
  7. // Defining paths  
  8. var paths = {    
  9.     js: webroot + "js/**/*.js",  
  10.     minJs: webroot + "js/**/*.min.js",  
  11.     css: webroot + "css/**/*.css",  
  12.     minCss: webroot + "css/**/*.min.css",  
  13.     concatJsDest: webroot + "js/site.min.js",  
  14.     concatCssDest: webroot + "css/site.min.css"  
  15. };  
  16. // Bundling (via concat()) and minifying (via uglify()) Javascript  
  17. gulp.task("min:js"function () {    
  18.     return gulp.src([paths.js, "!" + paths.minJs], { base: "." })  
  19.         .pipe(concat(paths.concatJsDest))  
  20.         .pipe(uglify())  
  21.         .pipe(gulp.dest("."));  
  22. });  
  23. // Bundling (via concat()) and minifying (via cssmin()) Javascript  
  24. gulp.task("min:css"function () {    
  25.     return gulp.src([paths.css, "!" + paths.minCss])  
  26.         .pipe(concat(paths.concatCssDest))  
  27.         .pipe(cssmin())  
  28.         .pipe(gulp.dest("."));  
  29. });   

You can, then, schedule tasks like these to run on certain events (i.e. Build, Publish, etc.) to ensure that your files stay in sync. But, what if you aren't a big fan of JavaScript, or this all just looks too complicated? Surely, there must be an easier way!

An Easier Way - The Bundler and Minifier Extension

Mads Kristensen, the mad scientist behind Visual Studio extensibility, decided to automate this process with the release of the Bundler and Minifier Extension, which integrates into Visual Studio and allows you to easily select and bundle the files you need, without writing a line of code.

Some of the current features are as follows.

  • Bundles CSS, JavaScript or HTML files into a single output file
  • Saving a source file triggers re-bundling automatically
  • Support for globbing patterns
  • MSBuild support for CI scenarios supported
  • Minify individual or bundled CSS, JavaScript and HTML files
  • Minification options for each language is customizable
  • Shows a watermark when opening a generated file
  • Task Runner Explorer integration
  • Command line support
  • Shortcut to update all bundles in solution
  • Suppress output file generation
  • Convert to Gulp

After installing the extension, you select all of the specific files that you want to include within a bundle and use the "Bundle and Minify Files" option from the extension.

This will prompt you to name your bundle and choose a location to save it at. You'll then notice a new file within your project called bundleconfig.json which looks like the following.

  1. [  
  2.   {  
  3.     "outputFileName""wwwroot/app/bundle.js",  
  4.     "inputFiles": [  
  5.       "wwwroot/lib/jquery/dist/jquery.js",  
  6.       "wwwroot/lib/bootstrap/dist/js/bootstrap.js",  
  7.       "wwwroot/lib/jquery-validation/dist/jquery.validate.js",  
  8.       "wwwroot/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.js"   
  9.     ]  
  10.   }  
  11. ]   


The order in which the files are selected, will determine the order that they appear in, within the bundle. So, if you have any dependencies, ensure that you take that into account.

Now, the previous step will simply bundle your files. If you want to minify the bundle, then you need to indicate this within the bundleconfig.json file. Simply add a minify block like the following to your existing bundle to indicate that you want it minified.

  1. [  
  2.   {  
  3.     "outputFileName""wwwroot/app/bundle.js",  
  4.     "inputFiles": [  
  5.       "wwwroot/lib/jquery/dist/jquery.js",  
  6.       "wwwroot/lib/bootstrap/dist/js/bootstrap.js",  
  7.       "wwwroot/lib/jquery-validation/dist/jquery.validate.js",  
  8.       "wwwroot/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.js"   
  9.     ],  
  10.     "minify": {  
  11.       "enabled"true  
  12.     }  
  13.   }  
  14. ]   

Automating Bundles (via the extension)

Finally, if you want to automate this process, you can schedule a task to run whenever your application is built, to ensure that your bundles reflect any changes within your application.

To do this, you'll need to do the following.

  • Open the Task Runner Explorer (via Tools > Task Runner Explorer).
  • Right-click on the "Update All Files" option below JSON file name.
  • Select your preferred binding from the "Bindings" context menu.

Using dotnet bundle

Since there are quite a few developers out there that are big command-line fans, I couldn't leave you folks out.

The ASP.NET Core RTM release introduced BundlerMinifier.Core, a new Bundling and Minification tool that can be easily integrated into existing ASP.NET Core applications and doesn't require any external extensions or script files.

Using BundlerMinifer.Core and Configuring Your Bundles

To use this tool, simply add a reference to BundlerMinifier.Core within the tools section of your existing project.json file, as seen below.

  1. "tools": {  
  2.   "BundlerMinifier.Core""2.0.238",  
  3.   "Microsoft.AspNetCore.Razor.Tools""1.0.0-preview2-final",  
  4.   "Microsoft.AspNetCore.Server.IISIntegration.Tools""1.0.0-preview2-final"  
  5. }   

After adding the tool, you'll need to add a bundleconfig.json file in your project that will be used to configure the files that you wish to include within your bundles. A minimal configuration can be seen below.

  1. [  
  2.   {  
  3.     "outputFileName""wwwroot/css/site.min.css",  
  4.     "inputFiles": [  
  5.       "wwwroot/css/site.css"  
  6.     ]  
  7.   },  
  8.   {  
  9.     "outputFileName""wwwroot/js/site.min.js",  
  10.     "inputFiles": [  
  11.       "wwwroot/js/site.js"  
  12.     ],  
  13.     "minify": {  
  14.       "enabled"true,  
  15.       "renameLocals"true  
  16.     },  
  17.     "sourceMap"false  
  18.   },  
  19.   {  
  20.     "outputFileName""wwwroot/js/semantic.validation.min.js",  
  21.     "inputFiles": [  
  22.       "wwwroot/js/semantic.validation.js"  
  23.     ],  
  24.     "minify": {  
  25.       "enabled"true,  
  26.       "renameLocals"true  
  27.     }  
  28.   }  
  29. ]   

Building Your Bundles

After your bundles have been configured, you can bundle and minify your existing files, using the following command.

dotnet bundle 

Automating Your Bundles

The Bundling and Minification process can be automated as part of the build process by adding the "dotnet bundle" command in the precompile section of your existing project.json file.

  1. "scripts": {  
  2.   "precompile": [  
  3.     "dotnet bundle"  
  4.   ]  
  5. }   

Available Commands

You can see a list of all the available commands and their descriptions below.

  • dotnet bundle - Executes the bundle command using the bundleconfig.json file to bundle and minify your specified files.
  • dotnet bundle clean - Clears all of the existing output files from disk.
  • dotnet bundle watch - Creates a watchers that will automatically run dotnet bundle whenever an existing input file from the bundleconfig.json configuration to bundle your files.
  • dotnet bundle help - Displays all available help options and instructions for using the Command-Line Interface.

That's all there is to it.

While bundling and minification may be a bit different that you may have been accustomed to, it's going to be one of the smaller things that you have to worry about transitioning to this new modular ASP.NET Core world.

Regardless of the approach that you choose, bundling and minification can translate into smaller requests and quicker load times for your users; and as demonstrated here, it hardly takes any work at all.