Bundling in .NET Core MVC Applications with BundlerMinifier.Core

Today’s header image was created by Annie Spratt, the original source for the image is available here

I mentioned, last week, that there are a lot of options when it comes to bundling. Today I’m going to discuss another of those options: BundlerMinifier.Core

If you’re confused as to what bundling is or why we should do it, then check out last week’s article on bundling with webpack.

What Is It?

BundlerMinifier.Core is a .NET Core compatible build of BundlerMinifier by Mads Kristensen.

Wait, I know that name.

You might recognise his name, because he’s one of the Senior Program Managers at Microsoft. He also created (amongst others) the WebEssentials, NpmTaskRunner, and BrowserSync extensions for Visual Studio.

How Do I Add BundlerMinifier.Core To My App?

There are two ways to add BundlerMinifier.Core:

  • The Easy Way
  • The Hard Way

If you haven’t created your MVC app yet, then we can take the easy way. If you’ve already started work on your MVC app, then we’ll have to go the hard way.

Don’t worry, it’s not hard at all. I’ll show you how.

The Hard Way

We’re going to start with an MVC application that you’ve already been working on. Perhaps while testing you’ve noticed that your pages load slowly because they make lots of GET requests for resources. Well, lets increase the speed of your page by setting up bundling.

Adding BundlerMinifier.Core

Open your project in Visual Studio Code, and open on your project.json. Once you’ve got the project.json open, add the following line to the dependencies section:

"dependencies": {
"Microsoft.NETCore.App": {
"version": "1.0.0",
"type": "platform"
},
"Microsoft.AspNetCore.Authentication.Cookies": "1.0.0",
"Microsoft.AspNetCore.Diagnostics": "1.0.0",
"Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore": "1.0.0",
"Microsoft.AspNetCore.Identity.EntityFrameworkCore": "1.0.0",
"Microsoft.AspNetCore.Mvc": "1.0.0",
"BundlerMinifier.Core": "2.2.306"
},
view raw adding-project.json hosted with ❤ by GitHub

Version 2.2.306 was the latest stable version as this post was published. You can check the NuGet packages page for the latest version

Before we can continue we’ll need to restore packages. If Visual Studio Code tells you to restore your packages, then let it do that for you. Otherwise, head over to the terminal and run our favourite restore command:

After CDing into the correct directory, of course.

dotnet restore
view raw shell.sh hosted with ❤ by GitHub

Adding a Bundler Config

Add a file to the root of your project called bundleconfig.json and paste the following code in:

[
{
"outputFileName": "wwwroot/css/site.min.css",
"inputFiles": [
"wwwroot/css/site.css"
]
},
{
"outputFileName": "wwwroot/js/site.min.js",
"inputFiles": [
"wwwroot/js/site.js"
],
"minify": {
"enabled": true,
"renameLocals": true
},
"sourceMap": false
}
]
view raw bundleconfig.json hosted with ❤ by GitHub

There’s a lot going on, so let’s quickly dissect it’s contents.

Each object in the outer array within the config file tells BundlerMiniifier.Core to do a separate task. The first being to bundle our CSS:

{
"outputFileName": "wwwroot/css/site.min.css",
"inputFiles": [
"wwwroot/css/site.css"
]
},
view raw bundleconfig.json hosted with ❤ by GitHub

Here we’re creating a single CSS bundle which will be outputted as site.min.css in the wwwroot/css directory. We’re also only including a single css file (‘site.css’ in the wwwroot/css directory – which is where static files are served from), but we can add as many as we want to be included in this bundle.

In your application, you’ll need to add an entry for each of the bundles that you need – perhaps a single entry per page, or an entry for the common CSS.

{
"outputFileName": "wwwroot/js/site.min.js",
"inputFiles": [
"wwwroot/js/site.js"
],
"minify": {
"enabled": true,
"renameLocals": true
},
"sourceMap": false
}
view raw bundleconfig.json hosted with ❤ by GitHub

Then we’re creating a JS minification bundle, which will be outputted as site.min.js in the wwwroot/js directory. As with the CSS bundle, we’re only adding a single js file, but we can add as many as we need for this bundle.

The two major differences between CSS and JS bundling (which I’ve highlighted) provide minification and source maps for our bundled JS files.

Again, as with the CSS bundles, we can add as many entries as we need

A Quick Note About Minifiation

From Wikipedia:

Minification (also minimisation or minimization), in computer programming languages and especially JavaScript, is the process of removing all unnecessary characters from source code without changing its functionality. These unnecessary characters usually include white space characters, new line characters, comments, and sometimes block delimiters, which are used to add readability to the code but are not required for it to execute.

What we’re going to do is tell BundlerMinifier.Core to remove any characters from the bundled files which match the above criteria. This will make the bundled files smaller, which means that the browser will spend less time downloading them, which in turn will make our pages load faster.

Once they’re cached by the browser, obviously.

Bundling on Compilation

Once your packages have been restored and you’ve added your config, we can tell Roslyn to run the bundler before compiling your project by adding the following to your project.json:

"scripts": {
"precompile": [
"dotnet bundle"
],
"prepublish": [
"bower install"
],
"postpublish": [
"dotnet publish-iis --publish-folder %publish:OutputPath% --framework %publish:FullTargetFramework%"
]
}
view raw adding-project.json hosted with ❤ by GitHub

This will tell Roslyn to bundle your JS and CSS (via the dotnet bundle command) before starting any compilation tasks.

The Easy Way

Let’s say that you haven’t created your MVC project yet, but you want to add bundling to it via BundlerMinifier.Core when you do create it. Let’s do that for with a new project, with our old pal the Yeoman Generator.

Always with the Yeoman Generator, Jamie

yo aspnet
view raw shell.sh hosted with ❤ by GitHub

Choose Web Application as the project type and give the project a name (I chose Mvc-with-bundlerminifier-core).

I chose that name because I hate myself, apparently

Whether you choose Bootstrap or SemanticUI is a choice that you will have to make, based on what your project needs.

Then cd into the directory and restore packages.

cd Mvc-with-bundlerminifier-core
dotnet restore
view raw shell.sh hosted with ❤ by GitHub

Open the directory with VS Code, and you’ll notice that BundlerMinifier.Core has been added to your project.json:

"tools": {
"BundlerMinifier.Core": "2.2.306",
"Microsoft.AspNetCore.Razor.Tools": "1.1.0-preview4-final",
"Microsoft.AspNetCore.Server.IISIntegration.Tools": "1.1.0-preview4-final",
"Microsoft.EntityFrameworkCore.Tools.DotNet": "1.1.0-preview4-final",
"Microsoft.Extensions.SecretManager.Tools": "1.1.0-preview4-final",
"Microsoft.VisualStudio.Web.CodeGeneration.Tools": {
"version": "1.1.0-preview4-final",
"imports": [
"portable-net45+win8"
]
}
},
view raw project.json hosted with ❤ by GitHub

Yeoman has even added the pre-compile entry for us:

"scripts": {
"precompile": [ "dotnet bundle" ],
"prepublish": [ "bower install" ],
"postpublish": [ "dotnet publish-iis --publish-folder %publish:OutputPath% --framework %publish:FullTargetFramework%" ]
},
view raw project.json hosted with ❤ by GitHub

It’s also added the bundeconfig.json for us:

[
{
"outputFileName": "wwwroot/css/site.min.css",
"inputFiles": [
"wwwroot/css/site.css"
]
},
{
"outputFileName": "wwwroot/js/site.min.js",
"inputFiles": [
"wwwroot/js/site.js"
],
"minify": {
"enabled": true,
"renameLocals": true
},
"sourceMap": false
}
]
view raw bundleconfig.json hosted with ❤ by GitHub

So the easy way is just: create a new app? Cool

Running BundlerMinifier.Core

Regardless of whether we take the Hard Way or the Easy Way, you have two ways to tell Roslyn to bundle your CSS and JS:

  • Running from the terminal
  • Building or Running your application
Bundling from the terminal

To run from the terminal, just run the following command:

dotnet bundle
view raw shell.sh hosted with ❤ by GitHub

This will open the bundlerconfig.json file in the root of your project and run each of the bundles found in it. The default configuration will provide something similar to this output:

Bundling with configuration from /path/to/source/bundleconfig.json
Processing wwwroot/css/site.min.css
Minified
Processing wwwroot/js/site.min.js
view raw shell.sh hosted with ❤ by GitHub
Building or Running The Application

Building and running from the terminal will provide the similar output to the following (after issuing the dotnet run command):

Project Mvc-with-bundlerminifier-core (.NETCoreApp,Version=v1.0) will be compiled because project is not safe for incremental compilation. Use --build-profile flag for more information.
Compiling Mvc-with-bundlerminifier-core for .NETCoreApp,Version=v1.0
Bundling with configuration from /path/to/source/bundleconfig.json
Processing wwwroot/css/site.min.css
Minified
Processing wwwroot/js/site.min.js
Minified
Compilation succeeded.
0 Warning(s)
0 Error(s)
Time elapsed 00:00:04.8390327
view raw shell.sh hosted with ❤ by GitHub

In both instances, we can see that BundlerMinifier.Core actually bundled and minified our content files:

