Pull to refresh

Кеширование всего HTML и подключение JS «на ходу»

Reading time5 min
Views16K
Однажды я совершил страшное дело – посмотрел исходный код главной страницы Яндекса и задумался – что-то тут не так. Я вижу 5 заголовков последних новостей, несколько картинок, поле поиска, ссылочки и вот вопрос:
Где тут 700Кб, которые требуют 1,56с моего времени? (по данным веб-инспектора Safari 7.0.3)

Все мы ходим по сайтам, получаем килобайт полезной для себя или вообще воспринимаемой человеком информации и при скорости в 3Мбит/с тратим на это секунды. В этом посте я постараюсь описать, как можно передавать только тот килобайт, который нужен человеку, а все остальное либо кешировать, либо вообще грузить только когда понадобится.

image

Первый на очереди к оптимизации – HTML


Было бы неплохо кешировать весь HTML или хотя бы его большую часть – для этого можно использовать блочное кеширование: страницу разбиваем на блоки и эти блоки выносим в отдельный JS файл, который потом остается у пользователя. Плюс такого подхода в простоте, но в нашем случае будет нетривиальное решение – надо все немного усовершенствовать.

Страницу можно не просто разбивать на блоки, а все в body заменить на XML структуру, соблюдая все отношения родительства – радикально и эффективно. Никаких стилей и излишеств. Для примера возьму код Яндекса для вывода 5-ти новостей:

<div class="b-tabs__items">
<div id="tabs088905610376_0">
<ul class="b-news-list">
<li class="b-news-list__item">
<span class="b-news-list__item_num">1.</span>
Главная военная прокуратура <a class="b-news-list__item-link" href="http://news.yandex.ru/yandsearch?cl4url=www.rg.ru%2F2014%2F05%2F06%2Fserdukov-anons.html&lang=ru&lr=172" onmousedown="cp('v12.news.news.links.1',this)">признала законной амнистию Сердюкова</a>
</li>
...
</ul></div></div>


Получилось следующее:

<news>
<tab ids="tabs088905610376_0">
<newsr num="1" pre="Главная военная прокуратура" src="http://news.yandex.ru/yandsearch?cl4url=www.rg.ru%2F2014%2F05%2F06%2Fserdukov-anons.html&lang=ru&lr=172" onmd="cp('v12.news.news.links.1',this)" ahr="признала законной амнистию Сердюкова">
...
</tab>
...
</news>


Каждая новость теперь становится одним тегом. Если подобным образом представить всю страницу, то получится как раз некая помесь HTML и XML, можно назвать это [X]HTML, потому что фактически это HTML, только теперь он расширяемый – в прямом смысле этого слова. Допустим страница представлена в XML, теперь для каждого блока я добавляю в JS файл его шаблон – то, как он должен выглядеть на самом деле. По аналогии с PHP я выделил '$' в шаблонах переменные, которые надо брать из атрибутов и $HTML$ – innerHTML тега.

var news='<div class="b-tabs__items">$HTML$</div>';
var tab='<div id="$ids$"><ul class="b-news-list">$HTML$</ul></div>';
var newsr='<li class="b-news-list__item"><span class="b-news-list__item_num">$num$.</span> $pre$ <a class="b-news-list__item-link" href="$src$" onmousedown="$onmd$">$ahr</a></li>';


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

Оптимизация JS


Если JS файл весит 100-200Кб, то его загрузка это около 0,5с. Он загрузится и останется в кеше, а вот задержка перед его выполнением все равно останется – это связано с тем, что JS файл сначала загружается, потом весь анализируется и только после этого начитает выполняться. Такая особенность позволяет, например, вызвать функцию еще до ее объявления. Чем больше файл – тем дольше аналаз, до 300мс, а иногда и больше. Есть очень хороший способ сократить время — сделать файл дискретным: разбить на отдельные функции или просто части и подключать их, когда они нужны. Они так же будут кешироваться, только время их анализа будет гораздо меньше – в пределах 1-2мс.

Пусть у нас есть функция, которая в реальном времени подключает JS файлы, например, вот такого вида:

inc({
	//список подключаемых функций/файлов вида name:'id' – name-название функции (для вызова), id-постфикс для файла
},function() {
	//JS с использованием подключенных функций и файлов
});


Для нее есть несколько требований. Во-первых, она должна запоминать уже подключенные функции и файлы, во-вторых поддерживать загрузку из разных каталогов, а так же быть асинхронной. Для указания каталога можно использовать глобальную переменную includesrc=«любойсайт.ру/каталог» для подключения файлов и «любойсайт.ру/каталог/функция_» для функций – к этому пути будет добавляться id (постфикс). Допустим, что надо разбить файл на несколько частей или функций, тогда полученные файлы должны быть примерно такого вида:

//если файл с одной функцией, то
ludes["id"]=function(a1, .., aN){
//код
}

//если с несколькими:
//функции (просто часть исходного файла)
ludes["id"]++;//id - это имя файла, без расширения


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

ESS


Выше я написал, как можно вынести весь HTML и кешировать, передавать только нужную информацию в XML виде, потом написано, как можно свести задержку перед выполнением JS к нескольким миллисекундам, но нет самого решения проблемы – вот оно. Его можно назвать ESS – Easy Stylesheets, по аналогии с CSS. Состоит из 2+1 функций. Для ESS есть документация, в которой описан синтаксис и наглядные примеры использования.

Вспомогательная функция g(a, b, c); – ищет объект по строчкам CSS вида. a — с каким tag/#id/@ name/.class искать объект, b — в каком объекте искать и с — флаг 0/1, указывающий, надо ли в любом случае возвращать массив.

ess(a, b, ch, f); – функция, заменяющая XML блоки на странице шаблонами из JS файла, ей надо передать a — элемент DOM и b — строчку-шаблон, ch — символ, которыми выделены переменные и f — функцию для вызова после завершения. Для примера выше это:
ess(g('newsr'), newsr, '$', function() {
ess(g('tab'), tab, '$', function() {
ess(g('news'), news);
});});


inc(); – функция, которая позволяет асинхронно подключить любой файл, с любого домена и затем продолжить работу. На ней следует остановиться подробнее. Вот пример из документации для подключения файла:
//для подключения файла
includesrc='http://fous.name/ess/';//каталог с файлом
inc({
	file1:'file1.js'
}, function() {
	ggg();
});

//содержимое file1.js
function ggg() {
	alert('Файл подгрузился и все функции готовы к вызову.');
}
ludes["file1"]++;

Указывается полный путь к каталогу и имя файла – очень похоже на require.js. Отличие в том, что подключать можно не только файлы, но и функции:
includesrc='http://fous.name/ess/function_';
inc({
	goodjob:'1'// "1" - постфикс, который совпадает с индексом массива ludes в подключаемом файле
}, function() {
	goodjob();//вызов подключенной функции
});

//содержимое файла function_1.js
ludes["1"]=function(){alert('Функция загрузилась и успешно выполнилась.');}

Это тоже пример из документации, но ни по одному примеру не видно преимуществ использования такого подхода – для этого нужно хорошее Демо и подробный сравнительный анализ, которые будут в следующей статье. В ней я постараюсь учесть все Ваши советы и замечания, так как это лишь моя первая статья – из песочницы.
Tags:
Hubs:
If this publication inspired you and you want to support the author, do not hesitate to click on the button
-14
Comments19

Articles