Клиентская оптимизация

индекс
163,90

Проблемы сжатия и объединения Javascript

сжатие текстовых файловПосле публикации ряда заметок на тему сжатия и объединения JavaScirpt-файлов стоит все же осветить наиболее характерные проблемы этого самого сжатия и объединения.

Начнем с простого: как JS-сжатие способно испортить нам настроение. И как его поднять обратно :)

UPD стартовал конкурс ускорения сайтов. В призах: монитор, веб-камера, мышь. Все гипер-быстрое.

Javascript-сжатие


Вообще стоит сразу упомянуть, что сжатие Javascript-файлов даст нам всего лишь 5-7% уменьшение размера относительно обычного gzip, который можно использовать везде (нет, реально, везде — начиная от конфигурации Apache через .htaccess и заканчивая статическим сжатием через mod_rewrite + mod_mime и конфигурацией nginx (или LightSpeed). Но вернемся к теме топика: мы хотим минимизировать Javascript-файлы, как это лучше всего сделать?

Два года назад был произведен обзор текущих инструментов, за это время ситуация не сильно изменилась (разве что появился Google Compiler). Но все по порядку.

  • Начнем с простого. JSMin (или его клон, JSMin+). Работает очень универсально (по принципу конечных автоматов). Почти всегда минимизируемый файл даже исполняется. Дополнительный выигрыш (здесь и далее относительно простого gzip) — до 7% в случае продвинутой версии, т.е. совсем мало. Процессор кушает умеренно (продвинутая версия, JSMin+ сильнее, и память значительно), но не анализирует область видимости переменных, в связи с чем не умеет сокращать их длину. В принципе, может применяться почти для всех скриптов, но иногда возможны нюансы. Например, удаляются условные комментарии (это лечится) или неверно распознаются различные конструкции (например, + + преобразуется в ++, это ломает логику), это тоже лечится, но сложнее.
  • YUI Compressor. Наиболее известный (до недавнего времени еще и наиболее мощный) инструмент для сжатия скриптов. Базируется на движке Rhino (насколько известно, корни движка где-то рядом с фреймворком Dojo, т.е. очень-очень давно). Сжимает скрипты отлично, работает над областью видимости (может уменьшать длину переменных). Степень сжатия — до 8% сверх gzip, однако, процессор кушает будь здоров (в связи с использованием виртуальной машины Java), так что с использованием в режиме онлайн стоит быть осторожным. Также в связи с уменьшением длин переменных возможны различные проблемы (и их даже потенциально больше, чем для JSMin).
  • Google Closure Compiler появился недавно, но уже завоевал доверие общественности. Базируется на том же движке Rhino (ага, нет ничего нового под луной), но использует более продвинутые алгоритмы уменьшения размера исходного кода (отличный обзор во всех деталях), до 12% относительно gzip. Но тут стоит быть втройне острожным: может быть вырезана весьма существенная часть логики, особенно при агрессивных преобразованиях. Однако для jQuery уже используется этот инструмент. По процессорным издержкам он, видимо, еще тяжелее YUI (данный факт не проверялся).
  • и Packer. Данный инструмент уже уходит в прошлое в связи с развитием каналов связи и отставанием процессорных мощностей: ибо сжатие в нем (алгоритм, аналогичный gzip) производится за счет Javascript-движка. Это обеспечивает очень значительное (до 55% без gzip) уменьшение размера кода, но дополнительные издержки вплоть до 500-1000 мс на распаковку архива. Естественно, это становится не актуальным, если процессорные мощности ограничены (привет, IE), а скорость соединения весьма высока (+ gzip уже почти везде применяется и поддерживается). Дополнительно, данный метод оптимизации наиболее склонен к различным багам после минимизации.

Резюме здесь: проверяйте Javascript не только на том сервере, где он разрабатывается, но и после оптимизации. Лучше всего — по одним и тем же unit-тестам. Узнаете много нового про описанные инструменты :) Если это не критично, то используйте просто gzip (лучше всего статический с максимальной степенью сжатия) для обслуживания Javascript.

Проблемы объединения Javascript-файлов


