Our responsive redesign has been a great improvement for metaltoad.com. I was still not entirely satisfied with the speed of our Drupal site, especially while waiting for my train at the busy Pioneer Square!
One of the major obstacles for mobile networks is lag, and so I set out to cut down the number of HTTP requests. By improving the site's stylesheets and scripts, I was able to eliminate a dozen extra requests. Results were validated with PhantomJS and the OS X Network Link Conditioner.
hook_js_alter
First, I applied the hook_js_alter
and hook_css_alter
described in an earlier blog. Taking it a step farther, I replaced Drupal's built-in jQuery with a copy from Google APIs, and tweaked the sort order to get the fewest possible groups. That took us from four scripts down to just one, and six stylesheets down to three (all, screen, and print).
Data URIs with Compass
Since we're using Dan's Boilerplate theme with Compass, another easy step was to use the compass inline-image()
function to replace some of our small images. Six more requests were removed this way.
By replacing url("images/dotted-line.gif")
with inline-image("dotted-line.gif")
, the compiled CSS becomes:
background: transparent url('data:image/gif;base64,R0lGODlhAwABAIAAAP///zIyMiH5BAEHAAAALAAAAAADAAEAAAICDFAAOw==') repeat-x left top;
Compass automatically keeps the data URIs up-to-date if and when the image file changes. For me, this is a major advantage of using Compass – image optimizations can be integrated into the workflow, instead of only happening when the front-end dev has time and Wheaties to spare.
Did it work?
To measure the results, I needed a way to take repeated samples of the page load time. Refreshing the page a few times isn't going to give an accurate picture, given the relatively high deviation in load time. PhantomJS (a headless WebKit interface) fits the bill. By running the test with the OS X Network Link Conditioner enabled, we can simulate a lossy 3G network. The simulated link was 780/330 kbps, with a 100 millisecond delay and 1% packet loss.
The result? Average load time time dropped by 10%, and the upper quartile was cut by 20%!
Source code
/**
* Implements hook_js_alter().
*/
function mtm_custom_js_alter(&$javascript) {
if (user_is_anonymous()) {
// Move Google Analytics up so it can't break groups.
foreach ($javascript as $name => $script) {
if ($script['type'] == 'inline' &&
strstr($script['data'], '_gaq.push')) {
$javascript[$name]['weight'] = -1000;
$javascript[$name]['group'] = JS_LIBRARY;
}
}
// Replace JQuery with the same version from Google's CDN.
if (isset($javascript['misc/jquery.js'])) {
$javascript['misc/jquery.js']['data'] =
'//ajax.googleapis.com/ajax/libs/jquery/1.4.4/jquery.min.js';
$javascript['misc/jquery.js']['type'] = 'external';
$javascript['misc/jquery.js']['weight'] = -100;
}
uasort($javascript, 'mtm_custom_sort_css_js');
$i = 0;
foreach ($javascript as $name => $script) {
$javascript[$name]['weight'] = $i++;
$javascript[$name]['group'] = JS_DEFAULT;
$javascript[$name]['every_page'] = FALSE;
}
}
}
/**
* Implements hook_css_alter().
*/
function mtm_custom_css_alter(&$css) {
if (user_is_anonymous()) {
uasort($css, 'mtm_custom_sort_css_js');
$i = 0;
foreach ($css as $name => $style) {
$css[$name]['weight'] = $i++;
$css[$name]['group'] = CSS_DEFAULT;
$css[$name]['every_page'] = FALSE;
}
}
}
/**
* Custom sort order to optimize scripts and styles.
*/
function mtm_custom_sort_css_js($a, $b) {
// Normalize the order of browser keys.
if (isset($a['browsers'])) {
ksort($a['browsers']);
ksort($b['browsers']);
}
// Sort by integer weight values. This allows explicit weights to break out
// of a group, but the 0.001 automatically added to preserve insert order
// cannot break a group.
if ($return = ((int)$a['weight'] - (int)$b['weight'])) {
return $return;
}
// Group common browsers together.
elseif (isset($a['browsers']) && $return =
strcmp(serialize($a['browsers']), serialize($b['browsers']))) {
return $return;
}
// Group media types together.
elseif (isset($a['media']) && $return = strcmp($a['media'], $b['media'])) {
return $return;
}
// Finally, order by weight. Multiply by 1000 to undo
// the division in drupal_add_css.
elseif ($return = ($a['weight'] * 1000 - $b['weight'] * 1000)) {
return $return;
}
else {
return 0;
}
}
Want more awesome content like this? Check out our top 20 Drupal tips of all time