Вопрос, стоит ли хранить javascript и css ресурсы веб-страницы в LocalStorage браузера или позволить ему самому отрабатывать кэширование, не имеет однозначного ответа. Есть плюсы и минусы. С моей точки зрения, основной плюс — скорость загрузки — перевешивает все остальное. Это очень хорошо чувствуют пользователи EDGE и 3G.
Для поклонников стандартного кэша браузеров, гордо показывающих на слово «Кэшировано» в Средствах разработчика, советую открыть Fiddler и увидеть, что по каждому кэшированному ресурсу за 304 HTTP ответом всё равно идет запрос. Затем советую зайти на что-нибудь типа pingdom.com и увидеть, что непосредственно передача данных во всем времени исполнения запроса занимает проценты. То есть толку в абсолютном значении от такого кэширования — кот наплакал, особенно если файлы небольшие.
Предлагаемая схема хранения ресурсов в LocalStorage достаточно проста.
Во-первых, реализовано отслеживание изменения файлов, для этого используется время последнего изменения файла как его «версия». При его изменении ресурс перегружается в локальное хранилище.
Ресурс линкуется следующим образом:
<script type="text/javascript">
<?php include("ls.js"); ?>
requireResource(
'mobile.css',
'css',
'<?php echo filemtime(__DIR__ . "/css/mobile.css") ?>',
'<?php echo "/css/mobile.css?" . filemtime(__DIR__ . "/css/mobile.css") ?>');
</script>
Функции requireResource передается название ресурса (под этим именем он пойдет в локальное хранилище), тип, версия и url ресурса. Логика кода ls.js простая — если ресурс есть в хранилище и версия его совпадает с указанной — инлайнится он. Если нет — грузится заново, помещается в хранилище и инлайнится в HTML код.
function _cacheResource(name, t, version, url) {
var xmlhttp = new XMLHttpRequest(); // code for IE7+, Firefox, Chrome, Opera, Safari
xmlhttp.onreadystatechange = function() {
if (xmlhttp.readyState == 4) {
if (xmlhttp.status == 200) {
localStorage.setItem(name, JSON.stringify({
content: xmlhttp.responseText,
type: t,
version: version
}));
} else {
console.warn('error loading '+url);
}
}
}
xmlhttp.open("GET", url, true);
xmlhttp.send();
}
function _loadResource(url, type, name, version, callback) {
if (type == "js") {
document.write('<script id="' + name + '" src="', url, '"></script>n');
} else if (type == "css") {
document.write('<link id="' + name + '" rel="stylesheet" href="', url, '" />n');
}
var s = document.getElementById(name);
if (s.readyState) { //IE
s.onreadystatechange = function() {
if (s.readyState == "loaded" || s.readyState == "complete") {
s.onreadystatechange = null;
_cacheResource(name, type, version, url);
if (callback) callback();
}
};
} else { //Others
s.onload = function() {
_cacheResource(name, type, version, url);
if (callback) callback();
};
}
}
function _injectResource(content, url, name, version, callback) {
var c = JSON.parse(content);
// cached version is not the request version, clear the cache, this will trigger a reload next time
if (c.version != version) {
localStorage.removeItem(name);
_loadResource(url, c.type, name, version, callback);
return;
}
if (c.type == "js") {
var s = document.createElement('script');
s.type = "text/javascript";
} else if (c.type == "css") {
var s = document.createElement('style');
s.type = "text/css";
}
var scriptContent = document.createTextNode(c.content);
s.appendChild(scriptContent);
document.getElementsByTagName("head")[0].appendChild(s);
if (callback) callback();
}
function requireResource(name, type, version, url, callback) {
var c = localStorage.getItem(name);
if (c == null) {
_loadResource(url, type, name, version, callback);
} else {
_injectResource(c, url, name, version, callback);
}
}
Есть выбор как инлайнить код — или через document.write(); или через вставку элемента в DOM. В первом случае мы получаем копию того, как если бы ресурс подцеплялся самой страницей. Второй, в свою очередь, логически более правильный, но есть минусы — js код не исполняется, например. Нужно в ручном режиме проводить инициализацию — что несложно, но нужно об этом помнить.
Еще один момент — очередность загрузки ресурсов. Если нужен определенный порядок, это нужно предусмотреть — или компоновкой ресурсов в этом порядке в один файл, или синхронной загрузкой.
В данном примере используется 2 типа ресурсов — js и css. В принципе, можно расширить на всё, что можно сериализовать в локальное хранилище браузера.
Результат очевиден — мои css и js файлы больше в Средствах разработки браузера и Fiddler'e не появляются (если не происходит их обновление на сервере). Сайт открывается ощутимо быстрей. Процесс js и css дебага на продакшне затрудняется не сильно. А на девелопмент машине все js и css файлы грузятся по отдельности для удобства работы.
Нужно учитывать, что средства замера скорости и производительности сайта типа Google Pagespeed не понимают такого самодельного кэширования и не покажут прироста производительности.
Основа js кода взята с GitHub и доработана.
Автор: altrus