После того как мы разобрались со сжатием Javascript-файлов, хорошо бы затронуть тему их объединения. Средний сайт имеет 5-10 Javascript-файлов + несколько встроенных (inline) кусков кода, которые могут так или иначе вызывать подключаемые библиотеки. В итоге — 10-15 кусков кода, которые можно объединить вместе (плюсов от этого море — начиная от скорости загрузки на стороне пользователя и заканчивая отказоустойчивостью сервера под DDoS — тут каждое соединение будет на счету, даже статическое).

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

Итак, у нас есть 10-15 кусков кода (часть из них в виде встроенного кода, часть — в виде внешних библиотек, которые мы можем так же «слить» вместе). Нам нужно гарантировать их независимую работоспособность. В чем она заключается?

Если у нас есть Javascript-ошибка в файле, то браузер прекращает выполнение этого файла на ошибке (некоторые наиболее древние еще и прекращают выполнение всех Javascript-файлов на странице в этом случае, но мы не совсем про это разговариваем). Соответственно, если первая же библиотека, которую мы хотим объединить в общий файл, выдает ошибку, то во всех браузерах у нас клиентская логика после объединения развалится. Грустно.

Дополнительно стоит отметить, что встроенный (inline) код достаточно тяжело отлаживать. Его можно либо исключить из объединения (например, расположив вызов объединенного файла до или после кода), или же при его использовании вообще отменить объединение файлов.

Обратная совместимость


Что мы можем с этим поделать? Наиболее простой путь: исключать проблемные файлы (при этом ошибки могут проявляться только на этапе объединения, отдельные же файлы могут отрабатывать на ура) из логики объединения. Для этого нужно будет отслеживать, в каком месте происходить ошибка, и собрать конфигурацию для объединения для каждого такого случая.

Но можно поступить несколько проще. Для Javascript мы можем воспользоваться конструкцией try-catch. Ага, мысль уловили? Еще нет? Мы можем все содержимое файлов, которые объединяем, заключать в try {}, а в catch(e) {} вызывать подключение внешнего файла примерно следующим образом:
try {
	... содержимое Javascript-библиотеки ...
} catch (e) {
	document.write('исходный вызов Javascript-файла');
// или console.log('нужно исключить из объединения Javascript-файл');
}

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

Проблемы производительности


Очевидно, что данный подход не является «самым правильным». Наиболее логичным было бы определить Javascript-ошибки, их устранить, и загружать для всех пользователей один файл. Но не всегда это возможно. Также стоит учесть, что try-catch конструкция тяжеловата для исполнения в браузерах (добавляет 10-25% ко времени инициализации), стоит быть с ней осторожным.

Но описанный подход может замечательно применяться для отладки конкретно объединения Javascript-файлов: ведь он позволяет точно определять, какие файлы «сыпятся» (если файлов несколько десятков, это очень и очень полезно).

Небольшое резюме


После дополнительной минимизации Javascript-файлов обязательно проверяйте их функциональность. А отладку корректности объединения Javascript-файлов можно легко автоматизировать, или даже настроить обратную совместимость в случае невозможности отладки конкретных скриптов.
+30
5 марта 2010, 03:38
68

комментарии (45)

–1
Wott #
1. минификация JS кода легко может быть сделана на стадии компоновки проекта в IDE. Или на сервере на первом запуске или любым другим способом до рабочей загрузки кода страницы. Так что главное для упаковщиков-минимфикаторов это оставить код рабочим.

Да, пользую YUI компрессор. Проблем не наблюдалось, но я отключаю все фишки оптимизатора.

2. Слияние скриптов в один включая сжатие легко делается управляемым. По-моему все зрелые CMS умеют это делать. Если кто не умеет — стащить код у тех кто умеет.
–1
sunnybear #
Подход, естественно, верный. Но не работает для миллионов «кривых» сайтов. Например, для WordPress с его десятками тысяч плагинов. Ручная оптимизация изучена вдоль и поперек — здесь уже вряд ли будет что-то новое. А вот автоматическая на неизвестном окружении — ммм…
–1
Wott #
Wordpress имеет встроенный загрузчик скриптов и правильно запиханный скрипт им подхватывается, к стати… жалко что не все фишечки вытащены в API.

