Настройка автоматической минификации CSS на Джумле

Оптимизация CSS состоит из трех этапов:

  • установка и настройка инструментов
  • объединение CSS в css/style-big.css, последующее сжатие стилей в css/style.css
  • замена всех стилей объединенным файлом стилей

Установка и настройка инструментов

Установка компоновщика

Makefile - положить в каталог на уровень выше дерева веб-документов. В примерах ниже дерево веб-документов - в каталоге www.

Makefile
#
# Настройка путей. Makefile необходимо положить выше дерева web-документов,
# либо подправить следующие относительные пути
#
 
### Настройки
# web-документы
WEB_ROOT=www
# каталог для сохранения объединенной и минифицированной версии
CSS_OUT=$(WEB_ROOT)/media
# каталог для сохранения промежуточных вариантов CSS
SRC_ROOT=src/css
# файл с перечислением всех CSS файлов, участвующих в объединении
CSS_SRC=src/css.txt
 
# файл с перечислением всех JS файлов, участвующих в минификации
JS_SRC=src/js.txt
 
### Подготовка данных
CSS_SRC_FILES=$(shell cat $(CSS_SRC))
CSS_FILE_DEST=$(addsuffix .less, $(addprefix $(SRC_ROOT)/, $(CSS_SRC_FILES)))
 
JS_SRC_FILES=$(shell cat $(JS_SRC))
JS_FILE_DEST=$(addsuffix .mjs, $(JS_SRC_FILES))
 
### Подготовка файла CSS: удаление знака BOM, замена относительных путей на абсолютные
define abs_build
cat $1 | sed '1s/^\xef\xbb\xbf//' | ./relabs.php --webroot='$4' --abspath='$3' --color > $2
endef
 
### Цели и зависимости
.PHONY: all
 
all: css js
 
css: $(CSS_OUT)/style.css
 
$(CSS_OUT)/style.css: $(CSS_FILE_DEST)
	# Объединение файлов
	@echo
	@echo $(CSS_FILE_DEST)
	@echo
	$(shell cat $(CSS_FILE_DEST) > $(CSS_OUT)/style-big.css)
	# Минификация (1 вариант) с помощью csso https://github.com/css/csso
	# csso -i $(CSS_OUT)/style-big.css -o $(CSS_OUT)/style.css
	# Минификация (2 вариант) с помощью yuicompressor http://yui.github.io/yuicompressor/
	# java -jar yuicompressor.jar -o $(CSS_OUT)/style.css $(CSS_OUT)/style-big.css
	# Минификация Он-лайн (3 вариант) с помощью https://code.google.com/p/reducisaurus/
#	wget -O $(CSS_OUT)/reducisaurus-style.css http://reducisaurus.appspot.com/css?url=http://ruszvuk.ru/css/style-big.css
 
$(CSS_FILE_DEST): $(CSS_SRC_FILES)
 
$(SRC_ROOT)/$(WEB_ROOT)/%.css.less: $(WEB_ROOT)/%.css
	@mkdir -p $(@D)
	$(call abs_build,$<,$@,$(*D),$(WEB_ROOT))
 
js: $(JS_FILE_DEST)
 
$(WEB_ROOT)/%.js.mjs: $(WEB_ROOT)/%.js
	java -jar yuicompressor.jar $< -o $@

Необходимо создать каталоги: www/css, src/css, в файле src/css.txt прописать используемые CSS-файлы (относительные пути):

src/src1.txt
www/components/com_virtuemart/assets/css/vmsite-ltr.css
...
www/templates/yourtheme/css/style.css

Если на сайте используются внешние CSS, например http://code.jquery.com/ui/1.11.3/themes/smoothness/jquery-ui.css, их необходимо скачать и сохранить в отдельный каталог, например в www/css/external/, заменив в них ссылки на изображения - либо на свои скачанные, либо прописать обращение к внешнему сайту. Затем вписать файл в src/src1.txt.

Для преобразование относительных путей в абсолютные - скрипт relabs.php:

:!: работает, если веб-сайт не находится в подкаталоге домена, т.е. если сайт расположен по адресу site.ru/blog/, возможно, что потребуется доработка

relabs.php
#!/usr/bin/env php
<?php
 
// Этот скрипт служит для исправления относительных путей в CSS на абсолютные с целью объединения в один файл
 
if (php_sapi_name() != 'cli') die('Run it in cli mode!');
 
