Minifying Files in Hippo

In single page web applications (SPAs), it's a standard practice to minify the generated files (HTML, JavaScript and CSS) before publishing them. In contrast, Hippo CMS by default doesn't include such a step in its publishing process. However, there's nothing preventing you from adding it if you want to optimize the files that are sent to the browser.

Minifying HTML

HTML is generated at runtime from FreeMarker templates. Hence, that's the place where the minification step must be added. Fortunately, there's a compress directive available for this exact purpose. All the markup placed inside it will have all the unnecessary whitespace removed before being served. The simplest way to make sure that all the generated HTML will be minified, is to put all the markup in every template file inside a single compress element:

<#include "../include/imports.ftl">

<#compress>
<@hst.setBundle basename="essentials.homepage"/>
<div>
  <h1><@fmt.message key="homepage.title" var="title"/>${title?html}</h1>
  <p><@fmt.message key="homepage.text" var="text"/>${text?html}</p>
  <#if !hstRequest.requestContext.cmsRequest>
    <p>
      [This text can be edited
      <a href="http://localhost:8080/cms/?1&path=/content/documents/administration/labels/homepage" target="_blank">here</a>.]
    </p>
  </#if>
</div>
<div>
  <@hst.include ref="container"/>
</div>
</#compress>

To prevent HTML comments from being included in the output, you should always use FreeMarker comments instead of HTML comments in your templates:

<!-- Avoid using HTML comments in templates -->
<#-- Always use FreeMarker comments instead -->

With this approach, the HTML markup will be minified at runtime instead of at build time, but the performance impact should be minimal. Although dedicated HTML minifying tools would probably do a slightly better job at minification, the results should still be more than good enough for most use cases.

Minifying JavaScript

JavaScript isn't modified at runtime even in a CMS. Therefore, it can safely be minified at build time. To minimize the impact of this step on the development process, I decided to minify each JavaScript file separately and avoid bundling. This way the same JavaScript files can be referenced in the templates during development (when they are not minified) and after minification. If you're serving the content over HTTP/2, this will still bring most of the benefits even without bundling.

Since Hippo CMS projects are built using Maven, I started out by looking for a Maven plugin for JavaScript minification. I found two: YUI Compressor Maven Mojo and uglifyjs-maven-plugin. Unfortunately, neither of them supports ECMAScript 6 which made me look for a better solution.

I learned that terser is currently the best tool for minifying ECMAScript 6. To run it on all files in a folder, terser-folder can be used. With all the JavaScript files being in the repository-data/webfiles submodule, that's where I created a package.json file and installed the package:

npm init
npm install terser-folder --save-dev

I added the terser-folder command to package.json as an NPM script:

"scripts": {
  "minify": "terser-folder target/classes/site/js -e -x .js -o target/classes/site/js"
}

Notice that I'm minifying the files in the target folder and not in the src folder. That's because I'm processing them in place and I don't want to overwrite the original files. I can run the script using the following command:

npm run minify

To run this command from Maven, I ended up using the frontend-maven-plugin. AMong other things, it can run any NPM script using a locally installed Node and NPM distribution. I added it to the repository-data/webfiles/pom.xml file:

<plugin>
  <groupId>com.github.eirslett</groupId>
  <artifactId>frontend-maven-plugin</artifactId>
  <!-- Use the latest released version:
  https://repo1.maven.org/maven2/com/github/eirslett/frontend-maven-plugin/ -->
  <version>1.7.5</version>
  <executions>
    <execution>
      <id>install node and npm</id>
      <goals>
        <goal>install-node-and-npm</goal>
      </goals>
      <configuration>
        <!-- See https://nodejs.org/en/download/ for latest node and npm (lts) versions -->
        <nodeVersion>v10.15.3</nodeVersion>
      </configuration>
    </execution>
    <execution>
      <id>npm install</id>
      <goals>
        <goal>npm</goal>
      </goals>
      <configuration>
        <arguments>install</arguments>
      </configuration>
    </execution>
    <execution>
      <id>npm run minify</id>
      <goals>
        <goal>npm</goal>
      </goals>
      <phase>process-resources</phase>
      <configuration>
        <arguments>run minify</arguments>
      </configuration>
    </execution>
  </executions>
</plugin>
<plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-resources-plugin</artifactId>
  <configuration>
    <includeEmptyDirs>true</includeEmptyDirs>
  </configuration>
</plugin>

The configuration should mostly be self-explanatory. Notice however that I specified the process-resources phase for the npm run minify step. By default, the plugin runs all the steps in the generate-resources phase but at that time the files are not yet copied to the target folder.

As I already mentioned, the minified files are not served during development when you're running Hippo CMS using the cargo.run profile. They will however be packaged in the project distribution archive created by the dist profile.

If you want to check that the files are actually minified before deploying the package, you can find the files in the generated .tar.gz file placed in the target folder at the root of your Hippo project. In the webapps folder inside the archive you'll find the cms.war file. You can open it as a ZIP file and navigate to Web-inf/lib. Open the ${artifactId}-repository-data-webfiles-${version}.jar file inside it as a ZIP file and check the JavaScript files in site/js. They should be minified.

I had no need for minifying the CSS files because they were already minified in the design template I used. To minify them during build, you can use your favorite CSS tool from the Node.js ecosystem and follow the same approach that I used with terser.

Copyright
Creative Commons License