В универсальном окружении — это упаковка на лету сервером или кэшем? тут куча подводных камней, например зависимость от браузера, версии и прочая интересная, но имхо совершенно излишняя работа. Потому как на условно среднем сайте самые длинные и замысловатые скрипты вянут на фоне загружаемых тонн графики. Но и кэшируются также хорошо браузером. Иногда даже слишком хорошо :)
–1
sunnybear #
графики на фоне скриптов загружается совсем не тонны. Например, характерное распределение неоптмизированного сайта
–1
rednaxi #
в данном случае еще неплохо было бы привести timeline
дело в том, что большинство браузеров, когда грузят JS файлы блокируют загрузку всех остальных файлов (некоторые, правда, грузят JS параллельно с CSS) и если на странице JS файлов много то могут возникнуть проблемы с загрузкой:
Как известно, протокол HTTP 1.1 позволяет создавать браузеру только 2 соединения с 1 доменом в каждый момент времени (правда единственный браузер, который следует стандартам это IE, FF создает по 6 параллельных соединений с доменом).
так что размер скриптов тут не так важен, как их количество — 2 скрипта по 80 кб загрузятся гораздо быстрее чем 16 по 10кб, просто потому что в случае с 16 скриптами будет происходить упирание в пинг + задержка очереди загрузки в браузере. а 1 скрипт в 167кб, подключенный асинхронным способом, вообще не затормозит загрузку страницы в реалиях нынешних скоростей интернета.

Но вообще да, это не тонны графики тормозят загрузку страницы, это если у вас на странице тонна графики необходимо думать об оптимизации JS иначе может возникнуть проблема что куча JS файлов забила всю очередь загрузки (как я уже говорил если началась загрузка JS загрузка картинок блокируется, кроме тех что уже грузятся) и пользователь полминуты пялится на страницу без картинок. а если у картинок еще не заданы размеры (height и width) то пользователь может еще получить в итоге веселую «прыгающую» страницу.

опять же, использование css sprites наше все в случае с тонной графики, 2-3 большие картинки прогрузятся быстрее чем 20-30 маленьких.
–1
Evengard #
Хоть в чём то IE следует стандартам...))
+3
rednaxi #
А в чем он не следует?
Все современные стандарты (CSS 2.1, HTML 4.01) он поддерживает в достаточной мере, на уровне остальных браузеров (я, естественно, говорю об IE8, говорить об IE 6 это все равно что говорить что FF 1.5 не поддерживает каких то стандартов, а Opera 4 вообще платный)
CSS 3, HTML 5- это еще все в разработке и их поддержка встраивается в браузеры от доброй души разработчиков браузеров (кстати, в IE 8 частично тоже реализована поддержка некоторых элементов этих стандартов).

Кроме того, в IE уже с 5 (вроде бы) версии поддерживается замечательный аттрибут для тега <script> defer, который означает что скрипт надо грузить и выполнять только после загрузки DOM. непонятно, почему в других браузерах это до сих пор не сделано и приходится извращаться с DOMContentLoaded и т.д.
0
Evengard #
Про IE8 щас говорить особо смысла нету — доля IE7 и IE6 ещё достаточно велика… Особенно в корпоративном секторе.
Да и ява скрипт у него всё таки если не ошибаюсь какой то другой, не стандартный
0
sunnybear #
ошибаетесь. Точнее, HTML в IE можно с такой же долей уверенности называть «каким-то другим», но так никто не делает.
–1
aps #
>проблемы объединения Javascript-файлов
Главная проблема объединения, имхо, — критерий по которому нужно объединять. Например, если наиболее тяжеловесные скрипты (какая-нибудь сложная процедура регистрации или подачи объявлений) исрользуются одноразово или крайне редко — зачем их тащить в общую кучу?
–1
sunnybear #
вот тут была неплохая статья на тему JS-объединения с этой точки зрения
webo.in/articles/habrahabr/57-javascript-merging/
а вот здесь — одно из возможных решений (алгоритмическое кэширование)
webo.in/articles/habrahabr/48-flow-slices-optimization/
–1
aps #
Читал давно. Особенно запомнилось:
«Спешу заверить — это легко5 решается вручную выделением 23 независимых групп со своими собственными ядрами.»
Это называется «жопа» :(
В идеале система объединения должна строиться на основе статистики по результатам тестирования/опытной эксплоатации. Директивная сборка все равно будет иметь большие зазоры.
–1
sunnybear #
да, Microsoft уже выкатило свое DOLOTO. Мы, наверное, тоже поднажмем и сделаем «интеллектуальное» объединение JS-файлов на основе статистики запросов. Но это не очень тривиальная задача :)
–1
aps #
Она станет тем более нетривиальной, если туда включить информацию о поведении пользователей :)
Например сайт вакансий с тремя разными группами пользователей:
1) незалогиненные
2) залогниненные рекрутеры, размещающие вакансии и ищущие резюме
3) залогиненные соискатели, размещающие резюме и ищущие вакансии.
–1
sunnybear #
да, тут непаханное поле. Но начинать можно с простых вариантов :)
Тем более, кластеризация все же решает :)
–1
Expert #
для сжатия js я всегда использую хттп://bananascript.com/
–1
rednaxi #
попробовал им сжать, там как я понял он еще кроме уменьшения размера классическими методами (укорачивание имен переменных и т.д.) еще «обфусцирует» код?
остается открытым вопрос, сколько времени тратит браузер на раскодирование этой обфускации и будет ли там реальн выигрыш во времени — в реалиях совеременных скоростей интернета лишний килобайт может загрузится быстрее чем браузер распарсит этот скрипт.

как известно, классические методы сжатия, которые убирают комментарии, заменяют имена переменных и т.д., кроме скорости загрузки также увлеичивают производительность JS в браузере: за счет убора всего лишнего и уменьшения имен переменных JS-движок быстрее парсит файлы.
+2
sunnybear #
он работает по принципу описанного Packer. Использовать не рекомендуется: на -2% после сжатия получите +100% на распаковку на клиенте.
–1
rednaxi #
ну вот, и я о том же — копейки сжатия приводят к существенно большей нагрузке на клиенттский браузер в итоге
0
reket #
-2%? Ну это вы загнули… Буквально вчера сжимал плагин для jQuery, результат: до 24 303 байт, после 7 912 байт.
0
sunnybear #
а вы попробуйте Google Compiler + gzip — относительно них сравнение идет
0
reket #
Да, что-то я оплошал, не внимательно прочитал…
–3
kashey #
имхо все кроме Google Compiler — половинчатые решения.
смысла сделать скрипт на 10кб меньше( и это для очень больших скриптов данные ) и иметь лого по размеру больше скриптов — фигня :)

А вот раскрытие в инлайн функций в гугле — она не только размер уменьшает, но и выполнение укоряет.
Местами мало, местами очень даже ничего
–1
rednaxi #
фишка в том что ваше лого не загрузится пока не загрузятся ваши скрипты если лого прописано как в а скрипты в просто потому что лого попадет в очередь загрузки позже, а загрузка JS блокирует загрузку всех остальных файлов.

поэтому, кстати, если способ клиентской оптимизации, когда все <script src=> переносятся в конец , что позволяет не тормозить очередь загрузки и быстрее прогружать клиентскую часть страницы (картинки, css, etc) а потом только подгружать скрипты.
–1
kashey #
если для клиента загрузка лишних 10кб может вызвать проблему — тогда об этом можно уже особо не париться :)
Тут лучше не брутфорсом подходить а хитростями

хинт:
в начале страницы начинаем загрузку скриптов как обычных файлов через ajax
по готовности делаем им eval или инжекстив в тело тутже созданого script тэга.

что получаем — асинхронную загрузку скрипта, котороая и в конце страницы нас не задержит
–1
rednaxi #
если просто разместить все скрипты в конце страницы ээфект будет почти какой же как если делать вашим способом, с той разницей что ваш способ может не сработать если не подгрузились какие то билиотеки, которые нужны для работы скриптов, которые уже подгрузились.

Я в курсе этого хинта, только у него есть дополнительные сложности — надо знать «зависимости» каждого из подгружаемых скриптов и евалить их только при удовлетворении этих зависимостей.

насчет лишних 10кб — если 10кб скриптов будут грузится до картинок то клиент вместо, например, фото товара, ничего не увидит.

