Статьи Архив статей

Автор: Мациевский Николай aka sunnybear
Опубликована: 5 марта 2010

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

сжатие текстовых файлов

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

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

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-файлов можно легко автоматизировать, или даже настроить обратную совместимость в случае невозможности отладки конкретных скриптов.

Читать дальше

Все комментарии (habrahabr.ru)