CSS, JavaScript, Site Optimization, Web Development

Using Assetic to manage CSS and JS

We’ve been using Assetic for some time and we’re quite happy with the results. Assetic can be used to consolidate your CSS and JS files into fewer files with options to minify (compress) them to minimize the file size and avoid multiple connections to the asset servers by limiting the number of asset downloads. It is quite easy to use and can be used in any frameworks without middle layer integration libraries.

Before Assetic

We are using CodeIgniter as our application framework for our fairly huge application. By huge, meaning, a lot of codes has been written already. We are just used to include new link tags or script tags whenever we add a new CSS or JS for a page or for all pages. We don’t care how it is organized (and we should never do) and we should just focus on writing the functionalities (and designs).

When we switched to AWS EC2 servers, we’ve noticed a very slow parallel downloads of CSS and JS files plus the font files as well. It turns out that our previous hosting company already optimized those things that we didn’t notice until we moved to EC2. I’m not surprised though. Imagine downloading 10 CSS and 10 JS files with some font files and images from a single server. We didn’t intend to serve this files off that server anyway since we might for sure move to a CDN based solution in the future.

So I decided to search some good asset management for PHP and I’ve found Assetic on top results. Time to experiment.

Installation

Installation is quite easy via Composer. All you need to do is install Composer properly since you might need to use it more often (like updating third party libraries). What I did was put it inside my bin directory as ~/bin/composer so that I can use it easily. For CodeIgniter, Assetic would go to the application/third_party.

cd application/third_party
mkdir assetic
cd assetic
composer require kriswallsmith/assetic

We still need more software to support Assetic. I chose to use some NodeJS based software called UglifyJS 2 and UglifyCSS. To install them, you need nodejs and npm software which can be easily installed via your Linux distro’s package managers. Ex: for CentOS 7:

sudo yum install nodejs npm

Once installed, you can now install those uglify stuffs this way (I chose to install globally):

sudo npm install -g uglify-js
sudo npm install -g uglifycss

These software newly installed will serve as filters that can be used in Assetic.

Integration

First, we need to make this library available to us by including its autoloader. In CodeIgniter, we can do it this way:

<?php

require_once APPPATH.'third_party/assetic/vendor/autoload.php';

Our integration uses 2 modes:

  • On-the-fly consolidation and minification
  • Save consolidated and minified CSS/JS to file/files

Regardless of the usage mode, the idea is just the same. For a specific page, we read from a giant configuration all the CSS and JS file paths needed for that page, feed them to Assetic, provide the filters (Uglify JS2 or Uglify CSS), then either dump the contents to the browser or save them to file.

We organized our file list this way: there should be a controller name and an action name. We organized our giant config file in a way where we list all CSS/JS files needed to a controller/action. For example, for the home page, the controller name is called index and the action is called index. With this information, we would know which files are needed.

Below is what the giant config looks like:

<?php

$config['assets'] => array(
    'index' => array(
        'index' => array(
            'css' => array(
                'assets/css/reset.css',
                'assets/css/theme.css',
                'assets/css/style.css',
                'assets/css/homepage.css',
            ),
            'js' => array(
                'assets/js/jquery.js',
                'assets/js/app.js',
                'assets/js/homepage.js',
            ),
        ),
        // More actions below
    ),
    // More controllers below
);

On-the-fly consolidation and minification

This mode is used in development environment where CSS/JS contents can change a lot. This is how process works.

  • At page controller level, identify the controller name and action name
  • Create a link tag or script tag pointing to the special controller which handles on-the-fly minification, passing the controller and action names
  • On the minify controller, read the passed controller and action name
  • Read the CSS/JS list from the giant config file based on the controller and action given
  • Feed all information into Assetic along with the filters
  • Dump the contents to the browser (echo/print) along the with proper CSS/JS headers

So this is what the link tag/script tag looks like (nothing fancy) but can be improved:

<link rel="stylesheet" href="/minified/css?controller=index&action=index" />
<script type="text/javascript" src="/minified/js?controller=index&action=index"></script>

The controller looks like this:

// Our usual autoloader
require_once APPPATH.'third_party/assetic/vendor/autoload.php';

// Classes needed for Assetic
use Assetic\Asset\AssetCollection;
use Assetic\Asset\FileAsset;
use Assetic\Filter\UglifyJs2Filter;
use Assetic\Filter\UglifyCssFilter;


// CodeIgniter controller trying to look like a generic controller.
// Simplified, not meant for general usage, you know how to improve these codes
class Minify extends CI_Controller {

    public function css() {
        $controller = $_GET['controller'];
        $action = $_GET['action'];

        // Foo
    }

    public function js() {
        $controller = $_GET['controller'];
        $action = $_GET['action'];

        // Foo
    }
}

CSS Minification

All CSS requests should hit the css action above. Below are the details:

public function css() {
    $controller = $_GET['controller'];
    $action = $_GET['action'];

    // Read the giant config
    $config = some_magic_function_that_reads_the_giant_config();
    $paths = array();
    if (!empty($config[$controller][$action]['css'])) {
        $paths = $config[$controller][$action]['css'];
    }

    // Create a collection of file assets
    $collection = array();
    foreach ($paths as $path) {
        // FCPATH is the project root dir constant were assets dir is located
        $collection[] = new FileAsset(FCPATH.$path);
    }

    // Filters, ex: what magic to apply to these files
    $filters = array();

    // Trying to be verbose
    $filters[] = new UglifyCssFilter();

    // Feed collection and filters to assetic
    $css = new AssetCollection($collection, $filters);

    // Send headers to tell the browser the type of content we are dumping
    header('Content-Type: text/css');

    // Dump the contents, should be minified at this stage
    echo $css->dump();
}

JS Minification

JS implementation should be not so different. It just uses a different set of filters and different headers.

public function js() {
    $controller = $_GET['controller'];
    $action = $_GET['action'];

    $config = some_magic_function_that_reads_the_giant_config();
    $paths = array();
    if (!empty($config[$controller][$action]['js'])) {
        $paths = $config[$controller][$action]['js'];
    }

    $collection = array();
    foreach ($paths as $path) {
        $collection[] = new FileAsset(FCPATH.$path);
    }

    $filters = array();
    $filters[] = new UglifyJs2Filter();

    $js = new AssetCollection($collection, $filters);

    header('Content-Type: text/javascript');
    echo $js->dump();
}

Pretty straightforward isn’t it? However, this is a bit annoyingly slow for our development workflow since it does the minification process over and over again every page view. What we did was send caching headers for 5 minutes to ease the pain a bit. Moreover, we have switches to turn this thing off and serve the actual files instead.

Saving the consolidated and minified CSS/JS to file

In staging or production, we didn’t enable the on-the-fly minification. Instead, we save those consolidated assets to files and serve these files directly. We do have an extra step before deploying our code to staging or production. There is a script that we run where it reads the giant config, consolidate and compress CSS/JS files and save them to files with some sort of naming convention.

We save these files into: assets/min/css and assets/min/js. The naming convention is somethine like below:

  • assets/css/controller_action.css
  • assets/css/index_index.css
  • assets/css/login_index.css
  • assets/css/account_profile.css
  • assets/js/account_profile.js

The process is a bit the same with on-the-fly minification, but instead of dumping the contents, we save them to file.

<?php

// Same codes for the on-the-fly process
$ac = new AssetCollection($collection, $filters);

// Create filename
$filename = $root_dir."/assets/min/css/{$controller}_{$action}.css";
file_put_contents($filename, $ac->dump());

Once these files are ready, the flow is a bit different when it comes the serving these files.

  • At controller level, identify the controller name and action name
  • Create a link tag or script tag pointing to the minified asset file
  • File is happily served by the server

That’s it! Enjoy and share.

Leave a reply

Your email address will not be published. Required fields are marked *