Подгрузка JS библиотек по мере применения

Когда смотришь на классные, мощные проекты, но видишь код в несколько мегабайт(причем даже сжатый), то хочется плакать от боли или смеяться во сне. Особенно, если ты помнишь ещё wml и рекомендации не делать странички свыше 2-4 кб. Такая экономия, это конечно перебор в наше время. Но при использовании массы библиотек(плагинов), проект априори становится неповоротливым чудовищем. А ведь не только на разных страницах, но и в пределах одной можно подгружать код библиотек тогда, и только тогда, когда он используется.

В качестве примера, возьмем главную wrong-mvc. Тут используется плагин Fancybox(137кб) и сборка редактора CodeMirror(356кб). В сумме, не много, ни мало пол мегабайта. При этом далеко не факт, что зашедший на страницу пользователь кликнет на картинку, или вызовет редактор кода.

А если это десятки библиотек?

Что общего у всех плагинов? Это в основном некие css файлы, js файлы, и функция-метод инициализации. Так почему бы это не использовать? Асинхронно загружать можно в том числе и свои коды, по мере необходимости.

Функция loadLibs(cssPathArray, jsPathArray, funcName) принимает в параметрах

  • cssPathArray - массив путей к файлам
  • jsPathArray - массив путей к файлам javascript
  • funcName - имя метода, оно же имя функции, которое обязательно появится в свойствах window после выполнения js кода

после загрузки всех файлов библиотеки функция возвращает разрешенный Promise. css просто добавляются в head страницы, а js файлы загружаются поочередно, именно в том порядке в котором указаны в массиве! Ведь у плагина может быть и несколько js файлов, и загрузиться они должны именно в определенном порядке. Это обеспечивается встроенным стеком с callback функциями.

function loadLibs(cssPathArray, jsPathArray, funcName) {
return new Promise((resolve, reject) => {
cssPathArray && cssPathArray.forEach(path => {
!$("link[href='" + path + "']").length && $('head').append('<link rel="stylesheet" href="' + path + '">');
});
if (jsPathArray && typeof window[funcName] !== 'function') {
loading();
let loads = [];
function func() {
f = loads.shift();
if (typeof f === 'function') {
f(func);
} else {
loading('hide');
resolve();
}
}
jsPathArray.forEach(path => {
loads.push((callback) => {
$.cachedScript(path).done(() => {
callback();
});
});
});
func();
} else {
resolve();
}
});
}

Свой кеширующий метод jquery ajax запроса мы могли бы и не добавлять, а обойтись стандартным ajax методом, но пусть будет, вдруг ещё пригодится:

$.cachedScript = function (url, options) {
options = $.extend(options || {}, {
dataType: "script",
cache: true,
url: url
});
return $.ajax(options);
};

Но как же быть с кешем и почему кешированно, ведь не применится, спросите вы. Там всё будет кешировано тогда, когда это нужно, и обновлено тогда когда нужно. И вот почему. Аргументы функции мы передаём, не в виде обычных массивов, а в виде обработанных бекендорм массивов, в которые добавлено в строку запроса время модификации файла. Обработка осуществяется методом Wrong\Html\Get::pathArrayJSON

Вот так мы подгружаем плагин Fancybox в шаблоне главной страницы для не авторизованных.

<script>
$(document).on('click', '[data-fancybox]', function(e) {
if (typeof Fancybox !== 'function') {
e.preventDefault();
_this = this;
loadLibs(<?= Wrong\Html\Get::pathArrayJSON(['/css/fancybox.min.css']) ?>, <?= Wrong\Html\Get::pathArrayJSON(['/js/fancybox.min.js']) ?>, 'Fancybox')
.then(() => {
Fancybox.bind('[data-fancybox]', {});
_this.click();
});
}
});
</script>

плагин со всеми его файлами подгружаются лишь единожды по клику на любой элемент с атрибутом data-fancybox. И далее работает на странице как обычно. Первоначально при клике происходит проверка на наличие функции Fancybox в window объекте. Если функции нет, подгружаем плагин при помощи loadLibs. После разрешения Promise мы выполняем необходимую инициализацию плагина и клик по тому самому объекту.

Принцип подгрузки плагина CodeMirror аналогичен, но здесь есть особенность - ведь он инициализируется в модальном окне, которое запрашивается триггером из api. Тем не менее, достаточно лишь 1 раз вызвать такую модалку - файлы плагина будут подгружены 1 раз, и в дальнейшем loadLibs Promise с этой библиотекой будет разрешаться моментально.