ShvetsGroup archive

Оптимизация JavaScript и CSS-файлов в Drupal

· Все статьи

Read in English

Ускорение сайта

С чего начинается сайт?

Как браузер рендерит страницу?

До момента полной загрузки JS из HEAD пользователь видит "белую страницу". Когда все внешние скрипты загружены, начинается выполение JS в порядке следования на странице (сверху вниз), а пользователь уже начинает видеть контент страницы, и по мере загрузки CSS и картинок, страница отрисовывается полностью.

Таким образом, чтобы увеличить скорость загрузки страницы нужно:

Оптимизация графики и создание спрайтов - работа для дизайнера, а мы займемся оптимизацией JavaScript и CSS-файлов в Drupal.

Давайте посмотрим как обстоят дела с этими файлами в Drupal.

Анализ ситуации

JavaScript

В вашем проекте количество и суммарный размер будет другой.
Pазмер ваших JS-файлов вы можете сами проверить командой:

find . -name '*.js' -exec ls -l {} \; | awk '{s+=$5} END {print s}'

В нашем проекте, кроме файлов ядра, есть ещё более 1300(!) JS-файлов, которые находятся в дополнительных модуляx и темах.
Общий размер всех JS-файлов - 14'746'899 байт.
В ядре Drupal 6 я нашел следующие Javascript-файлы:

  1. modules/comment/comment.js
  2. modules/profile/profile.js
  3. modules/openid/openid.js
  4. modules/taxonomy/taxonomy.js
  5. modules/system/system.js
  6. modules/block/block.js
  7. modules/color/color.js
  8. modules/user/user.js
  9. misc/autocomplete.js
  10. misc/drupal.js
  11. misc/collapse.js
  12. misc/batch.js
  13. misc/farbtastic/farbtastic.js
  14. misc/form.js
  15. misc/tableselect.js
  16. misc/ahah.js
  17. misc/tabledrag.js
  18. misc/textarea.js
  19. misc/progress.js
  20. misc/tableheader.js
  21. misc/teaser.js
  22. misc/jquery.form.js
  23. misc/jquery.js

Хорошо, что они не все загрузаются на одной странице, а некоторые вообще не используются. На совести разработчика модуля правильно настроить условия подключения JS и CSS-файлов на странице, чтобы бесполезный код не уменьшал скорость загрузки страницы.

CSS-файлы

В вашем проекте количество и суммарный размер будет другой.
Команда для самостоятельной проверки размера CSS файлов:
find . -name '*.css' -exec ls -l {} \; | awk '{s+=$5} END {print s}'

В нашем проекте, кроме файлов ядра, есть ещё около 450-ти в дополнительных модулях и темах.
Общий размер всех CSS-файлов - 1'674'793 байт.
Что касается CSS-файлов в ядре Drupal 6, то вот они:

  1. modules/locale/locale.css
  2. modules/aggregator/aggregator-rtl.css
  3. modules/aggregator/aggregator.css
  4. modules/update/update.css
  5. modules/update/update-rtl.css
  6. modules/poll/poll.css
  7. modules/poll/poll-rtl.css
  8. modules/comment/comment-rtl.css
  9. modules/comment/comment.css
  10. modules/tracker/tracker.css
  11. modules/forum/forum-rtl.css
  12. modules/forum/forum.css
  13. modules/book/book.css
  14. modules/book/book-rtl.css
  15. modules/profile/profile.css
  16. modules/search/search-rtl.css
  17. modules/search/search.css
  18. modules/openid/openid.css
  19. modules/node/node-rtl.css
  20. modules/node/node.css
  21. modules/taxonomy/taxonomy.css
  22. modules/system/system-menus-rtl.css
  23. modules/system/admin-rtl.css
  24. modules/system/admin.css
  25. modules/system/maintenance.css
  26. modules/system/defaults-rtl.css
  27. modules/system/defaults.css
  28. modules/system/system-rtl.css
  29. modules/system/system-menus.css
  30. modules/system/system.css
  31. modules/block/block.css
  32. modules/color/color.css
  33. modules/color/color-rtl.css
  34. modules/help/help.css
  35. modules/help/help-rtl.css
  36. modules/dblog/dblog.css
  37. modules/dblog/dblog-rtl.css
  38. modules/user/user.css
  39. modules/user/user-rtl.css
  40. misc/print-rtl.css
  41. misc/farbtastic/farbtastic.css
  42. misc/print.css
  43. themes/bluemarine/style.css
  44. themes/bluemarine/style-rtl.css
  45. themes/garland/print.css
  46. themes/garland/style.css
  47. themes/garland/minnelli/minnelli.css
  48. themes/garland/color/preview.css
  49. themes/garland/style-rtl.css
  50. themes/garland/fix-ie.css
  51. themes/garland/fix-ie-rtl.css
  52. themes/pushbutton/style.css
  53. themes/pushbutton/style-rtl.css
  54. themes/chameleon/common-rtl.css
  55. themes/chameleon/style.css
  56. themes/chameleon/marvin/style.css
  57. themes/chameleon/marvin/style-rtl.css
  58. themes/chameleon/common.css
  59. themes/chameleon/style-rtl.css

