22 декабря 2015
28 октября 2010
36
Организация, оптимизация и кеширование CSS и JS файлов
Всем привет!
В последний год я участвовал в разработке нескольких достаточно крупных (с точки зрения верстки и клиентских скриптов) проектов и сейчас я хочу поделить с вами своим опытом и программными наработками.
Этого вопроса в той или иной мере мы уже касались в некоторых наших статьях, но сегодня акцент будет не на внутренней организации файлов (кода), а на внешней (самих файлов). В конце статьи будет приведен скрипт для удобного автоматического сжатия и кеширования CSS и JS.
Исходные задачи
Где-то год назад я решил, что та система организации файлов CSS и JS, которую я использовал, становится все более и более неудобной. Тогда это было от двух до пяти файлов стилей, разделенные по типам содержимого (лейаут, дизайн). Для JS был один файл для всех функций сайта и отдельные файлы с плагинами.
Потом я понял, что когда работаешь над крупным проектом, достаточно сложно (мне по крайней мере) держать весь код или стили в 2-3 файлах. Когда количество строк переваливает за 2000-3000, найти что-то там или дописать в нужное место становится нереальным. Плюс увеличивается вес файлов, которые загружает клиент и сайт начинает работать медленнее.
Итак, задачи, которые я хотел решить:
- организовать файлы так, чтобы грузить клиенту только нужные ему в данный момент;
- файлы должны быть логично и удобно разложены на сервере; при этом сторонний разработчик должен быстро понять что и как здесь устроено;
- отдавать клиенту сжатые файлы (без комментариев и лишних символов);
- минимизировать количество отдаваемых клиенту файл, в идеале — по одному на стили и скрипты;
- сделать так, чтобы клиент не загружал файлы каждый раз заново (кеширование);
- при обновлении какого-то файла автомически должны обновляться и загружаться заново измененные файлы;
- процесс сжатия и кеширования должен быть максимально автоматизирован.
Решение организации файлов
Я решил разделять файлы в зависимости от разделов проекта, плюс иметь один или несколько общих файлов для общих стилей и функций. Плагины я решил класть в отдельную папку внутри папки со скриптами или стилями.
Для скриптов я сделал еще одно разделение. Файлы с функциями у меня отдельно, а вызов этих функций — в другом файле. То есть если для раздела блогов функции хранятся в файле blogs.js, то их вызов делается в init.blogs.js. Так, зайдя в init.blogs.js сразу видно, какие именно функции запускаются при загрузке раздела.
В начале каждого файла (стилей или скриптов) я решил делать небольшое описание того, что тут лежит, где оно используется и что нужно для его работы (последнее относится только к скриптам).
Чтобы стало немного понятнее, приведу пример файловой структуры одного из проектов:
Подключение JS файлов в HTML делается в таком порядке: сначала в нужном порядке идут файлы, содержащие описания функций, а потом — файлы, делающие вызов функций:
<script type="text/javascript" src="/js/common.js"></script>
<script type="text/javascript" src="/js/blogs.js"></script>
<script type="text/javascript" src="/js/init.js"></script>
<script type="text/javascript" src="/js/init.blogs.js"></script>
Решение сжатия и кеширования файлов
Здесь предстояло взять организованные файлы, сжать их, собрать в один и отдать клиенту. Как раз в это время мы разрабатывали проект на системе Вивво, и так было что-то похожее уже реализовано. Скрипт этой системы получал массив файлов, которые необходимо было сжать, удалял из них пробелы и комментарии, ставил заголовки для кеширования и отдавал клиенту.
То есть если обычное подключение файлов скриптов выглядит вот так:
<script type="text/javascript" src="/js/common.js"></script>
<script type="text/javascript" src="/js/blogs.js"></script>
<script type="text/javascript" src="/js/init.js"></script>
<script type="text/javascript" src="/js/init.blogs.js"></script>
То подключение через скрипт выглядит вот так:
<script type="text/javascript" src="/compress.php?js,js/common,js/blogs,js/init,js/init.blogs"></script>
В этом скрипте мне понравилась часть с заголовками, и я решил взять его за основу. Но хотелось, во-первых, лучше сжимать файлы, а, во-вторых, не делать это каждый раз, а только тогда, когда файлы изменялись.
Для решения первой задачи я решил использовать PHP библиотеки CSSmin и JSmin, соответственно. Для решения второго вопроса я сделал так, что скрипт сначала проверяет, есть ли сжатая версия этого набора файлов, и если есть, то отдает ее, а если нет, создает ее заново. В итоге я получил скрипт с такими возможностями:
- сжатие CSS и JS на с использованием CSSmin и JSmin, соответственно;
- объединение и сохранение сжатых версий файлов;
- автомическое обновление сжатой версии файлов, если какой-то из них был изменен;
- заголовки кеширования для браузеров.
Практическое применение
Сейчас, уже на третьем проекте, я делаю так. На тестовых макетах я подключаю все файлы отдельно, без использования скрипта сжатия. Так легче делать отладку, так как FireBug покажет, в каком файле и строчке произошла ошибка.
На живом сайте стили и скрипты подключаются через скрипт-сжималку, что существенно ускоряет загрузку сайта и экономит трафик.
Как использовать
Скрипты для работы нужны библиотеки CSSmin и JSmin. Так же нужно указать папку, где хранить сжатые версии файлов. У меня это /css/compressed/ и /js/compressed/, соответственно.
Файлы, которые необходимо сжать, нужно указывать следующим образом. Сначала указывается тип (css или js), потом через запятую путь до файл (только имя, без расширения) относительно файла-компрессора. В моем случае при условии, что compress.php и папки css и js находятся в одной директории, подключение выглядит как-то так:
<script type="text/javascript" src="/compress.php?js,js/common,js/blogs,js/init,js/init.blogs"></script>
Итак, ниже есть архив с файлом compress.php и необходимыми библиотеками:
Буду рад выслушать ваше мнение, вопросы и предложения в комментариях. Кстати, если кто-то сможет сделать такой же скрипт на других языках (.NET, Ruby, etc.), буду очень благодарен.
//margin:20px 0 0;
Один вопрос: как Ваш скрип определяет был ли изменен файл?
Или используйте условные комментарии для IE вместо хаков.
Алексей, по времени изменения файла.
Он позволяет сжимать, объединять CSS и JS файлы прямо во время разработки. Доступны разные инструменты сжатия css и js (Yahoo Compress, Google Closure Compiler, Microsoft AJAX и еще че-то).
А самое на мой взгял крутое - это возможность использовать LESS синтаксис в CSS. Про это у нас уже была статья.
Ясно, что если из файла вырезать комментарии и пробелы, то весить он станет заметно меньше. А если отдавать клиенты не 20 файлов, а 2, то загрузятся они так же быстрее.
Я как-то провел такое исследование на одном проекте, но цифр сейчас не осталось.
http://tigor.org.ua/wp-minify/
Про количество файлов тут очевидно, конечно, ускорение должно быть и значительное. Я к тому, что неужели на самом деле удобней вот так в разных CSS всё редактировать? Откуда там столько кода набирается для разделения? Возможно, изначально непродумана архитектура оформления.
Да, набирается обычно из-за того, что в различных разделах сайта есть элементы, которые есть только там, и смысла отдавать это туда, где это не нужно, нет.
Я бы порекомендовал еще добавить номер версии текущего «минифицированного» файла, и сохранять его физически (на файловую систему или в мемкеш, или не важно куда )), а генерировать если запрашиваемый файл не существует. Что бы после генерации отдавать только средствами веб сервера. А например в случае nginx'а еще и делать .gz версию и поключить замечательный модуль «ngx_http_gzip_static_module» который при наличии подготовленного заархивированного файла автоматом отдает его. И да он еще и проверяет, а может ли клиент GZIP )
Т.е. запрашвать например: /compress.php?js,js/common,js/blogs,js/init,js/init.blogs -> /js/xxxx/common__blogs__init__init_blogs.js, а на файловой системе соответсвенно будут: /js/xxxx/common__blogs__init__init_blogs.js и /js/xxxx/common__blogs__init__init_blogs.js.gz, где xxxx номер текущей версии, это позволит:
1) отдавать максимально долгоживущие заголовки
2) единовременно принудительно обновлять кеш пользователя при обновлении версии.
Сейчас скрипт сохраняет сжатые версии физически. А GZIP у меня обычно сервер делает для все, что возможно.
Сделав предварительное сжатие вы не грузите этим в будущем процессор ;)
JSmin конечно староват лучше Google Closure Compiler, если хостинг позволяет выполня Java.
Все сжимает, стиль схоронятся и доступен, но сам сайт почему-то не отображает стили...
Я что-то упустил?
1. Количество CSS-файлов у меня должно быть строго меньше 32, т.к. IE игнорирует большее количество.
2. Для сборки файлов я использовал Ant. Сильно помогла статья Сергея Чикуёнка.
3. Проблему с кэшированием я своими силами не решил, придется просить помощи разработчиков.
Только у меня вопрос. Как подключить сss?
Причём не только для минифицированных скриптов и стилей, но и для самого сгенерированного html документа.
Это имеет смысл делать даже на высокопосещаемых сайтах с частой сменой контента. Проверено
http://tigor.org.ua/wp-minify/
о такой системе начал думать года 3 назад. реализовал около года назад :) тоже использую minify, но у него есть недостаток: сам он как-то "криво" кеширует файлы -- то только какую-то часть закеширует, то вообще не хочет обновлять кеш после перезаливки файлов.
обошел так: перед выдачей склеиваю "ИМЯ_ФАЙЛА+РАЗМЕР", получаю md5-хеш, затем смотрю, есть ли такой файл? если нету, склеиваю все файлы, задаю 32-х символьное имя и отдаю его minify -- точно съест )
еще есть момент - фоновые изображения (background:url(...)), которые естественно находятся на сервере -- часто они попадают в кеш юзера и даже после обновления не видны (особенно касается спрайтов) -- тут тоже надо что-то думать :)
я делаю так: прохожу регекспами по тексту, надожу изображения на сервере и ставлю им дату изменения в UNIX-формате, типа этого: "img.gif?1294900905". после изменения CSS и изображения, адрес меняется и оно перезагружается...
а кто как еще обходит это? :)
Под Ruby есть замечательная система Sprockets: https://github.com/sstephenson/sprockets