Создание gulp-плагина на примере построения графа зависимостей для модулей Angular JS

от автора


Предисловие

В данной статье я поделюсь с вами опытом, как быстро и безболезненно создавать простые плагины для 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 основных действия:

  1. получить массив всех ангуляр-модулей объявленных в файлах, которые примет в себя плагин
  2. создать .dot файл графа на основе массива модулей
  3. создать визуальное представление графа (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 с возможностью зумирования.

ссылка на оригинал статьи http://habrahabr.ru/post/257275/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *