Frontender Magazine

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.

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

Если вы заметили ошибку, вы всегда можете отредактировать статью, создать issue или просто написать об этом Антону Немцеву в skype ravencry.

Travis Maynard
Антон Шувалов

Комментарии (31 комментарий, если быть точным)

Автар пользователя
ishaba

Не успел я приобщиться к Grunt'у, как все уже перешли на Gulp, весело живем:)

Автар пользователя
korochinsky

@ishaba, это было найдено мной на одном из форумов:

"К 30ку самообразование уже надоедает. У меня родственник - переводчик. Он за год английский освоил и работает. Дак вот, английский из моды не выйдет и не изменится. Гражданский летчик на другой тип судна переучивается после выпуска и летает всю жизнь часы налетывает, а потом командир. А программист? Постоянно догоняет последний вагон поезда."

Автар пользователя
ihorzenich

Используя gulp, мы уменьшили количество строк в нашем коде с 52 до 30.

wtf? Это то преимущество ради которого нужно переходить на Gulp?

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

А это вообще не преимущество, а огромный недостаток, что нужно мало того что несовместимые, да ещё и другие плагины?

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

И что хорошего в этом? Где мощь и гибкость Grunt через который можно что угодно сделать, хоть песню в WinAmp'e запустить по успешной компиляции?

Вывод: Gulp - очередная новомодная хипстерская фигня.

Автар пользователя
A

@delka это всего лишь значит что тебя всем устраивает Grunt, и Gulp тебе не нужен =)

Автар пользователя
ai

Мне вообще кажется, что Make-архитектура плохо подходит для сборки ассетов. Как минимум потому, что есть большая разница в работе во время разработки и во время деплоя на продакшен.

Так что мне гораздо ближе Assets Pipeline из Рельс или аналог для ноды — mincer.

Автар пользователя
iamstarkov

@delka

Используя gulp, мы уменьшили количество строк в нашем коде с 52 до 30.

wtf? Это то преимущество ради которого нужно переходить на Gulp?

нет, но согласись это приятнее и выгоднее


А это вообще не преимущество, а огромный недостаток, что нужно мало того что несовместимые, да ещё и другие плагины?

Чем же это недостаток, это ведь обычная издержка при смене инструмента.


И что хорошего в этом?

Раскрой, пожалуйста свой вопрос.


Где мощь и гибкость Grunt через который можно что угодно сделать, хоть песню в WinAmp'e запустить по успешной компиляции?

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


Вывод: Gulp - очередная новомодная хипстерская фигня.

не будь так строг к новичку Галпу

Автар пользователя
ai

Вывод: Gulp - очередная новомодная хипстерская фигня.

Это вы не разобрались. В этой статье не указан главный плюс Гульпа — колоссальное снижение времени сборки. Иногда до 10 раз.

Автар пользователя
ai

Кстати, мы недавно столкнулись с интересными вопросом по архитектуре Гульпа — как со всеми этими потоками передавать карты кода (source map)?

Решили пока работать с заинлайненными картами в комментарии-аннотации, но, например, Сасс для ноды инлайнить не умеет — нужно патчить Сасс для Гульпа.

Автар пользователя
A

@ai @delka для меня огромный плюс gulp — CommonJS вместо JSON. Не нужно больше разбивать одну задачу на кучу конфигов для плагинов. Ну и то, что API nodejs не скрывается за огромной абстракцией меня тоже очень радует.

Автар пользователя
ihorzenich

Главный плюс Гульпа — колоссальное снижение времени сборки. Иногда до 10 раз.

Вот это уже аргумент. Правда очень слабый, не стоящий того чтоб переходить. Ну будет у меня сборка не 4 секунды, а полсекунды-секунда. Это мелочь. А через полгода - обновится или Grunt и станет быстрее, или железо у меня в ноуте - и тоже будет секунда.

Автар пользователя
ai

А через полгода - обновится или Grunt и станет быстрее, или железо у меня в ноуте - и тоже будет секунда.

В Гранте-то проблема не по процессору, а из-за постоянной записи на диск. В ближайшей перспективе SSD ускоряться не будет, да и постоянно ненужная запись на диск плохо для здоровья SSD.

Например, секундная задержка компиляции — это очень много во время разработки. Это секунда между вводом кода и проверкой в браузере.

Я согласен, что плюсов не так много, но и переход очень дешёв. Консерватизм очень опасен для программиста. Одна утилита экономит 10 %, вторая — 8 %, третья — 12 %, а в итоге все уже уехали на поезде в мир новой архитектуры ;).

Автар пользователя
iamstarkov

@ai @delka @shuvalov-anton вот кстати этот твит:

Gulp Sass compile, auto prefix and minify: 902ms. Grunt Sass compile, auto prefix and minify: 24.152s. Exact same set up. cc/@wearefractal

Автар пользователя
ihorzenich

Кстати, мы недавно столкнулись с интересными вопросом по архитектуре Гульпа — как со всеми этими потоками передавать карты кода (source map)?

И это только первая проблема. А многих плагинов под него просто нет и не факт что будут. Нет никакого лёгкого перехода. А переход должен быть только по очень веским причинам. Хороший консерватизм - полезен.

@ai @delka для меня огромный плюс gulp — CommonJS вместо JSON. Не нужно больше разбивать одну задачу на кучу конфигов для плагинов.

Ну так настройки для плагинов же можно вынести во внешние файлы!

CSSLint

csslint: { main: { options: { csslintrc: '.csslintrc' }, src: '<%= compass.main.options.cssDir %>' + '/*.css' } },