Суммарный размер CSS-файлов на порядок меньше, чем размер JS-файлов. Но нужно учесть, что CSS-файлов на странице намного больше, чем JS-файлов - примерно в 2 раза больше. Кроме того, стили, как правило, загружаются для всех страниц (это стили темы) и только стили модулей могут загружаться для определённых страниц. Таким образом и файлы стилей, и скрипты нуждаются в нашем внимании в одинаковой степени.

Ограниченный Internet Explorer

Браузер IE 6-8 имеет ограничение на количество и размер CSS-файлов:

Ограничение протокола HTTP

Интересно есть ли у браузеров ограничение на количество АJAX-соединений?

Согласно спецификации HTTP 1.1 браузер должен устанавливать не более 2 одновременных соединений (и это справедливао для IE6/7) к одному хосту. В Firefox и Opera этот параметр настраиваемый и составляет не меньше 4 по умолчанию. По некоторым данным в IE8 - 6 соединений с одним хостом.

Источник информации: Raising network.http.max-persistent-connections-per-server?

Итог анализа

Разберемся с терминологией.

Виды оптимизации

Минимизация скрипта — это удаление из кода всех несущественных символов с целью уменьшения объема файла скрипта и ускорения его загрузки. В минимизированном коде удаляются все комментарии и незначащие пробелы, переносы строк, символы табуляции. В случае с JavaScript, это уменьшает время загрузки страницы, так как размер файла уменьшается. Две самых популярных утилиты для минимизации JavaScript — JSMin и YUI Compressor.

obfuscated_javascript_code_0.png

Обфускация является альтернативным способом сокращения исходного кода. Также, как минимизация, она удаляет пробельные символы и вырезает комментарии, но в дополнение она измененяет сам код. К примеру, во время обфускации имена функций и переменных заменяются на более короткие, что делает код более компактным, но менее читабельным. Обычно этот прием используется для усложнения реверс-инжиниринга программы. Но обфускация помогает также уменьшить код настолько, насколько это не получится сделать одной минимизацией. С выбором средства для обфускации JavaScript не все так ясно, но я думаю, что самая распространенная утилита для этого — Dojo Compressor (ShrinkSafe).

Минимизация JavaScript — безопасный и довольно простой процесс. С другой стороны, обфускация из-за своей сложности может вносить в код ошибки. Обфускация также требует правки вашего кода для выделения в нем API-функций и других элементов, которые не должны быть изменены.

Аггрегация - это объединение нескольких файлов на странице в один. Аггрегация безопасна также как и минимизация, потому что код не меняется. Иногда "склейка" файлов может давать сбой из-за некорректных комментариев в конце файлов. Аггрегация позволяет уменьшить количество файлов подключаемых на странице. Начиная с Drupal 6 аггрегация CSS и JS-файлов встроена в ядро, а до этой версии нужно было установить дополнительный модуль.

HTTP-cжатие - это способ сжать контент (в основном текстовый), который передается с веб-серверов через интернет в браузеры. Основное преимущество сжатия в том, что уменьшается количество байт, которые передаются и таким образом достигается большая производительность. HTTP-сжатие использует общедоступные алгоритмы сжатия, чтобы кодировать HTML, XML, JavaScript, CSS и другие форматы файлов на стороне сервера. Этот метод доставки сжатого контента основан на стандартах и включен в HTTP/1.1 и все современные браузеры поддерживают протокол HTTP/1.1. Таким образом они могут распаковывать сжатые файлы автоматически на стороне клиента. Это означает, что никакого дополнительного ПО или участия пользователя на стороне клиента не нужно.

Запрос на сервер без использования сжатия

HTTP_request.png

Запрос на сервер и сервер отдает сжатый контент

HTTP_request_compressed.png

Для каскадных таблиц стилей можно применять только минимизацию, аггрегацию и HTTP-сжатие. Обфускация для CSS-файлов не используется. А вот для JavaScript-файлов любой из методов подходит.

Обзор существующих Javascript-компрессоров.

Сравнить степень сжатия одного и того же файла можно с помощью онлайн-сервиса: JavaScript Compressor and Comparison Utility.

Степень сжатия у всех компрессоров разная, потому как и методы сжатия тоже разные. Сжатие маленьких файлов, как правило, дает очень незначительное сжатие, а файлы больше 10Кбайт сжимаются очень хорошо. На диаграмме ниже показана зависимость степени сжатия от размера файла:

