Commit b86f45b1 authored by Steven Wittens's avatar Steven Wittens

#100516: CSS preprocessor to cache and compress all .css files. Benchmarks...

#100516: CSS preprocessor to cache and compress all .css files. Benchmarks show up to 40% faster page loads.
parent 57eecc45
......@@ -851,8 +851,8 @@ function drupal_maintenance_theme() {
require_once './includes/unicode.inc';
require_once './modules/filter/filter.module';
unicode_check();
drupal_add_css(drupal_get_path('module', 'system') .'/defaults.css', 'core');
drupal_add_css(drupal_get_path('module', 'system') .'/system.css', 'core');
drupal_add_css(drupal_get_path('module', 'system') .'/defaults.css', 'module');
drupal_add_css(drupal_get_path('module', 'system') .'/system.css', 'module');
$theme = '';
}
......
......@@ -1383,19 +1383,27 @@ function drupal_add_link($attributes) {
* Adds a CSS file to the stylesheet queue.
*
* @param $path
* The path to the CSS file relative to the base_path(), e.g., /modules/devel/devel.css.
* (optional) The path to the CSS file relative to the base_path(), e.g., /modules/devel/devel.css.
* @param $type
* The type of stylesheet that is being added. Types are: core, module, and theme.
* (optional) The type of stylesheet that is being added. Types are: module or theme.
* @param $media
* (optional) The media type for the stylesheet, e.g., all, print, screen.
* @param $preprocess
* (optional) Should this CSS file be aggregated and compressed if admin has enabled this feature?
* @return
* An array of CSS files.
*/
function drupal_add_css($path = NULL, $type = 'module', $media = 'all') {
static $css = array('core' => array(), 'module' => array(), 'theme' => array());
function drupal_add_css($path = NULL, $type = 'module', $media = 'all', $preprocess = TRUE) {
static $css = array();
if (!is_null($path)) {
$css[$type][$path] = array('path' => $path, 'media' => $media);
// Create an array of CSS files for each media type first, since each type needs to be served
// to the browser differently.
if (isset($path)) {
// This check is necessary to ensure proper cascading of styles and is faster than an asort().
if (!isset($css[$media])) {
$css[$media] = array('module' => array(), 'theme' => array());
}
$css[$media][$type][$path] = $preprocess;
}
return $css;
......@@ -1413,17 +1421,104 @@ function drupal_add_css($path = NULL, $type = 'module', $media = 'all') {
*/
function drupal_get_css($css = NULL) {
$output = '';
if (is_null($css)) {
if (!isset($css)) {
$css = drupal_add_css();
}
foreach ($css as $type) {
foreach ($type as $file) {
$output .= '<style type="text/css" media="'. $file['media'] .'">@import "'. base_path() . $file['path'] ."\";</style>\n";
$preprocess_css = variable_get('preprocess_css', FALSE);
$directory = file_directory_path();
$is_writable = is_dir($directory) && is_writable($directory) && (variable_get('file_downloads', FILE_DOWNLOADS_PUBLIC) == FILE_DOWNLOADS_PUBLIC);
foreach ($css as $media => $types) {
// If CSS preprocessing is off, we still need to output the styles.
// Additionally, go through any remaining styles if CSS preprocessing is on and output the non-cached ones.
foreach ($types as $type => $files) {
foreach ($types[$type] as $file => $preprocess) {
if (!$preprocess || !($is_writable && $preprocess_css)) {
// If a CSS file is not to be preprocessed and it's a module CSS file, it needs to *always* appear at the *top*,
// regardless of whether preprocessing is on or off.
if (!$preprocess && $type == 'module') {
$no_module_preprocess .= '<style type="text/css" media="'. $media .'">@import "'. base_path() . $file .'";</style>' ."\n";
}
// If a CSS file is not to be preprocessed and it's a theme CSS file, it needs to *always* appear at the *bottom*,
// regardless of whether preprocessing is on or off.
else if (!$preprocess && $type == 'theme') {
$no_theme_preprocess .= '<style type="text/css" media="'. $media .'">@import "'. base_path() . $file .'";</style>' ."\n";
}
else {
$output .= '<style type="text/css" media="'. $media .'">@import "'. base_path() . $file .'";</style>' ."\n";
}
}
}
}
if ($is_writable && $preprocess_css) {
$filename = md5(serialize($types)) .'.css';
$preprocess_file = drupal_build_css_cache($types, $filename);
$output .= '<style type="text/css" media="'. $media .'">@import "'. base_path() . $preprocess_file .'";</style>'. "\n";
}
}
return $output;
return $no_module_preprocess . $output . $no_theme_preprocess;
}
/**
* Aggregate and optimize CSS files, putting them in the files directory.
*
* @param $types
* An array of types of CSS files (e.g., screen, print) to aggregate and compress into one file.
* @param $filename
* The name of the aggregate CSS file.
* @return
* The name of the CSS file.
*/
function drupal_build_css_cache($types, $filename) {
$data = '';
// Create the css/ within the files folder.
$csspath = file_create_path('css');
file_check_directory($csspath, FILE_CREATE_DIRECTORY);
if (!file_exists($csspath .'/'. $filename)) {
// Build aggregate CSS file.
foreach ($types as $type) {
foreach ($type as $file => $cache) {
if ($cache) {
$contents = file_get_contents($file);
// Return the path to where this CSS file originated from, stripping off the name of the file at the end of the path.
$path = base_path() . substr($file, 0, strrpos($file, '/')) .'/';
// Fix all paths within this CSS file, ignoring absolute paths.
$contents = preg_replace('/url\(([\'"]?)(?![a-z]+:)/i', 'url(\1'. $path . '\2', $contents);
// Fix any @import that don't use url() and is not absoslute.
$data .= preg_replace('/@import\s*([\'"]?)(?![a-z]+:)/i', '@import \1'. $path . '\2', $contents);
}
}
}
// @import rules must proceed any other style, so we move those to the top.
$regexp = '/@import[^;]+;/i';
preg_match_all($regexp, $data, $matches);
$data = preg_replace($regexp, '', $data);
$data = implode('', $matches[0]) . $data;
// Perform some safe CSS optimizations.
$data = preg_replace('<
\s*([@{}:;\)])\s* | # Remove whitespace around separators.
/\*([^*\\\\]|\*(?!/))+\*/ | # Remove comments that are not CSS hacks.
[\n\r] # Remove line breaks.
>x', '\1', $data);
// Create the CSS file.
file_save_data($data, $csspath .'/'. $filename, FILE_EXISTS_REPLACE);
}
return $csspath .'/'. $filename;
}
/**
* Delete all cached CSS files.
*/
function drupal_clear_css_cache() {
file_scan_directory(file_create_path('css'), '.*', array('.', '..', 'CVS'), 'file_delete', TRUE);
}
/**
......
......@@ -1284,7 +1284,7 @@ function _locale_export_remove_plural($entry) {
*/
function _locale_string_language_list($translation) {
// Add CSS
drupal_add_css(drupal_get_path('module', 'locale') .'/locale.css');
drupal_add_css(drupal_get_path('module', 'locale') .'/locale.css', 'module', 'all', FALSE);
$languages = locale_supported_languages(FALSE, TRUE);
unset($languages['name']['en']);
......
......@@ -406,7 +406,7 @@ function theme_page($content) {
function theme_maintenance_page($content, $messages = TRUE, $partial = FALSE) {
drupal_set_header('Content-Type: text/html; charset=utf-8');
drupal_add_css('misc/maintenance.css', 'core');
drupal_add_css('misc/maintenance.css', 'module', 'all', FALSE);
drupal_set_html_head('<link rel="shortcut icon" href="'. base_path() .'misc/favicon.ico" type="image/x-icon" />');
$output = "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n";
......@@ -437,7 +437,7 @@ function theme_maintenance_page($content, $messages = TRUE, $partial = FALSE) {
function theme_install_page($content) {
drupal_set_header('Content-Type: text/html; charset=utf-8');
drupal_add_css('misc/maintenance.css', 'core');
drupal_add_css('misc/maintenance.css', 'module', 'all', FALSE);
drupal_set_html_head('<link rel="shortcut icon" href="'. base_path() .'misc/favicon.ico" type="image/x-icon" />');
$output = "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n";
$output .= '<html xmlns="http://www.w3.org/1999/xhtml">';
......
......@@ -205,7 +205,7 @@ function block_admin_display($theme = NULL) {
global $theme_key, $custom_theme;
// Add CSS
drupal_add_css(drupal_get_path('module', 'block') .'/block.css');
drupal_add_css(drupal_get_path('module', 'block') .'/block.css', 'module', 'all', FALSE);
// If non-default theme configuration has been selected, set the custom theme.
if ($theme) {
......
......@@ -41,7 +41,7 @@ function _color_page_alter(&$vars) {
// Override stylesheet
$path = variable_get('color_'. $theme_key .'_stylesheet', NULL);
if ($path) {
$vars['css']['theme'] = array($path => array('path' => $path, 'media' => 'all'));
$vars['css']['all']['theme'][$path] = TRUE;
$vars['styles'] = drupal_get_css($vars['css']);
}
......@@ -88,11 +88,11 @@ function color_scheme_form($theme) {
$info = color_get_info($theme);
// Add Farbtastic color picker
drupal_add_css('misc/farbtastic/farbtastic.css');
drupal_add_css('misc/farbtastic/farbtastic.css', 'module', 'all', FALSE);
drupal_add_js('misc/farbtastic/farbtastic.js');
// Add custom CSS/JS
drupal_add_css($base .'/color.css', 'module');
drupal_add_css($base .'/color.css', 'module', 'all', FALSE);
drupal_add_js($base .'/color.js');
drupal_add_js(array('color' => array(
'reference' => color_get_palette($theme, true)
......
......@@ -37,7 +37,7 @@ function help_menu($may_cache) {
*/
function help_main() {
// Add CSS
drupal_add_css(drupal_get_path('module', 'help') .'/help.css');
drupal_add_css(drupal_get_path('module', 'help') .'/help.css', 'module', 'all', FALSE);
$output = t('
<h2>Help topics</h2>
......
......@@ -422,7 +422,7 @@ function profile_admin_overview() {
*/
function profile_browse() {
// Add CSS
drupal_add_css(drupal_get_path('module', 'profile') .'/profile.css');
drupal_add_css(drupal_get_path('module', 'profile') .'/profile.css', 'module', 'all', FALSE);
$name = arg(1);
list(,,$value) = explode('/', $_GET['q'], 3);
......
......@@ -1076,7 +1076,7 @@ function theme_search_block_form($form) {
*/
function search_data($keys = NULL, $type = 'node') {
// Add CSS
drupal_add_css(drupal_get_path('module', 'search') .'/search.css');
drupal_add_css(drupal_get_path('module', 'search') .'/search.css', 'module', 'all', FALSE);
if (isset($keys)) {
if (module_hook($type, 'search')) {
......
......@@ -25,8 +25,6 @@ function system_help($section) {
return '<p>'. t('Welcome to the administration section. Here you may control how your site functions.') .'</p>';
case 'admin/by-module':
return '<p>'. t('This page shows you all available administration tasks for each module.') .'</p>';
case 'admin/settings/page-caching':
return '<p>'. t('Enabling the cache will offer a significant performance boost. Drupal can store and send compressed cached pages requested by "anonymous" users. By caching a web page, Drupal does not have to create the page each time someone wants to view it.') .'</p>';
case 'admin/build/themes':
return '<p>'. t('Select which themes are available to your users and specify the default theme. To configure site-wide display settings, click the "configure" task above. Alternately, to override these settings in a specific theme, click the "configure" link for the corresponding theme. Note that different themes may have different regions available for rendering content like blocks. If you want consistency in what your users see, you may wish to enable only one theme.') .'</p>';
case 'admin/build/themes/settings':
......@@ -229,11 +227,11 @@ function system_menu($may_cache) {
'callback' => 'drupal_get_form',
'callback arguments' => array('system_error_reporting_settings'));
$items[] = array(
'path' => 'admin/settings/page-caching',
'title' => t('Page caching'),
'description' => t('Enable or disable page caching for anonymous users.'),
'path' => 'admin/settings/performance',
'title' => t('Performance'),
'description' => t('Enable or disable page caching for anonymous users, and enable or disable CSS preprocessor.'),
'callback' => 'drupal_get_form',
'callback arguments' => array('system_page_caching_settings'));
'callback arguments' => array('system_performance_settings'));
$items[] = array(
'path' => 'admin/settings/file-system',
'title' => t('File system'),
......@@ -310,13 +308,13 @@ function system_menu($may_cache) {
if (arg(0) == 'admin') {
global $custom_theme;
$custom_theme = variable_get('admin_theme', '0');
drupal_add_css(drupal_get_path('module', 'system') .'/admin.css', 'core');
drupal_add_css(drupal_get_path('module', 'system') .'/admin.css', 'module');
}
// Add the CSS for this module. We put this in !$may_cache so it is only
// added once per request.
drupal_add_css(drupal_get_path('module', 'system') .'/defaults.css', 'core');
drupal_add_css(drupal_get_path('module', 'system') .'/system.css', 'core');
drupal_add_css(drupal_get_path('module', 'system') .'/defaults.css', 'module');
drupal_add_css(drupal_get_path('module', 'system') .'/system.css', 'module');
}
return $items;
......@@ -643,7 +641,7 @@ function system_error_reporting_settings() {
return system_settings_form($form);
}
function system_page_caching_settings() {
function system_performance_settings() {
$description = '<p>'. t("The normal cache mode is suitable for most sites and does not cause any side effects. The aggressive cache mode causes Drupal to skip the loading (init) and unloading (exit) of enabled modules when serving a cached page. This results in an additional performance boost but can cause unwanted side effects.") .'</p>';
......@@ -656,8 +654,13 @@ function system_page_caching_settings() {
else {
$description .= '<p>'. t('<strong class="ok">Currently, all enabled modules are compatible with the aggressive caching policy.</strong> Please note, if you use aggressive caching and enable new modules, you will need to check this page again to ensure compatibility.') .'</p>';
}
$form['cache'] = array(
$form['page_cache'] = array(
'#type' => 'fieldset',
'#title' => t('Page cache'),
'#description' => t('Enabling the cache will offer a significant performance boost. Drupal can store and send compressed cached pages requested by <em>anonymous</em> users. By caching a web page, Drupal does not have to construct the page each time someone wants to view it.'),
);
$form['page_cache']['cache'] = array(
'#type' => 'radios',
'#title' => t('Caching mode'),
'#default_value' => variable_get('cache', CACHE_DISABLED),
......@@ -667,13 +670,33 @@ function system_page_caching_settings() {
$period = drupal_map_assoc(array(0, 60, 180, 300, 600, 900, 1800, 2700, 3600, 10800, 21600, 32400, 43200, 86400), 'format_interval');
$period[0] = t('none');
$form['cache_lifetime'] = array(
$form['page_cache']['cache_lifetime'] = array(
'#type' => 'select',
'#title' => t('Minimum cache lifetime'),
'#default_value' => variable_get('cache_lifetime', 0),
'#options' => $period,
'#description' => t('On high-traffic sites it can become necessary to enforce a minimum cache lifetime. The minimum cache lifetime is the minimum amount of time that will go by before the cache is emptied and recreated. A larger minimum cache lifetime offers better performance, but users will not see new content for a longer period of time.')
);
$form['bandwidth_optimizations'] = array(
'#type' => 'fieldset',
'#title' => t('Bandwidth optimizations'),
'#description' => t('These options can help reduce both the size and number of requests made to your website. This can reduce the server load, the bandwidth used, and the average page loading time for your visitors.')
);
$directory = file_directory_path();
$is_writable = is_dir($directory) && is_writable($directory) && (variable_get('file_downloads', FILE_DOWNLOADS_PUBLIC) == FILE_DOWNLOADS_PUBLIC);
$form['bandwidth_optimizations']['preprocess_css'] = array(
'#type' => 'radios',
'#title' => t('Aggregate and compress CSS files'),
'#default_value' => variable_get('preprocess_css', FALSE) && $is_writable,
'#disabled' => !$is_writable,
'#options' => array(t('Disabled'), t('Enabled')),
'#description' => t("Some Drupal modules include their own CSS files. When these modules are enabled, each module's CSS file adds an additional HTTP request to the page, which can increase the load time of each page. These HTTP requests can also slightly increase server load. It is recommended to only turn this option on when your site is in production, as it can interfere with theme development. This option is disabled if you have not set up your files directory, or if your download method is set to private."),
);
$form['#submit']['system_settings_form_submit'] = array();
$form['#submit']['drupal_clear_css_cache'] = array();
return system_settings_form($form);
}
......@@ -1102,6 +1125,7 @@ function system_settings_form_submit($form_id, $form_values) {
else {
drupal_set_message(t('The configuration options have been saved.'));
}
menu_rebuild();
}
......@@ -1109,6 +1133,8 @@ function system_settings_form_submit($form_id, $form_values) {
* Menu callback; displays a listing of all themes.
*/
function system_themes() {
drupal_clear_css_cache();
$themes = system_theme_data();
ksort($themes);
......@@ -1485,6 +1511,9 @@ function system_modules_submit($form_id, $form_values) {
if ($dependencies && !isset($form_values['confirm'])) {
return FALSE;
}
drupal_clear_css_cache();
return 'admin/build/modules';
}
......
......@@ -74,7 +74,7 @@ function tracker_track_user() {
*/
function tracker_page($uid = 0) {
// Add CSS
drupal_add_css(drupal_get_path('module', 'tracker') .'/tracker.css');
drupal_add_css(drupal_get_path('module', 'tracker') .'/tracker.css', 'module', 'all', FALSE);
if ($uid) {
$sql = 'SELECT DISTINCT(n.nid), n.title, n.type, n.changed, n.uid, u.name, l.last_comment_timestamp AS last_post, l.comment_count FROM {node} n INNER JOIN {node_comment_statistics} l ON n.nid = l.nid INNER JOIN {users} u ON n.uid = u.uid LEFT JOIN {comments} c ON n.nid = c.nid AND (c.status = %d OR c.status IS NULL) WHERE n.status = 1 AND (n.uid = %d OR c.uid = %d) ORDER BY last_post DESC';
......
......@@ -781,7 +781,7 @@ function user_menu($may_cache) {
else {
// Add the CSS for this module. We put this in !$may_cache so it is only
// added once per request.
drupal_add_css(drupal_get_path('module', 'user') .'/user.css', 'core');
drupal_add_css(drupal_get_path('module', 'user') .'/user.css', 'module');
if ($_GET['q'] == 'user' && $user->uid) {
// We want to make the current user's profile accessible without knowing
// their uid, so just linking to /user is enough. To achieve this goal,
......
......@@ -53,7 +53,7 @@ function watchdog_menu($may_cache) {
else {
if (arg(0) == 'admin' && arg(1) == 'logs') {
// Add the CSS for this module
drupal_add_css(drupal_get_path('module', 'watchdog') .'/watchdog.css');
drupal_add_css(drupal_get_path('module', 'watchdog') .'/watchdog.css', 'module', 'all', FALSE);
}
}
......
......@@ -785,6 +785,9 @@ function update_create_cache_tables() {
update_fix_watchdog();
update_fix_sessions();
// Clear any cached CSS files, in case any module updates its CSS as well.
drupal_clear_css_cache();
$op = isset($_REQUEST['op']) ? $_REQUEST['op'] : '';
switch ($op) {
case 'Update':
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment