Модульный подход к разработке web-приложений с использованием JavaScript: AMD и RequireJS
http://habrahabr.ru/post/152833/http://sly-and-fluffy.blogspot.ru/2013/02/amd-requirejs-javascript.html
При разработке приложений с модульной структурой на 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' : {
deps: [],
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!
Философия AMD, RequireJs и модульная разработка веб-приложений на JavaScript
Разделяй и влавствуй - подход на все времена. Концепция модульного
программирования не нова, и хорошо себя зарекомендовала. В мире
разработки web-приложений на JavaScript существует подход Asynchronous Module Definition или AMD, который
specifies a mechanism for defining modules such that the module and its dependencies can be asynchronously loaded [1]
т.е. по сути определяет API, следуя которому можно реализовать
асинхронную загрузку самих модулей и их зависимостей. Следуя этому
подходу, непроизвольно создаешь более элегантные решения, но это всего
лишь бонусный побочный эффект. Главное - уменьшается хаос js-кода,
сложность разработки и поддержки приложения. Каждый модуль явно
указывает свои зависимости и получает только их, работая изолированно,
что означает минимум мусора глобальной области видимости.
Всё, что нужно, чтобы начать использовать AMD подход в проекте, - это загрузчик, который знает, что такое AMD-модуль, и умеет подгружать зависимости. Итак,
Всё, что нужно, чтобы начать использовать AMD подход в проекте, - это загрузчик, который знает, что такое AMD-модуль, и умеет подгружать зависимости. Итак,
Require.js
Ключевым объектом в Require.js, как и в AMD-философии в целом, является модуль. Опеределяется модуль с помощью функции define:
define("<module_name>",
["dependency1", "dependency2],
function(dependency1, dependency2) {
}
);
Метод, который идёт последний аргументом в списке, - это фабрика,
которая создает и возвращает сам модуль. Фабрика выполняется только
после загрузки всех зависимостей, указанных во втором аргументе. Модули,
указанные как зависимости, ищутся в той же папке, где находится и сам
Require.Js (подробнее на теме загрузки модулей я останавлюсь ниже).
Ппервый аргумент - это имя модуля. Всё вроде просто. Значит, надо
создать свой модуль в AMD-стиле! Выходя за рамки учебных простых
примеров, создадим что-то более близкое к задачам из реальной жизни.
Например, мне нужен объект, который умеет строить календарь на указанный
месяц и год и отмечать в нём даты.
//
zeitproject.calendar.js
define(
'calendar',
['jquery'],
function( $ ){
function Calendar()
{
var self = this;
self.builCalendar = function(month, year)
{
var body = "";
//неинтересные детали
return body;
}
self.markDays = function(days_array, css_class, calendar)
{
//неинтересные детали
}
};
return new Calendar();
}
);
Из зависимостей - только jquery (ну куда без него!). Знак $ в параметрах
функции-фабрики - конечно же, ссылка на jquery. Фабрика возвращает
экземпляр класса Calendar, объявленного в внутри самой фабрики, что
ограничивает область видимости класса. А куда и кому она возвращает
объект? Туда, откуда кем-то была вызвана. А кем и когда она была
вызвана? А вызвана она могла быть, когда модуль попал в чей-то список
зависимостей, и Require.Js пытается подгрузить модуль. Продолжая пример,
Calendar.js является одной из зависимостей в app.js:
//app.js
require(["jquery", "calendar", "tooltip"], function($, calendar, tooltip) {
//init vars
//...//
calendar.builCalendar(month, year, $('.calendar'));
calendar.markDays(days[i][0], days[i][1], $('.calendar'));
tooltip.init();
});
app.js - это точка входа в наше приложение. В нём конфигурируется
Require.Js и инициализируется логика приложения. Это тот единственнй
javascript-файл, который подгружается через Require.Js в index.html.
Путь к нему указывается в атрибуте data-main:
<script data-main="/js/app" src="/js/require-jquery.js"></script>
Для краткости расширение .js можно не указывать.
Конечно, тут есть маленькая хитрость - в примере подгружается Require.Js сразу в связке с jquery, поэтому не нужно отдельно подгружать jquery.
Ручная настройка
Require.Js - гибкая легконастраиваемая штука. Например, нужно настроить загрузку AMD-модулей для такой структуры каталогов:Для этого в app.js перед вызовом require() нужно сконфигурировать Require.Js.
require.config({
baseUrl: "/js",
paths: {
"calendar" : "zeitproject.calendar",
"jquery" : "require-jquery",
"formatter": "/utils/formatter",
"tooltip" : "/controls/zeitproject.tooltip"
,
"tiptip" : "/controls/jquery.tiptip.min"
}
});
Всё довольно интуитивно: baseUrl - корневая папка, относительно которой
указываются все последующие пути. Дальше указывается имя AMD-модуля (имя
мы указывали в методе define() первым аргументом во время определения
модуля). А что, если нужно подгрузить не AMD-модуль, который добавляет в
глобальное пространство имён переменные? По сути, таким модулем может
быть любой jquery-плагин. Или, даже, фреймворк: тот же Backbone.js
является AMD-несовместимым. Не переписывать же его под AMD-сигнатуру?!
Для этого в конфиге есть специальный раздел shim (наверно, имелось в виду "shame, не AMD-модуль!"):
require.config({
baseUrl: "/js",
paths: {
/*...*/
"tiptip" : "
/controls/
jquery.tiptip.min",
"tooltip" : "
/controls/
zeitproject.tooltip"
},
shim: {
'tiptip' : {
deps: ['jquery'],
exports: 'tiptip'
}
}
});
В shim-секции определяется, какие зависимости есть у не AMD-модуля и
псевдоним, под которым его можно указывать в списке зависимостей
AMD-модулей.
//zeitproject.tooltip.js
define("tooltip", ["jquery", "tiptip"], function($){
function Tooltip()
{
/*..*/
});
Вот и всё. Модули изолированы друг от друга, и знают лишь о
существовании тех, без которых не могут работать. Это позволяет держать
под контролем зависимости и облегчает жизнь. Надеюсь, вы прониклись
Require.js и AMD-подходом к разработке веб-приложений.
AMD - описание API
Ссылки
http://requirejs.org официальный сайт require.js, отличная документация и примерыAMD - описание API
Комментариев нет:
Отправить комментарий