Optimize CSS & JavaScript using Minifier TagHelper

Optimize CSS & JavaScript using Minifier TagHelper

Apr 30, 2016     Viewed 6592 times 0 Comments
Posted in #TagHelpers  #Optimization 

Minification is the process of removing unnecessary characters from the source code without changing its functionality.

In this post I will show you how we can optimize the JavaScript & CSS files using a custom minifiers tag helpers.

Now let us start with the CssMinifier which is nothing but a utility class that using online service https://cssminifier.com to minimize the CSS code an remove unnecessary characters.

CSS Minifier

using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Threading.Tasks;
namespace HelloMvc.TagHelpers
{
    public static class CssMinifier
{
    private const string URL_CSS_MINIFIER       = "https://cssminifier.com/raw";
    private const string POST_PAREMETER_NAME    = "input";
    public static async Task<String> MinifyCss(string inputCss)
    {
        List<KeyValuePair<String, String>> contentData = new List<KeyValuePair<String, String>>
        {
            new KeyValuePair<String, String>(POST_PAREMETER_NAME, inputCss)
        };
        using (HttpClient httpClient = new HttpClient())
        {
            using (FormUrlEncodedContent content = new FormUrlEncodedContent(contentData))
            {
                using (HttpResponseMessage response = await httpClient.PostAsync(URL_CSS_MINIFIER, content))
                {
                    response.EnsureSuccessStatusCode();
                    return await response.Content.ReadAsStringAsync();
                }
            }
        }
    }
}
}

Similarly JavaScriptMinifier which did the exact thing for the JavaScript content using https://javascript-minifier.com.

JavaScript Minifier

using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Threading.Tasks;

namespace HelloMvc.TagHelpers
{
    public static class JavaScriptMinifier
{
    private const string URL_JS_MINIFIER       = "https://javascript-minifier.com/raw";
    private const string POST_PAREMETER_NAME    = "input";

    public static async Task<String> MinifyJs(string inputJs)
    {
        List<KeyValuePair<String, String>> contentData = new List<KeyValuePair<String, String>>
        {
            new KeyValuePair<String, String>(POST_PAREMETER_NAME, inputJs)
        };

        using (HttpClient httpClient = new HttpClient())
        {
            using (FormUrlEncodedContent content = new FormUrlEncodedContent(contentData))
            {
                using (HttpResponseMessage response = await httpClient.PostAsync(URL_JS_MINIFIER, content))
                {
                    response.EnsureSuccessStatusCode();
                    return await response.Content.ReadAsStringAsync();
                }
            }
        }
    }
}
}

After we have the seen the core minification process, I will show you the code of both CssMinifierTagHelper and JavaScriptTagHelper that allow us to minifiy both inline styles and scripts as well as external files if it needs.

CSS Minifier TagHelper

using System.IO;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Razor.TagHelpers;
using Microsoft.Extensions.FileProviders;

namespace HelloMvc.TagHelpers
{
    [HtmlTargetElement("style")]
    [HtmlTargetElement("link", Attributes = MinifyAttributeName)]
    public class CssMinifierTagHelper: TagHelper
    {
        private const string MinifyAttributeName = "minify";
        private readonly IFileProvider _wwwroot;
        private readonly string _wwwrootFolder;
       
        public CssMinifierTagHelper(IHostingEnvironment env)        
        {
            _wwwroot = env.WebRootFileProvider;
            _wwwrootFolder = env.WebRootPath;
        }
       
        [HtmlAttributeName("rel")]
        public string Rel { get; set; }
       
        [HtmlAttributeName("href")]
        public string Href { get; set; }
       
        [HtmlAttributeName(MinifyAttributeName)]
        public bool? Minify {get; set;}
       
