Создание 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 с возможностью зумирования.


Поделиться публикацией
AdBlock похитил этот баннер, но баннеры не зубы — отрастут

Подробнее
Реклама

Комментарии 2

    0
    Тут всё про стримы да про стримы, а вот

    if (file.isStream()) {
      //бросим ошибку с помощью gulp-util
      this.emit('error', new gutil.PluginError('gulp-ng-graph', 'Streams are not supported!'));
      return callback();
    }
    
      0
      sinpo, спасибо за статью! Делаю небольшой проект, использую gulp. Вчера осознал, что часть работы специфической для проекта могу возложить на gulp. Стал разбираться, как писать плагины по официальному руководству. Ваша статья восполнила недостаток понимания.

      Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

      Самое читаемое