Как настраивать сборку проекта. Часть 2: простая сборка Gulp

В первой части мы разбирали, как настраивать сборку проекта с помощью CLI.

Во второй части давайте рассмотрим пример простой сборки с помощью известного таск-менеджера Gulp.

Простая сборка Gulp

Структура проекта будет такая же:

your-project
├── build
│   ├── css
│   ├── images
│   └── js
└── src
    ├── images
    ├── js
    ├── pug
    ├── resources
    └── scss

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

  • HTML
  • CSS
  • JS
  • Изображения
  • Прочие ресурсы (шрифты, аудио файлы и т.д.)

Gulp

Для начала в нашем проекте необходимо установить Gulp следующей командой:

npm install --save-dev gulp

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

Шаблоны

Будем использовать шаблонизатор Pug. Для этого нужно установить пакет gulp-pug:

npm install --save-dev gulp-pug

В начале файла gulpfile.js необходимо подключать все плагины, которые будут использоваться. А далее пойдут сами задачи. На данном этапе это выглядит так:

let gulp = require('gulp');
let pug = require('gulp-pug');

Создадим задачу, которую назовем «build:pug»:

gulp.task('build:pug', () => {
  return gulp.src('src/pug/*.pug')
    .pipe(pug({
      pretty: true
    }))
    .pipe(gulp.dest('build'));
});

src/pug/*.pug — путь к файлам, которые необходимо скомпилировать.

build — директория, куда будут помещаться уже скомпилированные html-файлы.

Внутри pug() можно указать объект с дополнительными опциями. Полный их список можно посмотреть в документации Pug.

Запуск задачи для компилирования Pug в HTML осуществляется таким образом:

gulp build:pug

Стили

Возьмем в качестве примера препроцессор Sass и плагин gulp-postcss.

Как и было в предыдущей статье про сборку CLI, компиляция стилей пойдет по такому же сценарию. Наш главный файл main.scss будет компилироваться из SCSS в CSS, затем выходной файл будет обрабатываться PostCSS, и его плагин autoprefixer расставит префиксы в нужных местах для того, чтобы весь наш код успешно работал во всех браузерах.

Для использования Sass в Gulp нужно установить пакет gulp-sass:

npm install --save-dev gulp-sass

Чтобы подключить gulp-postcss и autoprefixer, запустим следующую команду:

npm install --save-dev gulp-postcss autoprefixer

Документация gulp-postcss предлагает нам подключать и использовать плагины прямо в gulpfile.js, но есть еще один удобный способ. Нужно создать файл конфигурации postcss.config.js, где будут импортироваться необходимые нам PostCSS-плагины (в данном случае autoprefixer), а в gulpfile.js понадобится лишь просто вызвать PostCSS в нужном месте.

Файл postcss.config.js:

module.exports = {
  plugins: [
    require('autoprefixer')
  ]
};

Для совместимости с браузерами создадим файл .browserslistrc с текстом:

defaults

Подключим gulp-sass в gulpfile.js:

let sass = require('gulp-sass');

Пропишем задачу с названием «build:scss»:

gulp.task('build:scss', () => {
  return gulp.src('src/scss/*.scss')
    .pipe(sass().on('error', sass.logError))
    .pipe(postcss())
    .pipe(gulp.dest('build/css'));
});

src/scss/*.scss — путь к файлу, который нужно скомпилировать. Обратите внимание, что файл, который будет компилироваться, у нас один — main.scss. Остальные стили будут подключаться с помощью @import, и названия файлов должны начинаться с нижнего подчеркивания. Пример:

postcss() — вызов PostCSS с настройками, прописанными в конфигурационном файле.

sass().on('error', sass.logError) — вызов Sass с поддержкой вывода ошибок в консоль. Список всевозможных опций Sass можно посмотреть в документации.

build/css — директория для скомпилированного файла.

Скрипты

Для того, чтобы мы могли кроссбраузерно использовать любые современные возможности JavaScript, скрипты будут компилироваться с помощью Rollup и Babel.

Следующая команда устанавливает Rollup:

npm install --save-dev rollup

Для установки Babel воспользуемся следующей командой:

npm install --save-dev @babel/core @babel/preset-env

Далее необходимы следующие пакеты:

npm install --save-dev rollup-plugin-babel rollup-plugin-node-resolve

rollup-plugin-babel — для связи Rollup с Babel. rollup-plugin-node-resolve — для поддержки импорта из node_modules.

Еще нужно создать файл .babelrc с настройками:

{
  "presets": [
    ["@babel/preset-env", {"modules": false}]
  ]
}

В gulpfile.js заимпортируем только что подключенные плагины:

let rollup = require('rollup');
let resolve = require('rollup-plugin-node-resolve');
let babel = require('rollup-plugin-babel');

Пропишем задачу, которую назовем «build:js»:

gulp.task('build:js', () => {
  return rollup.rollup({
    input: 'src/js/main.js',
    plugins: [
      resolve(),
      babel({
        exclude: 'node_modules/**'
      }),
    ],
  }).then(bundle => {
    return bundle.write({
      file: 'build/js/main.js',
      format: 'iife'
    });
  });
});

В ней указаны все те настройки, которые обычно размещаются в файле конфигурации Rollup.

В input нужно указать путь к исходному JavaScript файлу.

В plugins вызываем ранее подключенные плагины, необходимые для компиляции.

В file задаем путь к скомпилированному файлу.

В format указываем формат вывода (amd, cjs, esm, iife или umd).

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

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

gulp build:js

Ресурсы

Все ресурсы в проекте будут просто копироваться в итоговую папку build без какой-либо обработки. Для этого нам ничего дополнительного подключать не нужно — этим будет заниматься сам Gulp.

Для изображений у нас предусмотрена папка images, а для прочих ресурсов (шрифты, аудиофайлы, видеофайлы, .htaccess и т.д.) директория resources. В этих папках может быть любое количество вложенных элементов.

Создадим задачу «build:images» для копирования изображений из src/images в build/images:

gulp.task('build:images', () => {
  return gulp.src('src/images/**/*', {
    allowEmpty: true
  })
    .pipe(gulp.dest('build/images'));
});

