Web Performance Calendar

The speed geek's favorite time of year
2010 Edition
ABOUT THE AUTHOR
Tim Kadlec photo

Tim Kadlec is web developer living and working in northern Wisconsin with his wife and two daughters. He writes (somewhat irregularly) about a variety of topics, including performance, at timkadlec.com. You can also find him sharing his thoughts in a briefer format on twitter.

Performance optimization has a lot of moving parts. Some methods of optimization, e.g. minification, might be applied to a production environment, but not in a development environment. If a solid process does not exist, things become unwieldy. This can mean too much time is spent performing simple optimization tasks and not enough time writing code. This is where using a good build process tool, like Phing, can help.

Phing is a PHP tool for building projects. Phing interprets a user created XML file to determine what tasks to perform on the files and folders within a project. Phing is incredibly flexible and can be used to perform tasks like generating documentation, checking into a version control system, running unit tests, replacing keywords, stripping comments, and much more. Furthermore, since it is a native PHP application, it can easily be extended with a little bit of PHP code.

Phing’s flexibility can be used to automate a large amount of performance optimization tasks, such as minification, concatenation, and image optimization. Since the focus of this article is specifically on how to automate performance tasks, I will not get into a lot of the other very cool and useful features Phing provides, but hopefully this whets your appetite.

Installing The Tools

For this article, an Mac OS X platform is assumed, however, most of the commands will work just as well on any Unix operating system. I’ll also assume a folder structure like the one shown below.

To start, let’s make sure that you have all the tools you will need for this article, starting of course with Phing. Getting Phing installed is incredibly easy to do thanks to PEAR. Simply run the following two commands from your terminal:

pear channel-discover pear.phing.info
pear install phing/phing

To verify that Phing is correctly installed, run phing -v. You should see a response like the one below informing you of the version of Phing you have installed.

Phing 2.4.2.1

As I said above, I will be keeping this example simple, so I only have a few more tools you will need to have installed.

Image Optimization Tool

A wide variety of PNG optimization tools which you can run from the command line exist. In the example, I’ve chosen to use OptiPng, but as you will see you can very easily swap it out for a different tool if you desire. Stoyan Stefanov has already done an excellent job outlining how to install each of these optimization tools, so I will simply refer you to his article for installation instructions.

YUI Compressor

The only other tool I will have you install is YUI Compressor for minifying both Javascript and CSS files. The YUI Compressor needs Java to work, so you will need to be sure to have a Java runtime installed. Apple supplies their own version of Java, but if you are on another platform, you should be able to grab one from Java.com.

To install YUI Compressor, just download it from the YUI Library. Once you have unzipped the package, you should see the jar file in the build folder. Move that file to where you would like it. In my case, I have also renamed it to be yuicompressor.jar so that I can save a few keystrokes.

Getting started

Now that you have all the tools you will need for this simple demo, let’s start to actually create your XML file which Phing will use. By default, Phing will look for an XML file called build.xml, so we should create a minimal build.xml file in the project folder to get started.

<?xml version="1.0" encoding="UTF-8"?>
    <project name="demo" default="main">
        <target name="main"></target>
    </project>

If we now type phing into the command line from within our project, we should see some minimal output telling us what target was run, that the build finished, and how long it took. Before we move on to more exciting things, let’s quickly take a look at the two elements used in the XML file so far.

The project element is the container for the rest of the build file. The name attribute is used to give the project an identifying name. The default attribute tells Phing which target should be called by default if no target is specifically specified at the command line.

A project consists of targets, which you can think of in general terms as kind of a function. A target groups together related tasks to perform a specific function. Tasks, which we will start using next, are where the real magic in Phing is as they are what actually tells Phing to do something useful.

Let’s use the main target to kick the rest of the build process off. To do so we have to add another target, and then use the depends attribute to tell Phing that the main target depends on the new target, like so:

...
<target name="build">
    <echo msg="Building" />
</target>
<target name="main" depends="build"></target>
...

You will notice above that we have also introduced our first task, the echo task. Much like the PHP function with the same name, echo will output a message to the terminal. This can be useful for seeing how we are progressing in the build process, and what tasks are being performed So now if we run Phing on the project, we should see “Building” outputted to the terminal as well as all the information previously seen.

Before we get into more complex tasks, we should create a quick properties file. A properties file is simply a file where we set up some properties that we can refer to in our XML file. The syntax is very simple, just state the name of the property and set it equal to the value we want it to contain. For now, name the file build.properties and drop the following two lines in:

base.dir = .
build.dir = ${base.dir}/build

The first line is a property declaring the root, or base directory. The second line creates a build.dir property and gives it the path to the folder we will be creating to place our built files into. You can see here the syntax for calling in a property is ${property}. That syntax will stay the same within the XML file as well.

So enough goofing around. Let’s start to do some more interesting things. Let’s change our build target to be load.properties, and inside of it, we will load the properties file we created. We will also add a new target to create the directory that we will place our final, built files into.

...
<target name="load.properties">
    <property file="build.properties"/>
</target>
<target name="prepare">
    <echo msg="Making directory ${build.dir}" />
    <mkdir dir="${build.dir}" />
</target>
<target name="main" depends="load.properties, prepare"></target>
...

The property task is used to load our properties file so we can use the properties we’ve declared within the XML file, and the mkdir task within the prepare target is used to create the build directory. We are also telling the build target that it depends on the load.properties and prepare targets in order to work. If we run Phing now, we will see that our build file will create a build directory for us.

Minifying CSS

Finally—some performance optimization. Let’s set up a target that will minify the CSS files, which are located in the aptly named ‘css’ folder. To do so, we will use the exec task. The exec task will execute a command (specified in the command attribute) in the terminal window. The exec task provides a lot of flexibility&#8212any command you can run in the terminal you can run within your build file using the exec task. Let’s also let Phing know that the default target depends on this new target.

...
<target name="css.minify">
    <echo msg="Minimizing css" />
    <exec command="java -jar yuicompressor.jar ./css/style.css -o ./css/style.min.css" />
</target>
...
<target name="build" depends="load.properties, prepare, css.minify">
...

That works, but it is very restrictive&#8212there can only be one stylesheet or you have to keep adding additional commands for each additional stylesheet. Depending on how you work, this may not be a very ideal situation. If you like to separate styles into several stylesheets during development, you will need a more robust solution.

Thankfully, Phing has a fileset task which will allows you to include files into one set that you can then reference later. Just like a target&#8212you need to give the fileset task a unique identifer so you can refer to it. With a fileset, however, the attribute you’ll need to set is id, not name.

<project name="demo" default="main">
    <fileset dir="./css" id="files.css">
        <include name="*.css" />
    </fileset>
...

By adding the above fileset to the top of the build file, we are creating a group of files, called files.css, from the css folder, that will include all files in that folder with the extension of .css. Next, let’s make another target that will loop through that fileset, calling the css.minify target for each file in the set. We can also adjust the css.minify target, and make it so that the build target depends on the loop, not on css.minify.

...
<target name="css.minify.loop">
    <mkdir dir="${build.dir}/css" />
    <copy todir="${build/dir}/css">
        <fileset refid="files.css" />
    </copy>
    <foreach param="filename" absparam="absfilename" target="css.minify">
        <fileset dir="${build.dir}/css">
            <include name="*.css"/>
        </fileset>
    </foreach>
</target>
<target name="css.minify">
    <echo msg="Minimizing css" />
    <exec command="java -jar yuicompressor.jar ${absfilename} -o ${absfilename}" />
</target>
...
<target name="build" depends="load.properties, prepare, css.minify.loop">
...

Let’s take a quick look at these targets because there is an awful lot going on here. The new css.minify.loop target starts out simple enough&#8212it creates the css directory in the build area. The copy target then copies all of the files in the files.css fileset (note the refid attribute in the fileset element) into this new directory.

Next, the foreach task loops through another fileset (this time all the CSS files in the new css folder in the build area). The target attribute on the foreach task tells Phing to call the css.minify target on each file in the fileset. The absfilename parameter gets passed to the css.minify target. The absfilename parameter will be a reference to the absolute location of each file.

We then refer to that property within the css.minify target. So now, the css.minify target will minimize each css file it is handed, and output the minimized version directly back into the file located in build area.

Minifying Javascript

The next optimization step would be minifying Javascript. This is where the pace starts to pick up a bit, as we can basically duplicate what we have just done for the CSS. We will need a fileset of the Javascript files, a loop target, and a minify target to be executed on each iteration of the loop. We will also need to add the new Javascript loop to the depends attribute of the build target.

<fileset dir="./js" id="files.js">
    <include name="*.js"/>
</fileset>
...
<target name="js.minify.loop">
    <mkdir dir="${build.dir}/js" />
    <copy todir="${build.dir}/js">
        <fileset refid="files.js" />
    </copy>
    <foreach param="filename" absparam="absfilename" target="js.minify">
        <fileset dir="${build.dir}/js">
            <include name="*.js"/>
        </fileset>
    </foreach>
</target>
<target name="js.minify">
    <echo msg="Minifying js" />
    <exec command="java -jar ~/yuicompressor.jar ${absfilename} -o ${absfilename}" />
</target>
...

At this point, if we run the build file, we should see a build directory created. Within the build directory there should be a css and js folder, each holding their respective minimized files.

