Создаем первый плагин для Grunt
В связи с усложнением процесса фронтенд разработки, системы сборки клиентской части набрали большую популярность. Существуют две причины для подобного усложнения: миграция функциональных задач на клиентскую сторону и улучшение возможностей представления. Самая взрослая и, пожалуй, самая известная из таких систем разработки - Grunt. Благодаря своей популярности, она развила большую экосистему, где каждый разработчик может найти плагин для своих целей.
Но, как бы там ни было, все же можно столкнуться с задачей, которую не решает ни один из готовых плагинов. В этом случае вам понадобится изучить принципы создания собственных плагинов для Grunt. В этой статье мы рассмотрим весь процесс создания плагина так, чтобы после её прочтения вы могли создавать свои собственные.
Начнем с grunt-init
Самый простой путь, чтобы начать создавать плагины для Grunt — использование grunt-init. Предполагаем, что вы уже установили Node (и npm), теперь установите с помощью командной строки
npm install -g grunt-init
Шаблон для плагина выложен на gitHub, давайте его клонируем:
git clone https://github.com/gruntjs/grunt-init-gruntplugin.git ~/.grunt-init/gruntplugin
Далее нужно запустить grunt-init в папке, в которой будет находится плагин:
grunt-init gruntplugin
Скафолдер задаст множество вопросов, которые помогут определить, как лучше создать структуру папки для плагина:
Grunt-init способен создавать множество разных вещей, связанных с Grunt. Полный список шаблонов и дополнительных инструкций Вы можете найти здесь.
Регистрируем задачу
Генерируемые файлы включают большое количество дополнительных ресурсов, таких как: readme, .jshintrc, license, .gitignore, package.json. Что нас на самом деле интересует, так это файл plugin.js
находящийся в папке tasks
.
Код самого плагина содержит registerMultiTask
функция:
grunt.registerMultiTask(taskName, [description, ] taskFunction)
Мультизадачной она называется потому, что содержит множество конфигурационных объектов для разных задач. Строка taskName
будет аналогичной свойствам основного объекта grunt.initConfig
.
API Grunt отлично документировано, так что в статье мы рассмотрим только самое важные для нашей задачи. Внутри функции задачи this
содержит множество полезных методов и свойств, например:
this.options()
Он возвращает объект
options
определенный в Gruntfile
, кроме того используя его можно определять значения по умолчанию настроек:
var options = this.options({
enabled: false
});
Массив this.files
содержит список всех пар, соответствующих паттернам конфигурации для src
и dest
, все глобальные паттерны должны быть обработаны до момента обращения к массиву. Большинству из вас понадобится цикл для этих файлов, таким образом, сгенерированный плагин будет содержать следующее:
this.files.forEach(function(f) {
var src = f.src.filter(function(filepath) {
if (!grunt.file.exists(filepath)) {
grunt.log.warn('Source file "' + filepath + '" not found.');
return false;
} else {
return true;
}
}).map(function(filepath) {
return grunt.file.read(filepath);
}).join(grunt.util.normalizelf(options.separator));
grunt.file.write(f.dest, src);
grunt.log.writeln('File "' + f.dest + '" created.');
});
Как вы могли убедиться, массив this.files
имеет свойство src
, которое на самом деле является массивом файлов. В самой обычной ситуации Вы будете фильтровать данные файлы по их расширению, содержанию или с помощью каких-то других критериев.
grunt.file
(http://gruntjs.com/api/grunt.file) содержит несколько полезных методов для работы с файловой системой.
grunt.util
(http://gruntjs.com/api/grunt.util) содержит вспомогательные функции.
grunt.log
(http://gruntjs.com/api/grunt.log) обеспечивает необходимые методы для загрузки информации в консоль для различных групп настроек.
Последнее, на что хотелось бы обратить особое внимание, это как правильно управлять асинхронными действиями в задачах.
var done = this.async();
someAsyncFunction(function(result){
done(result);
});
Метод this.async
возвращает callback done
, который должен вызываться при выполнении задачи. Если результатом является ошибка или false
, задача считается завершенной «провалом».
Примеры плагинов
После того как вы освоите основы внедрения плагинов Grunt, я покажу Вам несколько своих плагинов и опишу их особенности.
Grep
Grep - мой самый первый плагин и, возможно, самый сложный. Он был создан с целью упростить разработку в зависимости от ее среды. При разработке большого приложения вы определенно столкнетесь с необходимостью учитывать при его сборке особенности окружения или какие то другие критерии.
Заметка: в статье Эдди Османи (Addy Osmani) есть список схожих плагинов, решающих задачу сборки проектов с учетом особенностей окружения.
Вот пример использования grep
, при сборке для продакшена:
Источник
<link rel="stylesheet" href="./style.css"> <[email protected] dev-->
<link rel="stylesheet" href="http://some.cdn/style.css"> <[email protected] production-->
Результат
<link rel="stylesheet" href="http://some.cdn/style.css"> <[email protected] production-->
В отличии от большинства плагинов Grunt, grep не имеет никаких зависимостей, таким образом, это не просто задача, которую выполняет Grunt, но, скорее, отдельный модуль.
Его исходники можно найти здесь.
Главный функционал grep
реализован путем поиска регулярных выражений. Если хотите работать с содержимым файла в Grunt-плагине, следующий сниппет вам пригодится:
var src = grunt.util.normalizelf(src);
var lines = src.split(grunt.util.linefeed);
Он позволяет комфортно работать с текстовыми файлами, так как при разбиении на строки учитывает различные символы перевода строки, которые обычно сложно держать в голове. Grunt больше не поддерживает хеш grunt.util
, поэтому привожу исходный код этих свойств:
util.normalizelf = function(str) {
return str.replace(/\r\n|\n/g, util.linefeed);
};
util.linefeed = process.platform === 'win32' ? '\r\n' : '\n';
У grep масса преимуществ по сравнению с Grunt API. Все операции с файлами - чтение, запись, проверка присутствия, сравнение файлов из папок - реализованы с использованием grunt.file
. Если планируете работать с содержимым файла, исходный код grep будет вам полезен.
Capo
Grunt-capo тонкая обертка вокруг модуля Capo. Если коротко, плагин разработан для продолжительной интеграции модулей, помогая своевременно обновлять генерируемые файлы. Сам по себе Capo упрощает работу с архитектурой, управляемой событиями в JavaScript, более подробно об этом можно прочесть в этой статье.
Исходный код плагина очень лаконичный, основная часть написана всего в три строчки. Тем не менее, главной его ценностью для обучения является то, что данный плагин работает асинхронно, таким образом, выполнение заданий Grunt также будет выполняться асинхронно. Это достигается за счет метода описанного в начале статьи а для ещё лучшего понимания вы можете изучить исходный код.
Similar
Grunt-similar служит удобству выполнения grunt-задач из консоли, он ищет имя введенной задачи в Gruntfile и, в случае если вы ошиблись при вводе её имени, проверяет, существуют ли зарегистрированные задачи со схожими именами.
Давайте рассмотрим блоки, из которых построен этот плагин. Вот часть, которая получает имена всех задач и регистрирует новую задачу с введенным именем:
if (grunt.cli.tasks.length === 1){
var taskParts = grunt.cli.tasks[0].split(':');
var tasks = Object.keys(grunt.task._tasks);
if (tasks.indexOf(taskParts[0]) === -1){
grunt.registerTask(grunt.cli.tasks[0], ['similar']);
}
}
grunt.cli.tasks
массив задач, которые были введены через консоль и
grunt.task._tasks
массив всех задач зарегистрированных в конфигурации.
Метод grunt.registerTask
принимает имя задачи, которую нужно зарегистрировать, и массив заданий, который нужно будет вызвать. Таким образом проксируется выполнение похожих similar
задач.
Еще следует обратить внимание на то, как плагин реализует выполнение задачи с заданным именем:
grunt.task.run(similarTaskName);
Заключение
Мне кажется, что для полноценного использования любой системы сборки, типа Grunt, нужно полностью понять большинство ее внутренних механизмов. Я надеюсь, что моя статья их прояснила. Изучайте исходный код любого рассмотренного плагина, так как всегда проще учиться на конкретных примерах.
В завершении я бы хотел обратить внимание, что уже достаточно много сказано про другие системы сборки и про гонки производительности между ними, и что разработчики Grunt поставили себе цель выпуска новой, улучшенной версии проекта. Информацию о проекте и его развитии можно найти в репозитории grunt-next.