Qu.js

Утилиты и компоненты

Основные возможности

  • Очередь задач (Que — Qu.when) — откладывайте выполнение функций до момента, когда произойдут одно или несколько событий (DOMReady, загрузка библиотеки, кастомный сигнал).
    • Поддерживаются режимы multi (все события), series (последовательные) и ordered (строгий порядок).
    • Кеширование событий — гибко подписывайтесь на события, которые уже произошли, в асинхронных сценариях, благодаря встроенному кешу (useCache: true по умолчанию).
  • Управление событиями (on, off, trigger) — подписка на события с делегированием, поддержкой коллекций элементов и возможностью собирать ответы от обработчиков.
  • Отслеживание появления переменных (def) — реагируйте на момент, когда глобальная переменная или её вложенное свойство становится определённым или изменяется. Поддерживается интервальный режим для отслеживания изменений вне прямой записи.
  • Загрузка ассетов loadAssets — загружайте скрипты, стили и изображения с контролем порядка и промисами.
  • Модульная архитектура на промисах — основная библиотека служит ядром, а дополнительные модули (например, Lazy для ленивой загрузки) подключаются как плагины через Qu.lib(). Управляем зависимостями модулей через Qu.when на события типа qu:Module*:ready и инициализация модулей через Qu.libs().
  • и некоторые утилиты для работы с DOM и прокруткой:
    • scrollTo — плавная прокрутка с промисом; scrollToAccurate — повторная прокрутка после завершения первой для компенсации смещений (например, из-за загрузки шрифтов или изображений).
    • dragScroll — перетаскивание для прокрутки.
    • scrollFollowCursor — следование за мышью.
    • Адаптивные брейкпоинты система проверки размеров экрана (up, down, only, between) с автоматическим обновлением и событием qu:breakpoint:change.
    • ...

Философия

Que — от queue, Qu — от query. Ключевая идея проста: задачи попадают в очередь и выполняются по запросу.

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

Это не набор ES-модулей. Qu.js подключается целиком и работает через глобальный объект Qu. Импорт отдельных функций не предусмотрен и пока что не планируется.

Если вам близок такой подход, можете смело юзать.

Быстрый старт

<!-- Загрузчик в <head> самым первым синхронным скриптом -->
<script>window.Que||function(){var e=window.Qu&&window.Qu.bus||document,n=e.dispatchEvent,w=window._QueFired||(window._QueFired={}),i=window._QueQ||(window._QueQ=[]),t=window._QueDetails||(window._QueDetails=Object.create(null));e.dispatchEvent=function(i){return i&&i.type&&(w[i.type]=(w[i.type]||0)+1,void 0!==i.detail&&(t[i.type]=i.detail,window.Qu&&"function"==typeof window.Qu._touchEventCache&&window.Qu._touchEventCache(i.type,i.detail))),n.call(e,i)},window.Que=function(e,n,w,t,u){if(window.Qu&&"function"==typeof window.Qu.when)return window.Qu.when(e,n,w,t,u);i.push([e,n,w,t,u])}}();</script>
<link rel="dns-prefetch" href="//cdn.jsdelivr.net">
<link rel="preconnect" href="//cdn.jsdelivr.net">
<!-- Файлы Qu.js -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/qujs-dev/core@1.3.6/css/qu.min.css" media="print" onload="this.media='all'">
<script src="https://cdn.jsdelivr.net/gh/qujs-dev/core@1.3.6/qu.min.js" defer></script>

Пример кода

<script>
// готовность переменных
Que(function() {
console.log('DOM ready');
Qu.def(['fancybox', 'jQuery'], function(vars) {
console.log('Все библиотеки загружены!');
// vars.fancybox и vars.jQuery уже доступны как глобальные переменные
Qu.trigger(Qu.bus, 'app:ready', {
detail: {
fancyboxLoaded: true,
jQueryLoaded: true
}
});
});
});
// вызов на свой тригер
Que(function() {
console.log('✅ App ready!');
// todo
}, 'app:ready');
// инициация библиотеки написанной в стиле Qu
Que(function() {
Qu.libs(['Lazy'], { autoInit: true, initParams: {} }).then(function(loadedLibs) {
console.log('Lazy alredy');
});
});
</script>

Пример связь модулей async

В реальных задачах модули могут быть загружены в разных частях приложения и в разное время. В этом примере показана организация модульной архитектуры на основе Qu:

  • три модуля с иерархической зависимостью (A зависит от B, B зависит от C);
  • асинхронная инициализация каждого модуля через init(), возвращающая Promise;
  • использование событий qu:ModuleC:ready и qu:ModuleB:ready для ожидания готовности зависимостей внутри init();
  • генерация событий :init и :ready на каждом этапе (отслеживаются через Qu.when);
  • запуск всей цепочки через Qu.libs() с autoInit: true.

Модуль C инициализируется первым, затем B (после события qu:ModuleC:ready), и только потом A (после qu:ModuleB:ready). Это гарантирует правильный порядок выполнения.

