It is common practice in web development to minify your static assets (CSS, JavaScript, images) as part of your deployment process. This reduces the amount of data being sent over the network, without changing the function of the code within.
In contrast, it is not very common to minify the HTML returned as part of a standard web request. In this post I show an easy way to add to add HTML minification to your ASP.NET Core application at runtime.
Why minify?
First of all, lets consider why we would want to minify HTML at all. It has been shown time and again that a slower page response leads to higher bounce rates, and that a more performant site has a direct benefit in terms of greater sales, so the speed with which we can render something on the user's browser is critical.
Minification is a performance optimisation, designed to reduce the amount of data being transferred over the network. At the simplest level it involves removing white-space, while more complex minifiers can perform operations such as variable renaming to reduce name lengths, and rewriting if-else constructs to use ternary expressions for example.
Javascript libraries are often available on public CDNs, so you can gain an additional performance boost there, avoiding having to serve files from your own servers at all.
HTML on the other hand will likely always need to come directly from your server. In addition, it will normally be the very first request sent as part of a page load, so getting the data back to the browser as fast as possible is critical.
Why isn't HTML minified by default?
Given that the benefits of reducing the size of data sent across the network seems clear, you may wonder why HTML isn't minimised by default.
The CSS and Javascript for a web application are typically fixed at build time, which gives the perfect opportunity to optimise the files prior to deployment. You can minify (and bundle) them once when you publish your application and know you won't have to update them again.
In contrast, the HTML returned by an application is often highly dynamic. Consider a simple ecommerce site - different HTML needs to be returned for the same url depending if the user is logged in or not, whether the product is on sale, whether the related products have changed etc etc.
Given the HTML is not static, we either have to minify the HTML in realtime as it is generated and sent across the network, or, if possible, minify the HTML portion of the templates from which we are generating the final markup.
In addition, it is sometimes pointed out that using compression on your server (e.g. GZIP) will already be significantly reducing the data sent across the network, and that minifying your html is not worth the work. It's true that GZIP is very effective, especially for a markup language like HTML, however there are still gains to be made, as I'll show below.
How much could we save?
About two years ago, Mads Kristensen wrote a series of posts about HTML minification, in one of which he demonstrated the savings that could be made by minifying as well as using GZIP on some HTML pages. I decided to recreate his experiment using the web pages as they are today, and got the following results:
Mode | File Size (KB) | Minified | GZIP | Minified & GZIP | Saving |
---|---|---|---|---|---|
amazon.com | 355 | 332 | 77.1 | 72.9 | 5.4% |
xbox.com | 161 | 106 | 23.3 | 19.9 | 14.5% |
twitter.com | 724 | 675 | 77.1 | 65.0 | 15.6% |
Default MVC Template Home Page | 7.3 | 5.3 | 1.9 | 1.8 | 5.3% |
The results are broadly in line with those found by Mads. GZIP compression does perform the bulk of the work in reducing file size, but minifying the HTML prior to GZIP compression can reduce the file size by an additional 5-15%, which is not to be sniffed at!
Potential solutions
As mentioned before, in order to add HTML minification to your application you either need to minify the HTML at runtime as part of the pipeline, or you can minify the razor templates that are used to generate the final HTML.
Minifying razor templates
As with most software architecture choices, there are tradeoffs for each approach. Minifying the razor templates before publishing them seems like the most attractive option as it is a one-off compile time cost, and is in-keeping with the CSS and JavaScript best practices used currently. Unfortunately doing so requires properly parsing the razor syntax which, due to a few quirks, is not as trivial as might seem.
One such attempt is the ASP.NET Html Minifier by Dean Hume and is described in his blog post. It uses a standalone application to parse and minify your .cshtml files as part of the publish process. Under the hood, the majority of the processing is performed using regular expressions.
Another approach by Muhammed Rehan Saeed uses a gulp task to minify the .cshtml razor files on a publish. This also uses a regex approach to isolating the razor portions. Rehan has a blog post discussing the motivation for HTML minification in ASP.NET which is well worth reading. He also raised the issue with the MVC team regarding adding razor minification as part of the standard build process.
Minifying HTML at runtime
Minifying the razor templates seems like the most attractive solution, as it has zero overhead at runtime - the razor templates just happen to contain (mostly) already minified HTML before they are parsed and executed as part of a ViewResult
. However, in some cases the razor syntax may cause the Regex minifiers to work incorrectly. As minification only occurs on publish, this could have the potential to cause bugs only in production, as development requires working with the unminified razor files.
Minifying the HTML just before it is served to the client (and before compression) is an easier process conceptually, as you are working directly with the raw HTML. You don't need to account for dynamic portions of the markup and you can be relatively sure about the results. Also, as the html is compressed as part of the pipeline, the razor templates can be pretty and unminified while you work with them, even if the resulting html is minified.
The downside to the runtime approach is the extra processing required for every request. No minification is done up front, and as the HTML may be different every time, the results of minification cannot be easily cached. This additional processing will inevitably add a small degree of latency. The tradeoff between the additional latency due to the extra processing and the reduced download time due to small data size is again something you will need to consider in general.
In the previous ASP.NET there were a couple of options to choose from for doing runtime minification of HTML, e.g. Meleze.Web or Mads' WhitespaceModule, but the only project I have found for ASP.NET Core is WebMarkupMin.
Adding Web Markup Min to your ASP.NET Core app
WebMarkupMin is a very mature minifier, not just for HTML but also XML and XHTML, as well as script and style tags embedded in your HTML. They provide multiple NuGet packages for hooking up your ASP.NET applications, both for ASP.NET 4.x using MVC, HttpModules, WebForms(!) and luckily for us, ASP.NET Core.
The first thing to get started with WebMarkupMin is to install the package in your project.json. Be sure to install the WebMarkupMin.AspNetCore1 package for ASP.NET Core (not the WebMarkupMin.Core package):
{
"dependencies": {
"WebMarkupMin.AspNetCore1": "2.1.0"
}
}
The HTMl minifier is implemented as a standard ASP.NET Core middleware, so you register it in your application's Startup.Configure
method as usual:
public void Configure(IApplicationBuilder app)
{
app.UseStaticFiles();
app.UseWebMarkupMin();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}
As always with ASP.NET Core middleware, order is important here. I chose to register the WebMarkupMin middleware after the static file handler. That means the minifier will not run if the static file handler serves a file. If you are serving html files (or angular templates etc) using the static file handler then you may want to move the minifier earlier in the pipeline.
The final piece of configuration for the middleware is to add the required services to the IoC container. The minification and compression services are opt-in, so you only add the minifiers you actually need. In this case I am going to add an HTML minifier and HTTP compression using GZIP, but will not bother with XML or XHTML minifiers as they are not being used.
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.AddWebMarkupMin(
options =>
{
options.AllowMinificationInDevelopmentEnvironment = true;
options.AllowCompressionInDevelopmentEnvironment = true;
})
.AddHtmlMinification(
options =>
{
options.MinificationSettings.RemoveRedundantAttributes = true;
options.MinificationSettings.RemoveHttpProtocolFromAttributes = true;
options.MinificationSettings.RemoveHttpsProtocolFromAttributes = true;
})
.AddHttpCompression();
}
The services allow you to set a plethora of options in each case. I have chosen to enable minification and compression in development (rather than only in production), and have enabled a number of additional HTML minification options. These options will remove attributes from HTML elements where they are not required (e.g. the type="text"
attribute on an input
) and will strip the protocol from uri based attributes.
There are a whole host of additional options you can specify to control the HTML minification, which has excellent documentation on the GitHub wiki. Here you can control any number of additional parameters such as level of whitespace removal, preserving custom elements for e.g. angular templates, or defining required HTML comments for e.g. Knockout containerless binding directives.
With your services and middleware in place, you can run your application and see the sweet gains!
The image below shows the result of loading the xbox home page before and after adding WebMarkupMin to an ASP.NET Core application. I used the built in network throttle in Chrome Dev tools set to DSL (5ms latency, 2 Mbit/s download speed) to emulate a realistic network and compared the results before and after :
As you can see, adding the HTML minification + compression at runtime has a latency cost that is relatively significant when you add in minification, but this loss is well compensated for by the reduced size of the data transfer, giving an 80% reduction in total download time.
Summary
In this post I explained the motivation for HTML minification and showed the reduction in file size that could be achieved through HTML compression, with or without additional GZIP HTTP compression.
I described the options for using HTML minification, be that at publish or runtime, and presented tools to achieve both.
Finally I demonstrated how you could use WebMarkupMin in your ASP.NET Core application to enable HTML minification and HTTP compression, and showed the improvement in download time that it gives on a relatively large HTML file.
I hope you found the post useful, as usual you can find the source code for this and my other posts on GitHub at https://github.com/andrewlock/blog-examples. If you know of any other useful tools, do let me know in the comments. Thanks!
Useful links
- http://rehansaeed.com/minifying-html-for-asp-net-mvc/
- http://deanhume.com/home/blogpost/a-simple-html-minifier-for-asp-net/2097
- http://madskristensen.net/post/effects-of-gzipping-vs-minifying-html-files
- https://github.com/Taritsyn/WebMarkupMin/wiki
- https://github.com/Taritsyn/WebMarkupMin/wiki/HTML-Minifier
- https://github.com/meleze/Meleze.Web
- https://github.com/RehanSaeed/gulp-minify-cshtml