Making web-pages go faster using PHP

Javascript PHP Web development November 16th, 2008 by Eran Galperin

UI responsiveness is one of the basics of a good user experience. In the web environment, this often translates to the loading time of pages.

As it might be expected, there are several techniques to optimize the delivery of web pages. The Exceptional Performance guide by Yahoo is a great resource for a multitude of optimizations practices, including specifically two techniques which I will address in this article - script minifcation and concatenation.

Reducing script size and the number of request

Modern web sites and web application serve increasingly heavier javascript and CSS files. The price for the increase in script payload is felt both on the client side, where loading time increase making pages feel slugish, and on the server side, where bandwidth and requests are at a premium.

Javascript files especially block the browser while downloading (no more than 2 components can be downloaded simultaneously as per HTML specifications), and are the type of scripts seeing the steepest increase in size in the last several years.

Minification and concatenation

Minifying is the process of removing all unnecessary characters from the source code without changing its functionality (removing whitespaces, line-breaks and comments, replacing variables names and more). Concatenation is the process of unifying two strings into one. Applying both to our scripts we reduce total script size and the amount of requests.

There are several public algorithms for minifying Javascript, and Dean Edwards gives a nice overview of those on his blog.

For PHP we have the handy Minify library, which handles both the minifcation and concatenation of scripts. Minify is quite robust, and comes with a set of standalone classes that can be used in other contexts as well. It also sets Etags and far-future headers for better client script caching.

Deploying Minify

Downloading and extracting the package available on google code creates several text files and two sub-directories. The directory that is of interest to us is '/min' which contains the actual source code, and we'll place it somewhere on our server about our document root (so it can be accessed by a regular http request).

The basic workflow of using Minify is to replace our <script> and <link> tags which load our javascript and CSS respectively with calls to the minify script. We need to tell the Minify script which scripts to process, and there are two main ways to accomplish that:

- Passing the scripts as parameters in the call to the minify script. We pass the path to the scripts separated by commas, translating the following:

  1. <script type="text/javascript" src="/path/to/script_one.js"></script>
  2. <script type="text/javascript" src="/path/to/script_two.js"></script>

Into:

  1. <script type="text/javascript" src="/path/to/min/?f=/path/to/script_one.js,/path/to/script_two.js"></script>

There is actually a shorter way writing this, by using a common base path for both scripts:

  1. <script type="text/javascript" src="/path/to/min/?b=path/to&f=script_one.js,script_two.js"></script>

Provided that all the paths are correct, we have successful concatenated two scripts into one minified script. This can repeated for as many scripts as we like, though there is an artificial limit (due to performance and memory limitations I assume) of 10 scripts per Minify request. This limit can be adjusted in the configuration file which exists at the base path of the library (config.php).

- Using pre-defined script arrays. This method is less flexible as it requires hardcoding the script names into an array, but is slightly more performant. Minify calls those array 'groups', which look something like:

  1. return array(
  2. 'js' => array('//path/to/script_one.js', '//path/to/script_two.js')
  3. );

You can find an example of those in the installation folder of Minify (groupsConfig.php). The request to the script is then made by specifying the group name:

  1. <script type="text/javascript" src="/path/to/min/?g=js"></script>

There are several options when using groups, and you can read on those on the library's wiki.

Its important to set up the cache folder for Minify. Minifying the scripts can actually take a couple of seconds, which is a relatively long delay - however once generated once they will be cached until the scripts change (provided the cache folder is set-up properly).

To declare the cache folder, simply define a variable named $min_cachePath in the configuration file or the main app file before the main application start.

  1. $min_cachePath = '/path/to/cache';

Make sure the folder exists and has write permissions by PHP.

Real world use

I've implemented Minify in all of my recent projects to great effect. An extreme case would be script deployment in my current startup, Octabox web platform. Being a heavy-duty web-application, it consumes much more javascript and styles than your average marketing site.

Without Minify

Javascript requests:
Javascript
CSS requests:
CSS

With Minify

Javascript requests:
Minified Javascript
CSS requests:
Minified CSS

51 total requests weighing 540kb reduced to 5 requests weighing 116kb.

