Compressing JavaScript in PHP (no comments or whitespace)

Note: if you’re a web developer you might be interested in registering to become a ProgClub member. ProgClub is a free international club for computer programmers and we run some mailing lists you might like to hang out on to chat about software development, life, etc.

Given that I’ve been working on compressing CSS and compressing HTML in PHP, it’s only natural that I’m interested in JavaScript compression too. I haven’t done much research on the topic — I’m sure there are better tools out there than the one I’ve cobbled together — but for the sake of it here’s my first take on JavaScript compression in PHP:

function slib_compress_script( $buffer ) {

  // JavaScript compressor by John Elliot <jj5@jj5.net>

  $replace = array(
    '#\'([^\n\']*?)/\*([^\n\']*)\'#' => "'\1/'+\'\'+'*\2'", // remove comments from ' strings
    '#\"([^\n\"]*?)/\*([^\n\"]*)\"#' => '"\1/"+\'\'+"*\2"', // remove comments from " strings
    '#/\*.*?\*/#s'            => "",      // strip C style comments
    '#[\r\n]+#'               => "\n",    // remove blank lines and \r's
    '#\n([ \t]*//.*?\n)*#s'   => "\n",    // strip line comments (whole line only)
    '#([^\\])//([^\'"\n]*)\n#s' => "\\1\n",
                                          // strip line comments
                                          // (that aren't possibly in strings or regex's)
    '#\n\s+#'                 => "\n",    // strip excess whitespace
    '#\s+\n#'                 => "\n",    // strip excess whitespace
    '#(//[^\n]*\n)#s'         => "\\1\n", // extra line feed after any comments left
                                          // (important given later replacements)
    '#/([\'"])\+\'\'\+([\'"])\*#' => "/*" // restore comments in strings
  );

  $search = array_keys( $replace );
  $script = preg_replace( $search, $replace, $buffer );

  $replace = array(
    "&&\n" => "&&",
    "||\n" => "||",
    "(\n"  => "(",
    ")\n"  => ")",
    "[\n"  => "[",
    "]\n"  => "]",
    "+\n"  => "+",
    ",\n"  => ",",
    "?\n"  => "?",
    ":\n"  => ":",
    ";\n"  => ";",
    "{\n"  => "{",
//  "}\n"  => "}", (because I forget to put semicolons after function assignments)
    "\n]"  => "]",
    "\n)"  => ")",
    "\n}"  => "}",
    "\n\n" => "\n"
  );

  $search = array_keys( $replace );
  $script = str_replace( $search, $replace, $script );

  return trim( $script );

}

It’s funny, but jQuery actually choked on my original function because it contains a few strings like “*/*”. To fix the problem I had to patch jQuery with “*/”+”*”, but then I decided to handle that case in my code. Of course jQuery comes pre-minified by tools much more sophisticated than mine. My tool compresses the 230 KB jQuery file to 150 KB, whereas the tool jQuery uses compresses the file to 90 KB. So I think I have my work cut out for me! It was a fun hack though.

Compressing HTML in PHP (no comments or whitespace)

Note: if you’re a web developer you might be interested in registering to become a ProgClub member. ProgClub is a free international club for computer programmers and we run some mailing lists you might like to hang out on to chat about software development, life, etc.

In addition to compressing CSS in PHP I’ve been compressing HTML. My HTML compressor is a bit of a hack. It doesn’t handle CDATA sections for instance. But it should generally work OK. Here it is:

function slib_compress_html( $buffer ) {
  $replace = array(
    "#<!--.*?-->#s" => "",      // strip comments
    "#>\s+<#"       => ">\n<",  // strip excess whitespace
    "#\n\s+<#"      => "\n<"    // strip excess whitespace
  );
  $search = array_keys( $replace );
  $html = preg_replace( $search, $replace, $buffer );
  return trim( $html );
}

I use this function to compress HTML generated by my PHP scripts by putting ob_start( ‘slib_compress_html’ ) at the beginning of my script (after the ob_gzhandler) and ob_end_flush() at the end. My HTML compression code looks like this:

    if ( extension_loaded( 'zlib' ) ) { ob_start( 'ob_gzhandler' ); }
    ob_start( 'slib_compress_html' );

    run_app( $app_factory );

    ob_end_flush();
    if ( extension_loaded( 'zlib' ) ) { ob_end_flush(); }

Compressing CSS in PHP (no comments or whitespace)

Note: if you’re a web developer you might be interested in registering to become a ProgClub member. ProgClub is a free international club for computer programmers and we run some mailing lists you might like to hang out on to chat about software development, life, etc.

