Combine and minimize JavaScript and CSS files for faster loading

May 25th, 2009 by Samuel Santos Leave a reply »

Reduce HTTP requests

On most sites, the major component of download time is not the base HTML file itself, but the number of subsequent HTTP requests to load the page’s supporting files – CSS, JavaScript, images, etc.
Each of those are extra HTTP requests, and each unique request takes a relatively long time.
The fewer requests to the server that the browser has to make, the faster the page will load.
There is an inherent overhead in each HTTP request. It takes substantially less time to serve one 60K file than it does three 20K files and a lot less than it does six 10K files.

Combine and minimize files

This post will explain how to combine and minimize CSS and JavaScript files using YUI Compressor and Ant.

This can be done by just concatenating all files into two combined files (one for CSS and one for JavaScript) and minimize them. You can quickly go from 10 or more files down to 2, and their size can be greatly reduced.

To keep the modularity that comes with splitting these files out by section (or business unit), keep them split in your development process, and combine them in your build process. A first Ant task will combine them and a second task will generate their minimized versions.

This technique has been successfully used in libraries such as jQuery, MooTools, Dojo, ExtJS, YUI, etc, allowing developers to better organize their code.

Tools

Ant

Ant is a Java-based build tool. In theory, it is kind of like Make, without Make’s wrinkles and with the full portability of pure Java code.

YUI Compressor

The YUI Compressor is a JavaScript compressor which, in addition to removing comments and white-spaces, obfuscates local variables using the smallest possible variable name. This obfuscation is safe, even when using constructs such as ‘eval’ or ‘with’ (although the compression is not optimal in those cases). Compared to jsmin, the average savings is around 20%.

The YUI Compressor is also able to safely compress CSS files. The decision on which compressor is being used is made on the file extension (js or css).

Build

Download this build example source code.

Code example structure

This code example has the following file organization:

