Website development
PHP
CSS
JavaScript
Browsers
April 2018 28

Сохранение JS и CSS ресурсов в Локальном хранилище браузера

From Sandbox
Вопрос, стоит ли хранить 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 и доработана.
+1
9.7k 81
Comments 114
Top of the day