Подробности тестирования степени сжания разными компрессорами -- в статье JavaScript: жать или не жать?. Использована диаграмма из этой статьи.

js_compression_levels_chart.png

Варианты решения проблемы

Итак, у нас есть достаточно большое количество файлов, которые можно сжать и нам нужно разобраться с тем, что Drupal уже может нам предложить (в ядре и в дополнительных модулях) и найти наиболее оптимальное решение.

Начнем с анализа того, что мы уже имеем "из коробки".

Стандартные возможности Drupal

В ядре Drupal 6 реализованы аггрегация JavaScript и СSS-файлов и HTTP-сжатие.
На странице "Performance" ("Производительность" - admin/settings/performance) есть возможность включить/выключить оптимизацию CSS и JavaScript файлов:

Если оптимизация CSS и JS-файлов заблокирована - это значит, что кеш аггрегированных файлов не удается записать в файлы. Проверьте верно ли указан путь к папке files и права записи для этой папки на странице File System (Файловая система - admin/settings/file-system).

Оптимизация CSS и JS-файлов на странице настройки Производительности.

Как работает оптимизация JS и CSS-файлов в ядре Drupal?

Чтобы начать пользоваться этой возможностью - нужно включить оптимизацию JavaScript или CSS-файлов на странице настройки производительности.
Тогда во время темизации страницы из функции template_preprocess_page() будет вызвана фукнция drupal_get_css(). Именно эта функция и делает основную работу для CSS-файлов.

Как работает функция drupal_get_css()?

Что в себя включает оптимизация?

Итак, оптимизация, которую нам предлагает ядро Drupal является на самом деле минимизацией с аггрегацией в один файл.

Недостатки метода оптимизации в ядре Drupal

Метод оптимизации, который используется в ядре Drupal безопасен, то есть он не приводит к ошибкам в коде. Но метод не настолько эффективен, как кажется.

Дело в том, что на странице может быть десятка 2 разных скриптов, которые собираются в уникальный файл, который для данной страницы кешируется. Скрипты на странице могут подгружаться в зависимости от прав доступа пользователя или ещё каких-либо условий, что увеличивает количество вариантов для одной-единственной страницы.

Общие скрипты в аггегированных файлах
Таким образом мы создаем большой файл, который аггрегирует скрипты и браузер его кеширует для данной страницы. Для следующей страницы может добавиться или удалиться несколько скриптов и будет создан новый уникальный файл, который аггрегирует скрипты для данный страницы. Браузер закеширует и его, но в этих 2х больших файлах очень много общего кода, который дваждый передается и кешируется браузером отдельно в файлах с разными именами. Таким образом при эффективном кешировании браузера (если оно включено) этот метод может давать даже увеличение трафика.

Оптимизация JavaScript в ядре Drupal

Оптимизация JavaScript делается подобно оптимизации CSS-файлов.

Именно такой способ склейки может породить ошибки в аггрегированном файле, когда в конце скрипта последней строкой был комментарий.

При оптимизации JavaScript-файлов они объединяются в один большой файл несколько иначе - после каждого файла добавляется ";\n".
Сам код JS-файла никак не изменяется - это можно увидеть в коде функции drupal_build_js_cache(), а просто объединяется в один большой файл.

То есть ядро Drupal не делает даже минимизации JavaScript!

Использование HTTP-сжатия

На странице настройки производительности ("Perfomance") можно включать и выключать HTTP-сжатие:
Gzip-cжатие HTTP-трафика на станице настройки Производительности.

Фукнция page_set_cache() сохраняет сжатый контент страницы в кеше, если:

Drupal кеширует контент страницы, а при выполнении условий, указанных выше, сжимает и затем кеширует контент страницы..

Если браузер поддерживает HTTP-сжатие, то в запросе страницы он указывает с какими видами сжатия он умеет работать:

Accept-Encoding: gzip,deflate

A simple online web page compression / deflate / gzip test tool - сервис, который проверяет отдает ли сервер файл с использованием HTTP-сжатия и степень сжатия.

Если сервер использует HTTP-сжатие, то он будет сам сжимать и отдавать браузеру сжатую версию контента.
Но Drupal может и сам при наличии библиотеки zlib сжимать и кешировать уже сжатую версию HTML-страницы, но не JS и не CSS-файлы.
Это значит, что если веб-сервер настроен сжимать контент и браузер поддерживает такое сжатие, то сервер будет "на лету" сжимать JS и CSS-файлы. Но намного эффективнее было бы хранить сжатые копии этих файлов с максимально большой степенью сжатия, а не сжимать их "на лету" (в этом случае по умолчанию используется не максимальная степень сжатия).

Дополнительные модули

Модуль JavaScript Aggregator