<script>
// ========== ФУНКЦИЯ ДЛЯ ВРЕМЕННОЙ МЕТКИ ==========
const startTime = performance.now();
const getTimestamp = () => {
const elapsed = (performance.now() - startTime) / 1000;
return `[${elapsed.toFixed(3)}s]`;
};
// =================================================
setTimeout(function() {
Que(function() {
console.log(getTimestamp(), '================Пример связь модулей async');
// ======== Модуль C (базовый, не зависит от других) ========
var ModuleC = {
name: 'ModuleC',
version: '1.0',
loaded: function() {
console.log(getTimestamp(), '📦 ModuleC loaded');
},
init: function(quInstance, params) {
console.log(getTimestamp(), '🔄 ModuleC init started (асинхронно)', params);
return new Promise(function(resolve) {
setTimeout(function() {
console.log(getTimestamp(), '✅ ModuleC init finished');
resolve();
}, 2500);
});
},
doSomething: function() {
console.log(getTimestamp(), '⚡ ModuleC doSomething ');
}
};
// ======== Модуль B (зависит от C) ========
var ModuleB = {
name: 'ModuleB',
version: '1.0',
loaded: function() {
console.log(getTimestamp(), '📦 ModuleB loaded');
},
init: function(quInstance, params) {
console.log(getTimestamp(), '🔄 ModuleB init started (асинхронно) - ждём C...', params);
return new Promise(function(resolve) {
Qu.when(function(Qu) {
console.log(getTimestamp(), '✅ ModuleB получил сигнал, что C готов');
Qu.ModuleC.doSomething();
setTimeout(function() {
console.log(getTimestamp(), '✅ ModuleB init finished');
resolve();
}, 2500);
}, 'qu:ModuleC:ready');
});
},
doSomething: function() {
console.log(getTimestamp(), '⚡ ModuleB doSomething');
}
};
// ======== Модуль A (зависит от B) ========
var ModuleA = {
name: 'ModuleA',
version: '1.0',
loaded: function() {
console.log(getTimestamp(), '📦 ModuleA loaded');
},
init: function(quInstance, params) {
console.log(getTimestamp(), '🔄 ModuleA init started (асинхронно) - ждём B...', params);
return new Promise(function(resolve) {
Qu.when(function(Qu) {
Qu.ModuleB.doSomething();
resolve();
}, 'qu:ModuleB:ready');
});
},
doSomething: function() {
console.log(getTimestamp(), '⚡ ModuleA doSomething');
}
};
Qu.lib('ModuleC', ModuleC);
Qu.lib('ModuleB', ModuleB);
Qu.lib('ModuleA', ModuleA);
// ======== Подписки на события ========
// алиас Que
Qu.when(function(Qu) {
console.log(getTimestamp(), '🎯 qu:ModuleC:init (инициализация началась)');
}, 'qu:ModuleC:init');
Qu.when(function(Qu) {
console.log(getTimestamp(), '🎯 qu:ModuleC:ready (инициализация завершена)');
}, 'qu:ModuleC:ready');
Qu.when(function(Qu) {
console.log(getTimestamp(), '🎯 qu:ModuleB:init (инициализация началась)');
}, 'qu:ModuleB:init');
Qu.when(function(Qu) {
console.log(getTimestamp(), '🎯 qu:ModuleB:ready (инициализация завершена)');
}, 'qu:ModuleB:ready');
Qu.when(function(Qu) {
console.log(getTimestamp(), '🎯 qu:ModuleA:init (инициализация началась)');
}, 'qu:ModuleA:init');
Qu.when(function(Qu) {
console.log(getTimestamp(), '🎯 qu:ModuleA:ready (инициализация завершена)');
}, 'qu:ModuleA:ready');
// алиас Que
Qu.when(function(Qu, data) {
console.log(getTimestamp(), '🎯 qu:libs:done (все библиотеки готовы):', data);
if (data.detail && data.detail.libNames) {
var names = data.detail.libNames;
console.log(getTimestamp(), '🎯 qu:libs:done имена:', names.join(', '));
var instances = {};
names.forEach(function(name) {
instances[name] = Qu[name];
});
console.log(getTimestamp(), 'Инстансы:', instances);
}
}, 'qu:libs:done', { once: false, useCache: false });
// ======== Запуск ========
Qu.libs(['ModuleA', 'ModuleB', 'ModuleC'], {
autoInit: true,
initParams: {
ModuleA: { debug: true },
ModuleB: { debug: true },
ModuleC: { debug: true }
}
}).then(function({ ModuleA, ModuleB, ModuleC }) {
console.log(getTimestamp(), '✅ Все библиотеки готовы (Promise от libs разрешён)');
ModuleA.doSomething();
ModuleB.doSomething();
ModuleC.doSomething();
});
});
}, 2000);
</script>

Результат выполнения в текущей консоли.

TODO обновить документацию :D + глобальные темы