Jump to content

CSS/Javascript Aggregation/Compression


Michael Fillier
 Share

Recommended Posts

I was not able to find anything in terms of css and javascript aggregation.

Drupal does this wonderfully via a drupal_add_head() function that is used by themes and modules to add css and javascript to the head of the site. The theme then prints out a $head variable, instead of the hard-coded css and javascript includes.

This allows you to enable a perfomance option of aggregation and compression. This then combines the files together, saves them on the server and links to the aggregated files instead of the individual files.

This results in huge performance gains, especially if your site is using a css grid and any jquery plugins. Since each one comes with its own file and sometimes css styles.

I will do some research into how Drupal processes the css and js files. But in the meantime, any feedback on where this fits into processwire would be appreciated.

Link to comment
Share on other sites

Definitely worth checking out Minify if you have a lot of CSS and JS files (or even if you only have a few since it makes for less HTTP requests).

It used to be a bit of a hassle to use in the early days, but it's really simple now and shrunk my JS files to less than half their original size, even squeezing already-minimised versions of the jQuery core down further (presumably these are compresse some other way).

Link to comment
Share on other sites

ProcessWire has the functionality to add scripts and css using $config var.

$config->scripts->add(pathto.js)
$config->styles->append(pathto.css);

Then the output

<?php foreach($config->styles->unique() as $file) echo "\n	<link type='text/css' href='$file' rel='stylesheet' />"; ?>

<?php foreach($config->scripts->unique() as $file) echo "\n	<script type='text/javascript' src='$file'></script>"; ?>

This way a module could also add script using this. But there's no front-end module that does use it. It's up to the site builder.

Link to comment
Share on other sites

ProcessWire doesn't get involved in markup generation by intention. But Soma is right that it does keep $config->styles and $config->scripts for you, if you happen to want to utilize them in generating your <head> markup. As for things to minify and combine scripts, I think they are worthwhile if the selection of files is reasonably static and the browser can cache and use it. But again, ProcessWire doesn't want to step on the developers toes by crossing the line into your output. We don't want to assume that we know what is best for every implementation and developer. ProcessWire may power many web sites but it's also powering mobile phone applications, web services, shell scripts and more. This forum has always been a great place to find out about new tools and resources we can use with ProcessWire too (I need to check out that Minify at Google Code).

Link to comment
Share on other sites

I would question the need for a CMS to do this. This either can better and should be done using some setup server side or locally using grunt.js or similar there's many great resources.

Edit: Not excluding that there could be a simple module helping setting it up. Not sure if that would make sense though.

Link to comment
Share on other sites

I usually build my sites using the HTML5 Boilerplate as a starting point, including a Sass version of its CSS files, an up-to-date jQuery and some additional stuff I usually need. H5BP also has an additional build script, which takes care of a lot of tasks, including minification and concatenation of CSS and JS files (although I don't really use CSS concatenation since Sass handles that). This also plays very nicely with ProcessWire's templates – a typical CSS reference like

<link rel="stylesheet" href="<?php echo $config->urls->templates?>css/style.css">

is accepted by H5BP's build script (ant version) without any modification. The only thing I need to do is add all .inc files containing CSS/JS references (I like to put JS references at the bottom of the page) to the build script's project.properties files.

That being said … well, with PW's user base (hopefully) growing, it will attract more "non-expert users", which is by no means a bad thing. But to these users, a module is usually much easier to use than an external PHP script or a build script which requires additional software to be installed on their system. So in the long run, PW might actually benefit from having that kind of module, if only because it makes it even easier to create well-performing sites.

Link to comment
Share on other sites

Just took a look at an older Drupal module that aggregated and compressed javascript, here is a code snippet:

/**
* Helper function to minify and gzip files.
*/
function _javascript_aggregator_minify($scripts) {
 // Only process it is JavaScript Optimization is enabled.
 if (variable_get('preprocess_js', 0)) {
   // Strip out the aggregated JavaScript file.
   $path_to_files_directory = base_path() . file_directory_path();
   $pattern = "!(<script type=\"text\/javascript\" src=\"(.*?)$path_to_files_directory)(.*?)(\"(.*?)><\/script>)!";
   if (preg_match_all($pattern, $scripts, $matches) > 0) {
  $aggregated_file_name = $matches[3][0];
  $jsmin_file_name = $aggregated_file_name .'min.js';
  // Construct the final JSMin file path.
  $jsmin_file_path = file_directory_path() . $jsmin_file_name;
  // Create the JSMinified file if it doesn't exist yet.
  if (!file_exists($jsmin_file_path)) {
    if (variable_get('javascript_aggregator_jsminplus', FALSE)) {
	  // JSMin+ the contents of the aggregated file.
	  require_once(drupal_get_path('module', 'javascript_aggregator') .'/jsminplus.php');
	  // Strip Byte Order Marks (BOM's) from the file, JSMin+ cannot parse these.
	  $file = str_replace(pack("CCC", 0xef, 0xbb, 0xbf), "", file_get_contents(file_directory_path() . $aggregated_file_name));
	  $contents = JSMinPlus::minify($file);
    }
    else {
	  // JSMin the contents of the aggregated file.
	  require_once(drupal_get_path('module', 'javascript_aggregator') .'/jsmin.php');
	  $contents = JSMin::minify(file_get_contents(file_directory_path() . $aggregated_file_name));
    }
    // Code comments containing copyright notices and licensing information
    // are stripped when the file is minified. GPL and most other open
    // source licenses require the license text to be included whenever the
    // file is distributed, so include a reference to the un-minified file.
    $contents = '// Minified using Javascript Aggregator - see '. $path_to_files_directory . $aggregated_file_name ." for original source including licensing information.\n". $contents;
    // GZip the JavaScript if required.
    $htaccess = file_directory_path() . '/js/.htaccess';
    if (variable_get('javascript_aggregator_gzip', FALSE)) {
	  // Create the GZip file if it doesn't already exist.
	  if (!file_exists($jsmin_file_path .'.gz')) {
	    file_save_data(gzencode($contents, 9), $jsmin_file_path .'.gz', FILE_EXISTS_REPLACE);
	  }
	  // Make sure the .htaccess file is active to handle GZipped JavaScript files.
	  if (!variable_get('javascript_aggregator_no_htaccess', FALSE) && !file_exists($htaccess)) {
	    $rewrite_base = base_path() . file_directory_path() .'/js/';
	    $htaccess_contents = <<<EOT
<Files *.js.gz>
AddEncoding x-gzip .gz
ForceType text/javascript
</Files>
<IfModule mod_rewrite.c>
RewriteEngine on
RewriteBase $rewrite_base
RewriteCond %{HTTP_USER_AGENT} !".*Safari.*"
RewriteCond %{HTTP:Accept-encoding} gzip
RewriteCond %{REQUEST_FILENAME}.gz -f
RewriteRule ^(.*)\.js $1.js.gz [L,QSA]
</IfModule>
EOT;
	    file_save_data($htaccess_contents, $htaccess, FILE_EXISTS_REPLACE);
	  }
    }
    else {
	  // Delete .htaccess file so *.gz files do not get served.
	  if (file_exists($htaccess)) {
	    file_delete($htaccess);
	  }
    }
    // Save the contents to the JavaScript file.
    file_save_data($contents, $jsmin_file_path, FILE_EXISTS_REPLACE);
  }
  // Replace the aggregated file with the minified JavaScript file.
  $scripts = str_replace($aggregated_file_name, $jsmin_file_name, $scripts);
   }
 }
 return $scripts;
}

I am not a very experienced coder, so I want to know what your take is on this approach. The drupal module can be downloaded here: http://ftp.drupal.org/files/projects/javascript_aggregator-6.x-1.6.zip

Maybe we could adapt this to processwire and expand its functionality to css files as well.

Link to comment
Share on other sites

I would still choose Minify since it already handles CSS as well and doesn't require the .htaccess: http://code.google.com/p/minify/

I've got it working on one site already, but I don't know that it's really worth a module to be honest since its already easy to set up (there's a helper script included that pretty much builds the URL for you).

