9

I am currently working with minification of JS files for improvement of page speed. I have been able to find the simplest method that works with almost all my js files in exception of two. The problem is with the js files for a wmd editor I am trying to implement to my site. The js files wmd.js and showdown.js are not being compressd and cache by the function in scripts.php. I checked with firebug tool, in the response header of scripts.php neither file is included in the final compressed js file.

What is the issue with my process of compressing these js files(wmd & showdown) and combining it into one? EXAMPLE SITE

js/scripts.php- takes care of compression and cache of js files

<?php
error_reporting(E_ERROR);
// see http://web.archive.org/web/20071211140719/http://www.w3.org/2005/MWI/BPWG/techs/CachingWithPhp
// $lastModifiedDate must be a GMT Unix Timestamp
// You can use gmmktime(...) to get such a timestamp
// getlastmod() also provides this kind of timestamp for the last
// modification date of the PHP file itself
function cacheHeaders($lastModifiedDate) {
    if ($lastModifiedDate) {
        if (isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) && strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE']) >= $lastModifiedDate) {
            if (php_sapi_name()=='CGI') {
                Header("Status: 304 Not Modified");
            } else {
                Header("HTTP/1.0 304 Not Modified");
            }
            exit;
        } else {
            $gmtDate = gmdate("D, d M Y H:i:s \G\M\T",$lastModifiedDate);
            header('Last-Modified: '.$gmtDate);
        }
    }
}

// This function uses a static variable to track the most recent
// last modification time
function lastModificationTime($time=0) {
    static $last_mod ;
    if (!isset($last_mod) || $time > $last_mod) {
        $last_mod = $time ;
    }
    return $last_mod ;
}

lastModificationTime(filemtime(__FILE__));
cacheHeaders(lastModificationTime());
header("Content-type: text/javascript; charset: UTF-8");

ob_start ("ob_gzhandler");

foreach (explode(",", $_GET['load']) as $value) {
    if (is_file("$value.js")) {
        $real_path = mb_strtolower(realpath("$value.js"));
        if (strpos($real_path, mb_strtolower(dirname(__FILE__))) !== false || strpos($real_path, mb_strtolower(dirname(dirname(__FILE__)).DIRECTORY_SEPARATOR.'modules'.DIRECTORY_SEPARATOR)) !== false) {
            lastModificationTime(filemtime("$value.js"));
            include("$value.js");echo "\n";
        } 
    }
}
?>

The way i am calling the compressed.js

<script type = "text/javascript" src = "js/scripts.php?build=12345&load=wmd,showdown"></script>
  • 6
    Is there some reason you are trying to compress these on the fly rather than just minifying them and placing them on the server? – Mike Brant Aug 06 '12 at 21:27
  • 2
    @MikeBrant Yea, by compressing and combining into one, it will be less js requests made by the page. Therefore Faster speed. Also, my host doesnt let me gzip/cache through `.htaccess` –  Aug 06 '12 at 22:15
  • OK. Makes sense, it just seemed odd to me, because usually once someone is to the point of being that concerned about individual file download times, they would have moved their static files off onto a CDN, or be on a server where they would have control over the download behavior of the static files so you don't have to take additional server overhead to do this in PHP. – Mike Brant Aug 06 '12 at 22:22
  • Yes, The way you described it is how I generally deal with it but this host test server has lots of restrictions so for the mean time this my temporary solution which is working with other js files but not the wmd editor js combo. –  Aug 06 '12 at 22:38
  • I cant see where are you minifying yours js files. I only see that you are unifying them. If you want to compress them (gzip|deflate) it is not enough setting headers. You need to compress. There are a lot of tools to solve it. – Martin Borthiry Aug 09 '12 at 12:50
  • @MartinBorthiry Yes, you are right for the example the files are not minified. But in the server all the individual files are a minified and then unified with the function above. The problem is that both of those js files do not get included at all in the final unified js. Somehow they are skipped. I am not sure why(), since it works with all other js. –  Aug 09 '12 at 17:29
  • FYI: Since 4.3 `header(...)` has a 3rd argument that lets you set the HTTP response code. Also, since 5.4 there is a function called `http_response_code`. And if you really want to go at it you should "handle" the http `HEAD` request by flushing the buffer (you might still have you output a space or something) before you start the output buffer so that php aborts before you start combining the files. – dualed Aug 16 '12 at 18:27
  • @dualed +1 thanks for the tips, can you show me those tips in effect as part of answer so I and others can give you some credit. –  Aug 17 '12 at 06:47

2 Answers2

4

I suppose that the problem is the statement include("$value.js");echo "\n";
This way, if the included javascript file contains at least a "<?" string, and if you have enabled the "short_open_tag" option in your "php.ini" configuration file, part of the javascript code is being parsed from the PHP interpreter as if it was PHP code, probably throwing a syntax error and therefore ignoring the subsequent includes.
Looking at the "prettify.js" source code I've seen that effectively is present the "<?" string at line 471. Maybe changing the line include("$value.js");echo "\n"; to readfile("$value.js");echo "\n"; should solve the problem.

0

Since this was requested in the comments of the question, here are some comments to the code of the question

function cacheHeaders

This function could be made simpler and more stable by using the 3rd parameter of header() or the function http_response_code. I strongly suggest the latter, since even with the 3rd parameter, header() has some issues. See this answer for a http_response_code function for 4.3 <= PHP <= 5.4 and/or more info.

Also note that there is the format code r for date which formats the date according to RFC 2822, including the correct time zone (and not forced to GMT) (look up here)

The page you have that code from is at least 5 years old and was probably written for compatibility even then (PHP3! Holy...!), it is not really wrong, but it is time to move on. Dedicated functions provide better standards compliance and upwards compatibility compared to poorly - or not at all - documented behaviour of functions that were originally meant for something else.

output buffer, and the HEAD request

When handling a HEAD request, a HTTP server must process the request as if it was a GET request but must not return a message body. This is part of the accepted standard and there is no way around. So when a script is executed with a HEAD request either PHP or the web server (or both, I honestly don't know) will abort the script as soon as all headers are sent (or in other words when you try to output something).

Now however when you use userspace output buffering, like ob_gzhandler, headers may not be sent until the end of execution. This of course still achieves the goal of reducing client loading time by reducing the number of connections, but does not reduce server load - or rather introduces useless server load - and the client still has to wait until your script finishes, which is when the headers will finally be sent.

So the way to go is exiting the script "manually" right after all your headers are ready and set.

// setup, header generation, etc.
if($_SERVER['REQUEST_METHOD'] == 'HEAD')
    exit();
ob_start('ob_gzhandler');
// more pretty code, includes and whatnot

if you do not use an output buffer, you could also flush() together with some output at this point. However (according to the php manual) win32 versions of apache/php do not actually flush the buffer when you call flush(), which makes the above code still the best way to go.

Community
  • 1
  • 1
dualed
  • 10,262
  • 1
  • 26
  • 29