Gulp и его использование

Инструмент, которому можно поручить мелкие и скучные задачи.

Время чтения: 12 мин

Кратко

Секция статьи "Кратко"

Gulp (англ. «глоток» [/gʌlp/, /галп/]) — менеджер для организации и выполнения задач при разработке приложений с использованием платформы Node.js.

Gulp может выполнять разные задачи: формировать файлы стилей, объединять и минифицировать файлы, оптимизировать изображения, транспилировать код и т. д. При выполнении этих задач происходит взаимодействие с рабочим окружением: обратиться к файловой системе, создать или сохранить итоговый файл, запустить процесс локального web-сервера, показать результат в браузере.

Как понять

Секция статьи "Как понять"

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

Часто в своих проектах разработчики используют Webpack, но это — сборщик модулей для приложений, написанных на JavaScript. Он «из коробки» предлагает решения для сборки проектов с использованием уже подготовленных механизмов и настроек. Подробнее об этом инструменте читайте в нашей статье про Webpack.

Модули и задачи

Секция статьи "Модули и задачи"

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

  • проверять HTML-разметку и CSS-правила;
  • преобразовывать синтаксис CSS-препроцессоров в «чистый» CSS;
  • проверять JS-код на соответствие требуемым стандартам;
  • при необходимости объединять несколько файлов (CSS или JS) в один;
  • минифицировать большие файлы;
  • проверять в браузере результат, желательно по принципу «live-reload» («живая перезагрузка»);
  • формировать итоговую сборку проекта.

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

Все инструкции по организации работы проекта указываются в файле gulpfile.js. Сначала объявляются основные свойства, которые Gulp использует при выполнении задач:

        
          
          const { src, dest, parallel, series, watch } = require('gulp')
          const { src, dest, parallel, series, watch } = require('gulp')

        
        
          
        
      
  • src() выполняет чтение исходных файлов;
  • dest() выполняет запись итоговых файлов;
  • parallel() объединяет задачи для выполнения в параллельном режиме;
  • series() объединяет задачи для выполнения в последовательном режиме;
  • watch() запускает необходимые задачи при изменениях в файлах.

Потом таким же способом подключаются необходимые npm-модули, например:

        
          
          const sass = require('gulp-sass')(require('sass'))const browserSync = require('browser-sync').create()const autoprefixer = require('gulp-autoprefixer')
          const sass = require('gulp-sass')(require('sass'))
const browserSync = require('browser-sync').create()
const autoprefixer = require('gulp-autoprefixer')

        
        
          
        
      

Затем создаются рабочие задачи со следующей структурой:

  • указывается расположение исходных файлов;
  • с использованием оператора pipe() выстраивается последовательность выполнения операций;
  • определяется место, куда будут сохраняться итоговые файлы.

Чтобы к такой задаче можно было обратиться в любом месте gulpfile.js, ей обязательно присваивается своё название.

Задача — task

Секция статьи "Задача — task"

Подготовим инструкцию для обработки файла стилей, созданных с использованием CSS-препроцессора SASS, для которой установим следующую последовательность действий:

  • обратиться к исходному файлу в папке /src/styles/;
  • преобразовать синтаксис SASS в стандартный CSS;
  • вывести в терминале информацию о наличии ошибок в исходном файле;
  • сохранить итоговый файл в папку /public/styles/;
  • применить с помощью модуля browser-sync новые стили для открытой в браузере страницы.

До третьей версии Gulp задачи создавались встроенной функцией gulp.task():

        
          
          gulp.task('styles', function() {  return src('./src/styles/style.scss')  .pipe(sass().on('error', sass.logError))  .pipe(dest('./public/styles/style.css'))  .pipe(browserSync.stream())})
          gulp.task('styles', function() {
  return src('./src/styles/style.scss')
  .pipe(sass().on('error', sass.logError))
  .pipe(dest('./public/styles/style.css'))
  .pipe(browserSync.stream())
})

        
        
          
        
      

Эта функция регистрирует объявленную инструкцию в качестве общедоступной задачи с названием styles, которая может взаимодействовать с рабочим окружением. Теперь такую задачу можно запускать на выполнение как отдельно — командой gulp styles, так и в составе сводной задачи (watch, default и т. п.).

Задача — function

Секция статьи "Задача — function"

