Creating a gulp plugin using an example of building a dependency graph for Angular JS modules

Foreword


In this article, I will share with you experience on how to quickly and painlessly create simple plugins for gulp. The article is aimed at the same dummies, like me. For those who so far only used the finished gulp fruits, tearing them off the great Tree of Knowledge NPM, and had no serious experience with Node JS and its streams.

I will not answer questions like “Why create your own plugins if everything is already written that is possible?”. The time will come, and in half an hour you will need to write something very specific to your project. Break all npm, you will find one abandoned plugin with poor functionality, the author of which is unavailable, the code is terrible and so on. Or maybe it will be such a specific task that you will not find absolutely nothing.

Such a task for me was the visualization of a large project using Angular JS. There were a huge number of angular-modules, the connections between which were not so obvious to me. Plus, I wanted to see "saboteurs" - modules that somehow violated the general concept of the project (for example, they climbed into another module not directly through the provider, but directly).

After searching, I found such a solution to my problem. In principle, running grunt plugins in gulp is quite simple, but the implementation in this plugin did not impress me too much. I did not want to use third-party programs, namely graphviz as a means of graph visualization. Plus to everyone who knows what I need more, and dependence on third-party libraries always imposes restrictions.

If the reader is only interested in this plugin, and not the article itself, then here is a link to the project on github and on npm . To everyone else - welcome to cat.

Where to begin?


Gulp developers kindly help us in our endeavors by creating wiki documentation for novice plugin developers here . For successful development, just read the title and guideline . You can do without the latter, but if in the future you plan to put your module in public npm, then in order not to collect bricks on your head, I advise you not to go past the guidelines.

A brief summary of the philosophy of gulp plugins:
  • your plugin always accepts a set of vinyl objects
  • your plugin should always give a set of Vinyl objects (you may not have to do this, but then with the result of your plugin it will be impossible for other plugins to work. It will definitely shoot)
  • what kind of vinyl? Vinyl file object - in common people just a file. In the path property stores filename - the full path to the file, in the contents property - a buffer or stream with the contents of the file
  • never write plugins that will do the same as existing node packages. You will be taken to a blacklist . And quite rightly

In addition, developers are advised to familiarize themselves with well-written simple plugins. I would advise looking at the gulp-replace code

We realize our ideas


I will give the most well-established pattern for building gulp plugins, which is used in most good plugins. A detailed description of the implementation of my task is not the purpose of this article. The main goal is that everyone can quickly “enter” the example and go create their own plugin.

So, let's begin. It is assumed that node js is already installed globally on the system.
npm init

Main project file let it be index.js. After filling in the basic information, set the following
npm install --save through2 gulp-util vinyl

The first plugin will greatly simplify the processing of vinyl streams. The second is useful for generating errors by the plugin. The third is useful if you are going to create new vinyl files based on input files. It will come in handy in my task.

Let's do the design. So, I want to get two files. The first is the description of the graph in dot format to support graphviz, if all of a sudden. The second is an html file, opening which I will see a beautiful graph drawn with d3. Total in my task there are 3 main actions:
  1. get an array of all angular modules declared in files that the plugin will take
  2. create a .dot graph file based on an array of modules
  3. create a visual representation of the graph (html file with d3 script)

Create index.js, clean as a canvas, and throw more colors on it:

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);
};


It is important to remember that if you plan to return processed input files, you need to call this.push (file) in the bufferContents function after manipulating the contents of the file. But if you plan (as in my task) to generate new files based on input, then you definitely need the endStream function, where the stream is not closed yet and you can add your files to the empty stream.

Since the main goal of the article is to learn how to write gulp plugins with a specific example, I will not give here the implementations of ModulesReader , GraphBuilder and GraphVisualizaer that are specific to my specific task. If someone is interested in their implementation, then welcome to github

The result of the plugin is such a nice d3 project graph with the ability to zoom.



Also popular now: