Увеличиваем скорость загрузки web страниц

Владимир | | htaccess, JavaScript, PHP, Web разработка.

javascript box

Недавно я наткнулся на одну очень интересную тему для WordPressWP-Coda. Выглядит просто шикарно! Но дело не в этом.

Эта тема использует довольно много эффектов, реализованных с помощью JavaScript.

Мне стало интересно, во сколько эта красота обходится посетителям, и оказалось, что не так уж и дорого. Всего один js-файл размером 45кБ.

Дело в том, что автор упаковал семь исходных файлов в один и после этого сжал его упаковщиком вроде Packer JavaScript en PHP.

Но первый же эксперимент показал, что это не предел. Если упаковать этот файл в gzip архив, то его размер уменьшается до 21кБ. А большинство современных браузеров прекрасно работают с такими архивами.

При этом нет необходимости что-либо переделывать в самой теме.

Достаточно создать архив и положить его в одну папку с исходным файлом.

После этого в файл .htaccess добавляем правила:

<IfModule mod_rewrite.c>
	RewriteEngine On
	AddEncoding gzip .gz
	RewriteCond %{HTTP:Accept-encoding} gzip
	RewriteCond %{HTTP_USER_AGENT} !Safari
	RewriteCond %{REQUEST_FILENAME}.gz -f
	RewriteRule ^(.*)$ $1.gz [QSA,L]
</IfModule>

Работают эти правила так.
1) включаем RewriteEngine;
2) указываем, что файлы с расширением .gz имеют MIME тип gzip;
3) начинаем проверки:
3.1) если браузер принимает gzip;
3.2) если это не Safari (у него вроде есть проблемы с gzip);
3.3) если существует файл с таким же именем, как и у запрошенного, но с расширением .gz;
4) если все проверки прошли успешно, то добавляем к имени файла расширение .gz и отправляем этот файл браузеру.

Таким образом, если браузер не работает с gzip, то он получит исходный файл, если поддерживает – получит архив.

Посетитель разницы не заметит. Разве что немного увеличится время загрузки страницы.

В качестве примера, взгляните на скриншоты Firebug, сделанные для блога с темой WP-Coda.

Исходный вариант:

uncompressed javascript

После сжатия и установки правил в .htaccess:

compressed javascript

Кстати, заметьте, имя файла в обоих случаях одно и тоже. Изменяется только его размер. Т.е. браузер в обоих случаях считает, что получил файл, указанный в теге script (global.js), а сервер отправляет в первом случае global.js, а во втором — global.js.gz.

На мой взгляд, такой подход имеет только один недостаток. Во время разработки очень не удобно работать с одним большим js файлом, тем более сжатым 😉

Вручную упаковывать файлы тоже не интересно, поэтому нужно автоматизировать процесс.

Для этих целей я написал небольшой php скрипт.

//подключаем библиотеки
require_once 'libs/jsmin-1.1.1.php';

/**
 * Путь к папке со скриптами (отностильно этого скрипта)
 */
define('JS_DIR', "test_site/js/");

/**
 * Имя упакованного файла (будет размещен в папке со скриптами)
 */
define('JS_OUTPUT_FILE', 'global.js');

//определяем папку в которой находится скрипт
$scriptDir = substr($_SERVER['SCRIPT_FILENAME'], 0, strrpos($_SERVER['SCRIPT_FILENAME'], '/') + 1);

$resStr = "";

