Sitecore Optimization: Minify HTML Page Output

Google Page Speed

As part of our steps to decrease page load time, we have configured a new pipeline HttpRequestProcessor to intercept the content being output to the screen, and apply minification to it, so as to reduce the size of the packet sent to the browser.

I am adding this article to keep note of the required changes.

Create new class called MinifyMarkupService:

public class MinifyMarkupService
{
    public static string Minify(string markup, bool removeWhitespaces = true, bool removeLineBreaks = true, bool removeHtmlComments = true)
    {
        if (removeWhitespaces)
            markup = RemoveWhiteSpaces(markup);
        if (removeLineBreaks)
            markup = RemoveLineBreaks(markup);
        if (removeHtmlComments)
            markup = RemoveHtmlComments(markup);
        return markup;
    }

    public static string RemoveHtmlComments(string markup)
    {
        return Regex.Replace(markup, "<!--*.*?-->", string.Empty, RegexOptions.Compiled | RegexOptions.Multiline);
    }

    public static string RemoveLineBreaks(string markup)
    {
        return Regex.Replace(markup, "\\n", " ", RegexOptions.Compiled | RegexOptions.Multiline);
    }

    public static string RemoveWhiteSpaces(string markup)
    {
        return Regex.Replace(markup, "^\\s*", string.Empty, RegexOptions.Compiled | RegexOptions.Multiline);
    }
}

Add another class called MinifiedStream that will be used to call the first:

public class MinifiedStream : Stream
{
    private readonly StringBuilder _responseHtml;
    private readonly Stream _sink;

    public MinifiedStream(Stream sink)
    {
        _sink = sink;
        _responseHtml = new StringBuilder();
    }

    public override bool CanRead { get { return true; } }

    public override bool CanSeek { get { return true; } }

    public override bool CanWrite { get { return true; } }

    public override long Length { get { return 0; } }

    public override long Position { get; set; }

    public override void Flush()
    {
        _sink.Flush();
    }

    public override int Read(byte[] buffer, int offset, int count)
    {
        return _sink.Read(buffer, offset, count);
    }

    public override long Seek(long offset, SeekOrigin origin)
    {
        return _sink.Seek(offset, origin);
    }

    public override void SetLength(long value)
    {
        _sink.SetLength(value);
    }

    public override void Close()
    {
        _sink.Close();
    }

    public override void Write(byte[] buffer, int offset, int count)
    {
        var strBuffer = Encoding.UTF8.GetString(buffer, offset, count);
        var eof = new Regex("</html>", RegexOptions.IgnoreCase);
        _responseHtml.Append(strBuffer);
        if (!eof.IsMatch(strBuffer))
            return;
        var html = _responseHtml.ToString();
        html = MinifyMarkupService.Minify(html);
        var data = Encoding.UTF8.GetBytes(html);
        _sink.Write(data, 0, data.Length);
    }
}

Create a custom HttpRequestProcessor called MinifyMarkupProcessor:

public class MinifyMarkupProcessor : HttpRequestProcessor
{
    public override void Process(HttpRequestArgs args)
    {
        if (!ShouldMinify(args))
            return;
        args.HttpContext.Response.Filter = new MinifiedStream(args.HttpContext.Response.Filter);
    }

    protected bool ShouldMinify(HttpRequestArgs args)
    {
        if (!Sitecore.Configuration.Settings.GetBoolSetting("MarkupMinify.MinifyResponseMarkup", true))
            return false;
        if (args.HttpContext.Request.Url.OriginalString.Contains("/sitecore")
          || args.HttpContext.Request.Url.OriginalString.Contains("/speak")
          || args.HttpContext.Request.Url.OriginalString.Contains("/api"))
            return false;
        if (args.HttpContext.Request.AcceptTypes == null || !args.HttpContext.Request.AcceptTypes.Any(a => a.Equals("text/html", StringComparison.InvariantCultureIgnoreCase)))
            return false;
        return true;
    }
}

Create a patch file to add the new HttpRequestProcessor and add the necessary setting. Note it will only be applied on the ContentDelivery role.

<?xml version="1.0" encoding="utf-8"?>
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/" xmlns:role="http://www.sitecore.net/xmlconfig/role/">
  <sitecore role:require="ContentDelivery">
    <pipelines>
      <httpRequestBegin>
        <processor type="mysite.MinifyMarkupProcessor, mysite" patch:before="processor[@type='Sitecore.Mvc.Pipelines.HttpRequest.TransferRoutedRequest, Sitecore.Mvc']" />
      </httpRequestBegin>
    </pipelines>
    <settings>
        <setting name="MarkupMinify.MinifyResponseMarkup" value="true" />
    </settings>
  </sitecore>
</configuration>

Leave a Reply

Your email address will not be published. Required fields are marked *