1. Монолитный спагетти-код
• script.js, содержащий код всего сайта
• множество глобальных объектов, нет инкапсуляции,
конфликты
• функциональность разбросана внутри script.js и
inline-скриптов
• местами inline-код
Нет разделения сложной задачи на более простые — любая
задача будет сложной. Нужна модульность.
2 / 28
2. JavaScript модуль
(function () {
var privVar
function privFunc() {
}
return {
pubFunc: function () {
}
}
})()
• реализация скрыта
• нет глобальных
переменных
• простой внешний фасад
для сложной внутренней
реализации
• отдельная единица
загрузки
3 / 28
3. Использование модулей
• модули зависят от других модулей
• модули нужно загружать
• и только когда это необходимо
Нужно стандартное решение:
• обеспечивающее загрузку по требованию
• гарантирующую подгрузку зависимостей
• делающее это эффективно
4 / 28
5. AMD: id
define(id?, dependencies?, factory);
• строковый идентификатор — имя модуля
• необязателен, если не указан — идентификатор, который
был использован для загрузки файла
• всегда абсолютный, относительные имена не допускаются
6 / 28
6. AMD: dependencies
define(id?, dependencies?, factory);
• массив идентификаторов зависимостей
• зависимости разрешаются перед запуском factory
• экземпляры зависимостей передаются в factory в
соответствующих по порядку аргументах
• относительные идентификаторы — относительно
загружаемого модуля
• по умолчанию [’require’, ’exports’, ’module’]
7 / 28
7. AMD: factory
define(id?, dependencies?, factory);
• фабричная функция или объект
• функция выполняется только один раз
• возвращаемое значение функции — экспортируемое
значение модуля
• объект непосредственно присваивается экспортируемому
значению
8 / 28
8. AMD: factory
Специальные зависимости:
require функция получения объекта загруженного модуля
exports список экспортируемых значений
define(
’alpha’,
[’require’, ’exports’, ’beta’],
function(require, exports) {
exports.verb = function () {
return require(’beta’).something();
}
});
9 / 28
9. Require.js
http://requirejs.org
• одна из реализаций AMD (самая популярная)
• базовый стандарт + различные дополнения
• возможность статического анализа кода и последующая
оптимизация за счет сборки и компрессии модулей
10 / 28
11. Модули в Require.js
• один файл — один модуль
• объединение в один файл только оптимизатором
• не надо указывать имен — этим занимается оптимизатор
• модуль require — различные полезные утилиты
• относительные пути — с помощью модуля require:
define(function(require) {
var mod = require("./relative/name");
});
define(["require"], function(require) {
var cssUrl = require.toUrl("./style.css");
});
12 / 28
13. Конфигурирование
requirejs.config(options)
basePath основной путь к загружаемым модулям
paths отдельные пути к различным префиксам
shim внешние определения для не модульных скриптов
map псевдонимы для модулей
config параметры конфигурации модулей
callback пользовательский обрабочик события загрузки
urlArgs дополнительные параметры для запроса
...
14 / 28
14. Структура модулей приложения
www/
js/
app/ код приложения
lib/ библиотечные модули
lib1.js
lib2.js
common.js код, общий для всех страниц
page1.js код для отдельной страницы
page2.js
lib/ внешние библиотеки
config.js конфигурация require.js
Приложение отдельно, библиотеки отдельно, постраничное
разбиение — если необходимо.
15 / 28
15. Структура модулей библиотек
www/js/lib/ внешние библиотеки
jquery/
fancybox.js плагины jQuery
easing.js
jquery-ui/
timepicker-addon.js плагины jquery-ui
jquery.js jQuery
jquery-ui.js jQuery UI
require.js Require.js
Используем единую схему именования файлов плагинов для
получения простых имен соответствующих модулей
16 / 28
17. Загрузка модулей
Сначала загрузчик, потом конфиг, все остальное только после
конфига, чтобы проинициализировались пути.
<script src="js/lib/require.js"></script>
<script>
require([’js/config’], function(config) {
require([’app/common’, ’app/page1’]);
});
</script>
Сокращенная форма для одностраничных приложений:
<script src="js/lib/require.js" data-main="js/main.js">
18 / 28
18. Адаптация существующего кода
Проблемы:
• всегда глобальная область видимости
• вызов глобальных функций непосредственно из
HTML-кода
• inline код в HTML
• jQuery-плагины — не AMD модули
19 / 28
19. Что делать: модули
• группируем код по функциональности
• разбиваем на функции
• функции используются другими модулями —
библиотечный модуль, возвращает объект
• нужна только настройка DOM — просто выполняем
функцию
• все переменные и функции определяем внутри модуля,
снаружи они недоступны
20 / 28
22. Что делать: глобальные объекты
В идеале — не использовать, не получается — использование и
явный экспорт с помощью window.
function () {
window.checkForm = validate;
function validate(form) {
var mode = window.globalMode
...
}
}
Используем window, легче отследить и потом избавиться.
23 / 28
23. Что делать: inline-код в HTML
• <script> — выносим в страничные модули
• onclick ... — обычной привязкой событий
• компромиссный вариант — группировать весь inline-код
require([’js/config’], function(config) {
require([
’jquery’, ’app/common’, ’app/page1’
], function ($) {
// тут весь inline-код.
}
});
24 / 28
24. Что делать: jQuery-плагины
• jQuery плагины должны быть AMD-модулями для
асинхронной загрузки
• не забываем про зависимости
(function (root) {
var amdExports;
define([’jquery’], function (jQuery) {
...
}.call(root));
return amdExports;
}); }(this));
Преобразование вручную или программой, например, volo.
25 / 28
25. Внешние библиотеки — если
ничего не помогает
shim config
requirejs.config({
shim: {
’module’: {
deps: [’underscore’, ’jquery’],
exports: ’Module’
});
26 / 28
26. AMD/Require — архитектурные
преимущества
• минимальный API
• отсутствие жесткой структуры
• отсутствие глобального объекта приложения
• соответствие базовым паттернам JavaScript
• легко сконвертировать существующий код
27 / 28
27. Что читать
• https://github.com/amdjs/amdjs-api/wiki/AMD —
стандарт AMD
• http://requirejs.org — документация по Require.js
• https://github.com/requirejs/example-multipage —
пример для мультистраничных сайтов
• http://addyosmani.com/writing-modular-js/ — Writing
modular JavaScript with AMD, CommonJS and ES Harmony.
28 / 28