//открываем папку и получаем список файлов с расширением "js"
if ($dh = opendir($scriptDir.JS_DIR)) {
    while (false !== ($file = readdir($dh))) { 
        if ((substr($file, strrpos($file, '.') + 1) == "js") &&
        		($file != JS_OUTPUT_FILE)) {
        	$resStr .= "/*---------".$file."---------*/\n";
        	//сжимаем текущий файл
        	$resStr .= JSMin::minify(file_get_contents($scriptDir.JS_DIR.$file))."\n\n";
       	}
    }
    closedir($dh);
    //сохраняем сжатые данные в файл
    if ($fh = fopen($scriptDir.JS_DIR.JS_OUTPUT_FILE, 'w')) {
    	fwrite($fh, $resStr);
    	fclose($fh);
    }
    else {
    	echo "Ошибка: не могу открыть файл для записи: ".$scriptDir.JS_DIR.JS_OUTPUT_FILE;
    }
    
    echo "Скрипты упакованы файл: ".JS_DIR.JS_OUTPUT_FILE."<br />";

    //архивируем данные (используем самую высокую степень сжатия)
    $gzData = gzencode($resStr, 9);
    //создаем архив
    if ($fzip = fopen($scriptDir.JS_DIR.JS_OUTPUT_FILE.".gz", 'wb')) {
    	fwrite($fzip, $gzData);
    	fclose($fzip);
    }
    else {
    	echo "Ошибка: не могу открыть файл для записи: ".$scriptDir.JS_DIR.JS_OUTPUT_FILE.".gz";
    }
    
    echo "Создан архив: ".JS_DIR.JS_OUTPUT_FILE.".gz";
}
else {
	echo "Ошибка: не могу открыть папку со скриптами";
}

Принцип работы следующий. Вы указываете папку с js файлами (JS_DIR) и имя файла в который будут упакованы все скрипты (JS_OUTPUT_FILE).

Скрипт читает содержимое всех файлов с расширением js из папки и сжимает его.

Примечание. Для сжатия используется PHP версия библиотеки JSMin. Она просто удаляет пробелы и комментарии из исходных файлов.

После этого сжатые скрипты записываются в файл, указанный в JS_OUTPUT_FILE. Именно этот файл нужно подключать к вашим страницам.

Следующим этапом php скрипт создает архив (с помощью функции gzencode), который получает имя JS_OUTPUT_FILE.gz.

Для тестирования я взял библиотеку jQuery и написал js-файл с небольшой функцией, которая создавала пару эффектов.

Т.е. в исходном варианте было 2 файла:
jquery-1.2.3.js – 96кБ
script.js – 1кБ

Сжатый вариант:
global.js – 53,1кБ

Архивный вариант:
global.js.gz – 16кБ

Как видите, разница довольно ощутимая.

Если есть желание поэкспериментировать, качайте архив с скриптом и библиотекой JSMin.

