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>