Знакомство с gulp

Откладывайте Grunt в сторону, у нас появился новый исполнитель задач. Gulp - это интуитивная, код-над-конфигурацией, потоковая система сборки. Это факт.

Почему мне должно быть интересно? Хороший вопрос. Gulp’s код-над-конфигурацией делает не только лёгким написание задач, но его также легче читать и поддерживать.

Gulp использует node.js потоки, что делает быстрее сборку, так как не нужно писать на диск временные папки и файлы. Если вы хотите узнать подробнее о потоках - можете почитать —эту статью. Gulp позволяет вам вводить ваши файл(ы) источники, прокачивать их через множество плагинов и получать вывод в конце, вместо конфигурирования каждого плагина с входом и выходом как в Grunt. Давайте взглянем на характерный пример построения базового Sass в Grunt и в gulp:

Grunt:

sass: {
  dist: {
    options: {
      style: 'expanded'
    },
    files: {
      'dist/assets/css/main.css': 'src/styles/main.scss',
    }
  }
},

autoprefixer: {
  dist: {
    options: {
      browsers: [
        'last 2 version', 'safari 5', 'ie 8', 'ie 9', 'opera 12.1', 'ios 6', 'android 4'
      ]
    },
    src: 'dist/assets/css/main.css',
    dest: 'dist/assets/css/main.css'
  }
},

grunt.registerTask('styles', ['sass', 'autoprefixer']);

Grunt требует, чтобы каждый плагин конфигурировался отдельно, определяя источник и путь назначения для каждого плагина. Например, мы вводим один файл в Sass плагин, который далее сохраняет вывод. Далее нам нужно сконфигурировать Autoprefixer для ввода Sass вывода, который дальше выводит другой файл. Давайте взглянем на ту же конфигурацию с gulp:

Gulp:

gulp.task('sass', function() {
  return gulp.src('src/styles/main.scss')
    .pipe(sass({ style: 'compressed' }))
    .pipe(autoprefixer('last 2 version', 'safari 5', 'ie 8', 'ie 9', 'opera 12.1', 'ios 6', 'android 4'))
    .pipe(gulp.dest('dist/assets/css'))
});

С gulp мы используем для ввода только один файл. Он модифицируется Sass плагином, передаётся в Autoprefixer плагин и модифицируется и дальше на выходе мы получаем наш файл. Этот ускоряет процесс сборки, так как мы не читаем и не пишем ненужные файлы в конце и начале.

Итак, вы заинтересовались, дальше что? Давайте установим gulp и создадим базовый gulpfile с некоторыми ключевыми задачами для начала.

Установка gulp

Перед тем, как мы начнём конфигурировать задачи, нужно установить gulp:

npm install gulp -g

Это установит gulp глобально, давая доступ к gulp CLI. Далее нам нужно установить его локально для проекта. Зайдите - cd в вашу папку проекта и запустите следующее (убедитесь, что у вас есть файл пакетов package.json):

npm install gulp --save-dev

Это установит gulp локально для проекта и сохранит его в devDependencies в файле package.json.

Установка gulp плагинов

Мы собираемся установить некоторые плагины для следующих задач:

Для установки этих плагинов запустите следующую команду:

$ npm install gulp-ruby-sass gulp-autoprefixer gulp-minify-css gulp-jshint gulp-concat gulp-uglify gulp-imagemin gulp-notify gulp-rename gulp-livereload gulp-cache del --save-dev

Это установит все необходимые плагины и сохранит их в devDependencies в файл package.json. Полный список gulp плагинов можно найти здесь.

Загрузка плагинов

Далее, нам нужно создать gulpfile.js и загрузить плагины:

var gulp = require('gulp'),
    sass = require('gulp-ruby-sass'),
    autoprefixer = require('gulp-autoprefixer'),
    minifycss = require('gulp-minify-css'),
    jshint = require('gulp-jshint'),
    uglify = require('gulp-uglify'),
    imagemin = require('gulp-imagemin'),
    rename = require('gulp-rename'),
    concat = require('gulp-concat'),
    notify = require('gulp-notify'),
    cache = require('gulp-cache'),
    livereload = require('gulp-livereload'),
    del = require('del');

Хуух! Кажется намного больше работы, чем с Grunt, верно? Gulp плагины немного отличаются от Grunt плагинов – они предназначены для одной функции и делают её хорошо. Например: Grunt imagemin использует кеширование, чтобы избежать повторного сжатия изображений, которые уже сжали. С gulp это нужно делать с плагином кеширования, который также может быть использовать для других вещей. Это добавляет дополнительный слой возможности при процессе сборки. Круто правда?

Мы можем автозагрузить все установленные плагины как в Grunt, но в целях этого поста, мы будем следовать ручному методу.

