Кто бы смог мне подсказать, как require подключать

в 14:46, , рубрики: backbone, javascript, requirejs, Веб-разработка, Программирование, метки: , ,

Одним солнечным весенним утром, мне пришла в голову замечательная идея — заняться изучением популярной библиотеки RequireJS. Я уже давно читал много хорошего и о простоте использования и о преимуществах, которые она оказывает при использовании в проектах. Поэтому и подумать не мог, что подключение RequireJS к модульному проекту на Backbone может вызвать столько проблем. Я потратил два дня на то, что должно занять не более часа. А если у разработчика нет этих двух дней? Вот и решил поделиться с другими своим опытом, чтобы сэкономить время и нервы.

Немного теории (вместо предисловия)

Библиотека RequireJS действительно очень проста. Доступна она, как и документация, на официальном сайте разработчика. Принцип работы библиотеки заключается в разбивании джава скрипт кода на «модули» и дальнейшее их использование. Описываются модули с помощью директивы define(), а используются — с помощью директивы require(). В чем же тогда проблема? А проблема возникла при попытке подключить к проекту другие библиотеки. Вся документация, которую я смог найти в интернете, описывала только простейшие случаи — один маленький скрипт, который использует только библиотеку jQuery, и все файлы находятся просто в одной папке. Вот и пришлось мне поломать голову, чтобы подружить между собой RequireJS, jQuery и Backbone. А теперь довольно теории, переходим к практике.

Модули, модули, модули

Рано или поздно, каждый разработчик приходит к пониманию необходимости разбивания большого проекта на мелкие части (модули). Займемся этим и мы. Давайте создадим простое одностраничное приложение, для наглядной демонстрации. Чтобы не изобретать велосипед, используем готовую библиотеку Backbone, которая позволяет реализовать шаблон MVC для кода на джава скрипт. Что это значит? А значит это то, что весь наш код будет разбит на вот такие модули:

  • Контроллер (он же роутер)
  • модель
  • представление

Кроме того, у нас будет объект Application, который отвечает за работу приложения в целом и модуль для инициализации RequireJS (он же точка входа). Для работы нам понадобятся следующие библиотеки:

  1. RequireJS
  2. Backbone
  3. underscore (без него Backbone не работает)
  4. jQuery
  5. jStorage ( просто плагин для демонстрации работы зависимостей в RequireJS )
  6. json2 ( нужен для jStorage )

Размещаем все по своим папкам и получаем вот такую ​​структуру:

image

В каждом файле у нас находится один объект. Это требование 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 файле мы получим:

  1. Достаточно огромный список тегов в заголовке документа, что затрудняет контроль зависимостей (как в таком списке проверить, не загружается ли один из плагинов раньше самой библиотеки)
  2. Поскольку отдельные скрипты могут иметь большое время загрузки, то и суммарное время загрузки приложения будет очень большим (мы загружаем все модели со вспомогательными библиотеками, хотя в процессе выполнения приложения может быть использовано лишь несколько из них)
  3. Обычно работа дизайнера и программиста разделена, поэтому дизайнеру необходимо заранее знать, какие инструменты будет использовать программист, чтобы включить их в код страницы. При дальнейшей поддержке такого проекта (изменении технологий, рефакторинге) изменения нужно будет проводить как в код скриптов так и в код самой страницы

Только этих трех пунктов уже достаточно, чтобы задуматься над использованием RequireJS. Ведь это:

  1. Единая точка входа для скриптов (дизайнер не задумывается над тем, что будет использовать программист)
  2. Асинхронная загрузки скриптов (они подгружаются по мере необходимости, поэтому суммарное время загрузки приложения чрезвычайно мало)
  3. Упрощается контроль зависимостей между модулями

Надеюсь, что моя небольшая статья станет неплохим стартом в изучении RequireJS. Исходный код примера вы можете скачать здесь.

Автор: kambur

Источник

Поделиться

* - обязательные к заполнению поля