Gulp — как глоток свежего воздуха после Grunt

На днях состоялся релиз таск-менеджера gulp.js (англ. «глоток» [/gʌlp/, /галп/]), который является достойной альтернативой сверхпопулярному приложению Grunt. Давайте попробуем разобраться, зачем был создан gulp, и в чем его отличие от Grunt.

Когда дело доходит до JavaScript таск-менеджеров, Grunt — неоспоримый король среди них. Ну, по крайней мере, еще совсем недавно дела обстояли именно так. Ранее, в этом году, часть команды Fractal выразила свою озабоченность положением дел в проекте Grunt. Они выступили с предложением взять все хорошие идеи и сильные стороны Grunt, переработать, и воплотить их в новом проекте. Проект был назван лаконично — gulp. По сути, сейчас он решает те же проблемы, что и Grunt, однако, между ними есть очень большие различия «под капотом». Давайте рассмотрим эти различия подробнее.

Что такое «таск-менеджер»?

Кто-то из вас возможно уже знаком с Grunt. Для тех, кто не в курсе, немного проясним, что же такое таск-менеджер под JavaScript.

Таск-менеджер — небольшое приложение, которое используется для автоматизации скучных и рутинных, но, от того, не менее важных, задач, которые приходится постоянно выполнять в процессе разработки проекта. Такие задачи включают в себя, к примеру, запуск модульных тестов, конкатенацию файлов, минификацию, препроцессинг CSS. Просто создав таск-файл, вы можете проинструктировать таск-менеджер, каким образом следует выполнить ту или иную задачу. И после этого вы можете заниматься своими делами. Довольно простая идея, которая позволяет сэкономить очень много времени, и помогает сохранять фокус на задачах, связанных непосредственно с разработкой проекта.

Различия

Теперь, когда вы понимаете, о чем идет речь, вы можете задать резонный вопрос: «Чем, собственно, gulp отличается от Grunt, и почему меня должны волновать эти различия?»

Потоковая передача данных

Gulp, как система, построена вокруг концепции потоковой передачи данных. Здесь я хотел бы углубиться объяснение этой самой потоковой передачи, но, к счастью, уже есть замечательный источник, из которого вы можете почерпнуть знания о ней. Если вам, конечно, это интересно.

Если не вдаваться в подробности, потоковая передача дает вам больше контроля над происходящим и избавляет вас от промежуточных папок и файлов. Вы передаете файл в gulp, а затем сохраняете результат в другой файл. Всё очень просто.

Плагины

Когда дело доходит до расширения функциональности, ключевая мысль gulp в том, что каждый плагин должен выполнять только одно простое действие. Сам gulp всего лишь соединяет и организует эти действия в задачи. Здесь нет плагинов, дублирующих действия друг друга, и конфликтующих между собой или основным функционалом.

Пиши код, не занимайся настройками

Мне лично больше всего по душе то, что gulpfile — это прежде всего код, а не файл с настройками. Gulp следует спецификации CommonJS, и если вы знакомы с Node, у вас не возникнет никаких проблем. Gulpfile выглядит аккуратно и читаемо, благодаря тому, что код уже структурирован известным вам способом. Его написание не должно вызвать у вас существенных затруднений.

Примеры

Всё это трудно осмыслить без примеров кода, так что я продемонстрирую вам два таких примера. Ниже мы создадим gruntfile и gulpfile. Оба будут делать линтинг, конкатенацию и минификацию js-файлов в нашем проекте. Также, оба таск-менеджера должны отслеживать изменения в файлах проекта, и запускать связанные с этими файлами задачи повторно.

Начнем с gruntfile, а затем я покажу вам, как те же самые настройки выглядит в gulpfile. Это поможет вам наглядно понять, как работают оба таск-менеджера, и как именно gulp использовал и развил идеи Grunt.

gruntfile.js

module.exports = function(grunt) {
  grunt.initConfig({
    pkg: grunt.file.readJSON('package.json'),
    concat: {
      options: {
        separator: ';'
      },
      dist: {
        src: ['src/**/*.js'],
        dest: 'dist/<%= pkg.name %>.js'
      }
    },
    uglify: {
      dist: {
        files: {
          'dist/<%= pkg.name %>.min.js': ['<%= concat.dist.dest %>']
        }
      }
    },
    jshint: {
      files: ['gruntfile.js', 'src/**/*.js', 'test/**/*.js'],
      options: {
        globals: {
          jQuery: true,
          console: true,
          module: true,
          document: true
        }
      }
    },
    watch: {
      files: ['<%= jshint.files =>'],
      tasks: ['jshint', 'concat', 'uglify']
    }
  });

  // Подключаем наши плагины
  grunt.loadNpmTasks('grunt-contrib-jshint');
  grunt.loadNpmTasks('grunt-contrib-concat');
  grunt.loadNpmTasks('grunt-contrib-uglify');
  grunt.loadNpmTasks('grunt-contrib-watch');

  // Регистрируем задачу по умолчанию
  grunt.registerTask('default', ['jshint', 'concat', 'uglify']);

};

gulpfile.js

var gulp = require('gulp');
var jshint = require('gulp-jshint');
var concat = require('gulp-concat');
var rename = require('gulp-rename');
var uglify = require('gulp-uglify');

// Линтинг файлов
gulp.task('lint', function() {
  gulp.src('./src/*.js')
    .pipe(jshint())
    .pipe(jshint.reporter('default'));
});

// Конкатенация и минификация файлов
gulp.task('minify', function(){
    gulp.src('./src/*.js')
        .pipe(concat('all.js'))
        .pipe(gulp.dest('./dist'))
        .pipe(rename('all.min.js'))
        .pipe(uglify())
        .pipe(gulp.dest('./dist'));
});

// Действия по умолчанию
gulp.task('default', function(){
  gulp.run('lint', 'minify');

  // Отслеживаем изменения в файлах
  gulp.watch("./src/*.js", function(event){
    gulp.run('lint', 'minify');
  });
});

Используя gulp, мы уменьшили количество строк в нашем коде с 52 до 30. Вы должны были заметить, что хотя нам и потребовалось одинаковое количество плагинов, два из них различаются, несмотря на то, что мы выполняем совершенно одинаковую задачу в первом и во втором примерах. Это так же наглядно демонстрирует различие в подходах к плагинам, о котором я писал выше.

В gulp нет необходимости в плагине watch, потому как возможность реагировать на изменения в файлах уже включена в ядро. Это одна из тех функциональностей, которую вы ожидаете увидеть по умолчанию, а не посредством расширения возможностей с помощью плагина.

В дополнение, переименование файла в Grunt выполняется с помощью плагина uglify. Получается, что один и тот же плагин отвечает как за минификацию кода, так и за переименование файла на выходе. В gulp же каждый плагин выполняет одно простое действие, и отвечает только за него. Для переименования файла мы просто подключим плагин gulp-rename и добавим его в нашу задачу по минификации кода.

Вывод

В конечном счете, всё сводится к личным предпочтениям: лично я предпочитаю «node-подобный» путь в написании моих таск-файлов с gulp. И, всё же, я должен сказать, что я искренне рад тому времени, которое я провел с Grunt. Я так же хотел бы отметить, что очень важно не просто понимать, как устроены оба таск-менеджера, но и осознавать, какие именно решения и почему были приняты разработчиками за основу этих инструментов. Такой подход поможет вам узнать много нового, и сэкономит вам в дальнейшем массу времени в разработке. Что ж, если вы готовы начать — вперед, на GitHub за gulp.

Дополнительные материалы