При разработке приложений с модульной структурой на JavaScript возникает две проблемы:
- описание и удовлетворение зависимостей различных частей приложения, необходимость организации подключения зависимостей на серверной стороне;
- экспорт переменных в глобальную область видимости и их коллизия.
Обе эти задачи решаются при использовании подхода Asynchronous Module Definition. Он сводится к описанию модулей функцией define и подключению их с помощью require. На данный момент есть несколько инструментов, реализующих AMD. Я начал своё знакомство с ними с RequireJS и был удивлён, на сколько удобно и просто можно описывать зависимости модулей. Расскажу, как это работает, на простом примере.
Подключение загрузчика
Имеем следующую структуру каталогов:
siteroot/
js/
app.js
require.js
jquery.js
mymodule.js
index.html
Для начала, подключим в index.html загрузчик. Будем использовать RequireJS:
<script data-main="/js/app" src="/js/require.js"></script>
Отлично, это единственный тег script, который нам нужен. Остальную работу по подключению JS сделает загрузчик. Указанный в data-атрибуте файл (расширение .js для краткости в RequireJS всегда опускается) будет своеобразной точкой входа нашего приложения. В нём мы сможем подключить необходимые модули с помощью require и совершить задуманные действия.
Описание модуля
Опишем наш модуль в /js/module.js
с помощью define:
define(
'mymodule',
['jquery'],
function( $ ){
return {
foo : 'bar'
};
}
);
Первый аргумент — строка, название модуля, не обязателен. Вторым аргументом передаются зависимости в виде массива строк, также опционально. Третий аргумент — функция-фабрика, которая выполняется только после удовлетворения всех зависимостей (загрузки перечисленных файлов). В неё в качестве аргументов передаются экспортируемые зависимостями переменные. А возвращать она должна сам модуль. В данном случае это объект с одним полем.
Использование
В /js/app.js
подключим нужные модули с помощью JS и выполним свой код:
require(
['mymodule', 'jquery'],
function( Module, $ ){
$('body').append( Module.foo );
}
);
Module при этом не будет доступна в глобальной области видимости, как и другие переменные, экспортируемые библиотеками из зависимостей. Не смотря на то, что библиотека jQuery с версии 1.7 поддерживает AMD-архитектуру, она является исключением: экспортирует свой доллар в глобальную область видимости. Скорее всего, это сделано для сохранения совместимости с армией плагинов, написанных за многие годы.
Конфигурация
RequireJS обладает рядом параметров, которые можно передавать перед использованием. Для этого служит объект require.config
.
Что делать, если вам необходимо подключить модуль, которая не оформлен в виде AMD и экспортирует переменную в глобальную область видимости? Можно, конечно, модифицировать его исходный код, но это плохая практика. Для описания таких модулей служит параметр shim
. Можно вручную указать его зависимости и экспортируемую переменную, и он станет частью нашего приложения наравне с другими AMD-парнями:
require.config = {
shim: {
'oldmodule' : {
depts: [],
exports: 'OldModule'
}
}
};
Теперь можно указывать его в качестве зависимости:
require(
['mymodule', 'jquery', 'oldmodule'],
function(){}
);
Помимо shim есть ещё много параметров: корневая директория подключения файлов baseUrl
, псевдонимы для более удобного подключения paths
, и т.д.
Заключение
Надеюсь, концепция AMD зацепила вас, так же, как и меня. Она помогает избежать хаоса при использовании большого количества JS-файлов в разработке, подталкивает к написанию реюзабельного кода, снимает ответственность за подключение файлов с бэкенда. А если у вас реально большое MVC-приложение из пары десятков файлов, то подобная система просто незаменима.
На прощание, приведу несколько ссылок, которые помогут продолжить изучение вопроса:
- статья Addy Osmani Writing Modular JavaScript With AMD, CommonJS & ES Harmony;
- официальный сайт RequireJS;
- спецификация AMD.
Исходный код из статьи доступен в репозитории на GitHub.
Happy hacking!
Автор: clslrns