class Replacer {
 
  private $isAllRight = true;
  static $parms = array();
  public function run() {
    chdir(__DIR__);
    $this->getParms();
    $this->replace();
    die($this->isAllRight ? 0 : 1);
  }
 
  private $text;
  private function replace() {
    $this->text = file_get_contents("php://stdin");
 
    // replace @import "style.css";
    $this->text = preg_replace_callback('~(@import.*?;)~sui', array('self', '_replaceImport'), $this->text);
 
    // rel to abs
    $this->text = preg_replace_callback('~url\s*\(\s*(.*?)\s*\)~sui', array('self', '_replace'), $this->text);
    echo $this->text;
  }
 
  public static function _replaceImport($m) {
    self::error(sprintf('Warning: file contains @import: %s', $m[0]));
    return '';
  }
 
  public static function _replace($m) {
    $uri = trim(trim($m[1],'"\''));
    // inline-image
    if(preg_match('~^data:image~', $uri)) {
      return self::_wrap($m[1]);
    }
    // external image
    if(preg_match('~^(\/|https?:\/\/)~', $uri)) return self::_wrap($uri);
 
    $uri = '/'.self::$parms['abspath'].'/'.ltrim($uri, '/');
    self::checkFileExists($uri);
    return self::_wrap($uri);
  }
 
  public static function _wrap($uri) {
    return 'url('.self::_prepare($uri).')';
  }
 
  public static function _prepare($uri) {
    $uri = preg_replace('~\/[^/]+\/\.\.\/~', '/', $uri);
    return $uri;
  }
 
  private static function checkFileExists($uri) {
    $file = self::$parms['webroot'] . parse_url($uri, PHP_URL_PATH);
    if(!file_exists($file)) self::error("Warning: file $file not exists!");
  }
 
  private function getParms() {
    self::$parms = getopt('', array('webroot:', 'abspath:', 'color'));
    if(!isset(self::$parms['webroot'], self::$parms['abspath'])) {
      $this->man();
      die(1);
    };
  }
 
  private function man() {
    $this->error("Use: relabs.php --webroot=WEB_ROOT --abspath=ABS_PATH --color");
  }
 
  public function error($msg) {
    if(isset(self::$parms['color'])) {
      $msg = self::getColoredString($msg, 'red', null);
    }
    fwrite(STDERR, $msg . PHP_EOL);
  }
 
  private static $fgColors = array(
    'black' => '0;30', 'dark_gray' => '1;30', 'blue' => '0;34', 'light_blue' => '1;34', 'green' => '0;32',
    'light_green' => '1;32', 'cyan' => '0;36', 'light_cyan' => '1;36', 'red' => '0;31', 'light_red' => '1;31',
    'purple' => '0;35', 'light_purple' => '1;35', 'brown' => '0;33', 'yellow' => '1;33', 'light_gray' => '0;37',
    'white' => '1;37');
 
  private static $bgColors = array('black' => '40', 'red' => '41', 'green' => '42', 'yellow' => '43',
    'blue' => '44', 'magenta' => '45', 'cyan' => '46', 'light_gray' => '47');
  private static function getColoredString($string, $foreground_color = null, $background_color = null) {
    $colored_string = "";
    if (isset(self::$fgColors[$foreground_color])) {
      $colored_string .= "\033[" . self::$fgColors[$foreground_color] . "m";
    }
    if (isset(self::$bgColors[$background_color])) {
      $colored_string .= "\033[" . self::$bgColors[$background_color] . "m";
    }
    $colored_string .=  $string . "\033[0m";
    return $colored_string;
  }
 
}
 
$r = new Replacer();
$r->run();

Скрипту необходимо выставить права на выполнение:

chmod +x relabs.php

Установка инструментов минификации

Этот пункт не обязателен, возможно использовать объединенный файл css/style-big.css, прописав его в шаблоне. Либо после каждой сборки объединенного файла сжимать его каким-либо способом вручную, например так.

Следует установить одну из программ, в зависимости от способа:

  • Структурная минификация: csso
  • C помощью yuicompressor
  • Он-лайн инструментом Reducisaurus
  • Любой другой, прописав его в Makefile, зависимость $(CSS_OUT)/style.css: $(CSS_FILE_DEST)

Для выбора способа необходимо исправить соответствующую строку в Makefile

Объединение и сжатие CSS