root
|-- build
|   `-- yuicompressor-2.4.2.jar
|-- src
|   |-- css
|   |   |-- base.css
|   |   |-- fonts.css
|   |   |-- reset.css
|   |   `-- style.css
|   |-- js
|   |   |-- samaxesjs.core.js
|   |   `-- samaxesjs.toc.js
|   `-- index.html
`-- build.xml

The build folder contains all the files needed to build the project.
The src folder contains all the project source files.
Finally, the build.xml file is the Ant build script. Here’s where you configure your project build process.

Build script

Let’s take a look at the project build script.

<project name="Build example" default="all" basedir=".">
    <!-- Setup -->
    <property name="SRC_DIR" value="src" description="Source folder" />
    <property name="SRC_CSS_DIR" value="${SRC_DIR}/css" description="CSS source folder" />
    <property name="SRC_JS_DIR" value="${SRC_DIR}/js" description="JavaScript source folder" />
    <property name="DIST_DIR" value="dist" description="Output folder for build targets" />
    <property name="DIST_CSS_DIR" value="${DIST_DIR}/css" description="Output folder for CSS files" />
    <property name="DIST_JS_DIR" value="${DIST_DIR}/js" description="Output folder for JavaScript files" />
    <property name="BUILD_DIR" value="build" description="Files needed to build" />
    <property name="YUI" value="${BUILD_DIR}/yuicompressor-2.4.2.jar" description="YUICompressor" />

    <!-- Files names for distribution -->
    <property name="CSS" value="${DIST_CSS_DIR}/style.css" />
    <property name="CSS_MIN" value="${DIST_CSS_DIR}/style.min.css" />
    <property name="JS" value="${DIST_JS_DIR}/samaxesjs.js" />
    <property name="JS_MIN" value="${DIST_JS_DIR}/samaxesjs.min.js" />

    <!-- Targets -->
    <target name="html" description="Copy HTML files to the output folder">
        <mkdir dir="${DIST_DIR}" />
        <copy todir="${DIST_DIR}">
            <fileset dir="${SRC_DIR}">
                <include name="*.*" />
            </fileset>
        </copy>
    </target>

    <target name="css" depends="html" description="Concatenate CSS source files">
        <echo message="Building ${CSS}" />
        <concat destfile="${CSS}">
            <fileset dir="${SRC_CSS_DIR}" includes="reset.css" />
            <fileset dir="${SRC_CSS_DIR}" includes="fonts.css" />
            <fileset dir="${SRC_CSS_DIR}" includes="base.css" />
            <fileset dir="${SRC_CSS_DIR}" includes="style.css" />
        </concat>
        <echo message="${CSS} built." />
    </target>

    <target name="css.min" depends="css" description="Minimize CSS files">
        <echo message="Building ${CSS_MIN}" />
        <apply executable="java" parallel="false" verbose="true" dest="${DIST_CSS_DIR}">
            <fileset dir="${DIST_CSS_DIR}">
                <include name="style.css" />
            </fileset>
            <arg line="-jar" />
            <arg path="${YUI}" />
            <arg value="--charset" />
            <arg value="ANSI" />
            <arg value="-o" />
            <targetfile />
            <mapper type="glob" from="style.css" to="style.min.css" />
        </apply>
        <echo message="${CSS_MIN} built." />
    </target>

    <target name="js" depends="html" description="Concatenate JavaScript source files">
        <echo message="Building ${JS}" />
        <concat destfile="${JS}">
            <fileset dir="${SRC_JS_DIR}" includes="samaxesjs.core.js" />
            <fileset dir="${SRC_JS_DIR}" includes="samaxesjs.toc.js" />
        </concat>
        <echo message="${JS} built." />
    </target>

    <target name="js.min" depends="js" description="Minimize JavaScript files">
        <echo message="Building ${JS_MIN}" />
        <apply executable="java" parallel="false" verbose="true" dest="${DIST_JS_DIR}">
            <fileset dir="${DIST_JS_DIR}">
                <include name="samaxesjs.js" />
            </fileset>
            <arg line="-jar" />
            <arg path="${YUI}" />
            <arg value="--charset" />
            <arg value="ANSI" />
            <arg value="-o" />
            <targetfile />
            <mapper type="glob" from="samaxesjs.js" to="samaxesjs.min.js" />
        </apply>
        <echo message="${JS_MIN} built." />
    </target>

    <target name="clean">
        <delete dir="${DIST_DIR}" />
    </target>

    <target name="all" depends="clean, html, css, css.min, js, js.min">
        <echo message="Build complete." />
    </target>
</project>

The html target creates a dist (distribution) folder and copies all the files under src folder into it.

<target name="html" description="Copy HTML files to the output folder">
    <mkdir dir="${DIST_DIR}" />
    <copy todir="${DIST_DIR}">
        <fileset dir="${SRC_DIR}">
            <include name="*.*" />
        </fileset>
    </copy>
</target>

The css target concatenates all CSS files under scr/css folder into the file dist/css/style.css.

<target name="css" depends="html" description="Concatenate CSS source files">
    <echo message="Building ${CSS}" />
    <concat destfile="${CSS}">
        <fileset dir="${SRC_CSS_DIR}" includes="reset.css" />
        <fileset dir="${SRC_CSS_DIR}" includes="fonts.css" />
        <fileset dir="${SRC_CSS_DIR}" includes="base.css" />
        <fileset dir="${SRC_CSS_DIR}" includes="style.css" />
    </concat>
    <echo message="${CSS} built." />
</target>

The css.min target takes the dist/css/style.css file as the input, minimizes its content, and copies it to dist/css/style.min.css.

<target name="css.min" depends="css" description="Minimize CSS files">
    <echo message="Building ${CSS_MIN}" />
    <apply executable="java" parallel="false" verbose="true" dest="${DIST_CSS_DIR}">
        <fileset dir="${DIST_CSS_DIR}">
            <include name="style.css" />
        </fileset>
        <arg line="-jar" />
        <arg path="${YUI}" />
        <arg value="--charset" />
        <arg value="ANSI" />
        <arg value="-o" />
        <targetfile />
        <mapper type="glob" from="style.css" to="style.min.css" />
    </apply>
    <echo message="${CSS_MIN} built." />
</target>

The js target concatenates all JavaScript files under scr/js folder into the file dist/js/samaxesjs.js.

<target name="js" depends="html" description="Concatenate JavaScript source files">
    <echo message="Building ${JS}" />
    <concat destfile="${JS}">
        <fileset dir="${SRC_JS_DIR}" includes="samaxesjs.core.js" />
        <fileset dir="${SRC_JS_DIR}" includes="samaxesjs.toc.js" />
    </concat>
    <echo message="${JS} built." />
</target>

The js.min target takes the dist/js/samaxesjs.js file as the input, minimizes its content, and copies it to dist/js/samaxesjs.min.js.

<target name="js.min" depends="js" description="Minimize JavaScript files">
    <echo message="Building ${JS_MIN}" />
    <apply executable="java" parallel="false" verbose="true" dest="${DIST_JS_DIR}">
        <fileset dir="${DIST_JS_DIR}">
            <include name="samaxesjs.js" />
        </fileset>
        <arg line="-jar" />
        <arg path="${YUI}" />
        <arg value="--charset" />
        <arg value="ANSI" />
        <arg value="-o" />
        <targetfile />
        <mapper type="glob" from="samaxesjs.js" to="samaxesjs.min.js" />
    </apply>
    <echo message="${JS_MIN} built." />
</target>

The clean target removes the dist folder.

<target name="clean">
    <delete dir="${DIST_DIR}" />
</target>

all is the default target and executes all the previous by the order defined in its depends attribute.

<target name="all" depends="clean, html, css, css.min, js, js.min">
    <echo message="Build complete." />
</target>

Execute script

Now that you know the file organization and the build script details; the next step is to execute the script and have the production code ready to be deployed into your hosting/server.

To execute the script make sure you have Ant installed (requires JRE), go to the project root folder (the folder that contains the build.xml file), and type:
ant (executes the default target: all)

Your project structure should now have a new folder dist:

root
|-- build
|   `-- yuicompressor-2.4.2.jar
|-- dist
|   |-- css
|   |   |-- style.css
|   |   `-- style.min.jcss
|   |-- js
|   |   |-- samaxesjs.js
|   |   `-- samaxesjs.min.js
|   `-- index.html
|-- src
|   |-- css
|   |   |-- base.css
|   |   |-- fonts.css
|   |   |-- reset.css
|   |   `-- style.css
|   |-- js
|   |   |-- samaxesjs.core.js
|   |   `-- samaxesjs.toc.js
|   `-- index.html
`-- build.xml

HTML file

If you take a look at the header section of the index.html file you’ll see that I’m only using the style.min.css and samaxesjs.min.js files.

<!DOCTYPE html>
<html>
<head>
    <!-- Other header elements... -->

    <!-- CSS -->
    <link rel="stylesheet" href="css/style.min.css" type="text/css" />
    <!-- JavaScript -->
    <script type="text/javascript" src="js/samaxesjs.min.js"></script>
</head>
<body>
    <!-- Other body elements... -->
</body>
</html>

You don’t need to worry about the number of CSS and JavaScript files you have. As long as you add them to the build script the code will be added to the combined file.

Debug

Minimizing JavaScript files makes them nearly impossible to debug since the code will all be in one single line of code.
If you look carefully to the dist/js folder you will see two files there – samaxesjs.min.js and samaxesjs.js. So in order to debug your JavaScript just change the line <script type="text/javascript" src="js/samaxesjs.min.js"></script> in your HTML file to <script type="text/javascript" src="js/samaxesjs.js"></script>.
Easy!

Compression results

The following images are screenshots taken from the YSlow Statistics’ report comparing the debug and minimized versions.

Debug version (combined but not minimized):
Previous theme

Minimized version (combined and minimized):
Actual theme

In this example you have file size reduction gains of nearly 48% for JavaScript files and 59% for CSS files.
As you can see the compression gains are quite considerable.

Don't be shellfish...Tweet about this on TwitterShare on FacebookShare on Google+Share on LinkedInPin on PinterestBuffer this pageEmail this to someone
Advertisement

16 comments

  1. Yann Cébron says:

    take a look at Packtag – http://packtag.sf.net

    just a plain vanilla taglib setup in 5 mins w/o need for bloaty ant-scripts et al

  2. Hi Yann,

    You have a neat Java solution there. And it seems a lot practical than Jawr.

    But the goal of this article is to have a generic solution that can work independently of the server language you use.

    Another thing that I’m not a big fan of, is to have a filter to handle gzip. I prefer to delegate that for the web server.

    As for caching, I’ve my own solution for it: J2EE Cache Filter. But again, I think it’s better handled by the server.

  3. Yann Cébron says:

    Hi,

    agreed, your ANT solution is independent of your environment.

    As for the GZIP: AFAIK packtag does this once/in-memory, so there’s no overhead instead of letting the web-server handle it (in contrast to other “solutions” on the net).

    Yann

  4. Lisa says:

    Thank you so much for posting this. This gives me some ideas on how to create my own CSS-website.

  5. Jordi says:

    Hi Sam,
    While I think this is a good enough solution for a site with a low pace in javascript and css development, it seems to me that having to execute the ant build everytime you change a script or css file isn’t the most efficient way to work. That’s where more dynamic tools like Jawr or pack:tag (or similar ones for other languages) have a place.

    By the way, with Jawr it would be easy to delegate gzipping and caching to the server using three lines of configuration. Yes, that was a plug :-)

  6. I found a way around the problem mentioned by Jordi. I have a separate ant task used only for development mode. It doesn’t require you to execute an ant task everytime you change a css and js. I explain in my blog how to do it:

    http://simplyolaf.blogspot.com/2009/06/minimizing-number-of-http-requests-by.html

    and

    http://simplyolaf.blogspot.com/2009/06/minifying-css-and-js-with-yahoo.html

  7. ueli says:

    Thank you very much for your inputs. I am new to Ant and with your tutorial I was able to quickly setup a customized build task in NetBeans in a PHP project.

  8. Joe says:

    Is there a way to have Ant parse the HTML files, find src tags for js and css files, and replace them with the path to the minimized files? This way the code base could point to the min file for production builds and to the individual file for development and debugging (when the min script was not run).

    I’ve been looking at Maven and Ant solutions and so far have found nothing like this. While you might assume that everyone just point to the big file, the above suggestion would easily retrofit existing code bases and could even do versioning (i.e. replace foo.js with foo_min_buildXXX.js) so that you would be sure that caches would not prevent users from getting your new code.

  9. derRaab says:

    Thank’s a lot for sharing this informations! I was strongly inspired by it while writng my own blog post: http://blog.derraab.com/2011/07/07/javascript-library-development-using-ant-and-yuicompressor/

    Greetings!

  10. Paul says:

    Great post with a practical example of minifying js. Thanks for sharing.

  11. Thiago Nunes says:

    Hello,

    I downloaded the files, but do not know how to use. I need to send all files in the zip to the server? Need only include the files: style.min.css and samaxesjs.min.js in html? Is there any tutorial so I can learn?

    Thanks!

    • Hi Thiago,
      First you need to familiarize yourself with Ant.
      Then look at the Build script section and update the file names to include your actual files.
      The files to be included in your HTML are not static, they should map to the names you define in the build.xml script.

  12. Silvana says:

    Hello, thank you for your tutorial.
    I have just tried this code in my Netbeans environment, that has ANT inbuilt. Some tasks work fine, but the compression does not work. It say:
    “no main manifest attribute in path\yuicompressor-2.4.2.jar”
    Do you please have any idea why it is giving this message?

Leave a Reply