Создание задач

Компиляция Sass, автопрефикс и минификация

Во-первых, мы сконфигурируем Sass компиляцию. Мы собираемся скомпилировать Sass в стиле expanded, пропустить его через автопрефиксер и сохранить в нужном месте. Далее создадим минифицированную.min версию, автообновим страницу и сообщим, что задача выполенна:

gulp.task('styles', function() {
  return gulp.src('src/styles/main.scss')
    .pipe(sass({ style: 'expanded' }))
    .pipe(autoprefixer('last 2 version', 'safari 5', 'ie 8', 'ie 9', 'opera 12.1', 'ios 6', 'android 4'))
    .pipe(gulp.dest('dist/assets/css'))
    .pipe(rename({suffix: '.min'}))
    .pipe(minifycss())
    .pipe(gulp.dest('dist/assets/css'))
    .pipe(notify({ message: 'Styles task complete' }));
});

Маленькое объяснение перед тем, как продолжим

gulp.task('styles', function() { ... )};

Мы определяем исходные файл(ы) черезgulp.src API. Также может быть использован глоб паттерн, например /**/*.scss для захвата множества файлов. Возврат потока делает его асинхронным, убеждаясь в том, что задача полностью завершилась перед тем, как мы получим сообщение о том, что она завершилась.

return gulp.src('src/styles/main.scss')

Мы используем .pipe() для прокачки исходных файлов через плагин. Обычно параметры для плагина находятся на их соответствующей GitHub странице. Я указал их выше для справки.

.pipe(gulp.dest('dist/assets/css'));

gulp.dest API - это где мы установим путь назначения. Задача может иметь различные пункты назначения, один для вывода расширенной версии и другой для минифицированной. Это продемонстрированно в задаче styles.

Я бы рекомендовал пройтись по gulp API документации, чтобы получить большее понимание этих методов. Это не так страшно, как кажется!

JSHint, обрезание и минификация JavaScript

Надеюсь, что у вас теперь есть хорошее представление как создавать задачи для gulp. Далее мы установим скриптовые задачи для проверки, обрезания и минификации:

gulp.task('scripts', function() {
  return gulp.src('src/scripts/**/*.js')
    .pipe(jshint('.jshintrc'))
    .pipe(jshint.reporter('default'))
    .pipe(concat('main.js'))
    .pipe(gulp.dest('dist/assets/js'))
    .pipe(rename({suffix: '.min'}))
    .pipe(uglify())
    .pipe(gulp.dest('dist/assets/js'))
    .pipe(notify({ message: 'Scripts task complete' }));
});

Запомните одну вещь - нам нужно определить репортёр для JSHint. Я использую репортёр по-умолчанию, который подойдёт для большинства. Более подробно об этом можно узнать на JSHint вебсайте.

Сжатие изображений

Далее мы установим сжатие изображений:

gulp.task('images', function() {
  return gulp.src('src/images/**/*')
    .pipe(imagemin({ optimizationLevel: 3, progressive: true, interlaced: true }))
    .pipe(gulp.dest('dist/assets/img'))
    .pipe(notify({ message: 'Images task complete' }));
});

Это будет брать любые исходные изображения и прогонять их через плагин imagemin. Мы можем пойти чуть далее и использовать кеширование для избежания пересжатия уже сжатых изображений каждый раз при запусске задач. Всё, что нам нужно - это плагин gulp-cahce, который мы уже установили. Для настройки, нам нужно поменять эту строку:

.pipe(imagemin({ optimizationLevel: 3, progressive: true, interlaced: true }))

На эту:

.pipe(cache(imagemin({ optimizationLevel: 5, progressive: true, interlaced: true })))

Теперь будут сжиматься только новые изображения. Круто!

Очистка!

Перед деплоем хорошо будет очистить наши папки назначения и пересобрать файлы - просто на случай если что-то было удалено из исходных файлов и осталось в папках назначения:

gulp.task('clean', function(cb) {
    del(['dist/assets/css', 'dist/assets/js', 'dist/assets/img'], cb)
});

Нам не нужно использовать здесь плагин gulp, мы можем использоваться преимуществом Node модулей прямо всередине gulp. Мы будем использовать обратный вызов (cb) для обеспечения окончания задачи перед выходом.

Задача по-умолчанию

Мы можем создать задачу по-умолчанию используя $ gulp для запуска всех трёх созданных задач:

gulp.task('default', ['clean'], function() {
    gulp.start('styles', 'scripts', 'images');
});

Обратите внимание на дополнительный массив в gulp.task. Здесь мы можем определить зависимости для заданий. В этом примере, чистая задача будет запущена перед задачами в gulp.start. Задачи в Gulp выполняются одновременно и не имеют порядка, в котором они заканчиваются, поэтому нам нужно убедится, что задание clean завершено перед запуском дополнительных задач.