JSHint

jshint: { options: { jshintrc: '.jshintrc' }, files: [ 'Gruntfile.js', '<%= js_files %>' ] },

Автар пользователя
A

@delka https://npmjs.org/package/gulp-grunt

Автар пользователя
ihorzenich

Или даже можно вообще вот так:

``` JS gruntfile.js

// Обязательная обёртка module.exports = function(grunt) {

// Конфигурация grunt.initConfig({ // Пути к файлам и папкам JS configDir: 'config', sassDir: 'sass', sassFiles: '<%= sassDir %>' + '/main.scss', cssDir: '../css', jsSourceFiles: 'js/main.js', jsSourcePlugins: 'js/plugins/**/*.js', jsResultFile: '../js/scripts.js', });

// Загрузка конфигурации к задачам из отдельных файлов grunt.loadTasks('tasks'); ... ```

Автар пользователя
ihorzenich

Парсер неосилил, вот пример: аккуратного grunt-файла: https://github.com/ideus-interactive/html-framework/blob/master/dev/Gruntfile.js

Автар пользователя
iamstarkov

@ai @delka для меня огромный плюс gulp — CommonJS вместо JSON. Не нужно больше разбивать одну задачу на кучу конфигов для плагинов.

Ну так настройки для плагинов же можно вынести во внешние файлы!

во-первых, Антон насколько я понял имеет ввиду другое — ситуация, когда ты находишь плагины, пишешь для них неудобные json-конфиги с тасками, которые тебе на самом деле не нужны, а необходимы лишь косвенно. и только потом пишешь необходимые тебе build, release и тп таски — это всё раздражает (@shuvalov-anton, я ведь правильно понял?)

во-вторых, такой способ настройки тасков я встречал только у линтеров, а их не больше пары десятков

Автар пользователя
iamstarkov

https://github.com/ideus-interactive/html-framework/blob/master/dev/Gruntfile.js

json-конфиги всё равно никуда не исчезли https://github.com/ideus-interactive/html-framework/tree/master/dev/tasks

Автар пользователя
ihorzenich

Т.е. у вас есть отдельная папка /tasks/ и в неё закидывайте все настройки для плагинов: https://github.com/ideus-interactive/html-framework/tree/master/dev/tasks А gruntfile будет маленький и чистенький.

Автар пользователя
A

@matmuchrapna @delka Ага, я об этом же. Допустим, у меня есть задача — компиляция пользовательских данных из БД + assets в статический сайт. Получается, что для GruntJS у меня эта задача должна быть декомпозированна на набор плагинов и конфиги для них, в gulp же это выглядит более императивно, без лишней декомпозиции, в каких-то местах, возможно, будет проще использовать NodeJS а не плагины для Gulp, но в целом все будет выглядеть достаточно цельно.

Автар пользователя
A

@delka ну да, я практически, так и делаю с Grunt. Плюс файл с конфигом, где хранятся пути к директориям и те настройки, которые вероятнее всего будут меняться.

Автар пользователя
ai

Вы так говорите о нехватке плагинов, как будто плагины Гранта сложный проект. Обычно плагин — просто обвязка в один небольшой файл, которую можно написать за вечер.

Автар пользователя
ghost

Рано вы раздухарились на холивар. Gulp сырой, но интересный. Пока я вижу только вау-эффект от node-way конфигов и прироста в скорости. Появление Gulp'а хорошо тем, что теперь будет какая то конкуренция. Я бы подождал ответа от Grunt'a.

Автар пользователя
prBigBrother

Вопрос, так как не нашел решения.

Как обстоят дела с отладкой ошибок, что бы не было необходимости заново запускать Gult (к примеру при ошибке компилирования Less), а спокойненько вывести оповещение через тот же gult-notify?

может ссылку на источник.

Заранее благодарю.

Автар пользователя
A

@prBigBrother можно так:

JS var stream = gulp.src(...)...; stream.on('error', function(err) { if(err) console.log(''+err); });

Автар пользователя
prBigBrother

@shuvalov-anton можно поподробнее на тему var stream = gulp.src(...)...;

Автар пользователя
A

@prBigBrother gulp основанн на стимах ноды, стимы — инстанс EventEmitter, соответственно, в каждом стиме(а плагины, тоже стримы) можно подписаться на событие error, и обрабатывать ошибку так, как хочется, тo eсть на псевдо-коде как-то так:

JS gulpLess.on('error', function(err) { if(err) gulpNotify(''+err); });

Автар пользователя
ghost

К 30ку самообразование уже надоедает.

@korochinsky это скорее очередной шаг для освоения магии фронтэнда Node.js разработчиком. Вот в browserify.org все понятно, а с этим require.js нужно разбираться, чего-то читать. Главное в gulp, это то - что он работает по "родному", в стиле Node. Поскольку после 30 ...

Автар пользователя
blanzh

а подскажите. не хочет работать gulp.task('less', function () { gulp.src('less/main.less') .pipe(less({ sourceMap: true, sourceMapFilename: "Имя файла" })) .pipe(gulp.dest('./css')); });

не создает файл, а добавляет в конец css. читал что не работает с несколькими файлами сразу но это как то странно

Автар пользователя
krontill

Добавляем в задачу по стилям этот код:

.on('error', function(error) { console.log(error); this.end(); })

http://github.shvalyov.ru/markup-blueprint/#22

Автар пользователя
jmas

http://blog.keithcirkel.co.uk/why-we-should-stop-using-grunt/