Update 10/27/2011: This method is outdated. I recommend using the very awesome Carabiner for your minification/concatenation needs.
If you still want to stick to this method, I would recommend implementing some sort of caching for your minified/concatenated files. Carabiner does this for your automatically, however!
——–
As any good UI developer will tell you, one of the best practices for speeding up your website is to minimize HTTP requests. Since most of the user’s loading time is tied up in things like loading scripts, images, flash, stylesheets, etc., reducing the number of these requests can drastically help improve loading time. It also helps to Gzip the components as well, which we’ll also cover in this tutorial.
This first tutorial will cover how to concatenate and minify your javascript and CSS files into a single HTTP request, using the CodeIgniter framework. For sites that load lots of scripts like jQuery plugins, this can be very beneficial for performance. Additionally, minification of scripts and stylesheets can help reduce the size of files, saving you bandwidth and shortening loading times for user.
So, let’s get started!
NOTE: I am assuming in this tutorial that you are already using an .htaccess file to eliminate index.php from your URIs.
Create an assets directory
You’ll want to manage your static assets (js, css, images, etc) by placing them in a directory called /assets in your web root. Here’s what your CI folder structure should look like:
/assets
/css
/images
/js
/system
/application
...
index.php
You may have your application/ folder elsewhere, but for this tutorial we’ll have it in the default location, inside the /system folder.
You’ll also need the below two libraries, which are essential to this tutorial:
Let’s grab copies of the following two libraries:
JSMin-PHP, Ryan Grove’s PHP port of Douglas Crockford’s JSMin.
Joe Scylla’s CssMin
Name these two files JSMin.php and CssMin.php, respectively, and place them in your application/libraries folder.
Create an asset_url() helper function
Having a helper function to access your assets URL can be helpful in the event you decide to rename or change the location of your application. It works like the base_url() function, only it will link to your assets rather than your base URL (obviously). In config.php, add the following code:
/*
|--------------------------------------------------------------------------
| Assets Path
|--------------------------------------------------------------------------
|
| URL Path to static assets library
|
*/
$config['asset_path'] = 'assets/';
$config['css_path'] = 'assets/css/';
$config['js_path'] = 'assets/js/';
We’ll be using css_path and js_path a little later in this tutorial, so keep those in mind.
Next, add this function in /system/helpers/path_helper.php
/**
* Get asset URL
*
* @access public
* @return string
*/
if (!function_exists('asset_url'))
{
function asset_url()
{
//get an instance of CI so we can access our configuration
$CI =& get_instance();
//return the full asset path
return base_url() . $CI->config->item('asset_path');
}
}
Add the following to your config/autoload.php file, to helpers:
$autoload['helper'] = array('path');
Now you’re set! You can now call asset_url() anywhere to access your asset URL path. So instead of writing:
<img src="/assets/images/photo.jpg" alt="A Photo" />
You would write instead:
<img src="<?php echo asset_url(); ?> images/photo.jpg" alt="A Photo" />
Now that that’s out of the way, let’s get to the good stuff:
Create asset loader helper functions
In order to reduce our HTTP requests for our scripts to a single call, we’ll need a script tag that does the call for all files in one <script>tag. In your application/helpers folder, create a new file called asset_helper.php and insert the following code:
<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
/**
* Load JS
* Creates the <script> tag that links all requested js files
* @access public
* @param array
* @return string
*/
if (!function_exists('load_js'))
{
function load_js(array $files, $version = '')
{
//check if base url has a trailing slash, if not append it
$base_url = substr(base_url(), -1) == '/' ? base_url() : base_url() . '/';
foreach ($files as $index => $file)
{
//replace all backslash occurrences with tilde (~) to avoid URI confusion (gets converted back later in controller)
$files[$index] = str_replace('/', '~', $file);
}
return '<script type="text/javascript" src="' . $base_url . 'loader/js/' . implode('|', $files) . '/' . $version . '"></script>';
}
}
/**
* Load CSS
* Creates the <link> tag that links all requested css files
* @access public
* @param array
* @return string
*/
if (!function_exists('load_css'))
{
function load_css(array $files, $version = '')
{
//check if base url has a trailing slash, if not append it
$base_url = substr(base_url(), -1) == '/' ? base_url() : base_url() . '/';
foreach ($files as $index => $file)
{
//replace all backslash occurrences with tilde (~) to avoid URI confusion (gets converted back later in controller)
$files[$index] = str_replace('/', '~', $file);
}
return '<link type="text/css" rel="stylesheet" href="' . $base_url . 'loader/css/' . implode('|', $files) . '/' . $version . '" />';
}
}
/* End of file asset_helper.php */
/* Location: ./system/application/helpers/asset_helper.php */
These helpers will be used on pages that you need to load specific JS or CSS files. Usage would be:
$files_to_load = array('file1', 'file2', 'file3'); //filenames WITHOUT the .js extension!
echo load_js($files_to_load);
You might want to also add this helper to your autoload.php file as well, just like the path helper above. Just add it to the array.
Create a loader controller
In order to actually load our asset files, we’ll need a controller that handles the concatentation and minification of the scripts and files. Create a new file in your application/controllers directory called loader.php with the following code:
<?php
/**
* loader.php
*/
load_class('CSSMin', false);
load_class('JSMin', false);
class Loader extends Controller {
var $asset_output;
var $type;
var $files;
var $asset_path;
var $ext;
var $content_type;
var $modified_time;
function Loader()
{
parent::Controller();
$this->modified_time = 0;
}
function js($files)
{
$this->type = 'js';
$this->files = $files;
$this->asset_path = $this->config->item('js_path');
$this->ext = '.js';
$this->content_type = 'text/javascript';
$this->_output();
}
function css($files)
{
$this->type = 'css';
$this->files = $files;
$this->asset_path = $this->config->item('css_path');
$this->ext = '.css';
$this->content_type = 'text/css';
$this->_output();
}
function _output()
{
$files_array = explode("|", $this->files);
foreach ($files_array as $key => $file)
{
//replace chars for folder separation, replace ~ with /
$file = str_replace('~', '/', $file);
if (file_exists($this->asset_path . $file . $this->ext))
{
$this->asset_output .= file_get_contents($this->asset_path . $file . $this->ext)."\n";
$this->modified_time = max(filemtime($this->asset_path . $file . $this->ext), $this->modified_time);
}
}
//replace occurrences of /assets/ with asset_url()
//$this->asset_output = str_replace("/assets/", asset_url(), $this->asset_output);
switch ($this->type)
{
case 'js':
$this->asset_output = JSMin::minify($this->asset_output);
break;
case 'css':
$this->asset_output = CSSMin::minify($this->asset_output);
break;
default:
throw new LoaderException("Unknown file type.");
break;
}
//gzip
if (isset($_SERVER['HTTP_ACCEPT_ENCODING'])) {
if (stristr($_SERVER['HTTP_ACCEPT_ENCODING'], 'gzip') !== false) {
$this->asset_output = gzencode($this->asset_output);
header('Content-encoding: gzip');
} else if (stristr($_SERVER['HTTP_ACCEPT_ENCODING'], 'deflate') !== false) {
$this->asset_output = gzdeflate($this->asset_output);
header('Content-encoding: deflate');
}
}
//headers
header('Content-type: ' . $this->content_type);
header('Last-modified: ' . date('r', $this->modified_time));
header('Expires: ' . date('r', time() + 2592000));
header('Content-length: ' . strlen($this->asset_output));
echo $this->asset_output;
}
}
/* End of file loader.php */
/* Location: ./system/application/controllers/loader.php */
And that’s it!
Usage examples:
Javascript:
<?php echo load_js(array('jsfile1', 'dir/jsfile2' , 'jsfile3')); ?>
CSS:
<?php echo load_css(array('cssfile1', 'dir/cssfile2', 'cssfile3')); ?>
To load files inside a directory, for example, assets/js/plugins/example.js, you would include 'plugins/example' inside your the array as shown above.
If you update your css/js, you can use the option $version parameter. Simply use an incrementing integer value of your choice and the browser will read the file as a new file rather than loading the cached file.
Happy coding!