Speed up page page load combining javascript files with PHP


One of the golden rules when we want a high performance web site is minimize the HTTP requests. Normally we have several JavaScript files within our projects. It’s a very good practice to combine all our JavaScript files into an only one file. We can do it manually. Not a hard work. We only need to copy and paste the source files into our single js file. There’s even tools to do it. You can have look to Yslow (if you don’t know it yet). That’s a good solution if your project is finished. But if your project is alive and you are changing it, it’s helpful to spare your JavaScript files between several files. It’s good to organize them (at least for me). So we need to choose between high performance and development comfort. Because of that I like to use the simple script I’m going to show now. Let’s start.

The idea is the following one. Normally I have all js files into a folder called js (original isn’t?). I also have a development server and a production one (really original again, isn’t it?). When I’m developing my application I like to have my js files separated and in a human readable way (I’m human), but in production I want to combine them and even minimized and gziped to improve the performance.

The script I have is a simple script that combines all the JavaScript files, minimizes and gzips.

//js.php
require 'jsmin.php';

function checkCanGzip(){
    if (array_key_exists('HTTP_ACCEPT_ENCODING', $_SERVER)) {
        if (strpos($_SERVER['HTTP_ACCEPT_ENCODING'], 'gzip') !== false) return "gzip";
        if (strpos($_SERVER['HTTP_ACCEPT_ENCODING'], 'x-gzip') !== false) return "x-gzip";
    }
    return false;
}

function gzDocOut($contents, $level=6){
    $return = array();
    $return[] = "\x1f\x8b\x08\x00\x00\x00\x00\x00";
    $size = strlen($contents);
    $crc = crc32($contents);
    $contents = gzcompress($contents,$level);
    $contents = substr($contents, 0, strlen($contents) - 4);
    $return[] = $contents;
    $return[] = pack('V',$crc);
    $return[] = pack('V',$size);
    return implode(null, $return);
}

$ite = new RecursiveDirectoryIterator(dirname(__FILE__));
foreach(new RecursiveIteratorIterator($ite) as $file => $fileInfo) {
    $extension = strtolower(pathinfo($file, PATHINFO_EXTENSION));
    if ($extension == 'js') {
        $f = $fileInfo->openFile('r');
        $fdata = "";
        while ( ! $f->eof()) {
            $fdata .= $f->fgets();
        }
        $buffer[] = $fdata;
    }
}

$output = JSMin::minify(implode(";\n", $buffer));

header("Content-type: application/x-javascript; charset: UTF-8");
$forceGz    = filter_input(INPUT_GET, 'gz', FILTER_SANITIZE_STRING);
$forcePlain = filter_input(INPUT_GET, 'plain', FILTER_SANITIZE_STRING);

$encoding = checkCanGzip();
if ($forceGz) {
    header("Content-Encoding: {$encoding}");
    echo gzDocOut($output);
} elseif ($forcePlain) {
    echo $output;
} else {
    if ($encoding){
        header("Content-Encoding: {$encoding}");
        echo GzDocOut($output);
    } else {
        echo $output;
    }
}

As you can see the script checks recursively all the js files inside one folder, combine them and also use jsmin library for PHP to improve the download time in the browser.

It’s very easy now when we’re building our HTML file switch from one js file to another. Here you can see an example with Smarty template engine:

<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
        <title></title>
    </head>
    <body>
        Hello World
{if $dev}
        <script src="js1.js" type="text/javascript"></script>
        <script src="js2.js" type="text/javascript"></script>
        <script src="xxx/js1.js" type="text/javascript"></script>
{else}
        <script src="js.php" type="text/javascript"></script>
{/if}
    </body>
</html>

Yes. I know. There’s a problem with this solution. Maybe we’ve improved the client side performance reducing the number of HTTP requests but, what about our server side performance? We’ve changed from serving static js files to dinamic PHP file. Now our server’s CPU will work more. Another great hight performance golden rule is to place static files into a server dedicated to serve static files (without PHP support). Whit this golden rule we help to the browser to perform multiple downloads and also we reduce the use of CPU (static server will not instance any PHP session).

So a better solution is the offline generation of the static js file when we deploy the application. I do it with a simple curl at command line.

curl http://nov/js/js.php -o jsfull.minified.js

So the smarty template will change to

<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
        <title></title>
    </head>
    <body>
        Hello World
{if $dev}
        <script src="js1.js" type="text/javascript"></script>
        <script src="js2.js" type="text/javascript"></script>
        <script src="xxx/js1.js" type="text/javascript"></script>
{else}
        <script src="jsfull.minified.js" type="text/javascript"></script>
{/if}
    </body>
</html>

It’s also a good solution put a prefix in our JavaScript file and change it each time we build it (easy to automate) to ensure the cache renew.

<script src="jsfull.minified.20110216.js" type="text/javascript"></script>

And yes we can use the same trick with our css files.

21 thoughts on “Speed up page page load combining javascript files with PHP

  1. Great minds think alike! A couple of weeks ago, I posted about a wrapper class for JSMin that I wrote to handle all this in an encapsulated way: http://www.chrisrenner.com/2011/02/minify-and-compress-all-your-javascript-files-into-one-on-the-fly/

    My solution doesn’t do the recursive directory stuff but I was able to automate creating and including the static file by comparing file modification dates of the substrate files to that of the merged file, and skipping the minifcation if no changes have been made to the substrates.

    1. The idea is the same, indeed. I think is a good practice too the generation of the js file offline. If you don’t do it you are forcing to the server to execute PHP interpreter each time you request a static js file. So you need a server with PHP enabled to serve javascript

  2. Just got such a solution in mind for an optimization I am working on. This article just gave me the solution for my concept.. nice one!

    Btw: We’re working with server envs for development, staging, production which are easy to use in this context for js/css file inclusion.

  3. When you are recursively iterating through the js folder keep in mind that you have to name your JS files correct so they are packed in the right order 🙂

    1. I normally don’t need to follow a right order. It happens, the recursive iterator doesn’t work, and you need to select the files manually. It’s easy to change the script from directory iterator to preselected files. BTW, do you really need a right order?. All files will be combined into a single one so js parser will parse it once.

  4. hey thanks for the script

    could you also provide us with the command-line offline generation script as well?

    thanks 🙂

  5. I do it this way:

    1. in my controller I collect the names of all js files that are required for the output.

    2. in a development environment I don’t do anything special, all js files are linked into the output via a separate tag for each.

    3. in a production environment I do the following: I concatenate the names of the js files and get it’s hash code. This is the only file that the browser will have to download. If there is no js file with this name or it’s last modification time is earlier then the modification time of any of the original js files, then:

    4. concatenate the content of the files, minify it using JSMin, and write the minified content to the output file. Finally, this is the only file that will be linked into the html source with a tag.

    With this simple mechanism minification and cache invalidation can be solved quickly.

    1. Basically the same idea. Development server with plain js and production minified. Sometimes (when I’m paranoid about CPU usage) I create off-line the gzip and non-gzip version of the jsmin files to avoid the “on-the-fly” gzip compression by the server. After fighting against cache headers I realized that change the name of the js files is the best solution.

  6. Idea is quite good, but what when you have a lot of js files, divided into modules and shared libraries ? Combining all of them can slow down you page extensively.

    My way:
    1. Use ant for automatic deployment
    2. Configuration file for every controller, with list of used js files
    3. Configuration file with list of shared js files.
    4. During build: shared js library and combined js file for every controller are created and minified

    This saves time and is safe.

  7. SOunds like just what i need but why am i getting this error?

    PHP Fatal error: Uncaught exception ‘JSMinException’ with message ‘Unterminated string literal.’ in jsmin.php:127

    i presume I am running this script in my js folder as well?

    1. It looks like $buffer variable is empty. Check it before using minify function. Ensure your js files are located at the same level.

  8. If you are using jQuery and a bunch of other script that are depended of each other, then you have to combined them in a certain order and not by alphabetic order.

    I also might have a few other JS files in that folder, which I do not use, so any suggestion how to achieve this?

  9. If you need an especial order in your js files you cannot use the directory iterator function (line 25). In fact this function builds the $buffer array (line 34). We can create an array with the selected js files we need in our application (with the correct order) and populate the buffer array with a simple file_get_contents().

  10. I do trust all of the concepts you’ve presented to your post. They are very convincing and can definitely work. Nonetheless, the posts are very quick for starters. May just you please lengthen them a little from subsequent time? Thank you for the post.

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.