в интернете есть интересная статистика, в которой говорится так же о том, что уменьшение времени загрузки на 100мс у амазона сразу отразилось на продажах — продаж стало на 1% больше. 1% клиентов это неплохой прирост для 10кб скрипта, не считаете? :)
–1
rednaxi #
0
ZDreamer #
Я бы ещё добавил, что все компрессоры, начиная с самых слабых, полностью вырезают комментарии. Как результат, количество комментариев никак не влияет на размер сжатых файлов. Если использовать только GZip, такого эффекта не получится.
–1
Aux #
Я для себя решил, что лучше всего вырезать комментарии, немного минифицировать, слить скрипты в один и сжать gzip-ом.
+2
lol2Fast4U #
Сливает скрипты в один и минифицирует его замечательная штука django-assets ( https://launchpad.net/django-assets ). И то же самое делает с CSS.
Там нет никакого движка — это обертка. Сильно автоматизированная.
+2
egorinsk #
Недостатки компрессоров из моего опыта:

1) JSMin — ломает скрипты на раз, кроме того медленный, так как парсит скрипт посимвольно
2) Dean's Packer — требует после всех функций, и не только, ставить точки с запятой (а в яваскрипте, что конечно плохо, есть автоподстновка этой самой точки с запятой)
3) Ну YUIComprssor — ставить например на VPS с 256 Мб Яву — дикость.

Так как менять привычки и ставить всюду точки с запятой было лень :), проще оказалось написать свой маленький компрессор, который сохранял частично переводы строк (и иногда пробелы) и не ломал автоподстановку. Кроме того, он работал на регекспах, довольно-таки неплохо.

Конечно, жал он хуже упомянутых здесь скриптов, но ненамного, а главное что комментари резал и пробелы сжимал, что и требовалось :) К тому же все равно в итоге скрипты жмутся gzip, так что разница становится еще меньше.
–1
tenshi #
первый отработал, второй сломался

если сделать так как ты сказал, то первый отработает ещё раз, что черевато

–1
sunnybear #
было бы хорошо писать более развернутые комментарии, понятные не только их автору :)
–1
tenshi #
первый скрипт из пакета, второй, третий…
–1
sunnybear #
понятное дело, что чревато. Но обычно скрипты не настолько критичны к окружению — можно допустить, чтобы по два раза отрабатывали. Главное, чтобы отрабатывали.

А полностью исключить двойную обработку — имхо, слишком «дорогая» задача.
–2
tenshi #
ещё как критично. лучше, если скрипты вообще не отработают, чем буду работать кое как
–1
sunnybear #
У нас разный взгляд на критичность: для миллионов леммингов лучше двойной обработчик событий пусть навесится, и jQuery два раза проинициализируется, чем «галерея развалится».

Для разработчиков — пусть уж лучше галерея развалится (будет понятно, что чинить), чем такая слабо детектируемая фигня с утечками случится.

Вы посмотрите на проблему с разных точек зрения.
–2
tenshi #
ну и прикинь, тыкает он на «следующее фото» а ему показывают через одно и с дёрганной анимацией
–1
DYPA #
LightSpeed — o_O => lighttpd
0
sunnybear #
на западе распространено больше первое название
+1
DYPA #
это 2 разных веб сервера ;)
litespeedtech.com
www.lighttpd.net
0
13i #
поправьте ссылку — webo.in/articles/habrahabr/11-minifing-javascript/
–1
sunnybear #
гы-гы, напишите это администрации Хабра :) это их «кривой» движок анти-XSS
0
dulepov #
Чуть–чуть цифр:

ls -l extjs-3.3.1
-rw-r--r-- 1 x x 2.4M Mar 7 19:00 ext-all-debug.js
-rw-r--r-- 1 x x 550K Mar 11 17:54 ext-all-debug.js.gz
-rw-r--r-- 1 x x 641K Mar 2 20:50 ext-all.js
-rw-r--r-- 1 x x 175K Mar 11 17:54 ext-all.js.gz

Отсюда выводы:
— только gzip–сжатие: -78%
— только минификация (YUI): -72%
— минификация + gzip: -93%

Это была библиотека ExtJS 3.3.1. Ещё сжали примерно 200Кб клиентского кода подобным образом. Время загрузки падает с пары минут до менее 10 секунд при первом заходе на страницу (без клиентского кеширования и прочих серверных мер по кешированию сжатых файлов).

Топик очень интересный.

В целом минификацию и сжатие надо обязательно использовать вместе.

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.