Decoda

Decoda is a lightweight lexical string parser for BBCode styled markup.

CSS/JS/Asset Compression in CakePHP

Thursday, April 9th 2009, 12:23am
Topics: Tutorials, CakePHP
Tags: CSS, Javascript, Compression, Assets
Comments: 25
Permalink - Tinylink

The other day I spent way too many hours trying to figure out CakePHPs built in CSS and JS compression system. To say the least, its not really built in, you have to configure and do a lot of it yourself. I did much searching and found tutorials that didn't work, or using custom built helpers that were of no use, and in the end I wrote my own script (based off webroot/css.php written by gwoo). My script does the following:

  • Compresses both CSS and JS, using one file
  • Caches the assets, and recaches if a change is made to the original
  • Comes prebuilt with CSS compression
  • Must download and install JSMin
  • Extremely easy to install and configure


To use my asset compression script, create a file called assets.php within webroot, and then copy and paste the code found at the end of this post. Once you have done this, download and place the jsmin.php file within /vendors/ or /app/vendors/ (the JSMin file should be named jsmin.php to work). The next step is to create a folder called assets within your /app/tmp/cache/ directory, and chmod the permissions to 777. The final step is to uncomment the filtering in core.php and set the path to assets.php.

Configure::write('Asset.filter.css', 'assets.php');
Configure::write('Asset.filter.js', 'assets.php');


