Клиентская оптимизация: вынесение кода в post load на примере плагина jquery prettydate

4 марта 2010 г.

В интернете сейчас много говорится о скорости загрузки. Не буду повторяться о том, насколько эта проблема актуальна.

Введение

Речь пойдет об оптимизации страницы на стороне клиента. Один из рабочих методов – вынесение всего, что только можно, в событие post load. Напомню, что событие load срабатывает после того, как загрузится вся страница полностью (включая картинки и мультимедиа).

Здесь стоит выделить необходимое условие для того, чтобы можно было выносить код в post load:
Страница обязана корректно функционировать без кода, который будет загружаться в post load.

Рассмотрим плагин jquery prettydate. Его основное назначение – преобразовать даты вида «4 Марта 2010» в приятные глазу «Вчера» и т.д. Легко заметить, что при нормальной разметке, когда задана дата по умолчанию, скрипт удовлетворяет необходимому условию.

Локализация расширения

Стандартным подходом к локализации практически всех jquery плагинов, что я видел, является дополнительная загрузка файла с переводом для определенного языка. На мой взгляд, у этого подхода есть ряд весомых недостатков:

  • один дополнительный запрос на загрузку language-specific файла;
  • неочевидно, как синхронизировать локализованные строки, если они используются и на стороне сервера.

Однако есть простое решение: возвращать не js файлы, а один цельный html контейнер. Для случая с prettydate (используется JSP, но синтаксис простой и, скорее всего, легко реализуется для других платформ):

<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>

Если запросить эту страницу и добавить сгенерированный html к body документа, то получим работающий плагин.

Еще одно удобство html – это его универсальность. Если плагин требует дополнительно к скриптам css или html, то проблем нет – просто добавляем нужный код в соответствующий тэг, по аналогии с script.

Инициализация расширения

Если попытаться вызвать функцию prettyDate до того, как скрипт плагина будет получен с сервера, возникнет ошибка – функция не существует. Чтобы это исправить, воспользуемся шаблоном прокси. Алгоритм:

  1. Инициализируется прокси для того, чтобы определить функцию аддона (в нашем примере эта функция – prettyDate), однако с другим поведением;
  2. Клиентский код вызывает функцию аддона (в нашем примере это prettyDate);
  3. Прокси добавляет запрос на инициализацию аддона (в нашем примере это prettydate) и коллбэк с текущимим параметрами;
  4. Когда срабатывает load event, посылаются ajax запросы на получение всех аддонов, которые были запрошены прокси;
  5. После инициализации каждый аддон переопределяет свои функции (в данном случае prettyDate) а так же выполняет все свои коллбэки.

Реализация
Реализация алгоритма внутри функции. Важные моменты, которые следует упомянуть:

  • если селектор, который вызывает функцию аддона пустой, то подгружать аддон не нужно;
  • если функция аддона вызывается уже после того, как сработал load event, то необходимо сразу посылать ajax call за необходимым кодом.
    (function(mappings, urls) {
    
    var addons = {}, // map of required addons
    windowLoaded = false;
    
    function loadAddon(name, callbacks) {
    $.ajax({
    type: 'GET',
    url: urls[name],
    cache: true,
    success: function(html) {
    $('body').append(html);
    
    $.each(callbacks, function() {
    this.call();
    });
    // destroy all listeners
    addons[name] = null;
    }
    });
    }
    
    // setup proxies
    $.each(mappings, function(funcName, addonName) {
    $.fn[funcName] = function() {
    var args = arguments,
    callback = $.proxy(function() {
    // here will be called a real function
    this[funcName].apply(this, args);
    }, this);
    // skip empty selectors
    if (this.length > 0) {
    if ($.isArray(addons[addonName])) {
    // already have binded load event
    addons[addonName].push(callback);
    } else {
    if (windowLoaded) {
    // page is already loaded so just need to make an ajax call
    loadAddon(addonName, [ callback ]);
    } else {
    // put addon call into query that will execute when page will be loaded
    addons[addonName] = [ callback ];
    }
    }
    }
    
    return this;
    }
    });
    
    // setup on load event
    $(window).load(function() {
    windowLoaded = true;
    
    $.each(addons, loadAddon);
    });
    
    })({
    // pattern is "functionName : addonName"
    'prettyDate': 'prettydate',
    ...
    }, {
    // pattern is "addonName : addonUrl"
    'prettydate': 'http://example.com/addon/prettydate-1.0.html',
    ...
    });

Заключение

При грамотном использовании вынесение кода в post load позволяет очень благоприятно влиять на скорость загрузки страницы. Это касается как ее размера, так и скорости рендеринга.

P.S. И конечно же не стоит пренебрегать сжатием и кэшированием html-файлов аддонов.