I just put mine in somewhere like /site/lib/min/ and put the relevant URL in head.inc for the JS and CSS.

A module could save a little time, but in the case of Minify it would be about ten seconds ;)

EDIT: I did just think of a way a module could save a lot more time actually so I might build this module soon.

EDIT 2: my first thought was to make it inject the relevant code before the head tag but that's nt always ideal plus you don't always want all scripts/stylesheets in every template. I think I might just make it give the relevant code to copy and paste into the template instead.

Link to comment
Share on other sites

Since this is a common use case (I mean who doesn't like compressed, aggregated javascript and css?) I think it would be a good candidate for a module.

Looking at minify further, perhaps we can use soma's concept and feed the files to minify.

ProcessWire has the functionality to add scripts and css using $config var.

$config->scripts->add(pathto.js)
$config->styles->append(pathto.css);

Then the output

<?php foreach($config->styles->unique() as $file) echo "\n	<link type='text/css' href='$file' rel='stylesheet' />"; ?>

<?php foreach($config->scripts->unique() as $file) echo "\n	<script type='text/javascript' src='$file'></script>"; ?>

This way a module could also add script using this. But there's no front-end module that does use it. It's up to the site builder.

But since this approach will require a site builder to adapt their code to use config instead of plain old link and script tags, it is probably not favourable. I think most logical approach would be for the module to preg_match_all linked css and javascript files, serve the urls to minify and then replace the individual linked references with the minify'd references. Is there a hook that would let a module modify the page markup before rendering the page? Or would the approach I mentioned need to involve code in the theme.

Link to comment
Share on other sites

I'm halfway through a module for this already, but it will still just build a URL from a selection of files to paste into the template.

The whole ethos of ProcessWire is not making assumptions about what people are going to do with it or how they're going to work, so reducing the Minify process to a simple copy and paste is about as far as I feel comfortable taking it. There are occasions where you won't want every JS or CSS file being called for every page so I don't want it to make assumptions or rewrite anyone's HTML markup if I can help it.

It'll be a simple solution though - I promise :)

You will always have code in your templates by the way so that's not a problem. Take a look through the default template files in the site/templates directory to see what I mean.

  • Like 1
Link to comment
Share on other sites

What I meant was different files for different templates, so on one site I load extra JS and CSS on a page containing a Google Map and in that case I simply set that page to use a different template and in the head.inc file check the current page's template and - if it matches 'map' for example - output the relevant extra JS and CSS.

Module should be ready tomorrow or Sunday at the latest.

Link to comment
Share on other sites

What I meant was different files for different templates, so on one site I load extra JS and CSS on a page containing a Google Map and in that case I simply set that page to use a different template and in the head.inc file check the current page's template and - if it matches 'map' for example - output the relevant extra JS and CSS.

Module should be ready tomorrow or Sunday at the latest.

Thanks, I was wondering the best way to do that. Switching on the template in header.inc is a great solution. Taking a look at the module right now.

Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
 Share

  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...