Предисловие
В данной статье я поделюсь с вами опытом, как быстро и безболезненно создавать простые плагины для gulp. Статья ориентирована на таких же чайников, как и я. На тех, кто до сих пор лишь использовал готовые плоды gulp, срывая их с великого Древа Познания NPM, и не имел серьезного опыта работы с Node JS и его стримами.
Я не буду отвечать на вопросы вида «А зачем создавать свои плагины, если уже написано все, что только возможно?». Придет время, и вам за полчаса нужно будет написать что-то очень специфичное для вашего проекта. Перерыв весь npm, вы найдете один заброшенный плагин с убогим функционалом, автор которого недоступен, код ужасен и так далее. А может быть, это будет настолько специфичная задача, что вы не найдете абсолютно ничего.
Такой задачей для меня стала визуализация большого проекта, использующего Angular JS. Было огромное количество angular-модулей, связи между которыми были для меня уже не столь очевидными. Плюс мне хотелось видеть «диверсантов» — модули, которые каким-либо образом нарушали общую концепцию проекта (например, лезли в другой модуль не через провайдера, а напрямую).
Поискав, я нашел такое решение своей задачи. В принципе, запускать grunt плагины в gulp достаточно просто, но реализация в этом плагине меня не слишком впечатлила. Мне не хотелось использовать сторонние программы, а именно graphviz в качестве средства визуализации графа. Плюс ко всему, кто знает, что мне потребуется еще, а зависимость от сторонних библиотек всегда налагает ограничения.
Если читателя интересует лишь этот плагин, а не сама статья, то вот ссылка на проект на github и на npm. Всем остальным — добро пожаловать под кат.
С чего начать?
Gulp-разработчики любезно помогают нам в наших начинаниях, создав вики-документацию для начинающих разработчиков плагинов здесь. Для успешной разработки достаточно прочитать титульник и гайдлайны. Можно обойтись и без последних, но если в будущем вы планируете выкладывать свой модуль в публичный npm, то чтобы не собирать кирпичи на свою голову, советую не проходить мимо гайдлайнов.
Краткий конспект философии gulp-плагинов:
- ваш плагин всегда принимает набор Vinyl объектов
- ваш плагин всегда должен отдавать набор Vinyl объектов (вы можете этого и не делать, но с результатом вашего плагина потом невозможно будет работать другим плагинам. Это обязательно выстрелит)
- что за винил? Vinyl file object — в простонародье просто файл. В свойстве path хранит filename — полный путь до файла, в свойстве contents — буфер или стрим с содержанием файла
- никогда не пишите плагины, которые будут делать то же самое, что и существующие node пакеты. Вы попадете в блэклист. И вполне справедливо
Плюс ко всему разработчики советуют ознакомиться с хорошо написанными простыми плагинами. Я бы советовал посмотреть на код gulp-replace
Реализуем свои идеи
Я приведу наиболее устоявшийся шаблон построения gulp-плагинов, который используется в большинстве хороших плагинов. Детальное описание реализации моей задачи — не есть цель данной статьи. Основная цель в том, чтобы каждый мог быстро «въехать» на примере и пойти создавать свой плагин.
Итак, начнём. Предполагается, что node js уже стоит в системе глобально.
npm init
Main файл проекта пусть будет index.js. После заполнения основной информации, устанавливаем следующее
npm install --save through2 gulp-util vinyl
Первый плагин значительно упростит обработку vinyl-стримов. Второй пригодится для генерации ошибок плагином. Третий пригодится, если вы будете создавать новые vinyl-файлы, на основе входных файлов. В моей задаче он пригодится.
Займемся проектированием. Итак, я хочу получить два файла. Первый — это описание графа в формате dot для поддержки graphviz, если вдруг что. Второй — это html файл, открыв который я увижу красивый граф, нарисованный с помощью d3. Итого в моей задаче есть 3 основных действия:
- получить массив всех ангуляр-модулей объявленных в файлах, которые примет в себя плагин
- создать .dot файл графа на основе массива модулей
- создать визуальное представление графа (html файл c d3-скриптом)
Создаем index.js, чистый как холст, и бросаем на него побольше красок:
var through = require('through2'),
gutil = require('gulp-util'),
//ты будешь извлекать массив ангуляр-модулей
ModulesReader = require('./lib/modules-reader'),
//ты будешь строить граф
GraphBuilder = require('./lib/graph-builder'),
//а ты его визуализировать
GraphVisualizer = require('./lib/graph-visualizer');
//экспортируем функцию, вызывая которую в тасках gulp, пользователь инициирует наш плагин
module.exports = function(options) {
//#section инициализация
var modulesReader;
var graphBuilder;
var graphVisualizer;
options = options || {};
if (!modulesReader) {
modulesReader = new ModulesReader();
}
if (!graphBuilder) {
graphBuilder = new GraphBuilder();
}
if (!graphVisualizer) {
graphVisualizer = new GraphVisualizer();
}
//#endsection инициализация
//функция, которую будет вызывать through для каждого файла
function bufferContents(file, enc, callback) {
if (file.isStream()) {
//бросим ошибку с помощью gulp-util
this.emit('error', new gutil.PluginError('gulp-ng-graph', 'Streams are not supported!'));
return callback();
}
if (file.isBuffer()) {
//отдадим файл на чтение нашему читателю модулей ангуляра
modulesReader.read(file.contents, enc);
}
callback();
}
//функция вызывающаяся перед закрытием стрима
function endStream(callback) {
var modules = modulesReader.getModules();
if (!modules || !modules.length) {
return;
}
//соберем dot файл и объект графа
var builderData = graphBuilder.build({
modules: modules,
dot: options.dot || 'ng-graph.dot',
});
//соберем html файл на основе объекта графа
var htmlFile = graphVisualizer.render({
graph: builderData.graph,
html: options.html || 'ng-graph.html',
});
//отправляем результат в стрим
this.push(builderData.dotFile);
this.push(htmlFile);
callback();
}
return through.obj(bufferContents, endStream);
};
Важно помнить, что если вы планируете возвращать обработанные входные файлы, то необходимо в функции bufferContents вызывать this.push(file) после манипуляций с контентом файла. Но если вы планируете (как в моей задаче) генерировать новые файлы на основе входных, то вам обязательно потребуется функция endStream, где стрим еще не закрыт и вы сможете добавить ваши файлы в пустой стрим.
Так как основная цель статьи — научиться писать плагины gulp на конкретном примере, то я не буду приводить здесь реализации ModulesReader, GraphBuilder и GraphVisualizaer, являющиеся специфичными для моей конкретной задачи. Если кого-то заинтересует их реализация, то добро пожаловать на гитхаб
Результат работы плагина — вот такой вот приятный граф проекта на d3 с возможностью зумирования.
piumosso
Тут всё про стримы да про стримы, а вот