Use Ant and Closure Compiler to compress every JavaScript file in a project if you have lots of files


In a previous post, I set out how to use an Ant script to run every JavaScript file in a project through the Closure compiler.

For the most part, this has been working fine for me. Then I ran it against a project with some 50-odd JavaScript files and it started to throw PermGen OutOfMemoryError errors. So, it needed some work.

Iteration

In the first version of the script, I was using Ant Contrib’s foreach task to loop through all JS files in the project then pass these off to another task which would call the compiler. It seems that Ant creates a new classloader as each iteration of the loop calls the compression task. So, as the number of items to be iterated over increases, so does the memory usage.

I got to about 17 files before it failed on me.

For

The Ant Contrib for task solves this by using the same classloader for each iteration, so you don’t get a memory leak.

So, our updated build task looks like this:

<?xml version="1.0"?>
<project basedir="." default="run">

  <taskdef name="jscomp" classname="com.google.javascript.jscomp.ant.CompileTask" classpath="${basedir}/build-lib/compiler.jar"/>
  <taskdef resource="net/sf/antcontrib/antlib.xml">
    <classpath>
      <pathelement location="${basedir}/build-lib/ant-contrib/ant-contrib-1.0b3.jar"/>
    </classpath>
  </taskdef>

  <target name="run">
    <echo message="Searching for files ${jsCompressionWildcard} in ${jsCompressionRootDirectory}." />
    <for param="filename" keepgoing="true">
      <path>
        <fileset dir="${jsCompressionRootDirectory}" casesensitive="yes">
          <include name="${jsCompressionWildcard}"/>
        </fileset>
      </path>
      <sequential>

        <!-- This gets the filename without the directory path. -->
        <basename property="file.@{filename}" file="@{filename}"/>

        <propertyregex property="directory.@{filename}"
                input="@{filename}"
                regexp="^(.+)([^]+)$"
                select="1"
                casesensitive="false" />

        <echo message="Compressing file ${file.@{filename}} in ${directory.@{filename}}" />

        <jscomp compilationLevel="simple" debug="false" output="${directory.@{filename}}${file.@{filename}}" forceRecompile="true">

          <sources dir="${directory.@{filename}}">
            <file name="${file.@{filename}}"/>
          </sources>

        </jscomp>
      </sequential>
    </for>
  </target>

</project>

I’ve also removed the JavaScript code that was stripping the path from the current file and replaced it with a regex.

Update 28/01/2013
It was pointed out to me that there were a couple of issues with the original script. I’ve updated above as follows:

  • I’ve added in the full file path in line 32 as the old script would save the compressed file in its running directory.
  • In the same line, I’ve added the option to force compression of the file. When running using TeamCity, the script was ignoring the JS files without this in place.