|
Название: |
Разгони свой сайт
|
Автор: |
Мациевский Николай
|
Оценка: |
3.8 из 5, проголосовало читателей - 57
|
Жанр: | компьютерная литература |
Издание: | 2009 г. |
Содержание: |
скрыть содержание
-
Методы клиентской оптимизации веб-страниц
- Введение
- Об этой книге и проекте webo.in
-
Вопрос скорости загрузки веб-страниц привлекает внимание всех веб-разработчиков уже очень давно — практически с того момента, как в HTML-документе появились картинк
-
За последние 10 лет уже многократно менялся сам подход к созданию сайтов. В эпоху браузерных войн и ограниченного доступа по модему наиболее важными аспектами клиен
-
Но ситуация изменилась. Сейчас средняя веб-страница уже крайне тяжело вписывается в установленные когда-то рамки «загрузка за 10 секунд на модеме». В среднем на ней
-
Данное издание старается объединить в себе все современные подходы к построению высокопроизводительных веб-приложений и просто веб-сайтов, которые быстро загружа
-
Кроме теоретических аспектов производительности приведено также большое количество практических рекомендаций, примеров конфигурационных файлов, различных прие
- Web Optimizator
-
Идея организовать ресурс, как посвященный теоретическим аспектам оптимизации времени загрузки веб-страницы, так и предлагающий online-инструменты для этой самой опт
-
За основу online-инструмента были взяты замечательные примеры с
- Именно с этой целью и был создан Web Optimizator ( http://webo.in/ ).
- Благодарности
-
Книга не увидела бы свет без помощи, советов и рекомендаций огромного количества людей. Каждый из них добавил что-то новое в нижеизложенный материал, поэтому у меня
-
Во-первых, хочу высказать персональную благодарность Виталию Харисову за несколько очень своевременных замечаний относительно производительности CSS-движка в бра
-
Во-вторых, нельзя не упомянуть Павла Димитриева и его замечательный перевод классических советов от группы разработчиков Yahoo! (
-
Значительный вклад в продвижение идей «ненавязчивого» JavaScript и обратной совместимости в работе веб-сайтов (когда пользователь может взаимодействовать со странице
-
Также необходимо упомянуть Евгения Степанищева ( http://bolknote.ru/
-
Естественно, нельзя обойти вниманием всех моих соратников по российскому крылу Web Standards Group (
-
Кроме этого Антон Лобовкин ( http://anton.lobovkin.ru/ ), Денис Кузнецов
-
И конечно, обязательно хочу поблагодарить всех пользователей
- Глава 1. Что такое клиентская оптимизация?
- 1.1. Цели и задачи оптимизации
-
Каждая веб-страница состоит из основного HTML-файла и набора внешних ресурсов. Говоря о размере страницы (или сайта), очень часто имеют в виду размер именно первого фа
- Рис.1 .1 . Тенденция изменения размера страницы и числа объектов для сайтов, проверяемых через Web Optimizator в 2008 году
-
В настоящее время на каждой странице вызывается несколько десятков внешних объектов, а размер исходного файла составляет не более 5% от общего размера. Как показали
-
Естественно, что технологии эти не ограничиваются сжатием текстовых (HTML, CSS, JavaScript) файлов на стороне сервера. Как несложно понять, основную часть внешних объектов н
- Основные задачи оптимизации
- Если говорить кратко, то можно выделить 3 основных задачи клиентской оптимизации:
- Оптимизация размера файлов.
- Оптимизация задержек при загрузке.
- Оптимизация взаимодействия с пользователем.
- Краткий обзор технологий
- При этом все основные методы можно разбить на 6 групп (каждая из которых позволяет решить одну из заявленных задач).
-
Уменьшение размера объектов. Здесь фигурируют сжатие и методы оптимизации изображений, подробнее об этом можно прочитать во второй главе.
-
Особенности кэширования , которые способны кардинально уменьшить число запросов при повторных посещениях, раскрываются в третьей главе.
-
Объединение объектов. Основными технологиями являются слияние текстовых файлов, применение
-
Параллельная загрузка объектов , влияющая на эффективное время ожидания каждого файла. В пятой главе помимо этого приведены примеры балансировки запросов со стороны клиентского приложения.
-
Оптимизация CSS - производительности , что проявляется в скорости появления первоначальной картинки в браузере пользователя и скорости ее дальнейшего изменения. О CSS-производительности рассказывает ш
-
Оптимизация JavaScript . Есть достаточно много проблемных мест в JavaScript, о которых необходимо знать при проектировании сложных веб-приложений. Обо всем этом можно прочитать в седьмой главе.
-
Хочется отметить, что, несмотря на всю сложность затрагиваемой темы, первоначального ускорения загрузки веб-страницы можно добиться в несколько очень простых шаго
-
Все советы в книге упорядочены по увеличению сложности внедрения и уменьшению возможного выигрыша при загрузке страницы. Для простых веб-проектов можно ограничит
-
1.2. Психологические аспекты производительности
-
Терпимое время ожидания
-
Эффекты медленной скорости загрузки
-
Как время ответа сайта влияет на пользовательскую психологию
- 1.3. Стадии загрузки страницы
-
Рис. 1. 2. Стадии загрузки страницы
-
Расставляем приоритеты
-
Узкие места
-
1.4. Клиентская и серверная оптимизация: сходство и различия
-
Кэширование во главу угла
-
Меньше запросов — легче серверу
-
Архивировать и кэшировать на сервере
-
Кто у кого на службе?
- 1.5. Применение в разработке приложений
-
Пользователи обычно не знают, какие подходы применяются при разработке, как настроен сервер, какие клиентские и серверные средства разработки используются. Для ни
-
Ниже рассказывается, как можно организовать создание веб-приложения, ориентируясь на самые важные аспекты клиентской оптимизации.
- Этап 1: Доставка информации и оформления
-
На этом этапе разработчики должны сделать все возможное, чтобы не замедлить скорость загрузки страницы. Фактически идет речь об ускорении первой стадии загрузки. Н
-
При загрузке страницы браузер запросит все CSS-файлы, объявленные в head страницы, последовательно. Поэтому каждый файл добавляет задержку в загрузке, равную времени з
-
Итог первого этапа — это доставленный и оформленный HTML. И издержки на доставку JavaScript сведены к минимуму (на этом этапе он только мешает, поскольку замедляет отображ
- Этап 2: Кэширование файлов оформления и параллельные запросы
-
На данном этапе разработчики должны обеспечить быструю загрузку других страниц сайта (если посетитель решит туда перейти). Этот этап должен проходить параллельно
-
Одно или несколько дополнительных зеркал для выдачи статических ресурсов легко настраиваются в конфигурации, однако внедрить это в схему публикации изменений гор
-
CSS Sprites достаточно трудоемки в автоматической «склейке», поэтому их внедряют обычно на первом этапе (при создании макета страниц). При использовании метода data:URI на первом
- Этап 3: Жизнь после загрузки страницы
-
Целью данного этапа является создание различных обработчиков событий, которые должны взаимодействовать с пользователем. Это могут быть и всплывающие подсказки, и
-
Говорят, что иногда «грамм видимости важнее килограмма сути» — это как раз про JavaScript. Ведь именно на нем можно реализовать механизмы, упрощающие действия пользоват
-
К этому моменту мы должны иметь оформленную HTML-страницу, на которой все ссылки и формы
-
У нас должны быть готовы серверные интерфейсы для AJAX-запросов; структура страницы должна быть такой, чтобы для аналогичных кусков HTML-кода не приходилось реализовыв
-
Чтобы не уменьшать скорость доставки контента и оформления, JavaScript-файлы (лучше всего, конечно,
-
Задача по обеспечению взаимодействия пользователя с интерфейсом сайта сводится к выполнению следующих действий:
- Все это напрямую следует из концепции «ненавязчивого» JavaScript, которая описана в седьмой главе.
-
Поиск необходимых DOM-элементов должен нам дать список названий JavaScript-компонентов. Названия компонентов должны однозначно соответствовать названиям файлов на серв
-
Список названий компонент можно объединить в один запрос к серверу. В итоге на стадии пост-загрузки должны осуществляться запросы к файлам вида
- У данного подхода есть два недостатка:
-
В папке /jas/ (которую мы, например, используем для кэширования наиболее частых вариантов подключения модулей) через некоторое время может оказаться очень много файл
-
Иногда на странице может оказаться очень много компонент, причем так много, что длина имени запрашиваемого объединенного файла перевалит за возможности файловой с
- Этап 4: Предупреждаем действия пользователя
-
Если после посещения главной страницы большинство пользователей попадают внутрь сайта, то логично будет после полной загрузки главной страницы запрашивать стили
-
Аналогично можно рассмотреть и предзагрузку некоторых наиболее часто используемых картинок, которые отсутствуют на главной странице, и дополнительных JavaScript-моду
-
Естественно, что за балансировку третьей и четвертой стадий отвечает уже JavaScript-разработчик и фронтенд-архитектор — ведь именно в зоне ответственности последнего
- Глава 2. Уменьшение размера
-
2.1. Насколько ресурсоемко архивирование HTML
-
Издержки на использование mod_gzip
-
Формализация модели
-
Набор тестов
-
Результаты тестирования
-
Пара слов о файловой системе
-
Что быстрее: gzip или канал?
- Исследование степени gzip-сжатия и загрузки процессора
-
Рассмотрим далее, насколько сильно издержки на gzip зависят от степени сжатия, и как их прогнозировать с учетом всех остальных параметров. Новая серия тестов была нап
-
Как и ранее, на сервере проводились серии тестов по 10000 итераций в каждом. Замерялось время работы gzip при различных степенях сжатия. Затем оно усреднялось по серии, и
- Рис. 2.5 . Издержки на gzip от степени сжатия
- Далее график эффективности полученного сжатия (в % от оригинального размера файлов) от степени сжатия.
- Рис. 2.6 . Эффективность различных степеней gzip-сжатия
-
Окончательные выводы
-
Конфигурируем Apache 1.3
-
Конфигурируем Apache 2
-
2.2. CSS и JavaScript в виде архивов
-
Статическое архивирование в действии
-
Проблемы для Safari
-
Конфигурируем Apache
-
Маленькие «но»
-
Два слова о nginx
-
2.3. Все о сжатии CSS
-
Инструменты
-
Графические результаты
-
Рис. 2.7 . Эффективность различных инструментов для минимизации CSS-файлов по сравнению с
-
Рис. 2.8 . Эффективность различных инструментов для минимизации CSS-файлов вместе с дополнительным архивированием по сравнению с
-
Рис. 2.9 . Эффективность различных инструментов для минимизации CSS-файлов вместе с дополнительным архивированием, увеличенный масштаб
-
Выводы
-
Практический пример
-
2.4. JavaScript: жать или не жать?
-
Инструменты и методика
-
Графические результаты
-
Рис. 2 .10 . Эффективность различных инструментов для минимизации JavaScript-файлов вместе по сравнению с gzip
-
Рис. 2.1 1 . Эффективность различных инструментов для минимизации JavaScript-файлов вместе с дополнительным архивированием по сравнению с
-
Рис. 2.12 . Эффективность различных инструментов для минимизации JavaScript-файлов вместе с дополнительным архивированием, увеличенный масштаб
-
Промежуточные выводы
-
Есть ли жизнь после сжатия?
-
Скорость загрузки JavaScript-библиотек
-
Методы упаковки JavaScript
- Вариант
- Среднее время
- Уменьшенный
- 519.7214
- Упакованный
- 591.6636
- Нормальный
-
645.4818
-
Производительность загрузки JavaScript-библиотек
- Инструментарий
- Среднее время
- jquery-1.2.1
- 732.1935
- dojo-1.0.1
- 911.3255
- prototype-1.6.0
- 923.7074
- yahoo-utilities-2.4.0
- 927.4604
- protoculous-1.0.2
-
1136.5497
- Инструментарий
- Среднее время
- yahoo-utilities-2.4.0
- 122.7867
- Jquery-1.2.1
- 131.1841
- prototype-1.6.0
- 142.7332
- dojo-1.0.1
- 171.2600
- protoculous-1.0.2
-
276.1929
-
2.5. PNG против GIF
-
Алгоритмы сжатия
-
Возможности PNG
-
Поддержка PNG в браузерах
-
PNG и проблема соответствия для фоновых CSS-изображений
-
Анимированные PNG: MNG против "PNG+"
-
Двигаемся к маленьким PNG
- Полезные советы
-
Ниже приведено несколько простых советов, как текущие изображения можно дополнительно уменьшить в размере. Можно написать простой скрипт, который перебирает дире
- Преобразовывает GIF в PNG (и проверяет, есть ли при этом выигрыш):
- или так
- Уменьшает PNG-файлы в размере:
- если при этом нужно удалить и gAMA-чанк, то:
-
если при этом хотим удалить другие чанки, отвечающие за цветовую коррекцию, то:
- Уменьшает JPEG-файлы в размере (без потери качества):
-
Под Windows для уменьшения PNG-изображений можно использовать
-
Для отдельно взятой страницы общий размер изображений может быть уменьшен на 20–30% только благодаря следованию этим простым советам.
-
2.6. Разгоняем favicon.ico — это как?
-
Краткое описание формата
-
Боевое крещение
-
Оптимальные размеры
- Палитра
- Размер (в байтах)
- 2 бита
- 198
- 4 бита
- 318
- 8 бит
- 1406
- 24 бита
- 894
- 32 бита
-
1150
-
PNG — быть или не быть?
-
А если еще и сжать?
-
data:URI нас спасет?
-
Заключение
-
2.7. Режем cookie
-
Оптимизируем размер, зону и время действия
-
Хостинг для компонентов без cookie
- Глава 3. Кэширование
-
3.1. Expires, Cache-Control и сброс кэша
-
Настройка заголовка HTTP Expires
-
Спецификация кэширования
-
Практическое запрещение кэширования
-
Разрешение кэширования
-
Форсированный сброс кэша
-
3.2. Кэширование в IE: pre-check, post-check
-
Спецификация
- Рассматриваем подробнее
-
Рис. 3.1 . Диаграмма работы pre-check и post-check
-
Пример использования
- 3.3. Last-Modified и ETag
-
Давайте рассмотрим, какие существуют альтернативы прямому ограничению повторных загрузок ресурсов со стороны сервера и сохранению их в пользовательском кэше.
- Last-Modified
-
Дополнительно к заголовку Cache-Control, который предупреждает браузер, что последний может не запрашивать исходный документ с сервера некоторое время, будет полезно пр
-
Как это работает? Дополнительно к периоду кэширования сервер может также отправить заголовок Last-Modified, который будет обозначать время последнего изменения файла на
-
Данная схема позволяет экономить время, затрачиваемое на передачу данных, однако при ее использовании браузер все равно будет устанавливать соединение с сервером
- ETag
-
ETag ( англ . Entity Tags — тэги сущностей) — механизм, который используют браузеры и веб-серверы, чтобы определить, является ли объект, находящийся в кэше браузера, таким же, как соответств
-
Позднее, если браузер хочет определить актуальность компонента, он передает заголовок If-None-Match для передачи ETag обратно на сервер. Если ETag совпадают, ответ от сервера
- Включить ETag для Apache можно, например, следующей директивой в конфигурации:
- При этом ETag будет сформирован из даты изменения файла и его размера.
- Синхронизация ETag и Last-Modified
-
Проблема ETag состоит в том, что обычно они используют атрибуты, специфичные в пределах одного сервера. ETag не совпадут, если браузер загрузит компонент страницы с одн
-
Apache 1.3 и 2 генерирует ETag в формате inode-size-timestamp. Даже если один и тот же файл на разных серверах лежит в одной и той же папке, имеет те же права, размер и время, номер его иногда
-
IIS 5.0 и 6.0 имеют похожий формат ETag: Filetimestamp:ChangeNumber. ChangeNumber — внутренняя переменная IIS для отслеживания изменений в конфигурации самого IIS, и нет гарантии, что эта переменна
-
В результате ETag, которые сгенерирует Apache или IIS для одного и того же файла, будут отличаться на разных серверах. Если ETag не будут совпадать, пользователь не будет пол
-
Если сайт находится только на одном сервере, это не будет большой проблемой. Но если вы используете несколько серверов с Apache или IIS, устанавливающие ETag в соответстви
-
Если вы не получаете всех преимуществ, которые предоставляет ETag, тогда лучше совсем отключить его. Тег Last-Modified позволяет проверять актуальность компонента на основ
- в конфигурационный файл сервера.
-
3.4. Кэширование в iPhone
-
Попадание в кэш
-
Сжатые компоненты
-
После перезагрузки
-
Заключение
- Глава 4. Уменьшение числа запросов
-
4.1. Объединение HTML- и CSS-файлов
-
CSS- файлы в начале страницы
-
Объединение CSS-файлов
-
Практическое решение
-
Два слова об условных комментариях
- 4.2. Объединение JavaScript-файлов
-
Все внешние JavaScript-файлы с сайта можно слить в один большой, загружаемый только один раз и навсегда. Это очень удобно: браузер не делает тысячу запросов на сервер для
-
Как всегда, в бочке меда есть ложка дегтя: в объединенный файл попадает много того, что при первом запросе можно было бы и не загружать. Чаще всего для борьбы с этим п
- Конструктивные предложения
-
Для начала стоит разобрать используемый фреймворк на составные части. JSON — отдельно, AJAX — отдельно, работа с DOM — отдельно, формы — отдельно. После этого задача «вы
-
Информацию о зависимостях между составными частями можно хранить в удобном для автоматического использования виде. (Формы используют функции DOM, JSON — AJAX и так дале
-
Также можно хранить информацию о том, какие именно модули нужны сайту в целом. Используется ли AJAX? Если ли формы? Может быть, какие-то необычные элементы управления?
- Да, естественно, все это можно и нужно автоматизировать.
- В теории
-
С формальной точки зрения, после того как первые два предложения воплощены в жизнь, у нас появляется дерево зависимостей. Например, такое:
-
Дальше мы выбираем непосредственно нужные сайту вершины. Пусть это будут dom.js и animated.pane.js. Теперь это дело техники — обойти получившийся набор деревьев в глубину:
-
... удалить повторяющиеся элементы:
- и слить соответствующие модули воедино.
- На практике
-
Хранить информацию о зависимостях можно, например, следующим образом (добавляя в «модули» служебные комментарии):
-
Выделить подобные метки из текстового файла не составляет труда. Естественно, чтобы получить полное дерево зависимостей, надо будет пройтись по всем доступных фай
- К чему мы пришли?
-
Затратив один раз кучу времени на формирование модулей и зависимостей между ними, мы экономим время
-
Итак, мы оставили нового пользователя наедине с единственным JavaScript-файлом, не включающим ничего лишнего. Стал ли при этом пользователь счастливее? Ничуть. Наоборот
- Немного из теории HTTP-запросов
-
Время загрузки ресурса через HTTP-соединение складывается из следующих основных элементов:
-
Итак, время загрузки страницы будет состоять из времени загрузки HTML-кода и всех внешних ресурсов: изображений, CSS- и JavaScript-файлов. Основная проблема в том, что CSS и JavaS
- Общие временные затраты при этом составят 3(T1+L) + R(A+B+C).
-
Объединяя файлы, мы уменьшаем количество запросов на сервер:
- Очевидна экономия в 2(T1 + L).
-
Для 20 ресурсов эта экономия составит уже 19(T1 + L). Если взять достаточно типичные сейчас для домашнего/офисного Интернета значения скорости в 256 Кбит/с и пинга ~20-30 мс,
-
На первый взгляд, теория говорит, что загрузка страниц должна стать быстрее. В чем же она
- Суровая реальность
-
Пусть у нашего сайта есть три страницы — P1, P2 и P3, поочередно запрашиваемые новым пользователем. P1 использует ресурсы A, B и C, P2 — A, С и D, а P3 — A, С, E и F. Если ресурсы не о
-
Если мы слили воедино абсолютно все JavaScript-модули сайта, получаем:
-
Результатом становится увеличение времени загрузки самой первой страницы, на которую попадает пользователь. При типовых значениях скорости/пинга мы начинаем прои
-
Если мы объединили только модули, необходимые для текущей страницы, получаем следующее:
-
Каждая отдельно взятая страница при пустом кэше будет загружаться быстрее, но все они вместе — медленнее, чем в исходном случае. Получаем, что слепое использование
- Возможное решение
-
Конечно же, выход из сложившегося положения есть. В большинстве случаев для получения реального выигрыша достаточно выделить «ядро» — набор модулей, используемых
-
Вдумчивый читатель сейчас возмутится и спросит: «А что, если ядра нет? Или ядро получается слишком маленьким?». Ответ: это легко решается вручную выделением 2-3 незав
-
Реализация на PHP
-
PHP Speedy
-
4.3. Техника CSS Sprites
-
Простой rollover-эффект
-
Рис. 4.1 . Пример фонового изображения для простого rollover-эффекта. Источник: www.websiteoptimization.com
-
Сложный rollover-эффект
- Рис. 4.2 . Пример фонового изображения для сложного rollover-эффекта. Источник: www.spegele.com
-
Проблемные места в IE
-
CSS Image map
- Рис. 4.3 . Пример изображения для CSS Image Map. Источник: www.acronis.com
-
Статичные картинки
- Рис. 4.4 . Пример фонового изображения «все-в-одном». Источник: webo.in
-
Онлайн-генераторы
-
Полезные советы
-
Рис. 4.5 . Пример фонового изображения с расположением картинок «лесенкой». Источник: webo.in
-
4.4. Картинки в теле страницы с помощью data:URI
-
Поддержка браузерами
-
Схема data:URI
- Рис. 4.6 . Пример изображения, вставленного с помощью data:URI. Источник webo.in
-
CSS и встроенные изображения
-
Проблемы data:URI
-
Работа в Internet Explorer
-
Преимущества и недостатки data:URI
-
Дополнительные соображения по оптимизации
-
Кроссбраузерное использование data:URI
-
О, этот странный Microsoft!
-
Объединяем несовместимое
-
Панацея или ящик Пандоры?
-
Валидность
-
Некоторые итоги
-
Включение музыки (base64)
-
4.5. CSS Sprites и data:URI
-
Проблемы при верстке
-
Рис. 4.7 . Пример CSS Sprites со страницы поиска Google. Источник: www.google.com
-
Проблемы при загрузке
-
Проблемы при использовании
-
Шаг за шагом
- Выносим CSS-файлы в пост-загрузку
-
При использовании data:URI итоговый CSS-файл занимает довольно большой объем (фактически равный 110-120% от размера всех картинок и набору базовых CSS-правил). И это в виде арх
-
Для решения этой проблемы, во-первых, нам нужно разделить весь массив CSS-правил на относящиеся к фоновым изображениям и не относящиеся. Во-вторых, сообщить браузерам
-
Фактически, используя такой подход, мы создаем другой контейнер для фоновых изображений (не ресурсное изображение, а CSS-файл), который удобнее использовать в большинстве случаев. Мы объединяем все фо
- Теоретическое решение
-
Все гениальное просто, поэтому мы можем загружать в самом начале страницы достаточно небольшой CSS-файл (без фоновых изображений, только базовые стили, чтобы только
-
Тут есть и возможные минусы: после загрузки каждого дополнительного CSS-файла будет происходить перерисовка страницы. Однако если таких файлов всего 1 или 2, то отобр
-
Почему мы не может распараллелить загрузку файлов стилей в самом начале документа? Потому что два файла будут загружаться медленнее, чем один (файлы загружаются по
- На практике
-
На практике все оказалось не сильно сложнее. Мы загружаем в head страницы (до вызовов любых внешних файлов) наш «легкий» CSS:
-
а затем добавляем в комбинированный обработчик window.onload (подробнее о нем рассказывается в седьмой главе) создание нового файла стилей, который дополняет уже загрузи
-
В результате мы имеем максимально быстрое отображение страницы, а затем стадию пост-загрузки, которая вытянет с сервера все дополнительные картинки (тут уже сам бр
- А доступность?
-
Внимательные читатели уже заготовили вопрос: а что, если у пользователя отключен JavaScript? Тут всё должно быть просто: мы добавляем соответствующий
-
После небольших экспериментов было выделено следующее изящное решение, обеспечивающее работу схемы во всех браузерах (замечание: после многочисленных эксперимен
-
В результате браузер с включенным JavaScript запишет начало комментария, а закроет его только после
- Делаем решение кроссбраузерным
-
В ходе тестирования в Internet Explorer обнаружилось, что если добавлять файл стилей сразу параллельно со скриптами (в функции, которая для него срабатывает по onreadystatechange), т
-
В Safari же логика отображения страницы в зависимости от загружаемых файлов отличается от всех браузеров. Если в двух словах, то можно жестко определить начальный наб
-
У Safari второй подход, поэтому ничего лучше выноса загрузки динамического CSS-файла с фоновыми картинками после срабатывания window.onload для этого браузера пока не сущест
-
Итак, давайте объявим функцию для создания динамического файла стилей:
- Выигрыш
-
При наличии у вас большого количества маленьких декоративных фоновых изображений, которые к тому же могут повторяться по различным направлениям, может быть очень
-
Описанная техника (кроссбраузерный data:URL плюс динамическая загрузка файлов стилей) позволяет добиться всех преимуществ технологии CSS Sprites, не затягивая загрузку ст
-
Таким образом, data:URI (в смысле влияния на скорость загрузки) равносильны CSS Sprites (или даже предпочтительнее последней, если учесть, что для повторяющихся и полупрозра
- 4.6. Методы экстремальной оптимизации
-
Чем больше число внешних ресурсов, к которым браузер обращается при загрузке, тем больше время требуется для отображения страницы. Как правило, веб-страницы обраща
- Объединение JavaScript и CSS в одном файле
-
Однако существует способ объединения CSS с JavaScript и сведения количества загрузок к одной. Техника основана на том, как CSS и анализатор JavaScript ведут себя в IE и Firefox.
- Рассмотрим на примере
-
Когда анализатор CSS будет разбирать вышеупомянутый код, символы комментария HTML будут пропущены, и код станет эквивалентным следующему примеру:
- Анализатор CSS видит только CSS-код, а код скрипта закомментирован (/* ... */).
-
Когда анализатор JavaScript станет разбирать код, символы комментария HTML будут интерпретированы в комментарии строки (//), и, следовательно, код станет таким:
-
Анализатор JavaScript видит только код скрипта, а все остальное закомментировано. Чтобы ссылаться на этот ресурс, можно использовать теги
-
Заметим, что эти два тега ссылаются на один тот же ресурс и, следовательно, он загрузится всего один раз и будет интерпретирован и как стили, и как скрипты.
-
Есть еще одна вещь, о которой стоит позаботиться, — Content-Type ответа. Его необходимо выставлять в */*, чтобы дать подтверждение Firefox: содержание может быть обработано ка
-
Указанное решение не работает в Safari (1-5% пользователей), однако конкретно для этого браузера (определив его через User-Agent) уже можно вставить загрузку еще одного файла
- Объединение HTML, CSS и JavaScript в одном файле
-
Чтобы избежать дополнительных запросов со стороны браузера, можно включить непосредственно стилей и(ли) скриптов в сам HTML-документ.
-
Здесь стоит остановиться на следующем моменте: если размер CSS- (или JavaScript-) файла больше, чем 20% (и при этом больше 5 Кб в сжатом виде), лучше вынести его как отдельный ко
-
Рассматривать включение всех ресурсов в исходную HTML-страницу стоит только в том случае, если достаточно большой процент посетителей (больше 90%) пришли на нее в перв
-
Во всех остальных случаях — когда можно выделить достаточно большие ресурсные файлы или когда достаточное количество пользователей приходят не в первый раз — так
-
Как рабочий пример можно привести заглавные страницы Яндекса и Google — на них вызывается минимум внешних ресурсов, а стилевые правила включены в саму страницу.
- Глава 5. Параллельные соединения
- 5.1. Обходим ограничения браузера на число соединений
-
Активное ( англ. keep-alive ) соединение стало настоящим прорывом в спецификации HTTP 1.1: оно позволяло использовать уже установленный канал для повторной передачи информации от клиента к серве
-
Однако HTTP 1.1 добавил веб-разработчикам головной боли по другому поводу. Давайте будем разбираться, как нам устранить и эту проблему.
- Издержки на доставку объектов
-
Средняя веб-страница содержит более 50 объектов, и издержки на число объектов доминируют над всеми остальными задержками при загрузке большинства веб-страниц. Брау
-
Если число объектов на странице превышает 4, то издержки на ожидание доступных потоков и разбор чанков для присланных объектов превалируют над общим временем загру
-
При увеличении числа подключаемых объектов сверх 10 время, затрачиваемое на инициализацию соединения, возрастает до 80% и более от общего времени, уходящего на получ
-
Ограничения спецификации HTTP/1.1
-
Спецификация HTTP, приблизительно 1999 года, рекомендует, чтобы браузеры и серверы ограничивали число параллельных запросов к одному хосту двумя. Эта спецификация был
-
Времена меняются
-
«Режем» соединения
-
Вы можете, естественно, настроить несколько серверов для обслуживания выдачи картинок или других объектов, чтобы увеличить число параллельных загрузок. Например:
-
Однако каждый из этих поддоменов не обязан находиться на отдельном сервере.
-
Реальный выигрыш
- Подводим итоги
-
Сейчас средняя веб-страница состоит более чем из 50 объектов (для Рунета, по статистическим данным
-
При этом нужно помнить, что увеличение одновременных запросов повлечет задействование дополнительных ресурсов со стороны сервера (это может быть, например, как ма
-
Стоит коснуться еще одного, весьма интересного момента в оптимизации времени загрузки путем увеличения числа параллельных потоков. Заключается он в выравнивании
-
Можно пойти и дальше и загружать, например, 4 картинки по 50 Кб в 4 потока, достигая просто феноменального ускорения. Однако тут играет роль психологический фактор: по
-
Стоит подчеркнуть, что данный подход применим и к другим ресурсным (в том числе и HTML) файлам, однако стоит помнить о весьма жестких ограничениях браузеров на загрузк
-
5.2. Content Delivery Network и Domain Name System
-
Подключаем CDN
-
Yahoo! и Google
-
Количество DNS-запросов
- 5.3. Балансировка на стороне клиента
-
Балансировка нагрузки повышает надежность веб-сайта путем распределения запросов между несколькими (кластером) серверами, если один из них перегружен или отказал
- Round-Robin DNS
-
Популярным, хотя и очень простым подходом для балансировки запросов является циклический DNS. Он подразумевает создание нескольких записей в таблице DNS для одного д
-
После каждого пользовательского запроса к таблице DNS для www.loadbalancedwebsite.ru, запись, стоящая первой, меняется. Ваш браузер будет использовать первую запись, поэтому все
-
Можно, конечно, перенести IP-адрес на соседний сервер, который может нести нагрузку. Однако данная процедура весьма хлопотная, чтобы проводить ее в условиях аврала.
- Балансировка на сервере
-
Другим популярным подходом для балансировки запросов является создание одного выделенного сервера, который отвечает за распределение запросов. Примерами таких с
- Балансировка на стороне клиента
-
Существует еще один подход для распределения нагрузки на серверы от современных веб-приложений, который не нуждается в дополнительном балансирующем оборудовании
-
Вместо того чтобы сказать клиенту, что у нас единственный сервер, можно сообщить о нескольких серверах — s1.loadbalancedsite.ru, s2.loadbalancedsite.ru и так далее. При этом клиентское п
-
В отличие от веб-приложений, которые хранят код (Javascript или Flash) на одном сервере, обеспечивающем доступ к этой информации, клиентское приложение не зависимо от серве
- Рис. 5 .3 . Пример балансировки нагрузки и масштабируемости на клиенте
-
Итак, можно ли эту технику применить к веб-приложениям? Веб-приложения самой своей сутью размывают границу между клиентской и серверной частями любого стандартног
-
Сейчас сервер обеспечивает такие ресурсы, как картинки. Но этот факт становится не столь очевидным, если рассмотреть технику CSS Sprites, когда одна картинка является ис
-
Для обеспечения балансировки на стороне клиента от современного веб-приложения требуется три основных составляющих:
-
Заметно проще повысить доступность и масштабируемость HTML-кода страниц и других файлов, требуемых на клиенте, чем осуществить то же самое для серверных приложений:
-
Мы можем включить список доступных серверов в клиентский код точно так же, как сделали бы это для настольного приложения. У веб-приложения доступен файл servers.xml, в ко
- Осуществляем кросс-доменные запросы
-
Даже при небольшом опыте работы с AJAX уже должно было возникнуть закономерное возражение: «Это не будет работать из-за кроссдоменной безопасности» (для предотвраще
-
Для обеспечения безопасности пользователей веб-браузеры и Flash-клиенты блокируют пользовательские вызовы к другим доменам. Например, если клиентский код хочет обра
-
Для Flash-клиентов можно просто создать файл crossdomain.xml, который будет разрешать запросы на *.loadbalancedwebsite.ru:
-
Для клиентского кода на AJAX существуют жесткие ограничения на передачу данных между доменами, которые зависят от методов, используемых для серверных вызовов. Приме
-
Но что, если на клиенте используется XMLHttpRequest? XHR попросту запрещает клиенту запрашивать отличный от исходного домена сервер. Однако существует небольшая лазейка: е
-
А если все же AJAX?
- Проблема решена.
- Преимущества балансировки на стороне клиента
-
Итак, как только мы обговорили технику, позволяющую осуществлять кроссдоменные вызовы, можно обсудить, собственно, как работает сам балансировщик и как он удовлетв
-
Подведем итог: каковы же преимущества балансировки на стороне клиента перед балансировкой на стороне сервера? Наиболее очевидное заключается в том, что не требует
-
Другим преимуществом является то, что все серверы не обязаны быть расположенными в одном месте. Клиент сам выбирает, к какому серверу ему лучше подключиться, в отли
- Используем Cloud Computing для балансировки на стороне клиента
-
В качестве серверной основы приложения можно рассмотреть сервисы Simple Storage Service (S3) и Elastic Computing Cloud (EC2) от
-
Изначально сервис S3 предоставлял прекрасную возможность для хранения и доставки видеосообщений, а EC2 был спроектирован именно для работы с S3. Он позволяет расширят
-
Однако большим минусом для EC2 является невозможность проектирования балансировки нагрузки на стороне сервера, у которого не было бы уязвимых мест. Многие веб-прило
- Пример приложения
-
При использовании описанной выше балансировки на стороне клиента становится возможным избежать этого неприятного момента и существенно повысить надежность всег
-
Чуть раньше указывалось на использование файла servers.xml для оповещения клиента о доступных серверах, но для S3 можно использовать более простой способ. При обращении
-
Например, по адресу http://s3.amazonaws.com/application/?actions=loadlist будет находиться следующий файл:
- В этом примере присутствуют два EC2-сервера в кластере, с IP-адресами 216.255.255.1 и 216.255.255.2 соответственно.
-
Логика для скрипта, запускающегося по расписанию
-
Так как скрипт, запускающийся по cron, является частью виртуальной машины EC2, каждая такая машина автоматически регистрируется как доступный сервер в кластере. Клиен
-
Если виртуальная машина EC2 отказывает или выключается, то другие машины самостоятельно убирают ее запись из сегмента: в сегменте остаются только доступные серверы
-
5.4. Редиректы, 404-ошибки и повторяющиеся файлы
-
Редиректы
-
404- ошибки
-
5.5. Асинхронные HTTP-запросы
-
Моделируем параллельные запросы
- Рис. 5.4 . Влияние HTTP - конвейера и постоянного соединения на скорость передачи данных. Источник: www.die.net
-
Предварительные выводы
-
Рис. 5.5 . Выигрыш при включении постоянного соединения и нескольких хостов для различных пользователей. Источник: www.die.net
-
Влияние заголовков
-
Рис. 5.6 . Влияние заголовков на эффективную пропускную способность канала
-
5.6. Уплотняем поток загрузки
- Реальная ситуация
-
Рис. 5.7 . Диаграмма загрузки (неизмененного) сайта WebHiTech .
-
Шаг первый: простая страница
-
Шаг второй: уменьшаем изображения
-
Шаг третий: все-в-одном
-
Шаг четвертый: нарезаем поток
-
Шаг пятой: алгоритмическое кэширование
-
Итоговая таблица
- Номер шага
- Описание
- Общий размер (кб)
-
Время загрузки (мс)
-
Шаг шестой: балансируем стадии загрузки
-
Шаг седьмой: балансируем кэширование
-
Заключение
- Глава 6. CSS оптимизация
-
6.1. Оптимизируем CSS expressions
-
CSS- выражения
-
Динамические выражения
-
Вычисление постоянных
-
Использование
-
Реализация
-
Все так просто? Нет, еще проще
-
6.2. Что лучше, id или class?
-
Методика. Размер файлов
-
Время открытия страницы
-
Результаты
- Firefox 2
- Opera 9.5
- Safari 3
- IE 7
- IE 6
- IE 5.5
- p.class
- .class
- p#id
- #id
- div > p.class
- div > .class
- div > p#id
- div > #id
- div p.class
- div .class
- div p#id
- div #id
- div.div p.class
- div.div .class
- div.div p#id
-
div.div #id
-
Выводы
- 6.3. Влияние семантики и DOM-дерева
-
Давайте рассмотрим сейчас другой вопрос, а именно: как быстро браузер создает DOM-дерево в зависимости от наличия в нем элементов с id или class?
-
Для этого мы подготовим 3 набора HTML-файлов. Первый будет содержать 10000 элементов, у которых только часть будет иметь id (количество именованных элементов варьируется
- Графики влияния DOM-дерева
-
Ниже приведены разделенные графики по средневзвешенному (естественно, основную роль играет Internet Explorer, ибо сейчас им пользуются от 50% до 70% посетителей наших сайтов)
- Рис. 6.1 . Скорость создания документа, средневзвешено по всем браузерам
-
и график для времени выборки одного элемента из дерева (по идентификатору) при наличии в этом же дереве различного числа элементов с идентификаторами. ID (10000 get) пока
- Рис. 6.2 . Скорость выбора элемента, средневзвешено по всем браузерам
- Выводы по DOM-дереву
-
По графику средневзвешенных значений хорошо видно, что при прочих равных условиях создание документа с class обходится меньшей кровью, чем с id (в общем случае от 2% до 1
-
Для документа со 100 элементами выигрыш может составить почти 1 мс, для документа с 1000 — 8,5 мс! Стоит заметить, что средняя страница в интернете имеет 500–1000 элементов.
- javascript:alert(document.getElementsByTagName(*).length)
- Естественно, что приведенные цифры — это уже то, за что можно побороться.
-
В случае больших веб-приложений задержка в 100 мс (при числе элементов более 10000) уже может оказаться критичной. Ее можно и нужно уменьшать (наряду с другими «узкими» м
-
Что и требовалось доказать: значительную нагрузку составляет именно создание DOM-дерева в документе. В целом, на эту операцию уходит от 70% всего времени рендеринга (т
-
На скорость вычисления одного элемента по идентификатору, как ни странно, наибольшее влияние оказывает опять-таки DOM-дерево, а не количество таких элементов. Даже п
- Семантическое DOM-дерево
-
Логическим продолжением уже проведенных исследований CSS/DOM-производительности браузеров стало рассмотрение зависимости времени создания документа от числа тегов
- В итоге мы получили примерно следующую картину:
- Рис. 6. 3 . Средневзвешенное значение времени создания документа от числа узлов в DOM-дереве
- Что быстрее?
-
Да, очевидно, что размер DOM-дерева влияет на скорость загрузки страницы. Одной из целей данного исследования было показать, как именно влияет (в конкретных числах). С
-
Различия между линейной и древовидной структурами находятся в пределах погрешности, однако семантическое дерево оказалось самым медленным (чуть ли не на 50%). Но в л
-
Конечной же целью всех экспериментов было установить, есть ли различие в отображении HTML 4.0 Transitional и XHTML 1.0 Strict документов и какова реальная польза от использования с
- Методика для DOCTYPE
-
Была аккуратно выкачана главная страница Яндекса (она уже хорошо оптимизирована с точки зрения производительности, поэтому проводить эксперименты на ней весьма п
-
Далее была добавлена стандартная схема измерения загрузки (рендеринга) страницы: время в самом начале head засекается и затем отнимается от времени срабатывания соб
-
В качестве второй версии страницы бралось приведение ее к валидному XHTML Strict виду. Верстка при этом немного изменилась, но в целом результат получился весьма убедите
-
Далее в третьей версии уже были убраны все onclick. Больше ничего со страницей не делалось. Ожиданий данная версия не оправдала (только Safari показал значимые отличия от
-
В четвертом варианте — венце оптимизационных (в отношении CSS/HTML-кода) действий — использование id было сведено к минимуму, все селекторы для class задавались без тегов
- Результаты оптимизации
-
В таблице приведены результаты для основных браузеров (август 2008): размер каждого варианта в байтах и время его загрузки. Времена приведены в миллисекундах.
- Size (b)
- Gzip (b)
- IE6
- IE7
- IE8b
- Firefox 2
- Firefox 3
- Opera 9.5
- Safari 3.1
- 1
- 26275
- 8845
- 56
- 80
- 76
- 130
- 127
- 142
- 33
- 2
- 27173
- 8993
- 60
- 75
- 320
- 127
- 118
- 148
- 27
- 3
- 26260
- 8949
- 61
- 75
- 320
- 131
- 116
- 141
- 23
- 4
- 26153
- 8862
- 55
- 73
- 306
- 94
- 102
- 178
-
28
- «Экономия на спичках»?
-
В результате тестов удалось показать, что валидный XHTML не медленнее (а даже местами быстрее), чем HTML. И оптимизация реально играет роль (возможно ускорение загрузки H
-
Естественно, это все «копейки» для обычных пользователей (+/-50 мс —это совершенно не критично). Однако если речь идет про «экономию на спичках», когда нам важен кажды
-
И что важнее всего, если правильно расставить акценты, то загрузку XHTML можно сделать и быстрее, чем HTML. Различие в размере файлов оказалось в итоге минимальным (26153 пр
-
В целом в свете тотального распространения мобильных браузеров с их маломощными процессорами такой вид оптимизации выглядит весьма перспективно.
-
6.4. Ни в коем случае не reflow!
-
offsetHeight и style.display
- IE sp62
- IE8b
- Firefox 2.0.0.12
- Opera 9.22
- Safari 3.04b
- clean
- 128
- 153
- 15
- 15
- 16
- offsetHeight
- 23500
- 10624
- 4453
- 4453
- 5140
- style.display
- 171
- 209
- 56
- 56
- 34
- height vs. style
- 140 раз
- 50 раз
- 80 раз
- 80 раз
-
150 раз
-
Немного теории
-
Использование Computed Style
- IE sp62
- Firefox 2.0.0.12
- Opera 9.22
- Safari 3.04b
- offsetHeight
- 23500
- 4453
- 4453
- 5140
- style.display
- 171
- 56
- 56
- 34
- getStyle
- 5219
-
5318
-
Оптимизация: определение класса hide
- IE sp62
- Firefox 2.0.0.12
- Opera 9.22
- Safari 3.04b
- offsetHeight
- 23500
- 10624
- 4453
- 5140
- isHidden
- 231
- 351
- 70
- 71
- isHidden2
- 370
- 792
- 212
- 118
- offsetHeight vs. isHidden
- 102 раза
- 30 раз
- 73 раза
-
92 раза
-
Заключение
-
В качестве послесловия: стили или классы?
- Метод
- IE 6
- IE 7
- Firefox 1.5
- Firefox 2.0
- Opera 9
- element.className
- 512
- 187
- 291
- 203
- 47
- element.style.color
- 1709
- 422
- 725
- 547
- 282
-
Перерисовка страницы
-
Групповое изменение стилей
-
Два слова о таблицах
- Глава 7. Оптимизация JavaScript
-
7.1. Кроссбраузерный window.onload
-
Firefox впереди планеты всей
-
А Internet Explorer?
-
Условные комментарии
-
Все так просто?
-
Двойное выполнение
-
Избавляемся от внешнего файла
-
Полное решение
-
Неблокирующая загрузка JavaScript
-
Число загрузок с одного хоста
-
Неблокирующие скрипты
-
Зависимости
-
А если по-другому?
- Метод
-
Недостатки
-
В будущем
-
7.2. Основы «ненавязчивого» JavaScript
-
Javascript: храним отдельно
-
Javascript — это расширение
-
Доверять, но проверять
-
Доступ к элементам
-
Полезные советы
-
Добавляем обработчики событий
-
Ускоряем обработку событий
-
Немного усложним
-
Боремся с Internet Explorer
-
Пойдем дальше
- Обработка событий в браузерах
-
Давайте рассмотрим несколько практических способов работы с обработчиками событий в браузерах. Например, можно назначить обработчик напрямую:
-
Если нужно несколько событий или просто «осторожничаем», то можно воспользоваться следующей распространенной записью:
-
Или таким модицифицированным вариантом (меньше символов):
-
Можно также использовать отдельную переменную для обработчика события:
-
Или записать в одну строку с использованием условной компиляции:
- Работаем с событиями
-
Давайте рассмотрим, что мы можем извлечь из события после перехвата его с помощью соответствующего обработчика:
- 7.3. Применение «ненавязчивого» JavaScript
-
В предыдущих разделах были представлены некоторые теоретические аспекты построения клиентской логики, ориентированной на максимальное быстродействие и адекватн
-
Принципы «ненавязчивой» рекламы
-
document.write против innerHTML
-
Контекстная реклама
-
TopLine, Pop-Up, Pop-Under и RichMedia
-
Внутренние рекламные сети
-
Идеальная архитектура рекламной сети
-
Разгоняем счетчики: от мифов к реальности
-
Разбираем по косточкам
-
А если сложнее?
-
Делаем статистику динамической
-
7.4. Замыкания и утечки памяти
-
Шаблоны утечек
-
Циклические ссылки
-
Более сложный случай
-
Замыкания
-
Постраничные утечки
-
Псевдо-утечки
-
Проектируем утечки
-
7.5. Оптимизируем «тяжелые» JavaScript-вычисления
-
Оптимизируем вычисления
-
Улучшаем шаблон
-
Советы и замечания
-
Заключение
-
7.6. Быстрый DOM
-
DOM DocumentFragment: быстрее быстрого
-
Нормальное добавление
-
Добавление при помощи DocumentFragment
- Браузер
- Нормальный
- Fragment
- Firefox 3.0.1
- 90
- 47
- Safari 3.1.2
- 156
- 44
- Opera 9.51
- 208
- 95
- IE 6
- 401
- 140
- IE 7
- 230
- 61
- IE 8b1
- 120
-
40
-
А если еще быстрее?
-
innerHTML нам поможет
-
Его можно значительно ускорить, если добавлять узлы не последовательно один за другим, а сначала создав HTML-строку со всем необходимым кодом, которая будет вставлен
-
В данном примере кроме уже указанного ускорения еще используется первоначальное создание массива элементов, которые можно объединить через свойство join в строку. Д
-
7.7. Кэширование в JavaScript
-
Итерации и локальное кэширование
-
Кэширование ресурсоемких вызовов
-
Кэшируем цепочки вызовов
-
7.8. Быстрые итераторы, регулярные выражения и другие вкусности
-
Итераторы
- Браузер
- Обычный
- С кэшем
- for-in
- Обратный
- do-while
- Обратный while
- Firefox 3.0.3
- 714
- 657
- 835
- 280
- 297
- 217
- Safari 3.1.2
- 141
- 140
- 157
- 125
- 125
- 93
- Opera 9.61
- 188
- 125
- 765
- 94
- 94
- 78
- IE 6
- 1281
- 1219
- 1094
- 468
- 500
- 360
- IE 7
- 1391
- 1297
- 1250
- 515
- 532
- 406
- IE 8b2
- 954
- 906
- 922
- 406
- 422
- 328
- Chrome 0.2
- 288
- 246
- 332
- 117
- 114
-
95
-
Регулярные выражения
- Браузер
- search
- match
- «На лету»
- Локальный
- exec
- test
- Firefox 3.0.3
- 2120
- 2041
- 1295
- 1273
- 1225
- 1348
- Safari 3.1.2
- 453
- 469
- 344
- 359
- 360
- 359
- Opera 9.61
- 2141
- 2063
- 406
- 344
- 312
- 313
- IE 6
- 2594
- 2516
- 1875
- 1859
- 1953
- 1906
- IE 7
- 2562
- 2469
- 1859
- 1844
- 2000
- 1860
- IE 8b2
- 2140
- 2032
- 1453
- 1453
- 1547
- 1469
- Chrome 0.2
- 856
- 870
- 416
- 397
- 385
-
392
- Глава 8. Приложение
-
8.1. Обзор аналитических инструментов
-
Измеряем эффективную ширину канала пользователей
-
Apache Benchmark и JMeter
-
Кэширование на сервере
-
Web Developer Toolbar для Firefox
-
Firebug NET Panel для Firefox
-
Yslow для Firebug для Firefox
-
Web Inspector для Safari
-
HttpWatch
-
Fiddler
-
Live HTTP Headers
-
Прокси-эмулятор каналов Sloppy
-
Analyze.WebSiteOptimization.com
-
Octagate.com/service/SiteTimer/
-
Tools.Pingdom.com
-
AlertSite.com
-
Site-Perf.com
-
GetRPO.com
-
Webo.in
-
Профилирование JavaScript
- 8.2. Несколько советов для браузеров
-
Ускоряем загрузку страниц в Firefox 3
-
Как это работает?
-
Ускоряем загрузку страниц в Opera 9
-
Interner Explorer
- 8.3. Оптимизированные конфигурации
-
Конфигурация Apache 1.3
-
Конфигурация Apache 2
-
Конфигурация nginx 0.7+
-
Настройка IIS
-
8.4. Разбор полетов
-
vkontakte.ru
-
odnoklassniki.ru
-
yandex.ru
-
rambler.ru
-
mail.ru
-
rbc.ru
-
lenta.ru
-
kommersant.ru
-
marketgid.ru
-
habrahabr.ru
- Заключение
-
В качестве послесловия
|
|
Полный текст книги (читать онлайн): Разгони свой сайт
Скачать эту книгу (3535k) в формате: fb2, epub, mobi, txt, html
|
Комментарии