Внимание: Есть много советов проти использования gulp.start перед выполнением задач в массиве зависимостей, но в этом сценарие clean позволяет избежать негативные моменты и является лучшим выходом.

Наблюдение

Для наблюдения за нашими файлами и выполнения необходимой задачи при их смене, нам во-первых нужно создать новую задачу и далее использовать gulp.watch API для начала наблюдения за файлами:

gulp.task('watch', function() {

  // Наблюдение за .scss файлами
  gulp.watch('src/styles/**/*.scss', ['styles']);

  // Наблюдение .js файлами
  gulp.watch('src/scripts/**/*.js', ['scripts']);

  // Наблюдение файлами изображений
  gulp.watch('src/images/**/*', ['images']);

});

Мы укажим файлы, за которыми нужно наблюдать, через gulp.watch API и определим какие задачи нужно запускать через массив зависимостей. Теперь мы можем запускать $ gulp watch и любые изменения в .scss, .js или файлах изображений запустят соответствующие задачи.

LiveReload

Gulp может также брать на себя автоматическое обновение страницы при изменении файла. Нам нужно несколько модифицировать нашу watch задачу и сконфигурировать сервер LiveReload.

gulp.task('watch', function() {

  // Создание LiveReload сервера
  livereload.listen();

  // Наблюдайте за всеми файлами в dist/, перезагружайтесь при изменениях
  gulp.watch(['dist/**']).on('change', livereload.changed);

});

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

Собираем всё вместе

Теперь у нас есть полный gulpfile, можна взять gist отсюда:

/*!
* gulp
* $ npm install gulp-ruby-sass gulp-autoprefixer gulp-minify-css gulp-jshint gulp-concat gulp-uglify gulp-imagemin gulp-notify gulp-rename gulp-livereload gulp-cache del --save-dev
*/

// Загрузка плагинов
var gulp = require('gulp'),
sass = require('gulp-ruby-sass'),
autoprefixer = require('gulp-autoprefixer'),
minifycss = require('gulp-minify-css'),
jshint = require('gulp-jshint'),
uglify = require('gulp-uglify'),
imagemin = require('gulp-imagemin'),
rename = require('gulp-rename'),
concat = require('gulp-concat'),
notify = require('gulp-notify'),
cache = require('gulp-cache'),
livereload = require('gulp-livereload'),
del = require('del');

// Стили
gulp.task('styles', function() {
return gulp.src('src/styles/main.scss')
.pipe(sass({ style: 'expanded', }))
.pipe(autoprefixer('last 2 version', 'safari 5', 'ie 8', 'ie 9', 'opera 12.1', 'ios 6', 'android 4'))
.pipe(gulp.dest('dist/styles'))
.pipe(rename({ suffix: '.min' }))
.pipe(minifycss())
.pipe(gulp.dest('dist/styles'))
.pipe(notify({ message: 'Styles task complete' }));
});

// Скрипты
gulp.task('scripts', function() {
return gulp.src('src/scripts/**/*.js')
.pipe(jshint('.jshintrc'))
.pipe(jshint.reporter('default'))
.pipe(concat('main.js'))
.pipe(gulp.dest('dist/scripts'))
.pipe(rename({ suffix: '.min' }))
.pipe(uglify())
.pipe(gulp.dest('dist/scripts'))
.pipe(notify({ message: 'Scripts task complete' }));
});

// Изображения
gulp.task('images', function() {
return gulp.src('src/images/**/*')
.pipe(cache(imagemin({ optimizationLevel: 3, progressive: true, interlaced: true })))
.pipe(gulp.dest('dist/images'))
.pipe(notify({ message: 'Images task complete' }));
});

// Очистка
gulp.task('clean', function(cb) {
del(['dist/assets/css', 'dist/assets/js', 'dist/assets/img'], cb)
});

// Задача по-умолчанию
gulp.task('default', ['clean'], function() {
gulp.start('styles', 'scripts', 'images');
});

// Наблюдение
gulp.task('watch', function() {

// Наблюдение за .scss файлами
gulp.watch('src/styles/**/*.scss', ['styles']);

// Наблюдение за .js файлами
gulp.watch('src/scripts/**/*.js', ['scripts']);

// Наблюдение за файлами изображений
gulp.watch('src/images/**/*', ['images']);

// Создание сервера LiveReload
livereload.listen();

// Watch any files in dist/, reload on change
gulp.watch(['dist/**']).on('change', livereload.changed);

}); 

Также я собрал для сравнения Gruntfile, который выполняет те же задачи, можете его посмотреть в этом же gist.

Исходная статья Getting started with gulp