Пока JavaScript не обзавёлся настоящими модулями мы продолжаем импровизировать.
Так появилась на свет ещё одна реализация модулей — definer.
Главная идея этой модульной системы в отсутствии модульной системы. Исходные коды приложения удобно раскладываются по модулям с указанием зависимостей между собой, а затем собираются в один самодостаточный файл, который ничего про модули не знает.
Для хорошего понимания идеи, под катом примеры от простого к сложному.
Возьмём абстрактную страницу товаров интернет-магазина, index.html
:
<html>
<head>
<meta charset="utf-8"/>
<title>Каталог интернет-магазина</title>
<script src="jquery.js"></script>
<script src="https://rawgithub.com/tenorok/definer/master/definer.js"></script>
<script src="modules/cart.js"></script>
<script src="modules/list.js"></script>
</head>
<body>
<ul class="list">
<li class="item">
<span class="name">Компьютер</span>, <span class="price">250</span>
</li>
<li class="item">
<span class="name">Телевизор</span>, <span class="price">100</span>
</li>
<li class="item">
<span class="name">Холодильник</span>, <span class="price">300</span>
</li>
</ul>
</body>
</html>
Будем использовать jQuery для работы с DOM и definer для модулей.
Модуль Cart
, реализующий корзину интернет-магазина с возможностью добавить товар и получить суммарную стоимость добавленных товаров:
definer('Cart', function() {
function Cart() {
this.list = [];
}
Cart.prototype = {
add: function(target) {
var item = $(target);
this.list.push({
name: item.find('.name').text(),
price: +item.find('.price').text()
});
},
sum: function() {
return this.list.reduce(function(sum, item) {
return sum + item.price;
}, 0);
}
};
return Cart;
});
Модуль list
, зависящий от Cart
и реализующий взаимодействие посетителя с каталогом:
definer('list', function(Cart) {
var iCart = new Cart();
$(function() {
$('.item').on('click', function(e) {
iCart.add(e.currentTarget);
console.log(iCart.sum());
});
});
});
Получилась страница, где можно кликнуть по товару и в консоли увидеть суммарную стоимость добавленных товаров. Но, при этом, мы разделили функциональность на два самостоятельных модуля.
В данном примере, у нас осталась глобальная переменная, которую тоже можно вынести в модуль, это jQuery.
Подключим файл modules/clean.js
перед существующими модулями:
definer.clean('$');
Теперь переменной $
нет в глобальном контексте:
console.log($); // undefined
Чтобы продолжать использовать jQuery в модулях, добавим зависимость:
definer('Cart', function($) { ... });
definer('list', function($, Cart) { ... });
Сборка
Теперь всё готово и можно собрать исходники в единый файл.
Устанавливаем сборщик модулей:
npm install definer
Собираем все модули из директории modules
в файл index.js
:
./node_modules/.bin/definer -d modules/ index.js
Теперь в index.html
достаточно подключить только jQuery и собранный файл:
<script src="jquery.js"></script>
<script src="index.js"></script>
Сборка с помощью grunt-definer
Для удобства разработки есть grunt-плагин. Можно установить мониторинг на изменение файлов модулей и автоматически запускать сборку.
Устанавливаем всё, необходимое для гранта:
npm install grunt grunt-cli grunt-contrib-watch grunt-definer
Добавим в корень проекта файл Gruntfile.js
:
module.exports = function(grunt) {
grunt.initConfig({
watch: {
scripts: {
files: ['modules/*.js'],
tasks: ['definer:all']
},
},
definer: {
all: {
target: 'index.js',
directory: 'modules/'
}
}
});
grunt.loadNpmTasks('grunt-contrib-watch');
grunt.loadNpmTasks('grunt-definer');
};
И запустим мониторинг:
./node_modules/.bin/grunt watch
Теперь сборка будет выполняться автоматически в фоне, а мы можем изменять файлы модулей и обновлять страницу в браузере, чтобы увидеть результат.
Сборка сторонних файлов
Сейчас к странице подключается два файла — jQuery и собранные модули. Можно добавить jQuery в сборку и подключать к странице всего один файл.
Для этого достаточно добавить опцию clean
в grunt-цель:
definer: {
all: {
target: 'index.js',
directory: 'modules/',
clean: {
$: 'jquery.js'
}
}
}
Теперь в index.html
достаточно подключить только один собранный файл:
<script src="index.js"></script>
JSDoc
Возможно формирование JSDoc, содержащего информацию о собранном файле.
Для этого добавим опцию jsdoc
в grunt-цель:
jsdoc: {
"file": "Добавление товаров в корзину интернет-магазина",
"copyright": "2014 Artem Kurbatov, tenorok.ru",
"license": "MIT license",
"version": "package.json",
"date": true
}
Возможно указание относительного пути до JSON-файла, из которого сборщик получит значение одноимённого поля.
Положим в корень проекта файл package.json
:
{
"version": "0.1.0"
}
Перед модулями в собранном файле появится такой JSDoc:
/*!
* @file Добавление товаров в корзину интернет-магазина
* @copyright 2014 Artem Kurbatov, tenorok.ru
* @license MIT license
* @version 0.1.0
* @date 17 February 2014
*/
Итого
Definer помогает:
- разбить всё приложение на модули по зависимостям
- избавиться от глобальных переменных
- собирать все скрипты в один файл с помощью grunt-плагина
Документацию по definer и grunt-definer можно найти на гитхабе.
Автор: tenorok