Now to take it for a test spin! Once you have uncommented the configuration, direct your browser to the actual JS and CSS paths to see if it works (http://www.domain.com/ccss/style.css), and that's it! Hope this has been helpful and easy, like it should have been!

<?php 
/**
 * Compress and caches asset files: js, css (can be altered for other assets)
 *
 * Written by Miles Johnson (http://www.milesj.me), snippets from original CakePHP team
 *
 * CakePHP(tm) :  Rapid Development Framework (http://www.cakephp.org)
 * Copyright 2005-2008, Cake Software Foundation, Inc. (http://www.cakefoundation.org)
 *
 * Licensed under The MIT License
 * Redistributions of files must retain the above copyright notice.
 *
 * @filesource
 * @copyright     Copyright 2005-2008, Cake Software Foundation, Inc. (http://www.cakefoundation.org)
 * @link          http://www.cakefoundation.org/projects/info/cakephp CakePHP(tm) Project
 * @license       http://www.opensource.org/licenses/mit-license.php The MIT License
 */
 
// No cake installation
if (!defined('CAKE_CORE_INCLUDE_PATH')) { 
	header('HTTP/1.1 404 Not Found');
	exit('File Not Found'); 
} 

// Get asset type
$ext = trim(strrchr($url, '.'), '.');
$assetType = ($ext === 'css') ? 'css' : 'js';

// Wrong file
if (preg_match('|\.\.|', $url) || !preg_match('|^c'. $assetType .'/(.+)$|i', $url, $regs)) {
	die('Wrong File Name');
}

$cachePath = CACHE .'assets'. DS . str_replace(array('/','\\'), '-', $regs[1]);
$fileName = $assetType .'/'. $regs[1];

if ($assetType == 'css') {
	$filePath = CSS . $regs[1];
	$fileType = 'text/css';
} else {
	$filePath = JS . $regs[1];
	$fileType = 'text/javascript';
}

if (!file_exists($filePath)) {
	die('Asset Not Found');
}

/**
 * Compress the asset
 * @param string $path
 * @param string $name
 * @return string
 */
function compress($path, $name, $type) {
	$input = file_get_contents($path);
	
	if ($type == 'css') {
		$stylesheet = preg_replace('!/\*[^*]*\*+([^/][^*]*\*+)*/!', '', $input);
		$stylesheet = str_replace(array("\r\n", "\r", "\n", "\t", '/\s\s+/', '  ', '   '), '', $stylesheet);
		$stylesheet = str_replace(array(' {', '{ '), '{', $stylesheet);
		$stylesheet = str_replace(array(' }', '} '), '}', $stylesheet);
		$output = $stylesheet;
	} else {
		App::import('Vendor', 'jsmin');
		$output = JSMin::minify($input);
	}
	
	$ratio = 100 - (round(strlen($output) / strlen($input), 3) * 100);
	$output = "/* File: $name, Ratio: $ratio% */\n". $output;
	return $output;
}	

/**
 * Cache the asset
 * @param string $path
 * @param string $content
 * @return string
 */
function cacheAsset($path, $content) {
	if (!is_dir(dirname($path))) {
		mkdir(dirname($path));
		chmod($path, 0777);
	}
	
	if (!class_exists('File')) {
		uses('file');
	}
	
	$cached = new File($path);
	return $cached->write($content);
}

// Do compression and cacheing
if (file_exists($cachePath)) {
	$templateModified = filemtime($filePath);
	$cachedModified = filemtime($cachePath);

	if ($templateModified > $cachedModified) {
		$output = compress($filePath, $fileName, $assetType);
		cacheAsset($cachePath, $output);
	} else {
		$output = file_get_contents($cachePath);
	}
} else {
	$output = compress($filePath, $fileName, $assetType);
	cacheAsset($cachePath, $output);
	$templateModified = time();
}

header("Date: ". date("D, j M Y G:i:s", $templateModified) ." GMT");
header("Content-Type: ". $fileType);
header("Expires: ". gmdate("D, j M Y H:i:s", time() + DAY) ." GMT");
header("Cache-Control: max-age=86400, must-revalidate"); // HTTP/1.1
header("Pragma: cache_asset");        // HTTP/1.0
print $output; ?>
Related Entries:

25 Comments

10 / 2 = ?
Allowed: [code] [b] [i] [u]
  • Ron Chaplin
    mentalramblings.info
    Dec 13th 2009, 05:13
    16 One thing that I have noticed is that when using the debugkit, which is a plugin, as stated by others this breaks the relative location of assets.

    A workaround I employed was to wrap the Configure section in a if block.
    if(Configure::read('debug') == 0)
    {
        Configure::write('Asset.filter.css', 'assets.php');
        Configure::write('Asset.filter.js', 'assets.php');
    }
    


    As I have stated, this is simply a workaround, and applies logically, due to debugkit not being used unless debug lvl is set to 2, and simply the fact that if your in debugging phase, it's probably useful to be able to read your css/js in their natural state.
  • Daniel
    theworldofdan.co.uk
    Apr 14th 2010, 06:55
    17 Thank you - I had trouble getting another one to work, so I'm glad this one does.
  • Andrew
    Apr 29th 2010, 10:12
    18 Love the code and much easier to set up than cake's core...

    Noticed one mistake. You have double and triple white spaces replaced with none, which caused me an error when I had .pagination div (2 spaces) being collapsed into none. A solution would be to use a preg replace to trim all excess white space:

    $stylesheet = preg_replace('/\s\s+/', ' ', $stylesheet);
  • Professional Hosting
    farbyte.com
    Jun 14th 2010, 04:41
    19 This is just what we're looking for. I'm surprised this hasn't made it to the CahePHP core yet. Thanks!
  • rachana
    tipsforfun.com
    Aug 4th 2011, 03:46
    20 Thanks for your script.
  • Woodruff
    Sep 14th 2011, 06:54
    21 Hy, thanks for that code it's very helpfull.

    Just a question, when creating the compress file in the cache, the date of creation is at +1 hour of the server time.

    was looking why but didn't found.

    If you have any idea.

    Best regards
  • mr baker
    apple.com
    Dec 20th 2011, 16:33
    22 it doesn't handle plugin js's and css's any fix for this ???
  • mr baker
    cakephp.org
    Dec 21st 2011, 07:53
    23 here is workground for plugins css and js

  • mr baker
    cakephp.org
    Dec 21st 2011, 08:02
    24 it doesn't let me post the code here but here is the code link
    http://bin.cakephp.org/view/1250547562
  • Eric
    Jan 8th, 15:42
    25 Thank you! The instructions are easy to follow and everything works perfectly.