Известно, что следуя идеям старой школы, а именно, добавляя ссылки на JS и CSS в страницы, может обернуться большим временем загрузки страницы. Браузер отображает страницу по мере скачивания, но останавливается, если натыкается на тег script со ссылкой, до того момента, пока скрипт не будет загружен и выполнен. Сайты стали использовать всё большее количество скриптов, начальное отображение страницы занимает всё больше времени, к примеру, на этой странице, которую вы читаете, 13 скриптов, 7 из которых находятся в head'е. Ко всему прочему, некоторые браузеры по-прежнему придерживаются ограничений на одновременное количество загрузок с одного хоста.
Сразу предлагаю принять, что все JS файлы минимизированы, и передаются в сжатом виде.
Существует несколько решений, как то:
— поместить стили и скрипты прямо в страницу;
— установка аттрибутов async/defer тегу script;
— склеить все скрипты в один файл;
— помесить ссылки на скрипты в конец body;
— разместить все файлы на CDN/на разных хостах;
— свой вариант…
Эти решения работают, каждое лучше или хуже в зависимости от того, как построен сам сайт, но обладают рядом недостатков, которые я опишу ниже.
Существует интересная техника, которая решает проблему паузы перед начальным отображением страницы, а заодно добавляет некоторые удобства. Рискну предположить, что техника эта многим знакома, но тем не менее здесь я о ней упоминаний не видел.
Началось всё, конечно, с того, что я взялся за один проект, и в какой-то момент мне показалось, что простенькая страница достаточно долго загружается, и посмотрел на график загрузки, и на результаты YSlow. Огонь на секунду потух в моих глазах, но зная, что может быть лучше, я полез искать,
Разберём недостатки перечисленных выше методик.
Очевидно, что это решение подходит только для маленьких страниц, где не много скриптов стилей, так как в случае перезагрузки страницы будут повторно загружаться статические данные, и кеширование не может сработать. Решение вполне оправдано для небольших скриптов и стилей, жизненно необходимых с того самого момента, как страница загружена.
Спецификация тут.
Аттрибуты async и defer тега script поддерживается следующими бразуерами, что может показаться недостаточным для тех, кто делает сайты, на которые могут заходить люди с устаревшими браузерами, а также Opera, что особенно актуально в рунете.
Из недостатков также можно отметить, что скрипты, загруженные из тега с аттрибутом defer, не могут использовать document.write, так как их исполнение не синохронизировано с парсером страницы.
Есть интересная заметка (осторожно, англ.) и тест, которые показывают, что загрузка одного большого файла может длиться дольше, чем параллельная загрузка отдельных файлов. Однако, эта техника не только достойна упоминания и жизни, но и также крайне показана к применению в том случае, если разные страницы сайта используют один и тот же набор скриптов или стилей.
Достойно упоминания и использования, но в этом случае, как и в описанных выше, до момента document.ready могут быть неразрешённые зависимости между скриптами, и если для jQuery с плагинами это допустимо, то для варианта, когда мы загружаем библиотеку API Facebook'а или VKontakte, и хотим тут же запустить наш скрипт, который пошёт на API запрос определения, залогинен ли пользователь, это сулит костылями, либо загрузкой библиотеки API в начале страницы, блокируя её отображение, что отягощается необходимостью вызова DNS resolve.
Использование CDN может снизить загрузку на собственный сервер, но может и увеличить и уменьшить время загрузки, а может и полностью блокировать загрузку скрипта, как в случае атаки на CDN сервер или блокировки CDN сервера на провайдере или в сети предприятия.
Что если попробовать загружать скрипты в тот момент, пока страница грузится, но не выполнять их, и вообще скрывать от браузера, что это скрипты до того момента, пока они не догрузились, чтобы не блокировать первичное отображение страницы?
Где-то в заметках у меня нашёлся HeadJS, но с тех пор, как я впервые на него наткнулся, он серьёзно заматерел, и научился делать не только то, что нам нужно, но и многое другое. Несмотря на то, что библиотека явно хороша, а в минифицированном виде занимает всего 3КБ, я решил поискать альтернатив и нашёл аж 14 аналогичных библиотек, краткое и не всегда верное сравнение которых можно найти в этой заметке, плюс load.js и include.js. Бегло пробежавшись по представленным библиотекам и отметя сначала большие (>3КБ), а потом те, которые не понравились мне по синтаксису или принципу работы, я лично для себя выбрал YepNope.js, входящий в состав Modernizr. Авторы библиотеки сообщают, что библиотека не лучше и не хуже остальных, и выполняет ту же задачу, что и остальные, и что они сами в разных проектах используют также и другие загрузчики.
Итак, что же и как делает загрузчик ресурсов на примере YepNope:
Исполнение загруженных скриптов идёт в указанном порядке.
Далее в блоке инициализации:
Итак, мы получаем все желаемые плюшки, как то параллельная загрузка, определённость времени выполнения скрипта, зависиммости, обратные вызовы по готовности.
Пример загрузки стилей:
У YepNope и других загрузчиков есть также много дополнительных возможностей, описание которых в этом топике не уместно, но с которыми неплохо познакомиться, и каждому выбрать что-то для себя самому.
Авторы скриптов не могут прийти к единому принципу работы и интерфейсу API, и продолжают создавать всё новые загрузч ики. В связи с этим автор HeadJS предлагает встроить поддержку порядка загрузки в спецификации, и автор $script.js его в этом поддерживает, но пока это пройдёт через спецификации и будет работать одинаково во всех браузерах, нам предстоит пользоваться загрузчиками.
Каков же итоговый рецепт?
— встроить в head страницы script, указывающий на загрузчик;
— встроить inline скрипт, использующий загрузчик для подгрузки других скриптов и стилей;
— объединять скрипты и стили, использующиеся только совместно, в один для минимизации количества HTTP запросов;
— минимизировать скрипты и стили;
— убедиться в том, что сервер пакует передаваемые данные gzip'ом;
— убедиться в том, что сервер правильно кеширует;
— осторожно и вдумчиво использовать сторонние CDN и дополнительные хосты.
При написании топика делал оглядку на следующие материалы, рекоммендуемые к прочтению:
[1]. Очерёдность событий и синхронизация в JavaScript
[2]. Простая загрузка скрипта при помощи yepnope.js
[3]. Описание yepnope.js
[4]. Описание $script.js
UPD.
от sHinE Ещё одна таблица-сравнение различных загрузчиков, с более полным списком.
от S2nek Русский перевод описания YepNope.
Сразу предлагаю принять, что все JS файлы минимизированы, и передаются в сжатом виде.
Существует несколько решений, как то:
— поместить стили и скрипты прямо в страницу;
— установка аттрибутов async/defer тегу script;
— склеить все скрипты в один файл;
— помесить ссылки на скрипты в конец body;
— разместить все файлы на CDN/на разных хостах;
— свой вариант…
Эти решения работают, каждое лучше или хуже в зависимости от того, как построен сам сайт, но обладают рядом недостатков, которые я опишу ниже.
Существует интересная техника, которая решает проблему паузы перед начальным отображением страницы, а заодно добавляет некоторые удобства. Рискну предположить, что техника эта многим знакома, но тем не менее здесь я о ней упоминаний не видел.
Началось всё, конечно, с того, что я взялся за один проект, и в какой-то момент мне показалось, что простенькая страница достаточно долго загружается, и посмотрел на график загрузки, и на результаты YSlow. Огонь на секунду потух в моих глазах, но зная, что может быть лучше, я полез искать,
Разберём недостатки перечисленных выше методик.
Помещение скриптов и стилей прямо в страницу
Очевидно, что это решение подходит только для маленьких страниц, где не много скриптов стилей, так как в случае перезагрузки страницы будут повторно загружаться статические данные, и кеширование не может сработать. Решение вполне оправдано для небольших скриптов и стилей, жизненно необходимых с того самого момента, как страница загружена.
Установка аттрибутов async/defer тегу script
Спецификация тут.
Аттрибуты async и defer тега script поддерживается следующими бразуерами, что может показаться недостаточным для тех, кто делает сайты, на которые могут заходить люди с устаревшими браузерами, а также Opera, что особенно актуально в рунете.
Из недостатков также можно отметить, что скрипты, загруженные из тега с аттрибутом defer, не могут использовать document.write, так как их исполнение не синохронизировано с парсером страницы.
Склеивание скриптов и стилей
Есть интересная заметка (осторожно, англ.) и тест, которые показывают, что загрузка одного большого файла может длиться дольше, чем параллельная загрузка отдельных файлов. Однако, эта техника не только достойна упоминания и жизни, но и также крайне показана к применению в том случае, если разные страницы сайта используют один и тот же набор скриптов или стилей.
Помещение stylesheet в head, а script — в конец body
Достойно упоминания и использования, но в этом случае, как и в описанных выше, до момента document.ready могут быть неразрешённые зависимости между скриптами, и если для jQuery с плагинами это допустимо, то для варианта, когда мы загружаем библиотеку API Facebook'а или VKontakte, и хотим тут же запустить наш скрипт, который пошёт на API запрос определения, залогинен ли пользователь, это сулит костылями, либо загрузкой библиотеки API в начале страницы, блокируя её отображение, что отягощается необходимостью вызова DNS resolve.
Загрузка с CDN
Использование CDN может снизить загрузку на собственный сервер, но может и увеличить и уменьшить время загрузки, а может и полностью блокировать загрузку скрипта, как в случае атаки на CDN сервер или блокировки CDN сервера на провайдере или в сети предприятия.
Какие ещё есть решения?
Что если попробовать загружать скрипты в тот момент, пока страница грузится, но не выполнять их, и вообще скрывать от браузера, что это скрипты до того момента, пока они не догрузились, чтобы не блокировать первичное отображение страницы?
Где-то в заметках у меня нашёлся HeadJS, но с тех пор, как я впервые на него наткнулся, он серьёзно заматерел, и научился делать не только то, что нам нужно, но и многое другое. Несмотря на то, что библиотека явно хороша, а в минифицированном виде занимает всего 3КБ, я решил поискать альтернатив и нашёл аж 14 аналогичных библиотек, краткое и не всегда верное сравнение которых можно найти в этой заметке, плюс load.js и include.js. Бегло пробежавшись по представленным библиотекам и отметя сначала большие (>3КБ), а потом те, которые не понравились мне по синтаксису или принципу работы, я лично для себя выбрал YepNope.js, входящий в состав Modernizr. Авторы библиотеки сообщают, что библиотека не лучше и не хуже остальных, и выполняет ту же задачу, что и остальные, и что они сами в разных проектах используют также и другие загрузчики.
Итак, что же и как делает загрузчик ресурсов на примере YepNope:
<script src='/javascripts/yepnope-min.js'>
yepnope('/javascripts/jquery.min.js', '/javascripts/jquery.loadmask.min.js', '/javascripts/jquery.jgrowl_minimized.js'])
</script>
Исполнение загруженных скриптов идёт в указанном порядке.
Далее в блоке инициализации:
yepnope({
load: ['//connect.facebook.net/ru_RU/all.js', '/javascripts/facebook_auth_callback.js'],
complete: function(){
FB.init({appId: '273684999999999', xfbml: true, cookie: true, oauth: true})
FB.Event.subscribe('auth.statusChange', facebook_auth)
}
})
Итак, мы получаем все желаемые плюшки, как то параллельная загрузка, определённость времени выполнения скрипта, зависиммости, обратные вызовы по готовности.
Пример загрузки стилей:
yepnope(['/stylesheets/jquery.loadmask.css', '/stylesheets/jquery.jgrowl.css'])
У YepNope и других загрузчиков есть также много дополнительных возможностей, описание которых в этом топике не уместно, но с которыми неплохо познакомиться, и каждому выбрать что-то для себя самому.
Авторы скриптов не могут прийти к единому принципу работы и интерфейсу API, и продолжают создавать всё новые загрузч ики. В связи с этим автор HeadJS предлагает встроить поддержку порядка загрузки в спецификации, и автор $script.js его в этом поддерживает, но пока это пройдёт через спецификации и будет работать одинаково во всех браузерах, нам предстоит пользоваться загрузчиками.
Каков же итоговый рецепт?
— встроить в head страницы script, указывающий на загрузчик;
— встроить inline скрипт, использующий загрузчик для подгрузки других скриптов и стилей;
— объединять скрипты и стили, использующиеся только совместно, в один для минимизации количества HTTP запросов;
— минимизировать скрипты и стили;
— убедиться в том, что сервер пакует передаваемые данные gzip'ом;
— убедиться в том, что сервер правильно кеширует;
— осторожно и вдумчиво использовать сторонние CDN и дополнительные хосты.
При написании топика делал оглядку на следующие материалы, рекоммендуемые к прочтению:
[1]. Очерёдность событий и синхронизация в JavaScript
[2]. Простая загрузка скрипта при помощи yepnope.js
[3]. Описание yepnope.js
[4]. Описание $script.js
UPD.
от sHinE Ещё одна таблица-сравнение различных загрузчиков, с более полным списком.
от S2nek Русский перевод описания YepNope.