А для прочих ресурсов создадим другую задачу с названием «build:resources», которая будет брать файлы из src/resources и копировать их в build:

gulp.task('build:resources', () => {
  return gulp.src('src/resources/**/*', {
    dot: true,
    allowEmpty: true
  })
    .pipe(gulp.dest('build'))
});

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

Запуск задачи для копирования изображений:

gulp build:images

Запуск задачи для копирования прочих ресурсов:

gulp build:resources

Общий build

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

Запускать задачи параллельно в Gulp можно с помощью функции parallel().

В gulpfile.js создадим задачу с названием «build», которая параллельно запускает все ранее созданные:

gulp.task('build', gulp.parallel(
  'build:pug',
  'build:scss',
  'build:js',
  'build:images',
  'build:resources'
));

Запуск задачи для общего билда:

gulp build

Запуск локального сервера

Локальный сервер для разработки можно запустить, используя плагин Browsersync.

Пропишем задачу «serve»:

gulp.task('serve', () => {
  browserSync.init({
    server: {
      baseDir: 'build'
    }
  });
});

Эта задача отвечает за запуск сервера, используя файлы из директории build.

Для запуска, воспользуетесь командой:

gulp serve

Watch

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

Создадим в gulpfile.js новую задачу «watch»:

gulp.task('watch', () => {
  gulp.watch('src/pug/**/*.pug', gulp.series('build:pug'));
  gulp.watch('src/scss/**/*.scss', gulp.series('build:scss'));
  gulp.watch('src/js/**/*.js', gulp.series('build:js'));
  gulp.watch('src/images/**/*', gulp.series('build:images'));
  gulp.watch(['src/resources/**/*', 'src/resources/**/.*'], gulp.series('build:resources'));
  gulp.watch('build/**/*').on('change', browserSync.reload);
});

Для запуска этой задачи нужна следующая команда:

gulp watch

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

И наконец, создадим дефолтную задачу, которая будет запускать билд, локальный сервер и «вотчеры».

Код задачи «default»:

gulp.task('default', gulp.series(
  'build',
  gulp.parallel(
    'serve',
    'watch'
  )
));

Обратите внимание, что нам важно сначала запускать задачу «build», а потом остальные. Т.к. если это самый первый запуск, то необходимо прежде всего скомпилировать все файлы, а только потом на их основе будет запущен сервер, и начнется отслеживание изменений.

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

gulp default

Итоговый файл gulpfile.js выглядит так:

let gulp = require('gulp');
let pug = require('gulp-pug');
let sass = require('gulp-sass');
let postcss = require('gulp-postcss');
let rollup = require('rollup');
let resolve = require('rollup-plugin-node-resolve');
let babel = require('rollup-plugin-babel');
let browserSync = require('browser-sync').create();

gulp.task('build:pug', () => {
  return gulp.src('src/pug/*.pug')
    .pipe(pug({
      pretty: true
    }))
    .pipe(gulp.dest('build'));
});

gulp.task('build:scss', () => {
  return gulp.src('src/scss/*.scss')
    .pipe(sass().on('error', sass.logError))
    .pipe(postcss())
    .pipe(gulp.dest('build/css'));
});

gulp.task('build:js', () => {
  return rollup.rollup({
    input: 'src/js/main.js',
    plugins: [
      resolve(),
      babel({
        exclude: 'node_modules/**'
      }),
    ],
  }).then(bundle => {
    return bundle.write({
      file: 'build/js/main.js',
      format: 'iife'
    });
  });
});

gulp.task('build:images', () => {
  return gulp.src('src/images/**/*', {
    allowEmpty: true
  })
    .pipe(gulp.dest('build/images'));
});

gulp.task('build:resources', () => {
  return gulp.src('src/resources/**/*', {
    dot: true,
    allowEmpty: true
  })
    .pipe(gulp.dest('build'))
});

gulp.task('build', gulp.parallel(
  'build:pug',
  'build:scss',
  'build:js',
  'build:images',
  'build:resources'
));

gulp.task('serve', () => {
  browserSync.init({
    server: {
      baseDir: 'build'
    }
  });
});

gulp.task('watch', () => {
  gulp.watch('src/pug/**/*.pug', gulp.series('build:pug'));
  gulp.watch('src/scss/**/*.scss', gulp.series('build:scss'));
  gulp.watch('src/js/**/*.js', gulp.series('build:js'));
  gulp.watch('src/images/**/*', gulp.series('build:images'));
  gulp.watch(['src/resources/**/*', 'src/resources/**/.*'], gulp.series('build:resources'));
  gulp.watch('build/**/*').on('change', browserSync.reload);
});

gulp.task('default', gulp.series(
  'build',
  gulp.parallel(
    'serve',
    'watch'
  )
));

Это был пример простой сборки Gulp, которая делает необходимый минимум для разработки сайта. Постепенно ее можно улучшать, добавляя больше возможностей.