        public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
        {
            if (output.TagName == "style")
            {
                var content = output.GetChildContentAsync().Result.GetContent();
                var result = await CssMinifier.MinifyCss(content);
           
                output.Content.SetContent(result);
            }
           
            if (Rel == null || Href == null)
            {
                return;
            }

            if (output.TagName == "link" && Rel == "stylesheet")
            {
                if (!string.IsNullOrEmpty(Href))
                {
                    if(Minify.HasValue && !Minify.Value)
                    {
                        return;
                    }
                   
                    var fileInfo = _wwwroot.GetFileInfo(Href);
                    var cssDirectory = Href.Substring(0,Href.IndexOf(fileInfo.Name)-1);
                    var minFileName = fileInfo.Name.Replace(".css", ".min.css");
                    var minFilePath = Path.Combine(_wwwrootFolder, cssDirectory, minFileName);
                   
                    if (Rel != null)
                    {
                        output.Attributes.SetAttribute("rel", "stylesheet");
                    }
                   
                    if (File.Exists(minFilePath))
                    {
                        if (Href != null)
                        {
                            output.Attributes.SetAttribute("href", Href.Replace(".css", ".min.css"));
                        }
                       
                        return;
                    }
                   
                    using (var readStream = fileInfo.CreateReadStream())
                    using (var reader = new StreamReader(readStream, Encoding.UTF8))
                    {
                        var content = await CssMinifier.MinifyCss(await reader.ReadToEndAsync());
                       
                        using(var writer = new StreamWriter(File.Create(minFilePath), Encoding.UTF8))
                        {
                            await writer.WriteAsync(content);
                        }
                    }
                   
                    if (Href != null)
                    {
                        output.Attributes.SetAttribute("href", Href.Replace(".css", ".min.css"));
                    }
                }
            }
        }
    }
}

JavaScript Minifier TagHelper

using System.IO;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Razor.TagHelpers;
using Microsoft.Extensions.FileProviders;

namespace HelloMvc.TagHelpers
{
    [HtmlTargetElement("script")]
    public class JavaScriptMinifierTagHelper: TagHelper
    {
        private const string MinifyAttributeName = "minify";
        private readonly IFileProvider _wwwroot;
        private readonly string _wwwrootFolder;
       
        public JavaScriptMinifierTagHelper(IHostingEnvironment env)        
        {
            _wwwroot = env.WebRootFileProvider;
            _wwwrootFolder = env.WebRootPath;
        }

        [HtmlAttributeName("src")]
        public string Src { get; set; }
       
        [HtmlAttributeName(MinifyAttributeName)]
        public bool? Minify {get; set;}
       
        public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
        {        
            if (output.TagName == "script")
            {
                if (Src == null)
                {
                    var content = output.GetChildContentAsync().Result.GetContent();
                    var result = await JavaScriptMinifier.MinifyJs(content);
           
                    output.Content.SetContent(result);
                }
                else
                {
                    if(!Minify.HasValue)
                    {
                        if (Src != null)
                        {
                            output.Attributes.SetAttribute("src", Src);
                        }
                       
                        return;
                    }
                   
                    var fileInfo = _wwwroot.GetFileInfo(Src);
                    var jsDirectory = Src.Substring(0,Src.IndexOf(fileInfo.Name)-1);
                    var minFileName = fileInfo.Name.Insert(fileInfo.Name.Length-3,".min");
                    var minFilePath = Path.Combine(_wwwrootFolder, jsDirectory, minFileName);
                    
                    if (File.Exists(minFilePath))
                    {
                        if (Src != null)
                        {
                            output.Attributes.SetAttribute("src", Src.Replace(".js", ".min.js"));
                        }
                       
                        return;
                    }
                   
                    using (var readStream = fileInfo.CreateReadStream())
                    using (var reader = new StreamReader(readStream, Encoding.UTF8))
                    {
                        var content = await JavaScriptMinifier.MinifyJs(await reader.ReadToEndAsync());
                       
                        using(var writer = new StreamWriter(File.Create(minFilePath), Encoding.UTF8))
                        {
                            await writer.WriteAsync(content);
                        }
                    }
                   
                    if (Src != null)
                    {
                        output.Attributes.SetAttribute("src", Src.Replace(".js", ".min.js"));
                    }
                }
            }
        }
    }
}

For sake of the demo I didn't implement kind of watchers to monitor if some of the external styles or scripts change to minify them again, but this is totally possibly with the newly File System APIs in ASP.NET Core.

Finally I hope this post will give you some thoughts of how to start with your own custom tag helper.

You can download the source code for this post from my MinifiersTagHelpers repository on GitHub.


Leave a Comment