До встречи!

  • Подход категорически правильный.
    Какой смысл при каждом http-запросе выполнять сжатие когда можно сделать это один раз.
    Кстати, этот метод и с html и css отлично работает, не только js )

    А чтоб не возиться с скриптами которые будут сравнивать время обновления, я бы рекомендовал сделать скрипт который будет генерить Makefile, и переложить работу по сравнению времен src и dst файлов на программу make. То есть просто в Makefile указать например что file.html.gz зависит от file.html и для создания file.html.gz надо запустить команду gzip -k file.html. Теперь после изменений достаточно будет просто набрать make чтоб обновить все *.gz файлы 🙂

    А оценить эффективность сжатия можно например онлайн-тулзой для оценки скорости загрузки сайтов — http://Site-Perf.com/

    • ИМХО вместо make удобнее использовать Ant или Phing (если проект на PHP)

  • Подход категорически правильный.
    Какой смысл при каждом http-запросе выполнять сжатие когда можно сделать это один раз.
    Кстати, этот метод и с html и css отлично работает, не только js )

    А чтоб не возиться с скриптами которые будут сравнивать время обновления, я бы рекомендовал сделать скрипт который будет генерить Makefile, и переложить работу по сравнению времен src и dst файлов на программу make. То есть просто в Makefile указать например что file.html.gz зависит от file.html и для создания file.html.gz надо запустить команду gzip -k file.html. Теперь после изменений достаточно будет просто набрать make чтоб обновить все *.gz файлы 🙂

    А оценить эффективность сжатия можно например онлайн-тулзой для оценки скорости загрузки сайтов — http://Site-Perf.com/

    • ИМХО вместо make удобнее использовать Ant или Phing (если проект на PHP)

  • Дима

    Способ хороший.
    Столкнулся со следующей проблемой.
    На локалке упаковал js-файлы в архив, прописал строки в .htaccess. Всё ок, радости не было предела 🙂
    Когда всё это добро вылил в инет, браузер не хотел воспринимать упакованные файлы… 🙁 При прямом запросе js-файла предлагалось сохранить его на диск либо открыть WinRar'ом.
    Проблему решил прописыванием типа в .htaccess
    AddType application/x-javascript gz
    Может пригодится кому.

    • Интересно, честно говоря не сталкивался с такой проблемой, спасибо.

  • Дима

    Способ хороший.
    Столкнулся со следующей проблемой.
    На локалке упаковал js-файлы в архив, прописал строки в .htaccess. Всё ок, радости не было предела 🙂
    Когда всё это добро вылил в инет, браузер не хотел воспринимать упакованные файлы… 🙁 При прямом запросе js-файла предлагалось сохранить его на диск либо открыть WinRar'ом.
    Проблему решил прописыванием типа в .htaccess
    AddType application/x-javascript gz
    Может пригодится кому.

    • Интересно, честно говоря не сталкивался с такой проблемой, спасибо.

  • Решение очень понравилось. Но у меня возникли сложности с реализацией — что-то не так делаю с кодировками.
    Сжатый файл читается, но оказывается в неправильной кодировке. пробовал сохранять исходник в UTF-8, Unicode, ANSI — результат один. Это происходит как на localhost, так и на хостинге.
    Как с этим бороться?

    • Возможно, дело не в кодировке, а в сжатии (неправильно распаковывается браузером).
      Какой архиватор используете? Очень советую 7-zip. С ним проблем не было (формат архива GZip).

      • Как раз его и использую. И именно этот формат.

        • Тогда давайте сделаем так. Я вам почтой отправил два скрипта. Со сжатием и без. Посмотрите как они у вас будут работать.

      • Я нашел причину. Проблема в степени сжатия. Сжимал «ультра» — не работает, «максимальный» — работает.
        Ура! Спасибо!

        • Да, браузер все-таки не архиватор… Разработчикам есть над чем поработать.

  • Решение очень понравилось. Но у меня возникли сложности с реализацией — что-то не так делаю с кодировками.
    Сжатый файл читается, но оказывается в неправильной кодировке. пробовал сохранять исходник в UTF-8, Unicode, ANSI — результат один. Это происходит как на localhost, так и на хостинге.
    Как с этим бороться?

    • Возможно, дело не в кодировке, а в сжатии (неправильно распаковывается браузером).
      Какой архиватор используете? Очень советую 7-zip. С ним проблем не было (формат архива GZip).

      • Как раз его и использую. И именно этот формат.

        • Тогда давайте сделаем так. Я вам почтой отправил два скрипта. Со сжатием и без. Посмотрите как они у вас будут работать.

      • Я нашел причину. Проблема в степени сжатия. Сжимал «ультра» — не работает, «максимальный» — работает.
        Ура! Спасибо!

        • Да, браузер все-таки не архиватор… Разработчикам есть над чем поработать.

  • zzz

    Скрипт почемуто е заработал. Установил на локальном в корне и прописал путь site/js. В браузере пытаюсь загрузить http://localhost/compress.php/ но появляется:
    Parse error: parse error, unexpected T_CONST, expecting T_OLD_FUNCTION or T_FUNCTION or T_VAR or '}' in w:homelocalhostwwwlibsjsmin-1.1.1.php on line 49
    Я вероятно я что-то не так делаю. Подскажите пожалуйста как его запустить

    • Похоже, у вас версия PHP ниже 5. Эта библиотека (jsmin) с PHP4 не работает.

  • zzz

    Скрипт почемуто е заработал. Установил на локальном в корне и прописал путь site/js. В браузере пытаюсь загрузить http://localhost/compress.php/ но появляется:
    Parse error: parse error, unexpected T_CONST, expecting T_OLD_FUNCTION or T_FUNCTION or T_VAR or '}' in w:\home\localhost\www\libs\jsmin-1.1.1.php on line 49
    Я вероятно я что-то не так делаю. Подскажите пожалуйста как его запустить

    • Похоже, у вас версия PHP ниже 5. Эта библиотека (jsmin) с PHP4 не работает.