I was searching for methods to remove comments and whitespace from CSS files in PHP and I found this article (3 ways to compress CSS files using PHP).

The article suggests this code by Reinhold Weber, which I thought was a pretty good place to start:

<?php
  header('Content-type: text/css');
  ob_start("compress");
  function compress($buffer) {
    /* remove comments */
    $buffer = preg_replace('!/\*[^*]*\*+([^/][^*]*\*+)*/!', '', $buffer);
    /* remove tabs, spaces, newlines, etc. */
    $buffer = str_replace(array("\r\n", "\r", "\n", "\t", '  ', '    ', '    '), '', $buffer);
    return $buffer;
  }

  /* your css files */
  include('master.css');
  include('typography.css');
  include('grid.css');
  include('print.css');
  include('handheld.css');

  ob_end_flush();
?>

Later I found css_strip_whitespace by nyctimus in the documentation for the PHP method strip_whitespace. It looks like this:

function css_strip_whitespace($css)
{
  $replace = array(
    "#/\*.*?\*/#s" => "",  // Strip C style comments.
    "#\s\s+#"      => " ", // Strip excess whitespace.
  );
  $search = array_keys($replace);
  $css = preg_replace($search, $replace, $css);

  $replace = array(
    ": "  => ":",
    "; "  => ";",
    " {"  => "{",
    " }"  => "}",
    ", "  => ",",
    "{ "  => "{",
    ";}"  => "}", // Strip optional semicolons.
    ",\n" => ",", // Don't wrap multiple selectors.
    "\n}" => "}", // Don't wrap closing braces.
    "} "  => "}\n", // Put each rule on it's own line.
  );
  $search = array_keys($replace);
  $css = str_replace($search, $replace, $css);

  return trim($css);
}

The latter function is the one that I used, and I’m quite happy with it. I’ve been working on HTML and JavaScript compressors too, and those are much more difficult file formats to deal with than CSS.

Web page HTML/CSS/JavaScript file size

I found this article (Some Guidelines for Determining Web Page and File Size) today which talks about the average size of HTML and other files on the web. According the article (and I’m not clear how they got their data) the average HTML file is 25k, JPEG 11.9k, GIF 2.9k, PNG 14.5k, SWF 32k, external scripts 11.2k and external CSS 17k with the average total size of a web page being 130k. Interesting stuff. Particularly that scripts are typically 11.2k given that jQuery is 90k.

I’m really struggling with a design decision at the moment, being that I’m not sure whether it’s better to embed CSS/JavaScript content or to link it. The thing is that if you link it then the client has to send extra HTTP requests (at least two) to get the content, which is overhead and takes time. The thing is, if your users are returning customers then they might already have the linked files in their cache, meaning they don’t need to send extra HTTP requests, or if they do maybe those requests won’t need to return content. But then maybe a browser will cache a file when it shouldn’t (this can be avoided with good design), or maybe the user’s connection will fail while loading the linked files and they’ll see an unstyled page in their browser.

So many pros and cons, and it’s all hypothetical… what I really need is data. Anyway, I don’t have data, nor do I really have the tools to get it. So given that I have to fly in the dark, here’s my plan:

When I’m processing a request for a user who doesn’t have a browser cookie set I will embed CSS and JavaScript in the HTML. This is because if their browser cookie isn’t set then this is their first request to my web-site, maybe ever, or maybe just in a while. Either way, it’s probably safe to assume they’re a first-time visitor so they won’t have any content in their cache and they’d need to send additional requests for linked files. So I can save those additional requests and hopefully make my web pages load faster for users who are probably one-off visitors.

But for regular users having to download the same content over and over in every request gets tired fast. The linked files can be about half the size of the page, so embedding doubles the size of each transfer. When I’m processing a request if the user’s browser cookie is already set then I’ll assume they’re a regular visitor and link my JavaScript files rather than embedding them. I’ll still embed CSS content though, because my CSS content is relatively small and I want to avoid errors where the page loads but the styles don’t.

Then I’ll make the system configurable so users can change their link/embed settings for CSS and JavaScript if they’re not happy with the defaults. Regular power users can use this feature to turn on linking for all content so pages load as fast as possible for them.

Using PHP output buffering to read a file

There’s a function in PHP called readfile which will read a file and send its contents to stdout. That can be handy, but it’s not much good to you if you want to read the content of the file into a string.

There’s a neat trick using PHP’s output buffering that enables you to read the content of a file into a string without printing anything on stdout, and it goes like this:

  // start an output buffer
  ob_start();
  // print the file to the output buffer
  readfile( $file_path );
  // get the contents of the output buffer
  $content = ob_get_contents();
  // cancel the output buffer
  ob_end_clean();