Антон Гришан
Приёмы минификации в веб-приложениях
Понятие оптимизации охватывает огромное количество вопросов, начиная от настройки веб-сервера, заканчивая алгоритмами обработки данных. Одно из направлений оптимизации – минификация. Здесь и далее под термином «минификация» будем понимать вид оптимизации, направленной на уменьшение размеров результирующего HTML/CSS/JS-кода без изменения внешнего вида и функционала веб-страницы.
Минификация HTML-кода
Результатом работы большинства веб-приложений является HTML-документ. Чем короче код, тем быстрее его загрузит клиент. Рассмотрим несколько приемов, позволяющих минимизировать размер результирующего кода.
Верстка страниц без использования таблиц (Tableless)
Мощное средство минификации HTML-кода – верстка страниц на базе DIV-тегов и таблиц стилей (CSS). В отличие от табличной верстки данная методология имеет множество преимуществ, одно из которых – существенное сокращение размера HTML-кода.
Табличная верстка без использования стилей:
<html>
<head>
<title>Пример: табличная вёрстка vs DIV-ная вёрстка</title>
</head>
<body>
<table width="720px" border="0" align="center" cellpadding="0" cellspacing="0" style="margin-top:30px">
<tr>
<td colspan="3" align="center">Шапка сайта, логотип</td>
</tr>
<tr valign="top">
<td width="200px">Левая колонка</td>
<td width="320px">Центральная колонка</td>
<td width="200px">Правая колонка</td>
</tr>
</table>
</body>
</html>
Размер кода: 482 байта.
Верстка на базе DIV-тегов с использованием стилей:
<html>
<head>
<title>Пример: табличная вёрстка vs DIV-ная вёрстка</title>
<link rel="stylesheet" type="text/css" href="/main.css" />
</head>
<body>
<div id="head">Шапка сайта, логотип</div>
<div id="content">
<div id="cLeft">Левая колонка</div>
<div id="cCenter">Центральная колонка</div>
<div id="cRight">Правая колонка</div>
</div>
</body>
</html>
Размер кода: 384 байта.
Если сравнить размер среднестатистической HTML-страницы, сверстанной с помощью таблиц, и аналогичной страницы, сверстанной на базе DIV-элементов и каскадных таблиц стилей, можно заметить, что размер первого варианта не сильно отличается от второго, если ко второму добавить размер таблицы стилей. Однако таблица стилей в отличие от содержания страницы статична и кэшируется браузером, поэтому загружается единственный раз, при первом обращении к сайту. Таким образом, при каждой последующей загрузке страницы сайта экономия трафика (и времени) будет сопоставима с размером таблицы стилей.
Вынос JavaScript и CSS в отдельные файлы
Большинство современных веб-приложений для построения интерактивного интерфейса использует множество таблиц стилей и JavaScript. Существует два способа подключить JS/CSS-код к HTML-документу:
1. Включение JS/CSS-кода в тело HTML-документа.
«За»:
- не требует отдельного HTTP-запроса для загрузки JS/CSS-кода;
- возможность динамической генерации JS/CSS-кода.
«Против»:
- размещение JS/CSS-кода в теле документа увеличивает размер HTML-кода;
- не используется механизм кэширования, поэтому каждый раз при запросе документа пользователь будет повторно загружать JS/CSS-код.
2. Подключение внешних файлов, содержащих JS/CSS-код.
«За»:
- сокращение размера HTML-документа;
- при повторном обращении к документу браузер использует кэш, что позволяет избежать повторной загрузки JS/CSS-кода.
«Против»:
- осложняет динамическую генерацию JS/CSS-кода;
- требуется HTTP-запрос для загрузки каждого JS/CSS-файла.
Оба варианта обладают своими достоинствами и недостатками. Включение JS/CSS-кода в тело документа имеет смысл, если код генерируется динамически. В остальных случаях вынос кода во внешние файлы положительно сказывается на скорости загрузки веб-документа за счет использования кэша браузера и приемов минимизации HTTP-запросов.
Удаление символов, не влияющих на отображение документа
HTML-код содержит множество символов, не влияющих на отображение документа в браузере пользователя:
- символы перевода строки (\r и \n), табуляции (\t);
- несколько идущих подряд пробелов, пробелы между атрибутами тегов;
- необязательные кавычки вокруг значения атрибута тега;
- HTML-комментарии <!-- -->.
Наличие табуляций, переносов строки и комментариев делает код удобочитаемым для человека, но не влияет на отображение страницы в браузере и индексацию сайта поисковыми системами.
Удаление символов, не влияющих на отображение документа, позволяет сократить размер HTML кода на 3-20%. Данным приемом активно пользуются такие титаны, как Google.com и Yandex.ru (убедиться в этом можно посмотрев HTML-код главной страницы поисковиков).
Минификация HTML-кода с помощью PHP
Рассмотрим пример простейшего минификтора HTML-кода, построенного на базе PHP. Принцип работы заключается в удалении из HTML-кода символов \n \r \t и незначащих пробелов.
<?php
function htmlCompressor($html) {
return preg_replace('/(?:\n\s*|\s\s+)/i', '', $html);
}
ob_start("htmlCompressor");
?>
<html>
<head>
<title>Example: simple html compressor</title>
</head>
<body>
<h1>Hello World!!!</h1>
HTML document here
</body>
</html>
<?
ob_end_flush();
?>
Результатом исполнения данного скрипта будет HTML-код, имеющий вид:
<html><head><title>Example: simple html compressor</title></head><body><h1>Hello World!!!</h1>HTML document here</body></html>
В примере объявлена функция htmlCompressor, отвечающая за сжатие HTML-кода, переданного в аргументе. Используем ob_start() для буферизации результата работы приложения. Перед отправкой пользователю результатов работы приложения минимизируем с помощью функции htmlCompressor() хранящийся в буфере HTML-код.
Протестируем работоспособность метода, применив компрессор к HTML-коду различных сайтов (см. таблицу 1).
Таблица 1. Результаты применения компрессора к HTML-коду различных сайтов
Из результатов теста видно, что степень сжатия зависит от стиля кодирования. В среднем использование простейшего HTML-минификатора позволяет сократить размер документа на 9,4%. Остается множество резервов для увеличения степени сжатия.
- Удаление комментариев из HTML-кода.
- Удаление необязательных пробелов внутри тега (например: <div title="hello world"class="menu"id="mainMenu">).
Используя оставшиеся резервы, можно увеличить среднюю степень сжатия примерно до 19% (этот параметр зависит от стиля кодирования документа). Некоторые алгоритмы минификации HTML удаляют необязательные кавычки вокруг значения атрибута тега, например, так:
<div title="hello world" class=menu id=mainMenu>
Данная методика допустима для HTML-документа, так как не противоречит стандарту, но крайне нежелательна для XHTML. С точки зрения стандарта XML, кавычки вокруг значения атрибута тега обязательны, поэтому удаление их чревато не только появлением ошибок при проверке документа с помощью W3C-валидатора (http://validator.w3c.org), но и возникновением потенциальных проблем при использовании DHTML (нарушение структуры документа).
Корректное удаление комментариев из HTML-документа не имеет побочных эффектов, однако для корректного выполнения требуется учитывать ряд специфических случаев, когда удалять строку, которая похожа на комментарий, нельзя.
Пример:
<!--[if lte IE 6]><style type="text/css" media="all">@import url(http://www.domain.tld/css/ie6.css);</style><![endif]-->
<script language="JavaScript">
<!--
alert('<!--');
alert('Меня удалять нельзя!');
alert('//-->');
//-->
</script>
Можно разработать алгоритм, позволяющий корректно обрабатывать все возможные случаи, однако он будет достаточно сложен и потребует от сервера дополнительных ресурсов (процессорное время и память). Вызов алгоритма минификации HTML осуществляется при каждом запросе страницы, потому при разработке алгоритма необходимо найти баланс между степенью сжатия и нагрузкой на сервер.
Минификация скриптов и таблиц стилей
Большинство разработчиков не уделяют внимания оптимизации внешних JS/CSS-файлов, так как эти файлы загружаются один раз и при повторном запросе подключаются из кэша браузера.
Однако высокая скорость первой загрузки сайта дает существенное преимущество, потому что многие пользователи, просматривая результаты выдачи поисковых систем, открывают найденные страницы во множестве вкладок (окон) браузера и знакомятся с информацией по мере загрузки сайтов. Поэтому если сайт загружается быстрее, чем сайт конкурентов, то вероятность того, что вы получите нового клиента, существенно повышается. Минификация JS/CSS позволяет существенно увеличить скорость первой загрузки сайта.
Минифицированный код абсолютно нечитаемый, поэтому разумно иметь две версии:
- Версия для разработчиков. Код содержит все необходимые комментарии и визуальную разметку, что обеспечивает читабельность. Используется в момент разработки приложения.
- Версия для публикации. Код имеет минимальную длину, что обеспечивает высокую скорость загрузки.
Рассмотрим несколько популярных утилит, позволяющих осуществить автоматический перевод JS/CSS-кода из версии для разработчиков в версию для публикации.
JavaScript
В большинстве своём минификаторы для уменьшения размера JavaScript кода делают две операции:
- удаляют символы, не влияющие на исполнение кода;
- заменяют длинные названия локальных переменных на более короткие.
Протестируем работу наиболее известных JS-минификаторов на примере большого JavaScript-файла (URLForward.js, размер 61773 байт), взятого из реального проекта (см. таблицу 2).
Таблица 2. Результат работы наиболее известных JS-минификаторов
Таблица 3. Результат работы JS-минификаторов плюс gzip-компрессия
Таким образом, лучший результат продемонстрировал Packer + gzip. Из личного опыта (использую данное решение более года) могу сказать, что данная связка работает достаточно стабильно и никаких побочных эффектов не обнаружено.
Таблицы стилей (CSS)
Таблица стилей – важная часть HTML-документа, отвечающая за визуализацию страниц. Существует множество способов минимизировать размер CSS-кода без изменения внешнего вида документа.
Рассмотрим некоторые способы на примере следующего CSS-кода:
/* Описание стиля */
.c1 {
color:#FFFFFF;
font-weight: bold;
}
.c2 {
font-weight: lighter;
color: rgb(255,255,255);
background-color:blue;
border:none;
margin-top: 18px;
margin-right: 18px;
margin-bottom: 18px;
margin-left: 18px;
}
.c3 {
font-weight: bold;
color: #FFFFFF;
}
Минимизируем приведенный выше CSS-код с помощью следующих преобразований:
- удалим комментарии;
- удалим незначащие пробелы и символы перевода строки, табуляции;
- удалим точки с запятой перед закрывающей скобкой описания класса;
- объединим классы, имеющие одинаковое описание (c1 и c3);
- преобразуем код цвета в шестнадцатеричный формат (из rgb(255,255,255) в #FFFFFF);
- используем краткую запись шестнадцатеричного кода цвета, заменим #FFFFFF на #FFF;
- сократим запись атрибутов класса:
- заменим «margin-top:18px;margin-top:18px;margin-top:18px;margin-top:18px» на «margin:18px;»;
- заменим «border:none;» на «border:0;»;
- заменим «font-weight:bold;» на «font-weight:700;»;
- заменим «background-color:blue;» на “background:blue;».
В результате преобразований получим код:
.c1,.c3{color:#FFF;font-weight:700}.c2{font-weight:lighter;color:#FFF;background:blue;border:0;margin:18px}
Оба фрагмента кода работают одинаково, но первый код занимает 312 байт, а второй 109 байта (на 65,06% меньше).
Для удобства перевода CSS-кода из версии для разработчика в версию для конечного пользователя существует множество утилит, выполненных в виде прикладных программ и online-сервисов. Сравним несколько популярных online-сервисов для компрессии CSS-кода. В качестве тестового примера возьмем CSS-код, приведенный в начале этого раздела (см. таблицу 4).
Таблица 4. Сравнение нескольких online-сервисов для компрессии CSS-кода
Из результатов теста видно, что не все компрессоры работают одинаково эффективно. Лучше остальных с задачей минификации тестового CSS-кода справились Robson CSS Compressor и Online CSS Optimizer. Эти сервисы, в отличие от остальных, сумели объединить описание CSS-классов с1 и с2, за счет чего удалось увеличить процент сжатия.
Утилиты оптимизации CSS, как и любое другое программное обеспечение, не свободны от ошибок. Убедитесь в том, что выбранный компрессор корректно обрабатывает вашу таблицу стилей.
Компрессия HTTP-трафика
Наиболее ощутимый эффект по ускорению загрузки сайта дает компрессия трафика. Это принципиально иной способ уменьшения результирующего кода, идея которого заключается в компрессии данных перед отправкой пользователю с помощью алгоритмов сжатия, известных браузеру пользователя (чтобы браузер мог получить исходный документ). Данный способ крайне эффективен и позволяет сократить размер передаваемых данных в среднем на 70%.
Компрессия трафика и минификация HTML/CSS/JS-кода не взаимоисключающие техники, скорее наоборот, использованные совместно, позволяют добиться наилучших результатов.
Подавляющее большинство браузеров умеет работать со сжатым трафиком, но не все, кроме того, некоторые поисковые роботы не поддерживают компрессию. Иногда браузер поддерживает компрессию, но в запросе к серверу не поступает заголовок Accept-Encoding, такое бывает, потому что некоторые Proxy/Firewall вырезают данное поле из запроса пользователя. Поэтому, прежде чем сжимать данные, нужно убедиться, что клиент сможет их распаковать.
Другими словами, компрессия трафика возможна только при условии, что от пользователя получен список поддерживаемых алгоритмов компрессии и минимум один из них известен серверу. В соответствии с протоколом HTTP/1.1 названия алгоритмов перечислены через запятую в поле Accept-Encoding заголовка HTTP-запроса.
GET /index.html HTTP/1.1
Host: www.domain.tld
Accept-Encoding: gzip, deflate
User-Agent: SomeBrowser/1.0
На основании этого заголовка сервер определяет, какой метод сжатия можно использовать. Если заголовок Accept-Encoding отсутствует или перечисленные алгоритмы неизвестны серверу, то компрессия не должна применяться.
Для правильной интерпретации данных браузер должен знать, какой метод компрессии использовал сервер. Для этого сервер обязан добавить в заголовок ответа поле Content-Encoding, содержащее название использованного алгоритма сжатия.
HTTP/1.1 200 OK
Server: Apache
Content-Type: text/html
Content-Encoding: gzip
Content-Length: 36439
[Данные сжатые cпомощью gzip]
Компрессия полезна для несжатых данных, в основном для HTML/CSS/JS-кода. Форматы изображений, аудио/видеопотоки, PDF-документы, как правило, уже содержат данные в сжатом виде, поэтому повторная компрессия малоэффективна и приводит к необоснованной трате процессорного времени.
Алгоритмы компрессии данных Deflate и Gzip
В мире существует множество алгоритмов, пригодных для компрессии HTTP-трафика. Для того чтобы алгоритм мог быть использован для компрессии трафика, необходимо выполнение следующих условий:
- компрессия выполнялась без потери данных;
- алгоритм известен браузеру и серверу.
Наибольшей популярностью пользуются алгоритмы:
- Deflate (http://tools.ietf.org/html/rfc1951). Алгоритм сжатия данных без потерь, использует комбинацию алгоритмов LZ77 и Хаффмана. Deflate свободен от патентов. Успешно применятся во многих утилитах и форматах данных (например, графические файлы PNG).
- Gzip (http://tools.ietf.org/html/rfc1952). Формат данных, построенный на базе алгоритма сжатия Deflate.
Другими словами, gzip – это deflate + некоторые заголовки, специфичные для формата gzip. Исходя из этого, можно предположить, что для обоих алгоритмов уровень компрессии и скорость работы примерно одинаковы. В Интернете есть статьи, в которых говорится о том, что алгоритм deflate существенно быстрее (вплоть до 40%), чем gzip. Я провел собственное тестирование обоих алгоритмов, в ходе которого значительных различий по скорости работы или уровню сжатия обнаружить не удалось.
Компрессия данных с помощью веб-сервера
Большинство веб-серверов умеют выполнять компрессию данных перед отправкой. Например, в Apache для этого существуют модули, позволяющие выполнять компрессию на лету. Наиболее известные из них:
Подробную информацию об установке и настройке модулей моно найти на сайтах проектов. Оба модуля выполняют одну и ту же работу с минимальными отличиями, однако mod_deflate начиная со второй версии сервера Apache вошел в официальный дистрибутив и призван заменить mod_gzip. Подробную информацию об установке и настройке модулей можно найти на официальных сайтах проектов.
Компрессия в PHP-приложениях
Для того чтобы функции компрессии были доступны в PHP, необходимо установить расширение zlib. В большинстве случаев данное расширение установлено.
Во всех рассмотренных ниже примерах компрессия осуществляется с учетом полученного от клиента заголовка Accept-Encoding. Если заголовок в запросе отсутствует, то компрессия не применяется, иначе используется один из поддерживаемых клиентом алгоритмов компрессии (обычно gzip, реже deflate). Название использованного алгоритма будет передано клиенту в поле Content-Encoding HTTP-заголовка.
1. Компрессия с помощью ob_gzhandler(). Компрессия трафика осуществляется внутри PHP-приложения следующим образом.
<?php
ob_start("ob_gzhandler");
?>
Hello world!
<?
ob_end_flush();
?>
С помощью оператора ob_start() включается режим буферизации вывода, то есть данные, выводимые приложением во время работы, отправляются не прямиком в браузер пользователя, а в специальный буфер. В качестве обработчика буфера указана функция ob_gzhandler, которая отвечает за компрессию данных перед отправкой пользователю.
2. Прозрачное сжатие с использованием библиотеки zlib. Суть метода заключается в том, что всю работу по компрессии трафика выполняет интерпретатор PHP. Данный подход позволяет включить режим компрессии данных без внесения изменений в код приложения.
Для активации автоматической компрессии необходимо прописать в конфигурационном файле php.ini следующие директивы:
zlib.output_compression = On
Если не используется режим буферизации, то данные в браузер передаются порциями по 4 Кб, упакованные с помощью gzip. Изменить размер порции можно с помощью zlib.output_compression, указав вместо On нужный размер в байтах.
zlib.output_compression = 1048576
Передача файла, разбитого на порции, негативно влияет на общий уровень компрессии и повышает нагрузку на процессор. Желательно передавать сжатый файл целиком. Для этого необходимо включить буферизацию:
output_buffering = On
zlib.output_compression = On
С помощью директивы zlib.output_compression_level можно установить необходимый уровень компрессии. Параметр может принимать значения от -1 до 9, где -1 означает, что сервер сам выбирает уровень компрессии, 0 – компрессия отсутствует.
output_buffering = On
zlib.output_compression = On
zlib.output_compression_level = 5
Если нет доступа к php.ini, то аналогичного эффекта можно добиться, прописав в файле .htaccess следующие команды:
php_flag output_buffering On
php_flag zlib.output_compression On
php_flag zlib.output_compression_level 5
Результат работы обоих методов одинаков, однако второй более предпочтителен, так как не требует модификации приложения для включения режима компрессии данных. Использование одновременно обоих методов невозможно (не имеет смысла) и приводит к появлению сообщения вида:
Warning: ob_start() [ref.outcontrol]: output handler 'ob_gzhandler'
conflicts with 'zlib output compression' in /home/www/test.php on line 8
|
Заключение
Существует множество приемов, позволяющих применять компрессию не только к HTML-документам, но и к CSS/JS-коду. Однако в отличие от HTML-кода CSS/JS в большей мере статичные данные, которые должны кэшироваться браузером, поэтому компрессия CSS/JS важна только для первой загрузки сайта.
- Общее описание понятия «минификация» – http://en.wikipedia.org/wiki/Minification_(programming).
- Технология Tableless – верстка HTML-страниц без таблиц – http://en.wikipedia.org/wiki/Tableless_web_design.
- Советы по построению высокопроизводительных веб-приложений – http://developer.yahoo.com/performance/rules.html.
- Обсуждение примера реализации HTML-минификатора на базе PHP – http://forum.dklab.ru/viewtopic.php?t=26182.
- Тестирование инструментов для сжатия CSS – http://webo.in/articles/habrahabr/14-minifing-css.
- Тестирование инструментов для сжатия JS – http://webo.in/articles/habrahabr/11-minifing-javascript.
- Расширение Zlib для PHP – http://ru.php.net/manual/en/book.zlib.php.
- Модуль deflate для Apache – http://httpd.apache.org/docs/2.0/mod/mod_deflate.html.
- Модуль gzip для Apache – http://sourceforge.net/projects/mod-gzip.