Начиная с четвёртой версии Gulp, для создания задач рекомендуется использовать обычные функции. В этом случае задача из примера выше будет выглядеть следующим образом:

        
          
          function styles() {  return src('./src/styles/style.scss')  .pipe(sass().on('error', sass.logError))  .pipe(dest('./public/styles/style.css'))  .pipe(browserSync.stream())}
          function styles() {
  return src('./src/styles/style.scss')
  .pipe(sass().on('error', sass.logError))
  .pipe(dest('./public/styles/style.css'))
  .pipe(browserSync.stream())
}

        
        
          
        
      

Формирование задачи с использованием такой функции создаёт инструкцию, которая доступна только для внутреннего использования. Чтобы объявленная функция могла взаимодействовать с рабочим окружением и выполняться командой gulp, её необходимо экспортировать в общедоступную задачу:

        
          
          exports.styles = styles
          exports.styles = styles

        
        
          
        
      

Только после этого функция станет доступной для запуска и выполнения как отдельной командой gulp styles, так и в составе сводной задачи.

Отслеживание изменений в проекте

Секция статьи "Отслеживание изменений в проекте"

При работе над проектом разработчику необходимо видеть результат изменений, которые он вносит в файлы. Для этой цели в Gulp предусмотрен метод watch(), который проверяет файлы при их сохранении и запускает соответствующие задачи.

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

  • указывается расположение файлов, которые должны отслеживаться;
  • вызывается задача styles для обработки этих файлов;
  • при сохранении изменений выполняется перезагрузка открытой в браузере страницы.
        
          
          function watch_dev() {  watch('./src/styles/style.css', styles).on(    'change',    browserSync.reload  )}
          function watch_dev() {
  watch('./src/styles/style.css', styles).on(
    'change',
    browserSync.reload
  )
}

        
        
          
        
      

Работа с проектом и его сборка

Секция статьи "Работа с проектом и его сборка"

Запуск проекта в режиме разработки осуществляется сводной задачей default, название которой зарезервировано в Gulp. В примере ниже она будет выполнять и отслеживать в параллельном режиме задачи, ранее экспортированные из функций:

        
          
          exports.default = parallel(  styles,  scripts,  pages,  watch_dev)
          exports.default = parallel(
  styles,
  scripts,
  pages,
  watch_dev
)

        
        
          
        
      

Поскольку задача default для Gulp является задачей по умолчанию, запустить её можно командой gulp.

Запуск проекта в режиме разработки
Запуск default задачи в платформе Доки

Для сборки проекта обычно создаётся отдельная задача с произвольным названием, которая последовательно выполняет необходимые действия:

        
          
          exports.build = series(  styles,  scripts,  pages)
          exports.build = series(
  styles,
  scripts,
  pages
)

        
        
          
        
      

После этого сборка итоговой версии проекта осуществляется вводом в терминале команды gulp build.

Использование

Секция статьи "Использование"

Рассмотрим вариант проекта по разработке сайта.

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

Все исходные файлы размещаются в папке src, результат будет сохраняться в папку public:

.
├─ /public
│   ├─ /css
│   ├─ /images
│   ├─ /js
│   ├─ index.html
│   └─ order.html
├─ /src
│   ├─ /components
│   │   ├─ /header
│   │   │   ├─ header.html
│   │   │   └─ header.scss
│   │   ├─ /offer
│   │   │   ├─ offer.html
│   │   │   ├─ offer.js
│   │   │   └─ offer.scss
│   │   ├─ /order
│   │   │   ├─ order.html
│   │   │   ├─ order.js
│   │   │   ├─ order.scss
│   │   └─ /footer
│   │       ├─ footer.html
│   │       ├─ footer.js
│   │       └─ footer.scss
│   ├─ /fonts
│   ├─ /images
│   ├─ /js
│   │   └─ script.js
│   ├─ /pages
│   │   ├─ index.html
│   │   └─ order.html
│   └─ /styles
│       └─ style.scss
├─ gulpfile.js
└─ package.json

Сначала в рабочей папке командой npm init необходимо выполнить инициализацию проекта, в процессе которой будет предложено указать его данные:

Инициализация проекта с помощью npm

После этого в папке будет создан файл манифеста проекта package.json, в котором также будет сохраняться информация об используемых модулях. Модули устанавливаются стандартной командой npm install.

Нам понадобятся следующие npm-модули:

  • del для очистки папки public;
  • sass и gulp-sass для использования препроцессора SASS;
  • gulp-autoprefixer добавляет необходимые вендорные префиксы CSS;
  • gulp-group-css-media-queries группирует все media-запросы CSS в одном месте итогового файла стилей;
  • gulp-include подключает отдельные файлы компонентов в итоговые файлы HTML и JS;
  • browser-sync создаёт и запускает локальный веб-сервер.

В gulpfile.js сначала объявим все необходимые свойства и модули:

        
          
          const { src, dest, parallel, series, watch } = require('gulp')const del = require('del')const sass = require('gulp-sass')(require('sass'))const autoprefixer = require('gulp-autoprefixer')const gcssmq = require('gulp-group-css-media-queries')const includeFiles = require('gulp-include')const browserSync = require('browser-sync').create()
          const { src, dest, parallel, series, watch } = require('gulp')
const del = require('del')
const sass = require('gulp-sass')(require('sass'))
const autoprefixer = require('gulp-autoprefixer')
const gcssmq = require('gulp-group-css-media-queries')
const includeFiles = require('gulp-include')
const browserSync = require('browser-sync').create()

        
        
          
        
      

Подготовим задачу для создания модулем browser-sync веб-сервера с использованием свойств API Browsersync:

  • инициализируем веб-сервер;
  • указываем рабочую папку;
  • упрощаем ввод в браузере адреса страницы — без расширения .html;
  • назначаем номер порта для взаимодействия с веб-сервером;
  • назначаем номер порта для пользовательского интерфейса веб-сервера;
  • открываем в браузере главную страницу сайта.
        
          
          function browsersync() {  browserSync.init({    server: {      baseDir: './public/',      serveStaticOptions: {        extensions: ['html'],      },    },    port: 8080,    ui: { port: 8081 },    open: true,  })}
          function browsersync() {
  browserSync.init({
    server: {
      baseDir: './public/',
      serveStaticOptions: {
        extensions: ['html'],
      },
    },
    port: 8080,
    ui: { port: 8081 },
    open: true,
  })
}

        
        
          
        
      

Задача по формированию CSS-стилей выполняет следующие операции:

  • обращается к исходному файлу style.scss;
  • переводит синтаксис SASS в стандартный CSS;
  • показывает в терминале информацию о наличии ошибок в исходном файле;
  • с использованием Autoprefixer добавляет вендорные префиксы CSS, в т.ч. для работы Grid Layout в браузере IE;
  • группирует вместе все медиавыражения и размещает их в конце файла;
  • сохраняет итоговый файл в папку /public/styles/;
  • с помощью модуля browser-sync применяет новые стили для открытой в браузере страницы.
        
          
          function styles() {  return src('./src/styles/style.scss')  .pipe(sass().on('error', sass.logError))  .pipe(autoprefixer({ grid: true }))  .pipe(gcssmq())  .pipe(dest('./public/css/'))  .pipe(browserSync.stream())}
          function styles() {
  return src('./src/styles/style.scss')
  .pipe(sass().on('error', sass.logError))
  .pipe(autoprefixer({ grid: true }))
  .pipe(gcssmq())
  .pipe(dest('./public/css/'))
  .pipe(browserSync.stream())
}

        
        
          
        
      

Для формирования файлов скриптов создадим задачу, в которой укажем следующую последовательность действий:

  • обратиться к исходному файлу script.js;
  • обработать JS-файлы компонентов;
  • применить изменения для открытой в браузере страницы.
        
          
          function scripts() {  return src('./src/js/script.js')  .pipe(    includeFiles({      includePaths: './src/components/**/',    })  )  .pipe(dest('./public/js/'))  .pipe(browserSync.stream())}
          function scripts() {
  return src('./src/js/script.js')
  .pipe(
    includeFiles({
      includePaths: './src/components/**/',
    })
  )
  .pipe(dest('./public/js/'))
  .pipe(browserSync.stream())
}

        
        
          
        
      

Использование параметра includePaths для модуля gulp-include позволяет подключать в script.js файлы компонентов без указания полного пути к ним:

        
          
          //=include offer.js//=include order.js
          //=include offer.js
//=include order.js

        
        
          
        
      

