Dynamically combine and minify your Javascript and CSS files with CodeIgniter

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!

This entry was posted in Uncategorized. Bookmark the permalink. Post a comment or leave a trackback: Trackback URL.

13 Comments

  1. Luke
    Posted April 21, 2011 at 2:05 pm | Permalink

    Hi,

    Thanks for this it’s awesome :) .

    I’ve followed it but there is one small problem, none of my images will load :S I think it’s because the paths to the background images are no longer relative to the css file thats being created.

    If I put the full url in the background:url( etc in my css it works fine. But can’t figure out how to get it working otherwise, any help would be much appreciated.

    • calvin
      Posted April 21, 2011 at 3:48 pm | Permalink

      Hi Luke,

      If you see the commented out line in the code above:

      //replace occurrences of /assets/ with asset_url()
      $this->asset_output = str_replace("/assets/", asset_url(), $this->asset_output);

      Uncomment that and the script will replace your relative URLs with full URLs using your asset_url() path. Otherwise, your image files should point to your asset directory from index.php, which is where all CI requested are routed through. If your asset directory is above index.php your CSS might look like: background:url(‘assets/images/whatever.jpg’).

      • Luke
        Posted April 22, 2011 at 1:01 am | Permalink

        Hi, thanks for the quick reply.

        Yeah that sorted it thanks, it’ll teach me to read through tutorials better next time!

        • calvin
          Posted April 27, 2011 at 9:54 pm | Permalink

          Glad that helped. Keep in mind that doing this does increase your overhead since it needs to search and replace on the entire string of text. That’s why I had it commented out. The better method is to use relative paths that will reach your assets directory.

  2. Posted June 21, 2011 at 7:59 am | Permalink

    sir i got this error ” Call to undefined function load_js()”

  3. Posted November 16, 2011 at 6:47 am | Permalink

    Hello Sir,

    I have one website in codeigniter 1.73 version and in which i have put all your code and i have some done some custmization in code like i have remove the asset path, css_path and put directly like this in load_css and load_js function

    return ”;

    return ”;

    and download the two library for jsmin and cssmin and put inside the application/library folder and create controller of loader class but it is not working.

    my loader.php file put code like

    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->load->library(‘jsmin’);
    $this->load->library(‘cssmin’);
    $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($file . $this->ext))
    {

    $this->asset_output .= file_get_contents($file . $this->ext).”\n”;
    $this->modified_time = max(filemtime($file . $this->ext), $this->modified_time);
    }
    }

    Please help me.

    Thank You

    • calvin
      Posted January 16, 2012 at 5:36 pm | Permalink

      Hi kashyap,

      Sorry for the (really) late reply. It looks like you are missing some code that echoes the output. Check the above, you need to echo $this->asset_output at the end of your _output method.

  4. Posted November 16, 2011 at 7:33 am | Permalink

    Hello Sir,

    Main confusion is when the loader controller function js and css will call?

  5. Posted November 16, 2011 at 8:51 am | Permalink

    sir my page source is making one http request but can not load css and js

  6. Nichols Richard
    Posted November 16, 2011 at 6:18 pm | Permalink

    Hello calvin i and kashyap we both have same problem please help me as soon as possible.
    Question is how my loader controller js and css function will call? and how to replace my url from js~jquery.js|js~jqury.1.3.min.js same for css both are not loaded but load_js and load_css function which are in loader helper file are execute. i have also download jsmin and cssmin class file but no working at all. waiting for your response.

  7. Nichols Richard
    Posted November 25, 2011 at 2:56 am | Permalink

    Hello Sir,

    my js and css are not loaded because how to execute the loader controller because the helper will convert the all array in which all files name where make it jquery.js|~jquary.min.js but all this are not loaded because of loader class in controller is not executed. sir please help me i will wait for your kindly response i will reach upto 75% if you help me then i will get success.

  8. EvilKarter
    Posted March 8, 2012 at 2:05 pm | Permalink

    THX
    create script.

  9. Posted April 16, 2012 at 1:18 pm | Permalink

    this is really nice and helpful plugin.

2 Trackbacks

  1. By Combined and minfied Css and JS | Echo Base CMS 2011 on February 1, 2011 at 10:44 pm

    [...] I made some code tonight based on this article at RobotSlacker. In the end, it ended up a whole lot different and it still needs some fine tuning. I was planning [...]

  2. By Combined and minfied Css and JS | Björn Folbert on February 1, 2011 at 10:54 pm

    [...] I made some code tonight based on this article at RobotSlacker. In the end, it ended up a whole lot different and it still needs some fine tuning. I was planning [...]

Post a Comment

Your email is never published nor shared. Required fields are marked *

*
*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>