Ресурсы в архиве или как уменьшить количество подгружаемых файлов

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

    Очень часто веб-разработчики сталкиваются с проблемой большого количества мелких файлов. Картинки, скрипты, css — неудобно, все дела. Нужно как-то бороться с этим. import, спрайты, блаблабла — это хорошо, но можно попробовать и иначе. Мой вариант — упаковка всех необходимых ресурсов в архив на стороне сервера(реализация — на php), получение данных на клиенте, установка ресурсов в нужных местах.

    Проблемы на данный момент: не придумал толком, как кешировать полученный архив, таким способом не стоит паковать динамический контент или большие файлы.
    Для распаковки архива на клиенте использована библиотека JSZip, для удобства — jQuery.



    Итак, посмотрим на код.

    ziplogic.php:

    class ZipLogic extends ZipArchive{
            public $filename;
            public function __construct() {
                $filename = "current/latest.zip";
                $this->filename = $filename;
                if(file_exists($filename))                
                    unlink($filename);
                
                if ($this->open($filename, ZIPARCHIVE::CREATE)!==TRUE) {
                    $error = error_get_last();
                    echo $error["message"];
                    return null;
                }
            }
            
            public function pack($url){
                $this->addFile($url);
            }
        }
    


    Этот класс наследуется от ZipArchive и нужен только для чуть более удобной работы с архивом.

    Теперь — код для упаковки нужных ресурсов:

    index.php:

        include "ziplogic.php";
        $zpack = new ZipLogic();
        if($zpack==null)
        {
            echo 'wrong data';
            return;
        }
        $zpack->pack('img/screen.png');
        $zpack->pack('img/screen_1.png');
        $zpack->pack('img/screen_2.png');
        $zpack->pack('css/alotofstyles.css');
        $zpack->pack('js/cooljs.js');
        $zpack->close();
        include 'content.html';
    


    Тут, собственно, тоже нет ничего особо интересного: создаем объект ZipLogic, перечисляем все ссылки на ресурсы, которые нужно упаковать, закрываем объект, подключаем конечную html'ку. В результате в папке current создается архив latest.zip, который содержит в себе все нужные ресурсы(см. ziplogic.php).
    По хорошему, использовать его нужно перед публикацией страницы, каждый раз генерировать нет смысла.

    Ну и на закуску — сама html'ка. Верстку не критиковать, пожалуйста — ее здесь нет.

    content.html:
    <script src="js/jquery-1.9.1.js"></script>
    <script src="js/jszip.min.js"></script>
    
    <link type="text/css" zpack="css/alotofstyles.css" rel="stylesheet">
    <script zpack="js/cooljs.js"></script>
    <style>
        body{margin: 0;}
    </style>
    
    <div id="loader" style="position: absolute; width: 100%; height: 100%;background: rgba(148, 148, 148, 1);">
        <img src="" style="
            position: relative;
            top: 50%;
            left: 50%;
            height: 25px;
            width: 250px;
            margin-left: -125px;
            margin-top: -12.5px;
        ">
    </div>
    
    
    <img zpack="img/screen.png"/>
    <img zpack="img/screen_1.png"/>
    <img zpack="img/screen_2.png"/>
    
    <script>
        function getBinary(url, callback){
            var oReq = new XMLHttpRequest();
            oReq.open("GET", url, true);
            oReq.responseType = "arraybuffer";
    
            oReq.onload = function (oEvent) {
              var arrayBuffer = oReq.response;
              if (arrayBuffer) {
                var byteArray = new Uint8Array(arrayBuffer);
                callback(byteArray);
              }
            };
    
            oReq.send(null);   
        }
        
        function unpackResources(data){
            var pack = new JSZip();
            pack.load(data);
            
            $.each(pack.files, function(k, v){
                var blob = new Blob([pack.file(k).asArrayBuffer()], {type: 'application/octet-binary'});
                var url = URL.createObjectURL(blob);
                $('[zpack="'+k+'"').each(function(){
                    switch($(this).prop("tagName").toLowerCase())
                    {
                        case 'img' :
                            $(this).attr('src', url);
                            break;
                        case 'script' :
                            $(this).attr('src', url);
                            break;   
                        case 'link' :
                            $(this).attr('href', url);
                            break;  
                        case 'a' :
                            $(this).attr('href', url);
                            break;                           
                    }
    
                });
            });
        }
        
        $(document).ready(function(){
            getBinary('current/latest.zip', function(data){
                    unpackResources(data);
                    $('#loader').hide();
            });
        });
    </script>
    


    Тут стоит описать чуть подробнее.
    Функция getBinary, если не ошибаюсь, честно сперта со stackoverflow. Скачивает архив и передает полученный массив байт в callback.
    Функция unpackResources, собственно, и делает основную часть работы: обрабатывает содержимое архива, вытаскивает пути, находит все элементы, ресурсы в которых помечены через zpack, создает соотв. blob и подгружает его в нужный элемент.
    Пока все это добро не подгрузится, висит загрузчик.

    В итоге получаем довольно простой код, позволяющий упаковать большое количество ресурсов в 1 архив и распаковать его на стороне клиента.

    Спасибо за внимание, комментарии и идеи крайне приветствуются, критика без использования слов «идиот» и «криворукий ламер» — тоже.

    Similar posts

    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More
    Ads

    Comments 38

      +9
      А чем это лучше GZIP и переиспользования одного TCP соединения для множества GET запросов?
        –11
        GZip не всегда работает. Кроме того, описанный способ позволяет выбирать файлы для упаковки.
        Про 2 — можно поподробнее?
          +2
          Как уже сказали, GZIP можно и нужно использовать для уменьшения объема передаваемой информации. И ваш метед в этом слкчае не чуть не лучше, кроме случая если вы будете использовать более сильный алгоритм сжатия.
          А задержки на переоткрытие кучи коннектов как раз и решает механизм повторного использования ТСР соединения для передачи всех фацлов. То есть утрировано к серверу откроется всего одно соединение по которому пеиедадутся все данные без переконнекта на каждый файл.
            –3
            Ссылку на описание можно? Через что реализуется?
              –1
              SPDY и вроде как в HTTP2 планируется это же.
                +4
                Реализуется через http-заголовок Connection: Keep-Alive, см. RFC 2068
                  0
                  Благодарю)
            +1
            Подозреваю, что передать один файл вместо сотни будет в любом случае быстрее. Хотя сильно подозреваю, что это экономия на спичках и проблемы с архитектурой приложения.
              0
              Некоторые хостинг-площадки дополнительно считают кол-во запросов. Там это определённо не спички.
                +8
                Имхо, бежать нужно с таких площадок. Это уже совсем за гранью добра и зла.
                  0
                  Ну почему же? А облачный хостинг? Он ничем не хуже, там просто другие правила экономии.
                    0
                    Эм. А можно примеры хостингов, где тарификация включает в себя количество запросов?
                    S3 — не в счет.
                      0
                        0
                        Извините, но с такими ограничениями я все еще придерживаюсь вышеозвученного мнения.
                          0
                          В этом плане — согласен, хотя если на странице штук под 100 разного плана картинок, js'ок и css(к примеру, на моей странице вконтакте 106 запросов пришло) и посещаемость сайта хотя бы 1000 человек в день(даже если они открывают страницы 1 раз в день), то их
                          «Веб-сервер Apache
                          Статический сайт/контент (html, jpg/gif, …) 50 000 – 150 000»

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

                          В тарифе указывается именно % процессорной нагрузки, а дальше всё зависит от того как сайт написан. Сами запросы 1Гб не считает и не ограничивает.
                            0
                            Понятно, спасибо.
                          0
                          Вроде у селектела очень гибкая тарификация в плане «платишь за потребление», где учитывается буквально всё.
                          Кроме того, спрайты, в частности, ещё и исправляют совсем некрасивые подзагрузки при наведении и прочих действиях.
                          + время на открытие запроса на получение каждого файла в больших объёмах значительно увеличивает время загрузки. Выше написали про переиспользование коннекта, я об этом как-то не слышал раньше, нужно будет вникнуть подробнее.
                            0
                            Емнип, у селектела запросы считаются только к ихнему cloud storage, что как-бы не хостинг.
                              0
                              Кстати, а подход с упаковкой в архив позволяет отказаться/уменьшить количество спрайтов. Да и подгружается сайт при медленном коннекте не рывками, а единовременно(если не паковать гигибайтное видео в тот же архив, конечно).
                              Хотя вот про реюз соединения тоже в 1 раз слышу, если он прост в использовании, то действительно является отличным решением.
                                +1
                                С психологической точки зрения — пусть сайт подгружается дольше и рывками, чем быстрее, но после паузы в несколько секунд.

                                По той же причине принято делать прогресс-бары (хоть даже фейковые) везде, где что-то может долго обрабатываться.
                                  0
                                  Естественно, даже в тестовой версии сделал простенькое окно загрузки) И да, согласен с замечанием — применения загрузчика в итоге не дает разницы между обычной подгрузкой и подгрузкой 1 файлом.
                                    0
                                    Зависит от того с чем сравнивать. Несколько секунд — это когда уже совсем всё плохо.
                    0
                    … не стоит паковать динамический контент или большие файлы ...

                    Ну ладно, большие файлы ещё не страшно, но учитывая проблемы с динамическим контентом — то вся идея теряет свои преимущества. Тогда уж можно CSS встроить в тело HTML, а изображения вставить в виде base64, а потом сжать GZip'ом.
                      +1
                      Закрадывается мысль, что мы получим на выходе пакет PhoneGap или Cordova :)
                        +3
                        … сжатый в zip.
                        0
                        У base64 минус — увеличивается размер. На мелких файлах не критично, но все же.
                        Для крупных файлов метод, в принципе, использовать можно… но если есть крупные файлы на странице — то это уже проблема архитектуры)
                        Для динамического контента можно тоже использовать, но только если для большого количества единовременно подгружаемых элементов.
                        Для стриминга способ явно неприемлем.

                        Но вообще, ему вполне может быть найдено применение — к примеру, если сайт нужно просматривать через прокси, который не поддерживает gzip.
                          0
                          Даже с простым динамическим контентом (не стриммингом) будет проблема, так как потребуется дополнительное время на запаковку и распаковку. Возможно будет даже быстрее просто скачать это всё в живом виде, нежели в архиве. Надо проводить тесты производительности, в том числе на мобильных телефонах.
                            0
                            Ну вот для интереса забил произвольным текстом css на 4.77 метра. ZLib на хосте включен.
                            Время упаковки архива — около 0.1 секунды. Размер архива — 19кб.
                            Zlib'ом пожатая либа весит 27.9кб.
                            В принципе, сказать, что бы сильно большая разница была — так нет, разве что в пропорциях сравнивать)
                              0
                              Я больше волнуюсь за мобильные платформы: как они будут архивы распаковывать.
                                0
                                Под android Blob'ик сдох.
                                Впрочем, через BlobBuilder заработал, css'ка распаковалась и подгрузилась.
                                Изображения и js'ка исдохли, надо смотреть, почему.
                                  0
                                  Ан нет.
                                  Обновил браузер — все разом заработало через Blob. Так что для мобильных платформ способ также подходит, по крайней мере, для android.
                          0
                          Советую посмотреть в сторону SPDY и PageSpeed от гугла. Есть модули для nginx и apache. Использую на продакшене в одном проекте. Для использования придется скомпилировать nginx с этими модулями и добавить их в конфигурацию.
                            0
                            Возможно, хорошие штуки, но не всегда есть выделенный сервер и возможность менять конфиги.
                              0
                              Согласен, более того, не всегда есть возможность перекомпилировать nginx.
                              В моем случае nginx разворачивается через docker, и добавить эти модули было тривиально.
                                0
                                В любом случае, спасибо за совет)
                            +2
                            Я думаю минусы происходят от костности мышления. Автор не постулирует данный алгоритм на замену gzip'ования трафика. Данный подход еще надо осмыслить. Очевидно он будет иметь свои плюсы в каком-то узком классе задач. Я пока не понимаю, где именно. Но возможно например на интерфейсах, которые должны после загрузки страницы какое-то время работать в браузере без связи с сетью, например. Или возможность быстро откатить какие-то измения, например при работе в графическом онлайн редакторе. Хотя мне в голову не придёт так насиловать браузер и редактировать многослойный файл онлайн, тем не менее в XXI веке можно встретить онлайн-воплощение практически любой фантазии.

                            Наверное, стоит воспринимать этот способ не как имеющий своей целью сжатие, а скорее как нацеленный на передачу кучки файлов в архиве с возможностью оперировать ими на клиентской стороне в браузере. Когда это может понадобиться и кому — я не знаю. Но если кому-то по какой-то мистической причине окажется удобнее работать с одним архивом, то вот он способ. Описан и опробован.

                            И уж всяко, не зависимо от того, согласны вы с тезисом статьи или нет, этот материал более достоин Хабра, чем материал про то, как делать панораму одной кнопкой на автомате.
                            • UFO just landed and posted this here

                            Only users with full accounts can post comments. Log in, please.