Этот параметр будет использоваться и при обработке страниц, в результате чего таким же образом в HTML-файлах можно будет использовать файлы компонентов:

        
          
          <!--=include header.html --><!--=include offer.html --><!--=include order.html --><!--=include footer.html -->
          <!--=include header.html -->
<!--=include offer.html -->
<!--=include order.html -->
<!--=include footer.html -->

        
        
          
        
      

Страницы сайта будут обрабатываться задачей pages в следующей последовательности:

  • считывается исходный файл страницы;
  • обрабатываются HTML-файлы компонентов;
  • после сохранения изменений выполняется перезагрузка открытой в браузере страницы.
        
          
          function pages() {  return src('./src/pages/*.html')  .pipe(    includeFiles({      includePaths: './src/components/**/',    })  )  .pipe(dest('./public/'))  .pipe(browserSync.reload({ stream: true, }))}
          function pages() {
  return src('./src/pages/*.html')
  .pipe(
    includeFiles({
      includePaths: './src/components/**/',
    })
  )
  .pipe(dest('./public/'))
  .pipe(browserSync.reload({ stream: true, }))
}

        
        
          
        
      

Для использования шрифтов и картинок создадим задачи, которые в асинхронном режиме будут копировать файлы из папки src в папку public:

        
          
          function copyFonts() {  return src('./src/fonts/**/*')  .pipe(dest('./public/fonts/'))}function copyImages() {  return src('./src/images/**/*')  .pipe(dest('./public/images/'))}async function copyResources() {  copyFonts()  copyImages()}
          function copyFonts() {
  return src('./src/fonts/**/*')
  .pipe(dest('./public/fonts/'))
}

function copyImages() {
  return src('./src/images/**/*')
  .pipe(dest('./public/images/'))
}

async function copyResources() {
  copyFonts()
  copyImages()
}

        
        
          
        
      

Перед запуском проекта в режиме разработки, а также перед его сборкой, желательно удалить папку public c предыдущими версиями файлов, поэтому добавим задачу очистки:

        
          
          async function clean() {  return del.sync('./public/', { force: true })}
          async function clean() {
  return del.sync('./public/', { force: true })
}

        
        
          
        
      

Осталось создать задачу, которая для отслеживания изменений в файлах будет выполнять следующие действия:

  • следить за файлами, расположенными в указанных папках;
  • запускать задачи для обработки указанных файлов;
  • перезагружать открытую страницу после сохранения изменений.
        
          
          function watch_dev() {  watch(['./src/js/script.js', './src/components/**/*.js'], scripts)  watch(['./src/styles/style.scss', './src/components/**/*.scss'], styles).on(    'change',    browserSync.reload  )  watch(['./src/pages/*.html', './src/components/**/*.html'], pages).on(    'change',    browserSync.reload  )}
          function watch_dev() {
  watch(['./src/js/script.js', './src/components/**/*.js'], scripts)
  watch(['./src/styles/style.scss', './src/components/**/*.scss'], styles).on(
    'change',
    browserSync.reload
  )
  watch(['./src/pages/*.html', './src/components/**/*.html'], pages).on(
    'change',
    browserSync.reload
  )
}

        
        
          
        
      

В конце gulpfile.js экспортируем объявленные функции в общедоступные задачи, после чего создадим сводные задачи для запуска проекта в режиме разработки и сборки его итоговой версии:

        
          
          exports.browsersync = browsersyncexports.clean = cleanexports.scripts = scriptsexports.styles = stylesexports.pages = pagesexports.copyResources = copyResourcesexports.default = parallel(  clean,  styles,  scripts,  copyResources,  pages,  browsersync,  watch_dev)exports.build = series(  clean,  styles,  scripts,  copyResources,  pages)
          exports.browsersync = browsersync
exports.clean = clean
exports.scripts = scripts
exports.styles = styles
exports.pages = pages
exports.copyResources = copyResources

exports.default = parallel(
  clean,
  styles,
  scripts,
  copyResources,
  pages,
  browsersync,
  watch_dev
)

exports.build = series(
  clean,
  styles,
  scripts,
  copyResources,
  pages
)

        
        
          
        
      

Заключение

Секция статьи "Заключение"

Если вы работаете над web-проектом, который:

Gulp — ваш бро, не сомневайтесь.