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]
  • Éber Freitas Dias
    aquitanda.com
    Apr 9th 2009, 05:46
    1 This seems to be much lighter and "cakier" than Php Speedy that I'm using right now. I'll give it a try :) Thanks!
  • bkno
    twitter.com/bkno
    Apr 24th 2009, 12:27
    2 Thanks for sharing the code, I'll be giving this method a go.
  • Walker Hamilton
    walkerhamilton.com
    May 5th 2009, 14:31
    3 I think I'd like this to do two more things on top of this code. Do you have this one github for forking?
  • topherez
    May 8th 2009, 13:07
    4 Does this take care of gZipping components as well?
  • Miles Johnson
    milesj.me
    May 8th 2009, 14:05
    5 @topherez - No it just compresses the css/js and creates a cache file for it.
  • Bjorn
    bjornpost.com
    May 11th 2009, 11:43
    6 Nice work, do you mind if I put this in my cakephp-snippets repository at Github? (of course, your name will be on it, not mine. It's just for reference.)

    Please let me know, you have my e-mailaddress :).
  • Kyle Decot
    theskatepa...ectory.com
    May 11th 2009, 18:43
    7 Wow, how incredibly easy! This is how it should be built right into cake!
  • gargamel
    May 16th 2009, 12:20
    8 Thanks a lot, it's really easy to install and it works very well.

    But it breaks tinymce :(
  • gargamel
    May 16th 2009, 13:18
    9 Thanks a lot, it's really easy to install and it works very well.

    But it breaks tinymce :(
  • krusty999
    May 18th 2009, 12:31
    10 Hello, how about make this mergue all css before compress them.
    Thanks!
  • Geoff
    geoffoliver.org
    May 19th 2009, 15:52
    11 Yeah, it breaks TinyMCE :(
  • AndrewBoldman
    google.com
    Jun 4th 2009, 13:55
    12 Hi, good post. I have been woondering about this issue,so thanks for posting. I'll definitely be coming back to your site.
  • Daniel
    freakclimbing.com
    Jun 11th 2009, 01:48
    13 Hey Miles,
    thanks for the nice script!

    I just noticed that relative paths to images (background, etc) get broken! This is also the problem with tinymce.

    Cheers again!

    Dan
  • krusty999
    Jul 28th 2009, 10:59
    14
    To made TinyMCE work one solution is put the subdirectories of tinymce on /cjs like is on /js.

    example: /app/weebroot/js/tinymce/tinymce_subfolders.

    just copy to /app/weebroot/cjs/tinymce/tinymce_subfolders.

    Works for me.

    Same works with Shadowbox.
  • Robust Solution
    Oct 15th 2009, 15:11
    15 this is the best post about this subject, the guys who should read it should have basic http headers background...