PHP: Combine and compile CSS and JS into Gzipped files
This has actually been the secret behind a lot of our great system performance, but as it was open source originally, it's going back to the people. Its a relatively straightforward script and speeds up JS and CSS performance by a huge amount, especially if you are using multiple files (like we do!).
It should be noted that the compression and combining engine is standard on all hosting accounts - just skip straight to step 5.
The red settings represent paths that need to be set dependant on where you put the files.
It is based on the combine script by Rakaz (https://rakaz.nl/item/make_your_pages_load_faster_by_combining_and_compressing_javascript_and_css_files), but there were notable flaws in that version.
- gZip encoding was not handled correctly by the browser
- cache files build up (requires cron to remove routinely)
- content echo'ing and incorrect headers causing slowdowns
Our version is about 100-200% faster than the original build, we thank Rakaz for giving us the chance to build it.
New features include:
- automatic update of cache files when a file has been changed
- automatic generation of gzipped and non gzipped files (for browsers which do not support gzip)
- optimised headers
- independent JavaScript library support
- no CPU overhead of gzipping on the fly as the gzipped version is permanently stored the first time a visitor hits a page which hasn't been cached
(left) gZipped JavaScript includes: https://www.sonassi.com/index/
(right) Non-gzip JavaScript includes: https://www.sonassi.com/index-dummy/
Lets get going then
Pre-requisites: Enable mod_rewrite# a2enmod rewrite # apache2ctl restartAnd mod_expires
# a2enmod expires # apache2ctl restartAnd mod_deflate
# a2enmod deflate # apache2ctl restart
You need to edit a few files for the next stage:
apache2.conf
.htaccess
combine-gzip.inc.php
1 ) In the apache2.conf or .htaccess, add the following lines
RewriteRule ^/gzipjs/(.*.js) <span style="color: #ff0000;">/data/lib/php/combine-gzip.inc.php</span>?type=javascript&encoding=gzip&files=$1 RewriteRule ^/gzipcss/(.*.css) <span style="color: #ff0000;">/data/lib/php/combine-gzip.inc.php</span>?type=css&encoding=gzip&files=$1 <FilesMatch ".combine-cache.gzip$"> RewriteCond %{HTTP_USER_AGENT} ..*Safari.*. [OR] RewriteCond %{HTTP:Accept-Encoding} !gzip RewriteRule (.*).combine-cache.gzip$ $1.combine-cache [L] #AddType "text/plain" .gzip AddEncoding gzip .gzip </FilesMatch> <IfModule mod_deflate.c> SetEnvIfNoCase Request_URI .(?:exe|t?gz|zip|bz2|sit|rar|<strong>gzip</strong>)$ no-gzip dont-vary </IfModule>2 ) For a spot of extra performance, enable mod_expires, and enter this into your apache conf
<IfModule mod_expires.c> ExpiresActive On ExpiresDefault A300 ExpiresByType text/html A7200 ExpiresByType text/javascript A604800 ExpiresByType application/x-javascript A604800 ExpiresByType text/css A604800 ExpiresByType image/x-icon A31536000 ExpiresByType image/gif A604800 ExpiresByType image/jpg A604800 ExpiresByType image/jpeg A604800 ExpiresByType image/png A604800 ExpiresByType text/plain A604800 </IfModule>3) Then create the cache directory in your document_root
# cd <span style="color: #ff0000;">DOCROOT</span> # mkdir ./cache # chmod 775 ./cache4) Create the combine-gzip.inc.php, with the following content
<?php /************************************************************************ * CSS and Javascript Combinator 0.5 + SMS gZip patch * Original Author * https://rakaz.nl/item/make_your_pages_load_faster_by_combining_and_compressing_javascript_and_css_files * gZip patch by Sonassi Media Services (www.sonassi.com) */ $cache = true; $encoding = 'none'; $cachedir = <span style="color: #ff0000;">$_SERVER['DOCUMENT_ROOT'] </span>. 'cache'; $cssdir = <span style="color: #ff0000;">$_SERVER['DOCUMENT_ROOT'] </span>. 'css'; $jsdir = <span style="color: #ff0000;">$_SERVER['DOCUMENT_ROOT']</span> . 'js'; $allowed_encoding = array('none','gzip','deflate'); $js_library = '<span style="color: #ff0000;">/data/lib/js</span>'; # No trailing slash if(isset($_GET['encoding']) && in_array($_GET['encoding'],$allowed_encoding)) $encoding = $_GET['encoding']; // Determine the directory and type we should use switch ($_GET['type']) { case 'css': $base = realpath($cssdir); break; case 'javascript': $base = realpath($jsdir); break; default: header ("HTTP/1.0 503 Not Implemented"); exit; }; $type = $_GET['type']; $elements = explode(',', $_GET['files']); // Determine last modification date of the files $lastmodified = 0; while (list(,$element) = each($elements)) { if (strstr($element,'/lib/')) { $element = str_replace('/lib',$js_library,$element); } else { $element = $base . '/' . $element; } $path = realpath($element); if (($type == 'javascript' && substr($path, -3) != '.js') || ($type == 'css' && substr($path, -4) != '.css' && (substr($path, 0, strlen($base)) != $base || substr($path, 0, strlen($js_library))) )) { header ("HTTP/1.0 403 Forbidden"); exit; } if (!file_exists($path)) { header ("HTTP/1.0 404 Not Found"); exit; } $lastmodified = max($lastmodified, filemtime($path)); } // Send Etag hash $mhash = md5($_GET['files']); $hash = $lastmodified . '-' . $mhash; header ("Etag: "" . $hash . """); if (isset($_SERVER['HTTP_IF_NONE_MATCH']) && stripslashes($_SERVER['HTTP_IF_NONE_MATCH']) == '"' . $hash . '"') { // Return visit and no modifications, so do not send anything header ("HTTP/1.0 304 Not Modified"); header ('Content-Length: 0'); } else { // First time visit or files were modified if ($cache) { // Determine supported compression method $gzip = strstr($_SERVER['HTTP_ACCEPT_ENCODING'], 'gzip'); $deflate = strstr($_SERVER['HTTP_ACCEPT_ENCODING'], 'deflate'); // Determine used compression method if ($encoding != 'none') $encoding = $gzip ? 'gzip' : ($deflate ? 'deflate' : 'none'); // Check for buggy versions of Internet Explorer if (!strstr($_SERVER['HTTP_USER_AGENT'], 'Opera') && preg_match('/^Mozilla/4.0 (compatible; MSIE ([0-9].[0-9])/i', $_SERVER['HTTP_USER_AGENT'], $matches)) { $version = floatval($matches[1]); if ($version < 6) $encoding = 'none'; if ($version == 6 && !strstr($_SERVER['HTTP_USER_AGENT'], 'EV1')) $encoding = 'none'; } // Try the cache first to see if the combined files were already generated $plaincachefile = 'cache-' . $hash . '.' . $type . '.combine-cache'; $cachefile = $plaincachefile . ($encoding != 'none' ? '.' . $encoding : ''); if (file_exists($cachedir . '/' . $cachefile)) { if ($encoding=='gzip') { header("HTTP/1.1 301 Moved Permanently"); header("Cache-Control: access plus 7 days"); header("Location: /cache/$cachefile"); header("Connection: close"); exit; } else { header("HTTP/1.1 301 Moved Permanently"); header("Cache-Control: access plus 7 days"); header("Location: /cache/$plaincachefile"); header("Connection: close"); exit; } } } // Get contents of the files $contents = ''; reset($elements); while (list(,$element) = each($elements)) { if (strstr($element,'/lib/')) { $element = str_replace('/lib',$js_library,$element); } else { $element = $base . '/' . $element; } $path = realpath($element); $contents .= "nn" . file_get_contents($path); } // Send Content-Type header ("Content-Type: text/" . $type); // Store cache if ($cache) { if ($encoding != 'none') { $gcontents = gzencode("/*nGZIPPED $mhashn*/" . $contents, 9, $gzip ? FORCE_GZIP : FORCE_DEFLATE); if ($fp = fopen($cachedir . '/' . $cachefile, 'wb')) { fwrite($fp, $gcontents); fclose($fp); } } if ($fp = fopen($cachedir . '/' . $plaincachefile, 'wb')) { fwrite($fp, $contents); fclose($fp); } foreach (glob($cachedir . "/" . "*" . $type .".combine-cache*") as $filename) { if (strstr($filename,$mhash) && !strstr($filename,$cachefile) && !strstr($filename,$plaincachefile)) @unlink($filename); } } if ($encoding=='gzip') { header("HTTP/1.1 301 Moved Permanently"); header("Location: /cache/$cachefile"); header("Connection: close"); exit; } else { header("HTTP/1.1 301 Moved Permanently"); header("Location: /cache/$plaincachefile"); header("Connection: close"); exit; } } ?>5) Add the path into your tags with comma separated values, Eg.
<<span class="start-tag">link</span><span class="attribute-name"> rel</span>=<span class="attribute-value">"stylesheet" </span><span class="attribute-name">type</span>=<span class="attribute-value">"text/css" </span><span class="attribute-name">href</span>=<span class="attribute-value">"/gzipcss/style.css,nav.css,blog.css" </span><span class="attribute-name">title</span>=<span class="attribute-value">"default" </span><span class="error"><span class="attribute-name">/</span></span>> <<span class="start-tag">script</span><span class="attribute-name"> type</span>=<span class="attribute-value">"text/javascript" </span><span class="attribute-name">src</span>=<span class="attribute-value">"/gzipjs//lib/jquery.min.js,boss-site-search.js,/lib/superfish.js" /></span>Then sit back and relax enjoying your MASSIVELY reduced bandwidth and CPU overheads.
For examples of this script in use, check out any page of our website at www.sonassi.com
[syntaxhighlighter]