This tutorial will show how to use YUI-Compressor maven plugin to minify JavaScript files for Spring Boot based web application development.
Introduction
While I worked on my Java projects, I always wondered what would be a good approach to minify JavaScript files. It is part of fortifying the application for production deployment. To me, for personal projects, to minify and not to minify the JavaScript makes little difference, as long as the application does not expose sensitive data. But for real and valuable applications, minify the JavaScript files is the least one can do in order to improve the security of the application. I know that it is pretty easy to do this with ASP.NET MVC application, but for Java based application development, I didn't have the time to research this, not until now. There are many approaches, the most common one would be using a maven plugin to minify the JavaScript files during the build. I decided to give this a try.
The question is why I don't use node.js for what I needed. For my own projects, node.js packs too much fire power. For developing applications with most up to date technologies, node.js is the right tool. When one deals with legacy application, and working with legacy JavaScripts, using maven plugins for minifying the JavaScript files would be an ideal approach. The effort of adding such plugin into existing application, and making it work would involve least amount of researches and efforts. The only problem is that I don't know these plugins well enough. This is the reason I decided to do such a tutorial. To my surprise, I actually found something that was very useful. I will discuss this in the later sections.
Let me start by explaining the overall architecture of the sample application. It will also cover the overall structure of this tutorial. Without further ado, let's head to the next section.
The Overall Architecture
Let me be frank, there is not much of an architecture for the sample application. I had made quite a few tutorials last year. It is easy to pick an existing sample application, then add the maven plugin to minify the resources. Turned out that of all the tutorials I did last year, there is one that is ideal for this tutorial. It is the tutorial on HTML Editor using AngularJS. This is the link to my original tutorial - Reusable HTML Editor Control with AngularJS. The sample application in this tutorial has three JavaScript files, and one CSS file. One of the JavaScript files has 630+ lines. It would be nice to see this file being minified and work after it is included into the HTML. And it does.
Now that I have a sample application, all I have to do is finding a maven plugin, and add into the project. The biggest problem is that I need the un-minified resource files so that I can troubleshoot the issues, and I want to use minified files when I am ready to deploy the application. As you may know, choosing which kind of files to be part of the HTML code must be decided somehow during the runtime, or during packaging time. Which one should I choose? Turned out that choosing between un-minified and minified files during runtime is easier. This is the way ASP.NET MVC works using the bundle objects during app start up. And it can be easily replicated to Spring Boot based application, if one knows what to do to load the resource files dynamically. It can be done using Thymeleaf template engine for server side HTML rendering.
The idea is that I have a configuration file. In it, there is a property that will indicate the environment which the application will run, and the application will load the resource file accordingly. At build time, I will configure the build so that the minified files are in the same folder as the original resource files. Then use the configuration file to decide when to load the minified resource files. The next section will be split into two sub sections. The first sub section will discuss how to add the maven plugin for minifying the resource files. The second sub section will discuss how the resources are loaded using the configuration file.
Using Minified Resources in Spring Boot Web Application
I have explained how the minified resources can be built and utilized. It is time to explain how these two steps can be done. In the next sub section, I will explain why I choose the yui-compressor maven plugin and how it is added into the build configuration.
Using Yui-Compressor Plugin
The reason I choose yui-compressor as the tool for minify web application resource files is that this tool has been around for a long time, and its maven plugin is a fantastic plugin based on the reviews I have read. It also have very detailed documentation explaining how the plugin can be used. I didn't look into other plugins for more information. For me, I will trust more on a plugin that is well documented and has been around for a long time.
Adding this plugin into the build process is not hard at all. What is hard will be to configure the plugin and customize the resource file minifying process. In this tutorial, I only used the most basic configuration. For you the reader, please consult related documentation for advanced use of this plugin.
All the changes I have made for the build, can be found in my maven POM file. If you don't understand the changes I have described below, please reference this file and it will be clear.
The first thing I did is add the yui-compressor plugin dependency into the dependencies
section of the POM file.
...
<dependencies>
...
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>net.alchim31.maven</groupId>
<artifactId>yuicompressor-maven-plugin</artifactId>
<version>1.5.1</version>
</dependency>
</dependencies>
...
In the above code snippet, I have added two new dependencies, the first one is the ThymeLeaf starter dependency for server side rendering of HTML source code. And the second one which is in bold is the yui-compressor maven plugin that we needed for minifying the resource files.
Not done with the POM file yet. The next step is to configure the plugin, like this:
...
<build>
<plugins>
...
<plugin>
<groupId>net.alchim31.maven</groupId>
<artifactId>yuicompressor-maven-plugin</artifactId>
<version>1.5.1</version>
<executions>
<execution>
<goals>
<goal>compress</goal>
</goals>
<configuration>
<force>true</force>
<excludes>
<exclude>**/angularjs/**/*.*</exclude>
<exclude>**/bootstrap/**/*.*</exclude>
<exclude>**/jquery/**/*.*</exclude>
</excludes>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
...
This part can be very complicated. But I didn't need complication compression processing. So the above code snippet is not hard to understand. First, I configure the plugin to run at the phase of compress. I guess this is the phase where the jar file is being generated. I am not familiar with this particular phase, and cannot explain more. Please look it up if you wish to know more. In the configuration
section of the plugin, I set the flag force
to "true
". This is to remove the prior compressed resource files so that the original ones can be compressed again. I tested, if I don't set this flag, there will be some resolved compilation warnings. The warnings complain that the minified files exists and need to set this flag in order to remove this warning. As far as I can tell, this flag is used to define the behavior of the compressor either to keep the existing minified files or remove these minified files so that new ones can be created. There is also the excludes
section, in this section, I have added all the files of three directories here. The reason for this is these files are third party resource files, which are already minified, and when minified again would throw compilation errors. So they can be excluded from processing.
It is not too bad, isn't it? I won't spoil the fun. You, the reader, will see this plugin in action in the section of how to run the sample application. For the next sub section, I will be explaining how to use ThymeLeaf to load minified or unminified files by configuration.
Utilize Minified/Unminified Resource Files by Configuration
In my original tutorial, the index HTML file is a static file resided in the folder resources/static. This means whatever the resources files like CSS or JS files, are hard coded in this file. This will not work with my sample application. Now that I have both the original resource files and minified resource files. I cannot have both in the same file, and have to choose which one to be used. What I needed is that when I am developing the application, I want to use the un-minified resource files. When I am ready to deploy the application to production, then minified resources should be used. This is why I need a way to have both sets of resource files, and load one and not the other at the appropriate times.
The solution to this problem is already available with ASP.NET MVC via the app start up bundle configuration. That is the place where I can use .NET ConfigurationManager
to load a configuration value, then decide whether to bundle the un-minified or minified resource files. I can do the same in my Spring Boot application. In order to accomplish this, I need a way to render the HTML file on the server side. Then I can customize the resource files to be loaded for that page.
I have already included ThymeLeaf template engine dependency in my maven POM file. To use it, I need to create the ThymeLeaf HTML template. On top of the file, I have to use a for
loop to render all the CSS files, and on the bottom, I have to use a for
loop to render the JS files. This is the HTML template file I have created:
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,
initial-scale=1, shrink-to-fit=no">
<title>AngularJS HTML Editor Directive - by Han</title>
<link href="/assets/bootstrap/css/bootstrap.min.css" rel="stylesheet">
<link th:each="cssFile: ${allCssFiles}" th:href="${cssFile}" rel="stylesheet">
</head>
<body>
<div class="container" ng-app="testSampleApp"
ng-controller="testSampleController as vm">
<div class="row">
<div class="col-xs-12">
<h3>HTML Editor</h3>
</div>
</div>
<div info-display op-success="vm.opSuccess"
msg-to-show="vm.statusMessage" ></div>
<div class="row">
<div class="col-xs-6 text-right">
<button class="btn btn-info"
ng-click="vm.loadHtmlContent()">Load</button>
</div>
<div class="col-xs-6">
<button class="btn btn-success"
ng-click="vm.saveHtmlContent()">Save</button>
</div>
</div>
<div html-editor html-text="vm.htmlText"></div>
<hr>
<footer class="footer">
<p>© 2019, Han Bo Sun.</p>
</footer>
</div>
<script type="text/javascript" src="/assets/jquery/js/jquery.min.js"></script>
<script type="text/javascript"
src="/assets/bootstrap/js/bootstrap.min.js"></script>
<script type="text/javascript"
src="/assets/angularjs/1.7.5/angular.min.js"></script>
<script type="text/javascript"
src="/assets/angularjs/1.7.5/angular-resource.min.js"></script>
<script type="text/javascript" th:each="jsFile: ${allJsFiles}"
th:src="${jsFile}"></script>
</body>
</html>
This is the HTML Editor index page of my previous tutorial. What I did is integrate ThymeLeaf template engine for the page rendering. There are three different changes, all highlighted in bold. Those two are the place where the resource files will be rendered. All I have to do is in the API method that return the page to supply the list of CSS file names and JS file names (the URLs to these files). Then ThymeLeaf template engine would render these file names to the constructed page.
This is the API method that returns the index page:
package org.hanbo.boot.rest.controllers;
import java.util.List;
import java.util.Optional;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.servlet.ModelAndView;
@Controller
public class IndexController
extends PageControllerBase
{
@Value("${useMinifiedFiles}")
private Optional<String> useMinifiedFiles;
@GetMapping(value="/index")
public ModelAndView indexPage()
{
boolean useMinifiedFiles = isAppUsingMinifiedFiles();
List<String> allJsFiles = allAppJsFiles(useMinifiedFiles);
List<String> allCssFiles = allAppCssFiles(useMinifiedFiles);
ModelAndView retVal = new ModelAndView();
retVal.setViewName("index");
retVal.addObject("allJsFiles", allJsFiles);
retVal.addObject("allCssFiles", allCssFiles);
return retVal;
}
private boolean isAppUsingMinifiedFiles()
{
boolean retVal = false;
if (useMinifiedFiles.isPresent())
{
retVal = Boolean.parseBoolean(useMinifiedFiles.get());
}
return retVal;
}
}
In the above class, I have declared an instance property called useMinifiedFiles
. Its value is set by injection from application.properties file, hence the use of annotation of @Value
. The corresponding property that is declared in the application.properties file is named useMinifiedFiles
, same as the instance property name. After start up, Spring framework will inject the value of this property from the application.properties file into this instance property and it will be immediately available for use. I also used the Optional
as a wrapper. This will allow me to check whether the value is available before it can be used. And in the application.properties file, I can set the value to "true
", "false
", or empty string as possible inject-able values.
You might ask, where are the methods allAppJsFiles()
, and allAppCssFiles()
? They are in the base class. I put them in the base class, which is called PageControllerBase
, so that these two methods can be reused in multiple API methods. This is the class PageControllerBase
:
package org.hanbo.boot.rest.controllers;
import java.util.List;
import java.util.ArrayList;
public class PageControllerBase
{
public List<String> allAppCssFiles(boolean useMinifiedFiles)
{
List<String> retVal = new ArrayList<String>();
if (useMinifiedFiles)
{
retVal.add("/assets/styles/htmlEditor-min.css");
retVal.add("/assets/styles/infoDisplay-min.css");
}
else
{
retVal.add("/assets/styles/htmlEditor.css");
retVal.add("/assets/styles/infoDisplay.css");
}
return retVal;
}
public List<String> allAppJsFiles(boolean useMinifiedFiles)
{
List<String> retVal = new ArrayList<String>();
if (useMinifiedFiles)
{
retVal.add("/assets/app/js/directives/infoDisplay/infoDisplay-min.js");
retVal.add("/assets/app/js/directives/htmlEditor/htmlEditor-min.js");
retVal.add("/assets/app/js/app-min.js");
}
else
{
retVal.add("/assets/app/js/directives/infoDisplay/infoDisplay.js");
retVal.add("/assets/app/js/directives/htmlEditor/htmlEditor.js");
retVal.add("/assets/app/js/app.js");
}
return retVal;
}
}
It is pretty simple. There are two methods, one is for creating the list of CSS files. The other is for creating the list of the JS files. Both method use the same approaches to create the list, taking a parameter that indicates whether the resource files needed to be minified. Then the if
... else
blocks will create the list by hard coding the resource file URLs and put into a list. Combining with the controller class which loads the configuration value for useMinifiedFiles
, I can control when to use the minified file and when not to. Once the lists are created, they are packaged into a ModelAndView
object. Then it will be passed to the HTML view constructed by ThymeLeaf template engine to create the final page for the browser display.
This is my application.properties, all it has is just one line:
useMinifiedFiles=true
I moved this application.properties file to the base directory of the project, so that I can pass in as part of the command line argument. This is the final trick. I can have two different application.properties files. One for development use and one for production deployment use. For the sake of simplicity, I only provided one file, and I just modify the value, then restart the application to see the effect.
At this point, I have discussed all the technical details of this sample application. In the next section, I will show you how to run the sample application and see the effect of using minified resource files.
How to Run Sample Application
First thing first, in order to run the build of this sample application, please rename any file that has the extension of ".sj" to ".js".
Next, you can change the property value of useMinifiedFiles
to either "true
", "false
", or empty string
. This is done in the application.properties file. Depends on what you want to do, set to "true
" if you want to see the minified resource file in action; "false
" or empty string otherwise. Note this file is not being packaged into the jar file so you can change the value any time you want. After changing it, you have to restart the web application and the change would take effect.
It is time to build and package the web application. In the base directory of the sample project, run the following command:
maven clean install
I have a few comments on this step, the first is that YUI-compressor has JsLint integrated, so when we do the packaging, it actually checked the quality of my JS code and uncovered 100+ warnings. Here is a screenshot of the errors.
Turned out that I didn't have enough of JavaScript as I thought I did. But the errors were easy to fix. The complaint is mostly that I used more than one var
keyword to declare the local variables used in the methods. Once I fixed all that, the warnings during the build all disappeared. This is one unexpected surprise. Before this, there is no way for me to check the code quality of my JavaScript files, now I have a way. I need to do some more research and see if there is a way to just scan the JavaScript file without compressing it. For now, the check and the compression works just as I would have liked.
Another observation I made is that the compression was done twice. I believe the first one is the compression for the files in the target folder. The second one is the compression of files for creating the final jar file. This is not a good explanation. I myself don't buy it. The other plausible explanation is that a bug was in this maven plugin, hence it runs the compression twice. It does not matter because this will not have any negative impact to the final jar file.
Time to run the sample application. This is the command to start the application:
java -jar target/hanbo-js-minify-sample-1.0.1.jar
--spring.config.location=file:./application.properties
Unlike the commands I have used in the past tutorials, this one takes one extra command line argument the location of the application.properties file. This allows me to change the configuration without rebuilding the jar file. All I have to do is stop the application, change the configuration, then restart the application with the same command.
The application.properties file only has one line, and all I need to do is change the value of this line to true
or false
, the restart the application, I will see the differences of resource files being loaded by the application.
To see the application in action, please use the following URL in your browser:
http://localhost:8080/index
This is the screenshot of sample application running in the browser:
If I set the configuration property to value true
, and run the application. Once I open the Developer tools to check the source code, I can see the minified resources are being used by this application:
If I set the configuration property to value false
, and run the application. I should be able to see the same application. Once I open the Developer tools to check the source code, I can see the un-minified resources are being used by this application:
As you can see, it is pretty easy to set when to use minified resources and when not to. This allows me to troubleshoot issues at development time, and deploy for minified resources when it is time to push it to production.
In addition to the minified/un-minified resource files, I also fixed a bug with the application where I select a segment of text and apply the markup at the beginning and end of this selection. If the text is the end of the string, then the last character will be left out of the markup. It is an annoying bug which I didn't fix in my original tutorial. Here, I was able to resolve it.
Summary
As I have stated at the beginning, this tutorial is a pretty simple one. All I did is take an existing AngularJS application and add the minify resource files in the build process. I also explained how to load both the minified and un-minified resource files at run time. As a bonus, I showed how to run the application with command line argument to locate the application.properties file so that it does not need to be packaged in the jar file. This simple scheme allows me to set when to use minified resources and when not to. This allows me to troubleshoot issues at development time, and deploy for minified resources when it is time to push it to production.
One big surprise to me is that JsLint functionality was part of the YUI-Compressor maven plugin and was able to help me uncover some unpleasant issues in my JavaScript code. These were easily fixed. I also fixed a bug in the original JavaScript code, hence completed the sample application. Please review the content of this tutorial, hopefully there is something useful to your liking.
Looking forward, I will do more research on how to use JsLint for syntax check and not for compression, and other tools for syntax check and compression of the ES6 JavaScript module files. The latter one is slightly complicated, and hopefully, these can be collected into a new tutorial for the near future. Anyways, I hope you enjoyed this tutorial and will find some use out of it. Thank you for visiting.
History
- 2nd August, 2022 - Initial draft