Java

HTML minification in AEM

In order to improve performance, sometimes you may want to minify HTML.  However, there is no out of the box functionality within AEM for the same.

There is an option to have gzip enabled on dispatcher which does a great job of compression.

If we need few more bytes of compression we can go for HTML compression

We can write a custom filter to minify the HTML and this is how the filter config looks like:

The below is the rough code for html compression.

import java.io.IOException;
import java.util.Map;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletResponse;
import org.apache.felix.scr.annotations.Activate;
import org.apache.felix.scr.annotations.Deactivate;
import org.apache.felix.scr.annotations.Property;
import org.apache.felix.scr.annotations.PropertyUnbounded;
import org.apache.felix.scr.annotations.sling.SlingFilter;
import org.apache.felix.scr.annotations.sling.SlingFilterScope;
import org.apache.jackrabbit.oak.commons.PropertiesUtil;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.SlingHttpServletResponse;
import org.apache.sling.api.resource.Resource;
import org.osgi.service.component.ComponentContext;
import com.capella.website.common.CommonLogger;
import com.googlecode.htmlcompressor.compressor.HtmlCompressor;

/** 
 * This Class HtmlCompressionFilter will have two options in config to enable 
 * and disable html compression. We can also provide the paths where we need to 
 * add compression. 
 */
@SlingFilter(label = "HTML Compression Filter", description = "HTML Compression Filter", metatype = true, order = 0, scope = SlingFilterScope.REQUEST)
public class HtmlCompressionFilter implements Filter {
 private HtmlCompressor compressor;


 @Property(label = "Enable html compression", boolValue = false, description = "Please Check the property to enable html compression")
 public static final String HTML_COMPRESSION_ENABLED = "html.compression.enabled";


 @Property(value = {
  "/content/project/en"
 }, unbounded = PropertyUnbounded.ARRAY, label = "Compress html paths", cardinality = 50, description = "Please enter the paths where you need to compress the html")
 private static final String HTML_COMPRESSION_PATHS = "html.compression.paths";


 private boolean isHtmlCompressionEnabled;


 private String[] htmlCompressionPaths;


 @Override
 public void init(FilterConfig filterConfig) throws ServletException {
  compressor = new HtmlCompressor();
  compressor.setCompressJavaScript(true);
  compressor.setCompressCss(true);
 }


 @Override
 public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
 throws IOException, ServletException {
  if (!(request instanceof SlingHttpServletRequest) || !(response instanceof SlingHttpServletResponse)) {
   chain.doFilter(request, response);
   return;
  }
  final SlingHttpServletRequest slingRequest = (SlingHttpServletRequest) request;
  final Resource resource = slingRequest.getResource();
  String requestPath = resource.getPath() + ".html";
  if (isHtmlCompressionEnabled && startsWithInArr(htmlCompressionPaths, requestPath)) {
   CharResponseWrapper responseWrapper = new CharResponseWrapper((HttpServletResponse) response);
   chain.doFilter(request, responseWrapper);
   String servletResponse = responseWrapper.toString();
   response.getWriter().write(compressor.compress(servletResponse));
  } else {
   chain.doFilter(request, response);
  }
 }


 public boolean startsWithInArr(String[] htmlCompressionPaths, String requestPath) {
  boolean flag = false;
  for (int i = 0; i < htmlCompressionPaths.length; i++) {
   if (requestPath.startsWith(htmlCompressionPaths[i])) {
    flag = true;
    break;
   }
  }
  return flag;
 }


 @Override
 public void destroy() {}


 @Activate
 protected void activate(final Map < String, String > properties) {
  this.isHtmlCompressionEnabled = PropertiesUtil.toBoolean(properties.get(HTML_COMPRESSION_ENABLED), false);
  this.htmlCompressionPaths = PropertiesUtil.toStringArray(properties.get(HTML_COMPRESSION_PATHS));
 }


 @Deactivate
 protected void deactivate(ComponentContext componentContext) {}
}

CharResponseWrapper.java

import java.io.CharArrayWriter;
import java.io.PrintWriter;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;

public class CharResponseWrapper extends HttpServletResponseWrapper {
 private final CharArrayWriter output;
 @Override
 public String toString() {
  return output.toString();
 }

 public CharResponseWrapper(HttpServletResponse response) {
  super(response);
  output = new CharArrayWriter();
 }

 @Override
 public PrintWriter getWriter() {
  return new PrintWriter(output);
 }

Dependencies required:

<dependency>
    <groupId>com.googlecode.htmlcompressor</groupId>
    <artifactId>htmlcompressor</artifactId>
    <version>1.4</version>
</dependency>
<dependency>
    <groupId>com.yahoo.platform.yui</groupId>
    <artifactId>yuicompressor</artifactId>
    <version>2.4.6</version>
</dependency>

In the above dependencies we need to embed htmlcompressor dependency as we don’t have in it AEM.

We can either manually install the bundle or embed it from pom file.

About The Author

Leave a Reply

*