Project Mvc-with-bundlerminifier-core (.NETCoreApp,Version=v1.0) will be compiled because project is not safe for incremental compilation. Use --build-profile flag for more information.
Compiling Mvc-with-bundlerminifier-core for .NETCoreApp,Version=v1.0
Bundling with configuration from /path/to/source/bundleconfig.json
Processing wwwroot/css/site.min.css
Minified
Processing wwwroot/js/site.min.js
Minified
Compilation succeeded.
0 Warning(s)
0 Error(s)
Time elapsed 00:00:04.8390327
view raw shell.sh hosted with ❤ by GitHub

Other bundle Commands

There are two other options for the bundle command which useful to know:

  • clean
  • watch
clean

The clean command will tell Roslyn to delete any of the bundled files that it has previously generated. In our configuration, this means the site.min.js and site.min.css files.

Here is how you run the clean command:

dotnet bundle clean
view raw shell.sh hosted with ❤ by GitHub

Which will produce output similar to this:

Bundling with configuration from /path/to/source/bundleconfig.json
Deleted /path/to/source/
Deleted /path/to/source/
view raw shell.sh hosted with ❤ by GitHub

I’ve highlighted two lines here, because what’s outputted isn’t quite what’s happening. They make it look like Roslyn deletes the entirety of your source code, but it really doesn’t. It just deletes the two files that were created as part of the bundling.

watch

The watch command will tell Roslyn to watch all of the files that you marked as input files (in your bundleconfig.json file). Each time that you save a change to ANY of those input files, Roslyn will rerun the bundle command.

Here is how you run the watch command:

dotnet bundle
dotnet bundle watch
view raw shell.sh hosted with ❤ by GitHub

I’ll explain why we ran the bundle command first, in a moment.

Which will produce output similar to this:

Bundling with configuration from /path/to/source/bundleconfig.json
Watching... Press [Enter] to stop
Processing wwwroot/css/site.min.css
Processing wwwroot/css/site.min.css
Minified
Watching... Press [Enter] to stop
view raw shell.sh hosted with ❤ by GitHub

The highlighted lines show what happened when I saved a change to my site.css file.

One important caveat for this command is that you’ll need to have created the bundles before it can be run successfully (which is why we ran dotnet bundle first). If the bundled files cannot be found (i.e. site.min.css), then overwriting them will fail.

This can be hard to debug, because the error message isn’t that helpful:

Bundling with configuration from /path/to/source/bundleconfig.json
Watching... Press [Enter] to stop
An error occurred while processing
Watching... Press [Enter] to stop
view raw shell.sh hosted with ❤ by GitHub

So be aware of that.

Another way to avoid this is to build you application (earlier, we ran the bundle command), this will ensure that bundling happens (if it’s set as a precompile action in your project.json) and that the bundled files are saved to disk before the watch command attempts to access them.

Using The Bundles In Your Markup

Referencing the bundles in your markup is pretty easy, as you just have to use the tilde (~) accessor in the path to the file containing the bundle.

Here’s an example of what I mean, taken from my _layout.cshtml (with a lot of stuff stripped out):

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<environment names="Development">
<link rel="stylesheet" href="~/css/site.css" />
</environment>
<environment names="Staging,Production">
<link rel="stylesheet" href="~/css/site.min.css" asp-append-version="true" />
</environment>
</head>
<body>
<environment names="Development">
<script src="~/js/site.js" asp-append-version="true"></script>
</environment>
<environment names="Staging,Production">
<script src="~/js/site.min.js" asp-append-version="true"></script>
</environment>
</body>
view raw _layout.cshtml hosted with ❤ by GitHub

I’ve highlighted the lines where I’m using the tilde operator

You’ll also notice that I’m using the minified CSS and JS in staging and production only. This is pretty good practise for debugging your CSS and JS – especially if you decline to add source maps to your bundle objects – as breakpoints wont work so well in JS debuggers when there isn’t a source map present.

Conclusion

Adding BundlerMinifier.Core to a .NET Core MVC project is pretty easy to do, and getting Roslyn to use the bundleconfig.json (to actually) do the bundling is pretty simple too.

To find out more about BundlerMinifier (and how to use it in Visual Studio), head over to it’s GitHub page.

But why is BunclerMinifier.Core the default option for a new .NET Core a Web Application project via Yeoman? Well, you’ll have to wait until next week to find out.

Unless you’re a keen web searcher, that is.

Related Posts

A .NET developer specialising in ASP.NET MVC websites and services, with a background in WinForms and Games Development. When not programming using .NET, he is either learning about .NET Core (and usually building something cross platform with it), speaking Japanese to anyone who'll listen, learning about languages, writing for his non-dev blog, or writing for a blog about video games (which he runs with his brother)