«Ускоряем» открытие тяжелого сайта

  • Tutorial
На днях потребовалось ускорить открытие сайта. Проблема заключалась в том, что одни только JS-файлы, даже собранные в один и сжатые обфускацией, весили более 500kB, а ведь еще есть css тоже довольно крупный.
В связи с этим, пользователям, у которых файлы закешированы (например новый пользователь или после билда сменилась версия), с медленным интернетом приходилось ждать довольно долго, смотря на белый экран.

Причем файлы нужно загрузить до работы с сайтом, т.к. один большой JS-файл является клиентским движком, отвечающим за подгрузку необходимых страниц (js страницы, шаблон и данные), рендеринг и многое другое.
В итоге я решил написать мини-скрипт, который будет подгружать файлы.
В конце концов получилась еще одна страничка, на которой отображался прогресс загрузки всех файлов. По моему это лучше чем пялиться смотреть на белый экран и ждать непонятно чего.

Метод работает на IE9 (с нюансами естесственно), IE10 и выше — идеально, а так же на всех основных браузерах Chrome, Firefox, Opera, Safari.

Стили
.progress-bar {
    margin: 200px auto;
    top : -4px;
    display: none;
    width : 400px;
    border: 1px solid gray;
    -webkit-border-radius: 8px;
    -moz-border-radius: 8px;
    border-radius: 8px;
}

.progress-bar div {
    height : 16px;
    width  : 0;
    background: lightgray;
    -webkit-border-radius: 8px;
    -moz-border-radius: 8px;
    border-radius: 8px;
}

.progress-bar-text {
    text-align: center;
    margin-top: 200px;
}



Простейшая верстка:
<body>
    <div class="progress-bar">
        <div id="progressBar"></div>
    </div>

    <div id="loadingPanel" class="progress-bar-text">
        Подождите... Идет загрузка...
    </div>
</body>


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

Список того, что нужно загрузить будем хранить в переменной fileList.
var progressWidth  = 0,
    fileList = [
        {type : 'style',  url : '/css/bundle.css'},
        {type : 'script', url : '/js/bundle.js'}
    ];


Функция, запускающая загрузку файлов, по завершении вызовет callback переданный в нее.
function loadFiles(callback) {
    if (loaded) {
        // Дабы не загружать дважды
        window.console && console.error('Already loaded.');
        return;
    }

    loaded = true;

    var len = fileList.length,
        count = len;

    var _afterAll = function() {
        if (--count == 0) {
            if (typeof callback == 'function') {
                setTimeout(callback, 10);
            }
        }
    };

    for(var i = 0; i < len; i++) {
        loadFile(fileList[i], len, _afterAll);
    }
};


В цикле мы запускаем функцию отвечающую за получение файла.
Эта функция сама будет обновлять наш progressBar.
Но как получать прогресс загрузки? Оказывается XMLHttpRequest уже давно имеет событие onprogress для скачивания файла (подробнее можно почитать здесь).
Но к сожалению IE9 не умеет вызывать onprogress, и по этому мы проверяем содержит ли new window.XMLHttpRequest() в себе событие onprogress. Если нет отображаем заглушку, с уведомлением, ждите, мы грузимся.
По завершении загрузки, в зависимости от типа файла мы либо подключаем стиль (браузер в этот раз файл берет из кеша, для этого-то мы его и качали), либо евалим если это JavaScript.

function loadFile(file, len, callback) {
    var progressBar  = $('#progressBar'),
        loadingPanel = $('#loadingPanel');

    file.url += '?' + REVISION; // тут мы добавляем номер ревизии, чтобы при каждом билде файл перезакачивался заново

    $.ajax({
        xhr: function () {
            var xhr = new window.XMLHttpRequest();

            if ('onprogress' in xhr) {
                progressBar.parent().show();
                loadingPanel.hide();

                var last = 0, max = 100 / len;
                xhr.addEventListener("progress", function (evt) {
                    if (evt.lengthComputable) {
                        var percentComplete = Math.min((evt.loaded / evt.total) * max, max);

                        if (last < percentComplete) {
                            progressWidth += percentComplete - last;
                            last = percentComplete;
                            progressBar.width(progressWidth + '%');
                        }
                    }
                }, false);
            }

            return xhr;
        },

        type: 'GET',
        url: file.url,
        success: function (data) {
            if (file.type == 'style') {
                $('head').append('<link rel="stylesheet" type="text/css" href="' + file.url + '" media="screen" />');
            } else if (file.type == 'script') {
                evalJS(data);
            }

            callback();
        },
        cache : true
    });
}

function evalJS(text) {
    try {
        if (window.execScript) {
            window.execScript(text);
        } else {
            eval.call(window, text);
        }
    } catch (error) {
        window.console && console.error(error);
    }
}


В jQuery, в метод ajax я передал свойство cache: true, для того, чтобы браузер не качал файлы если они уже есть в кеше, иначе, поумолчанию, jQuery будет качать .js файлы все время, не зависимо есть ли они в кеше или нет.

В общем-то все просто, но как оказалось, в Opera тоже не все гладко (по крайней мере в 12 версии) evt.loaded может в разы превышать evt.total.
Других подводных камней я не обнаружил.

Demo
Исходники примера

Similar posts

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

More

Comments 11

    +6
    Почему «ускоряем» и «тяжелого» в кавычках? Вы или крестик снимите или трусы наденьте ))

    Ускорение-то у вас явно условное, а вот тяжесть сайта вполне реальная.
      0
      «Тяжелого» можно понимать немного по разному.
      Но снял кавычки. Спасибо.
      0
      удалено
        +5
        Демо?
          +2
          Добавил… Грузятся 3 файла, каждый примено по 1MB.
          К сожалению у меня на таких маленьких файлах все проскакивает моментально.
          Если много у кого будет слишком быстро работать, могу увеличить файлы.
            +1
            А GPRS модем по вашему для чего еще нужен? ;)
          +1
          500 кб это же без gzip?
          Скорость загрузки минифицированного, объединенного и сжатого javascript, как правило намного меньше чем время его выполнения.
          Может веб-сервер плохо настроен на отдачу статики?
            0
            Да 500 kB без учета gzip, после которого размер хоть и значительно уменьшается, но все же достаточно велик, по крайне мере суммарно для всех файлов которые необходимо загрузить (css, js).

            Да и gzip не решает проблему с медленным интернетом, например мобильный интернет в области. Скорость бывает по 10-20kBps. Зачем заставлять человека сидеть и смотреть на пустую страницу?
            А так он наглядно видит процесс загрузки.
            Плюс на эту страницу можно вынести различную полезную информацию.

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

            И я прекрасно понимаю что это не панацея от всех проблем, я лишь делюсь тем, как мне кажется, решил нашу проблему наилучшим образом, без глобального переписывания всего проекта.
              +1
              Да, белый экран не круто — я обычно размещаю в стартовом DOM какой-нибудь ajax-loader.gif. Заодно решается проблема его кеширования перед настоящими ajax-запросами. А если хочется совсем мгновенно можно через data: URL. Просто реально те 100-150 килобайт .js.gz пшик по сравнению с медиаконтентом страницы. А с шрифтами часто проблема из-за использования гугловского хранилища шрифтов. При локальном размещении всё быстро.
              Но вообще за статью спасибо, я бы еще сослался на gmail — думаю многие увидели впервые подобное решение у них.
                +2
                Лучше бы сделали облегченную версию сайта. С модой на js и client-side все вокруг словно с цепи сорвались в последнее время.
              +1
              Я просто оставлю это.

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