В Makefile прописаны необходимые команды для объединения и сжатия. Необходимо запустить make на выполнение из консоли:

make

В дальнейшем, при изменении исходных CSS стилей, этой одной командой запускается весь процесс пересборки. Принудительное выполнение:

make -B

В некоторых случаях, связанных с использованием на Windows-платформе, требуется принудительное выполнение всех правил.

Возможные ошибки

Warning: file www/templates/yourtheme/images/picture.jpg not exists!

Это - предупреждение о том, что указанный в CSS файл физически отсутствует.

make: csso: Команда не найдена

Не был установлен минификатор.

make: *** Нет правила для сборки цели `www/...', требуемой для `src/css/www/...'.  Останов.

Не настроены пути в Makefile, неправильные пути в src/src1.txt.

Замена стилей объединенным файлом CSS

Есть два способа:

  • добавить плагин, удаляющие «прежние» стили (простой)
  • внести изменения в шаблоны (трудоемкий)

Внесение изменений в шаблоны

Джумла автоматически добавляем стили в шаблон. Любое расширение, обращаясь к API движка, способно добавить свой набор стилей. В нашем случае следует исключить стили, попавшие в собранный выше финальный CSS файл.

Есть два способа: кардинальный - удалить блок:

<jdoc:include type="head" />

и прописать все вручную (JS, CSS, etc), либо выборочно отключить учтенные в сборке CSS файлы - этот способ и рассмотрим.

В файл templates/your-theme/index.php необходимо добавить следующее (перед доктайпом):

templates/your-theme/index.php
$excludeCss = array(
  '/components/com_virtuemart/assets/css/vmsite-ltr.css', // перечислить все отключаемые стили
  '/templates/your-theme/css/default.css', // возможно указать только часть URL, так как проверка идет регуляркой
);
$excludeCssRules = array();
foreach($excludeCss as $f) {
  $excludeCssRules[] = preg_quote($f);
}
 
$excludeCssRules = implode('|', $excludeCssRules);
 
foreach($this->_styleSheets as $k => $v) {
  if(preg_match('~('.$excludeCssRules.')$~', $k)) unset($this->_styleSheets[$k]);
}

Узнать какие стили подключаются возможно, открыв исходный код HTML-страницы. Либо так:

die(var_dump($this->_styleSheets));

После секции <jdoc:include type=«head» /> надо подключить наш собранный CSS:

<link rel="stylesheet" href="/css/style.css" type="text/css" />

В идеале - на этом все. Однако некоторые расширения добавляют стили «после», либо «грязными хаками», поэтому выпилить их возможно либо специальным (каким?) плагином, работающим по сформированной странице, либо доработав исходный код самих расширений. Об этом - далее.

Доработка кода «упрямых» компонентов

От отдельных CSS файлов описанным выше способ не избавиться. Часть - цепляет стили уже после, часть - использует хаки. Против всех работает метод грубой силы - изменение исходного кода. Проблему обновлений и хаков решает систему контроля версий. В примерах - конечный вариант. Суть - закомментировать добавление стилей.

Virtuemart

administrator/components/com_virtuemart/helpers/config.php
// vmJsApi::css('facebox');
...
// vmJsApi::css ( $cssFile ) ;

YTools

modules/mod_sj_vm_extraslider_res/mod_sj_vm_extraslider_res.php
// YTools::stylesheet('vmextraslider.css');
// YTools::stylesheet('css3.css');

BIT Vituemart Product Badges

plugins/system/bit_vm_product_badges/bit_vm_product_badges.php
// $css_link = "<link rel=\"stylesheet\" href=\"$plugin_short_path"."css/bitvmbadges.css\" type=\"text/css\" />";
...
$body = preg_replace ("/<\/head>/", "\n".$css_link."\n".$js_link."\n".$js_call."\n</head>", $body);

Другие расширения

modules/mod_market_reviews/tmpl/default.php
// $document->addStyleSheet('/modules/mod_market_reviews/css/default.css');

Что делать после установки новых расширений, добавляющих CSS

Выше мы отключили отдельные CSS файлы (из шаблона, а также с помощью комментариев), поэтому новые расширения изначально будут работать, но добавят дополнительные CSS стили.

Следует добавить в src/src1.txt эти файлы, а также подправить $excludeCss из index.php темы, либо подправить код самих расширений. После этого запустить компиляцию:

make