If you liked this article you should follow me on Twitter and/or share below:
  • http://www.arikfr.com/blog/ Arik

    Good post, but I wonder – why not minify them before hand and just let the web server (meaning Apache) serve the files, instead of going through the PHP parser (even though there’s caching)? It’s not that you change your statics in a production that often…

    And as an Octabox user, I can testify that your UI is as fast as hell :-)

  • http://www.arikfr.com/blog/ Arik

    Good post, but I wonder – why not minify them before hand and just let the web server (meaning Apache) serve the files, instead of going through the PHP parser (even though there’s caching)? It’s not that you change your statics in a production that often…

    And as an Octabox user, I can testify that your UI is as fast as hell :-)

  • http://www.techfounder.net Eran Galperin

    Once the files are cached, it’s the same as if I minified them before hand since the cache is stored as physical files on the server. This way I can have the same process for both development and production, which means one less thing to worry about when deploying :)

    It’s similar to how cache works for blogs for example – you could generate a static html for each page, but if you need to change something it becomes a chore.

  • http://www.techfounder.net Eran Galperin

    Once the files are cached, it’s the same as if I minified them before hand since the cache is stored as physical files on the server. This way I can have the same process for both development and production, which means one less thing to worry about when deploying :)

    It’s similar to how cache works for blogs for example – you could generate a static html for each page, but if you need to change something it becomes a chore.

  • http://www.arikfr.com/blog/ Arik

    But if I get it right, when doing it this way, when I request one of this minified files, the Apache calls the PHP parser which in turn gives back a static file. While the file isn’t being parsed by the PHP parser, the parser is still in the loop and that adds a certain delay.

    I don’t know how significant this delay is, but it might be significant when there’s large amount of traffic.

    I know that Rails has a similar caching mechanism for static content, but they create static file that will be served from the server and the Ruby script just inserts the right filename into the script.

  • http://www.arikfr.com/blog/ Arik

    But if I get it right, when doing it this way, when I request one of this minified files, the Apache calls the PHP parser which in turn gives back a static file. While the file isn’t being parsed by the PHP parser, the parser is still in the loop and that adds a certain delay.

    I don’t know how significant this delay is, but it might be significant when there’s large amount of traffic.

    I know that Rails has a similar caching mechanism for static content, but they create static file that will be served from the server and the Ruby script just inserts the right filename into the script.

  • http://www.techfounder.net Eran Galperin

    There is always room for improvement. However I strongly believe in the rule that 97% of the time, premature optimization is the root of all evil ;)

    True, there is some overhead for invoking the parser just to see if a file exists – but its in the millisecond range. Will it ever become significant? that’s hard to tell. In addition minify does more than just serve the files – it configures far-future and Etags headers and saves us the trouble of dealing with those.

    For now it’s very helpful to have the same process in development and production. When the time comes that we decide that offline deployment to static files is needed, we’ll go that route.

    We’re very glad Octabox is running smoothly for you btw :) we did put extra effort to make sure the server is in top-notch condition

  • http://www.techfounder.net Eran Galperin

    There is always room for improvement. However I strongly believe in the rule that 97% of the time, premature optimization is the root of all evil ;)

    True, there is some overhead for invoking the parser just to see if a file exists – but its in the millisecond range. Will it ever become significant? that’s hard to tell. In addition minify does more than just serve the files – it configures far-future and Etags headers and saves us the trouble of dealing with those.

    For now it’s very helpful to have the same process in development and production. When the time comes that we decide that offline deployment to static files is needed, we’ll go that route.

    We’re very glad Octabox is running smoothly for you btw :) we did put extra effort to make sure the server is in top-notch condition

  • http://www.octabox.com Adam Benayoun

    If it’s running that smooth in Israel (and Eran you know how bad routing can be between USA-Israel) Then I wonder how’s the UI would be served in the US :)

    Probably faster than your local development server.

  • http://www.octabox.com Adam Benayoun

    If it’s running that smooth in Israel (and Eran you know how bad routing can be between USA-Israel) Then I wonder how’s the UI would be served in the US :)

    Probably faster than your local development server.

  • http://www.arikfr.com/blog/ Arik

    Well, I agree regarding premature optimizations. I guess that my comments should be headed twords the developers of Minify. Maybe I should submit a patch that works like Rails caching…

    Anyway, keep up the good work! :-)

  • http://www.arikfr.com/blog/ Arik

    Well, I agree regarding premature optimizations. I guess that my comments should be headed twords the developers of Minify. Maybe I should submit a patch that works like Rails caching…

    Anyway, keep up the good work! :-)

  • http://bachowski.pl Michał Bachowski

    Hi!

    Why don`t you use .htaccess?

    HTML:
    <script type=”text/javascript” src=”/path/to/min/script_one.js,script_two.js”></script>

    .htaccess stored under /path/to/min/ directory:
    <IfModule mod_rewrite.c>
    RewriteEngine On
    RewriteCond %{REQUEST_FILENAME} !-d
    RewriteCond %{REQUEST_FILENAME} !-f
    RewriteRule ^(.*)$ index.php?files=$1 [L]
    </IfModule>

    and minifying script should produce file called “script_one.js,script_two.js”. So the PHP wouldn`t be involved in further serving this file. And if you want to refresh the cache- just remove this file :)

    Regards

    p.s. sorry for doubled comment, but HTML tags were stripped out :(

  • http://bachowski.pl Michał Bachowski

    Hi!

    Why don`t you use .htaccess?

    HTML:
    <script type=”text/javascript” src=”/path/to/min/script_one.js,script_two.js”></script>

    .htaccess stored under /path/to/min/ directory:
    <IfModule mod_rewrite.c>
    RewriteEngine On
    RewriteCond %{REQUEST_FILENAME} !-d
    RewriteCond %{REQUEST_FILENAME} !-f
    RewriteRule ^(.*)$ index.php?files=$1 [L]
    </IfModule>

    and minifying script should produce file called “script_one.js,script_two.js”. So the PHP wouldn`t be involved in further serving this file. And if you want to refresh the cache- just remove this file :)

    Regards

    p.s. sorry for doubled comment, but HTML tags were stripped out :(

  • http://www.techfounder.net Eran Galperin

    Hi Michael,
    What you suggest is yet another method for static deployment of files. In my post I described a dynamic way to serve scripts, which depends on the parameters sent to the minify script. As I’ve told Arik, this is of course possible if this is the route you want to go.

  • http://www.techfounder.net Eran Galperin

    Hi Michael,
    What you suggest is yet another method for static deployment of files. In my post I described a dynamic way to serve scripts, which depends on the parameters sent to the minify script. As I’ve told Arik, this is of course possible if this is the route you want to go.

  • Rob

    What you suggest is yet another method for static deployment of files.

    Actually, no. Michael’s suggestion is to have apache (rather than php) serve the file from the cache if it exists, otherwise it is generated, cached and served by php.

    This cuts the php interpreter out entirely for the common case.

  • Rob

    What you suggest is yet another method for static deployment of files.

    Actually, no. Michael’s suggestion is to have apache (rather than php) serve the file from the cache if it exists, otherwise it is generated, cached and served by php.

    This cuts the php interpreter out entirely for the common case.

  • Pingback: links for 2008-11-18 | Yostivanich.com

  • http://code.google.com/p/minify/ Steve Clay

    @Arik, you’re right, pre-encoding files to cut PHP from the client request cycle is one of Minify’s goals, but it’s already pretty fast. The ab testing I did on my (admittedly wimpy) servers showed that Minify served files at least faster than Apache alone with mod_deflate. mod_deflate seems to compress on every request.

  • http://code.google.com/p/minify/ Steve Clay

    @Arik, you’re right, pre-encoding files to cut PHP from the client request cycle is one of Minify’s goals, but it’s already pretty fast. The ab testing I did on my (admittedly wimpy) servers showed that Minify served files at least faster than Apache alone with mod_deflate. mod_deflate seems to compress on every request.

  • Pingback: mrclay.org » Archive » Minify getting out there

  • http://www.phptalk.net/ Kai Sellgren

    If we are talking about having the website to load up fast for the user, then 14 kB vs 102 kB is not a big deal nowadays. However, 2 requests vs 24 requests is a big deal.

  • http://www.phptalk.net/ Kai Sellgren

    If we are talking about having the website to load up fast for the user, then 14 kB vs 102 kB is not a big deal nowadays. However, 2 requests vs 24 requests is a big deal.

  • Norma

    I do not what you all are talking about all I want to do is to get my web pages to loud up faster that is all you guys have just gone way of base with the matter.