Немного теории (вместо предисловия)
Библиотека RequireJS действительно очень проста. Доступна она, как и документация, на официальном сайте разработчика. Принцип работы библиотеки заключается в разбивании джава скрипт кода на «модули» и дальнейшее их использование. Описываются модули с помощью директивы define(), а используются — с помощью директивы require(). В чем же тогда проблема? А проблема возникла при попытке подключить к проекту другие библиотеки. Вся документация, которую я смог найти в интернете, описывала только простейшие случаи — один маленький скрипт, который использует только библиотеку jQuery, и все файлы находятся просто в одной папке. Вот и пришлось мне поломать голову, чтобы подружить между собой RequireJS, jQuery и Backbone. А теперь довольно теории, переходим к практике.
Модули, модули, модули
Рано или поздно, каждый разработчик приходит к пониманию необходимости разбивания большого проекта на мелкие части (модули). Займемся этим и мы. Давайте создадим простое одностраничное приложение, для наглядной демонстрации. Чтобы не изобретать велосипед, используем готовую библиотеку Backbone, которая позволяет реализовать шаблон MVC для кода на джава скрипт. Что это значит? А значит это то, что весь наш код будет разбит на вот такие модули:
- Контроллер (он же роутер)
- модель
- представление
Кроме того, у нас будет объект Application, который отвечает за работу приложения в целом и модуль для инициализации RequireJS (он же точка входа). Для работы нам понадобятся следующие библиотеки:
- RequireJS
- Backbone
- underscore (без него Backbone не работает)
- jQuery
- jStorage ( просто плагин для демонстрации работы зависимостей в RequireJS )
- json2 ( нужен для jStorage )
Размещаем все по своим папкам и получаем вот такую структуру:
В каждом файле у нас находится один объект. Это требование RequireJS. Если вы захотите разместить в одном файле несколько объектов — нужно будет использовать оптимизатор. Давайте превратим эти объекты в модули, т.е. подключим к RequireJS.
define(['backbone'], function(Backbone){ var Controller = Backbone.Router.extend({ initialize: function (options) { this.appModel = options.model; }, routes: { "": "showMainPage", "result": "showResultPage", "page/:page": "showPage" }, showMainPage: function () { this.appModel.set({ type: "mainpage", page: 0 }); }, showResultPage: function () { this.appModel.set({ type: "resultpage", page: 0 }); }, showPage: function (pageNum) { this.appModel.set({ type: "page", page: pageNum }); }, }) return Controller; });
Данный блок описывает с помощью define модуль контроллера. Define принимает три параметра:
- Имя модуля (мы его опустили, потому, что использование имен для модулей требует использования оптимизатора)
- Зависимости (библиотеки или объекты, которые будут использоваться в коде блока)
- Функция (выполняется после загрузки всех зависимостей)
Как видим наш модуль зависит от библиотеки Backbone, и в функцию передается переменная Backbone — переменная, которую библиотека экспортирует в глобальную область видимости приложения (как $ для jQuery). Функция обязательно должна вернуть объект, который будет доступен для использования в дальнейшем. То же самое делаем и для других модулей.
define(['appModels/appModel', 'appViews/appView', 'appControllers/appController', 'jquery', 'backbone'], function(baseModel, View, Controller, $, Backbone){ var Application = (function() { var appView; var appTemplates = { "mainpage": _.template($('#main-page').html()), "page": _.template($('#page').html()), }; var appController; var appModel; var self = null; var module = function() { self = this; }; module.prototype = { constructor: module, init: function() { self.initModel(); self.initView(); self.initRouter(); }, initRouter: function() { appController = new Controller({ model: appModel}); Backbone.history.start(); }, initView: function() { appView = new View({ model: appModel, templates: appTemplates, el: $("#main-content")}); appModel.trigger("change"); }, initModel: function() { appModel = new baseModel(); }, }; return module; })(); return Application; });
Объект Application описывается точно так же, как контроллер. Конечно здесь больше зависимостей. И это нормально, ведь Application объединяет в одно целое контроллер, модель, представления (Views) и дает нам готовый объект, который умеет самостоятельно отслеживать изменения в приложении и эффективно реагировать (перерисовывать элементы на странице). А в функцию мы как раз и передаем все составляющие нашего приложения (объекты, которые возвращаются функцией в блоке define), плюс глобальные объекты для библиотек Backbone и jQuery. Все просто, и не понятным остается только одно — что это за пути к файлам такие ‘appModels/’, ‘appViews/ ‘, ‘appControllers/’, почему к библиотекам мы обращаемся просто ‘jquery’ или ‘backbone’ и как Backbone работает без underscore? Поиск ответа на этот вопрос занял у меня два дня, и оказался очень простым. Не хватает конфигурации.
Конфигурация
Давайте попробуем запустить наше приложение. Для этого добавим в заголовок html страницы строку
<script data-main="js/init" src="js/library/require.js"> < / script >
Именно так подключается RequireJS к проекту. Атрибут src указывает на размещение файла библиотеки, а data-main — на размещение файла, который является «точкой входа», то есть из него будет начата работа скрипта. У нас он будет выглядеть вот так:
require(["jquery", "../application"], function ($, Application) { $(document).ready(function() { var myApplication = new Application(); myApplication.init(); }); });
Как видим, мы ничего не описываем (не используем define), а просто используем готовые модули, описанные в других файлах, с помощью ключевого слова require (). Первым аргументом сюда передаются зависимости (в нашем случае это библиотека jQuery и объект Application), а второй — это функция, которая выполнится, когда все зависимости будут загружены. В отличие от define () эта функция ничего не возвращает. Все готово и можно проверить работу приложения в браузере. Открываем и видим что? Правильно, ничего. Казалось бы все, что описано в документации сделали. В чем же проблема? А проблема в правильном подключении библиотек. Их файлы называются не просто jquery.js или backbone.js (как в документации) и лежат в папке library, а не рядом с другими модулями. Поэтому и для правильной работы приложения мы должны как-то указать все это, то есть создать конфигурацию для RequireJS. Добавим ее перед вызовом оператора require в файле init.js
requirejs.config({ baseUrl: "js/library", paths: { jquery: 'jquery.min', backbone: 'backbone.min', underscore: 'underscore.min', storage: 'jstorage', json: 'json2', appControllers: '../Controllers', appModels: '../Models', appViews: '../Views' }, shim: { 'underscore': { exports: '_' }, 'backbone': { deps: ['underscore', 'jquery'], exports: 'Backbone' }, 'json': { exports: 'JSON' }, 'storage': { deps: ['json', 'jquery'], } } });
Рассмотрим конфигурацию подробнее
- baseUrl — это путь к библиотекам. Все пути в RequireJS берутся относительно этого базового пути. Если не указать baseUrl, то за базовый будет взят путь из атрибута data-main при подключении. Если и он не указан, то в качестве базового берется путь к странице, которая запустила скрипт
- paths — позволяет задать синонимы для определенных путей или файлов. Здесь мы создали синонимы для файлов библиотек, а также, для папок, в которых содержатся контроллеры, модели и представления. Как видим, для этих папок мы использовали относительный путь "../", ведь как отмечалось ранее, система считает корнем путь, который указан в baseUrl. Особенностью RequireJS является то, что нам не нужно указывать расширение .js для файлов
- shim — один из главных разделов конфигурации. Здесь подключаются библиотеки, которые имеют зависимости от других библиотек, и те, которые не поддерживают технологию AMD (Asynchronous Module Definition). Если говорить проще — то в этом блоке описываются библиотеки, в тело которых мы не можем добавить блок define, и соответственно RequireJS не будет ничего знать о зависимости этой библиотеки от других и не сможет ее загрузить. Самый простой случай — это jQuery плагины. Они не содержат define блоков и не могут быть использованы пока библиотека jQuery не будет полностью загружена
Чтобы описать библиотеку в блоке shim просто добавьте туда запись типа
'backbone': { deps: ['underscore', 'jquery'], exports: 'Backbone' }
Здесь ‘backbone’ — синоним модуля, который подключаем, deps — зависимости, exports — глобальная переменная, которую библиотека экспортирует в область видимости. То есть данный пример подключает библиотеку Backbone (ее псевдоним описан в path) и указывает, что она зависит от библиотек underscore и jQuery, а также в дальнейшем доступ к функциям данной библиотеки можно осуществлять через переменную Backbone. Вот еще один пример подключения плагина jQuery
'storage': { deps: ['json', 'jquery'], }
Как видим плагин storage зависит от jQuery и JSON2, ничего не экспортирует, ведь использование плагина происходит через идентификатор $ библиотеки jQuery.
Ура, успех! Или не совсем?
Мы хорошо поработали, и наше приложение заработало. Но зачем было столько мучиться? Неужели не проще ли просто подключить все эти библиотеки в html файле и даже не думать кто от кого зависит?
Скажу честно — для нашего небольшого примера проще. Я и сам задумался, а какой выигрыш, дает нам RequireJS, если в конечном итоге были загружены все без исключения библиотеки и все дополнительные файлы (контроллер, модель, представление, объект Application)?
Но давайте представим себе, что у нас, например 20 моделей и скажем 40 представлений (Views). Каждый модуль в отдельном файле. Кроме того, для каждой модели есть достаточно большая (требует длительного времени загрузки) вспомогательная библиотека функций. Если все это мы подключим в html файле мы получим:
- Достаточно огромный список тегов в заголовке документа, что затрудняет контроль зависимостей (как в таком списке проверить, не загружается ли один из плагинов раньше самой библиотеки)
- Поскольку отдельные скрипты могут иметь большое время загрузки, то и суммарное время загрузки приложения будет очень большим (мы загружаем все модели со вспомогательными библиотеками, хотя в процессе выполнения приложения может быть использовано лишь несколько из них)
- Обычно работа дизайнера и программиста разделена, поэтому дизайнеру необходимо заранее знать, какие инструменты будет использовать программист, чтобы включить их в код страницы. При дальнейшей поддержке такого проекта (изменении технологий, рефакторинге) изменения нужно будет проводить как в код скриптов так и в код самой страницы
Только этих трех пунктов уже достаточно, чтобы задуматься над использованием RequireJS. Ведь это:
- Единая точка входа для скриптов (дизайнер не задумывается над тем, что будет использовать программист)
- Асинхронная загрузки скриптов (они подгружаются по мере необходимости, поэтому суммарное время загрузки приложения чрезвычайно мало)
- Упрощается контроль зависимостей между модулями
Надеюсь, что моя небольшая статья станет неплохим стартом в изучении RequireJS. Исходный код примера вы можете скачать здесь.
ссылка на оригинал статьи http://habrahabr.ru/post/227449/
Добавить комментарий