Optimizing Images

Next, let’s optimize the images using OptiPng. Again, the format is going to be virtually identical to what we have already done with the CSS and Javascript files. The only difference is the commmand. For OptiPng, we will specify what level of optimization to use (I’m using -o7 – it is the slowest, but most effective level). Then OptiPng will optimize the image and save it. Again, we will need a fileset of our images, a target to loop through them, a target to call on each image and we will have to add the image loop target to the list of depends for the default target.

<fileset dir="./images" id="files.images">
    <include name="*.png"/>
    <include name="*.gif"/>
</fileset>
...
<target name="images.optimize.loop" depends="prepare">
    <echo msg="Looping through images" />
    <mkdir dir="${build.dir}/images" />
    <copy todir="${build.dir}/images">
        <fileset refid="files.images" />
    </copy>
    <foreach param="filename" absparam="absfilename" target="images.optimize">
        <fileset dir="${build.dir}/images">
            <include name="*"/>
        </fileset>
    </foreach>
</target>
<target name="images.optimize">
    <echo msg="Optimizing pngs and gifs" />
    <exec command="optipng -o7 ${absfilename}" />
</target>
...

The only real difference between this and what we did for our CSS and Javascript files, besides the command of course, is that we are using multiple includes on the fileset. You can have as many includes as you would like, referencing filetypes, patterns to filenames, or even specific files. You will see how that can become very useful in the next step&#8212which is to concatenate the Javascript files.

Concatenating Javascript

Now that everything is minimized, we can automate the concatenation as well to help reduce HTTP requests. Once again, Phing is quite up to the task. Phing includes an append task that appends text, or the content of a file, to the end of a specified file. Like the copy and foreach tasks, the append task can make use of a fileset to determine which files it should be appending. In this case, we will include the filesets within the append task. We will also need to be sure to remember to add the new target to the list of targets the default target depends on.

...
<target name="js.concatenate" depends="js.minify">
    <echo msg="Concatenate js" />
    <append destFile="${build.dir}/js/allinone.js">
        <fileset dir="${build.dir}/js">
            <include name="*.js"/>
        </fileset>
    </append>
</target>
...

In the target above, we are telling the append task that the destination file is allinone.js, within the js directly in the build area. The fileset retrieves all the Javascript files from the build directory and appends them into the new script. This works fine, but it needs just a little clean up. If this target is called twice somehow, it will continue to append to allinone.js without deleting the contents of the file first, so we will delete the file each time the target is called to make sure we have a fresh start.

...
<target name="js.concatenate" depends="js.minify">
    <echo msg="Concatenate js" />
    <delete file="${build.dir}/js/allinone.js" />
    <append destFile="${build.dir}/js/allinone.js">
        <fileset dir="${build.dir}/js">
            <include name="*.js"/>
        </fileset>
    </append>
</target>
...

So now if we run the build file, all of the Javascript files should be minimized, and then concatenated into one file. One thing of note here, is that if you need a specific javascript file to be first in your concatenated file, you will need to specify that with an extra fileset like so:

...
<target name="js.concatenate" depends="js.minify">
    <echo msg="Concatenate js" />
    <delete file="${build.dir}/js/allinone.js" />
    <append destFile="${build.dir}/js/allinone.js">
        <fileset dir="${build.dir}/js">
            <include name="test.js"/>
        </fileset>
        <fileset dir="${build.dir}/js">
            <include name="*.js"/>
            <exclude name="test.js"/>
        </fileset>
    </append>
</target>
...

In the example above, we tell Phing to append test.js first. We then exclude that file from the second fileset so that it doesn’t get appended twice. One thing I am not going to cover, but you will need to address, is how to toggle between pulling in your allinone.js file versus loading each script individually depending on our development environment. You can do this using some PHP config variables, or if you want, you can set up another properties file for your development environment and load that into your build.xml file when you want to work locally. That way, you can tell Phing to skip a few steps when working in your local environment. The choice will comet down to your work process and which solution fits best.

If you need to, you should be able to duplicate the js.concatenate target, swap out a few .js for .css and you will have a css.concatenate target that you can put into place.

Conclusion

This is as far as I will take it in this article, but hopefully you have gotten a little comfortable with the way Phing operates, and have seen a glimpse of the flexibility and power that a simple build file can give you. From here, you should be able to include targets for things like DataURIs using a command line tool like CSSEmbed and optimization to your jpeg files using a tool like JPEGtran. You can find the completed build file here.

Performance is important, but so is our time. If we work smarter, we don’t have to spend a lot of time to create faster loading sites and applications. Setting up an efficient build process can help reduce the complexity and time involved in maintaining a high performing site.