Модуль делает минимизацию JS-файлов, используя JSMin
Модуль может сжимать аггрегированные и минимизированные JS-файлы, используя gzip-сжатие. Сжатая копия хранится в файле на сервере и отдается браузеру вместо того, чтобы веб-сервер сжимал этот файл самостоятельно.

Пример сжатия:

FILENAME                                       SIZE              DESCRIPTION
6e13ccb6262e06b9f890414db56d3b1f.js            289.558 Bytes  >  Aggregated by Drupal Core
6e13ccb6262e06b9f890414db56d3b1f.jsmin.js      173.243 Bytes  >  Minified with JSMin
6e13ccb6262e06b9f890414db56d3b1f.jsmin.js.gz    47.618 Bytes  >  Minified and gzipped

Модуль CSS Gzip

css_gzip_0.png

Модуль Parallel

Parallel1.png

Модуль IE CSS Optimizer

ie-css-optimizer-screenshot.png

Модуль IE Unlimited CSS Loader

"don’t use @import" -- статья о том, почему использование @import не желательно с точки зрения производительности.

Модуль делает примерно тоже самое, что и IE CSS Optimizer, но немного иначе - использует @import, чтобы обойти ограниченность браузера IE в 31 СSS-файл.

Модуль CSSTidy

Что оптимизируется:

CSS-хаки, которые работают:

Тестирование скорости загрузки страницы

Был проведен замер времени загрузки страницы, чтобы выяснить насколько изменится среднее время загрузки при разных методах оптимизации JS-файлов. Тестовый сайт имеет более 1300 JS-файлов и почти 450-CSS-файлов. Установлено 227 модулей (включая модули ядра). Были исследованы разные методы оптимизации:

Условия проведения теста

Настройки браузера

Настройки сервера

Настройки в Drupal

Результаты тестирования

Среднее время загрузки страницы

Результат тестирования скорости разных режимов оптимизации

Диаграмма времени загрузки страницы в разных режимах

YSlow_tests_diagram.png

Анализ результатов тестирования

Таким образом экспериментальным путем было установлено, что оптимизация ядром Drupal JS-файлов не всегда показывает хороший результат. В нашем случае (без применения модуля JavaScript Aggregator) мы получили незначительное увеличение скорости загрузки (за счет сокращения количества запросов) и увеличение (по сравнению с исходными файлами) размера аггрегированного (и сжатого) файла.

Учитывая то, на многих страницах загружаются одни и те же JS и СSS-файлы, а добавляются/удаляются только несколько специфичных, то кеширование одного большого файла не даст прироста производительности. При этом кеширование браузером отдельных файлов стилей и скриптов на одной странице даст экономию на загрузке этих файлов на последующих страницах.
Пользователь, как правило, не загружает одну и ту же страницу несколько раз, а переходит дальше и дальше ("серфит") и таким образом кеширование браузером небольших файлов скриптов и стилей может быть эффективнее аггрегации в один большой файл. Но это только для пользователя, а для сервера выгоднее иметь как можно меньше запросов и отдавать аггрегированные файлы, вместо кучи небольших файлов.

Что можно сделать, чтобы улучшить ситуацию?

Перенос JavaScript в footer

В самом начале статьи мы выяснили, что браузер загружает весь CSS и весь JS из HEAD и только потом показывает страницу пользователю и начинает выполнение JS. Именно поэтому есть смысл переместить JS-файлы в footer, чтобы приблизить момент появления контента страницы в браузере. Это уменьшит время, когда пользователь видит "белую страницу" и визуально ускорит загрузку страницы (фактически страница будет загружаться столько же времени).

JS-файлы в Drupal подключаются следующим образом:

drupal_add_js($data = NULL, $type = 'module', <strong>$scope = 'header'</strong>, $defer = FALSE, $cache = TRUE, $preprocess = TRUE)

здесь
$scope (optional) The location in which you want to place the script. Possible values are 'header' and 'footer' by default. If your theme implements different locations, however, you can also use these.

По умолчанию $scope равно "header" и, если не указано иное, то все скрипты загружаются в HEAD...
Таким образом нужно во всех модулях поменять $scope = 'header' на $scope = 'footer'.

Простое перемещение <?php print $scripts ?> в footer результата не даст, потому что в HEAD страницы останутся inline-скрипты, которые ожидают наличия jquery.js и других скриптов в HEAD и при выполнении будут давать ошибки.

Для 7й версии Drupal, было обсуждение возможности сделать значением по умолчанию для $scope - 'footer' (Make 'footer' the default scope for drupal_add_js() so that pages render fast), но скорее всего это будет уже в 8й версии Drupal.

Полезные ссылки

Компрессоры скриптов

HTTP-сжатие

Порядок загрузки элементов страницы браузером

Перенос Javascript в footer

Разное