Современная сборка 2020 для frontend. Gulp4

Посмотрев на календарь, я понял, что уже 2020, а посмотрев на свою сборку, которая была с 2018 года, я понял, что пора её менять. В этой статье мы разберем структуру проекта, плагины (минимальный набор функционала) и их новые возможности, которые добавились за такое большое время. Мы разберем все моменты, чтобы новичок мог себе скачать эту сборку и начать с ней работать.

Не только древность моей сборки мотивировала на эту статью, но и еще одна причина: мне больно смотреть, когда заходят на онлайн-сервисы для конвертации scss, минификации javascript и других рутинных задач. Самое забавное — когда сделали мелкую правку, снова нужно проходить все круги ада копипаста.

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


Gulp

Начнем с главного в нашей сборке.

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


Пример минификации сss

ljg3hvoy9axohgzzd19rde6ziwo.png

Как видим, все просто: называем нашу задачу css, вызываем функцию src, которая берет данные по определенному шаблону и передает данные по трубе (функция pipe()). Внутри pipe вызываем функцию плагина, которая трансформирует наши данные и результат передает функцию dest. dest — записывает результат по указанному пути.

С pipe можно выстраивать любые цепочки.


Пример

iaarojs2o_yxufltm0wtwuglmvc.png

В четвертой версии много чего изменилось, но на что нужно точно обратить внимание — это на gulp.series() и gulp.parallel(). Позже мы разберем параллельное выполнение задач.

Как вы заметили, в примерах я экспортировал функции, в старом API использовали gulp.task():

gulp.task('taskName',function() {
    //do somethings
})

Больше не рекомендуется использовать gulp.task().

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


  • публичные задачи — экспортируются из вашего gulpfile, что позволяет запускать их командой gulp.
  • приватные задачи — создаются для внутреннего использования, как правило, в составе series() или parallel()

Пару слов о шаблонах поиска файлов в функции src.


  • templates/*.html — ищет файлы с расширением html в папке templates
  • templates/**/*.html — ищет файлы с расширением html во всех папках, которые в templates

Более подробная информация здесь, здесь и здесь.

После того, как мы разобрались с фундаментом нашего проекта, начнем делать первые шаги. Проверяем, установлены ли node.js и npm.


Команды в консоли

grvwjqqbyh2ntjayjcmo33p4uvm.png

Если они не установлены, следуйте инструкциям здесь.

Создаем папку для нашего проекта. Внутри инициализируем npm npm init --yes
Ключ --yes означает автоматические ответы вопрос по проекту.

Создадим три папки:


  • build — оптимизированные файлы для использования на сервере
  • src — рабочая папка, где будут храниться все наши исходники
  • gulp — здесь будут храниться наши tasks

Еще добавим файл .gitignore, чтобы системные файлы не попадали в репозиторий.
Папка /build задокументирована, потому что часто использую GitHub Pages для демонстрации работы.

Не забудьте установить gulp: npm install --save-dev gulp


.gitignore
# Файлы и папки операционной системы
.DS_Store
Thumbs.db

# Файлы редактора
.idea
*.sublime*
.vscode

# Вспомогательные файлы
*.log*
node_modules/

# Папка с собранными файлами проекта
# build/


HTML

У НTML сильно громоздкий синтаксис, и при большой вложенности тегов сложно разобрать код. Еще одна проблема в том, что многие забывают закрывать теги. Можно возразить, что сейчас умные IDE без проблем индицируют эти проблемы, но, как правило, новички не обращают внимания, что там им говорит IDE, и еще грешат форматированием кода.


Пример немного не реалистичный, но почти такое делают

bad format html

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


Пример базового функционала

pa_jg7rtrtanxwpv8tvd0s2ftma.jpeg

Новичкам советую обратить внимание еще на несколько возможностей:


  • Разделение на модули — удобно, когда используешь БЭМ: один блок — один файл. Подробнее.


Пример из документации

sxpplzqdystfm-q9skym2nvr860.jpeg


  • Миксины — удобно использовать для однотипных блоков. Например, карточки товаров или комментариев. Подробнее.


Пример с документации

jzhrfhh8czcyhetry6o2l9zmale.jpeg


Пример с документации

buujbnhfu4atbiffqueuhj6dfw8.jpeg

За последнее время сильно ничего не изменилось, только название с Jade на Pug. Подробнее.


Разделяем HTML

На нашем сайте будут две тестовые страницы about и index. Структура на страницах повторяется: есть блоки

,
, . Поэтому все нужно вынести в отдельные файлы модули.


Структура проекта

3qocpq4irq54xl73aj-ntmduues.jpeg

Разберем все более подробно.


  • pages — папка для наших страниц, где в корне хранятся непосредственно страницы
  • common — хранятся общие блоки для всех страниц
  • includes — хранятся модули страниц, где внутри еще одна папка, которая соответствует названию страницы

Пройдемся по файлам:


  • layout.pug — шаблон, который хранит основную структуру, и от него наследуются все другие страницы


layout.pug

fzscvmvbtb8unlkflcv3dljnsto.jpeg


  • index.pug и about.pug — наши страницы, которые наследуются от шаблона и подключают свои контентные модули


pages/index.pug и pages/about.pug

7eks_in5xmluxdnr0skrmosk6x0.jpeg

Еще, обратите внимание, у pug есть комментарии, которые попадают в html и которые нет. Подробнее здесь.

Установим плагин gulp-pug для компиляции наших шаблонов. Выполните в консоли команду: npm i gulp-pug
Создадим файл pug2html.js в папке gulp/tasks.


pug2html.js

k1u33z0_89gnnzufrt3djtrfcx0.png

Здесь все понятно: ищем файлы по указанному пути, компилируем и результат выгружаем в папку build. Еще добавим pug-linter, чтобы новички не косячили и сохраняли единый стиль написания кода. Для конфигурации создадим файл .pug-lint.json в корне проекта. Правила для линтера писал на свой вкус. Вы без проблем сможете изменить. Список правил.

Теперь подключаем нашу задачу в файле gulpfile.js.


gulpfile.js

ld58yrcupgcyq7d1wt4rkazzgiu.png

Здесь мы создаем серию с одной таски с названием start; потом мы добавим ещё. Теперь в консоли выполните команду gulp start, и в папке build должны появиться два файла: index.html и about.html.

Еще добавим gulp-w3c-html-validator, чтобы не было нелепых ошибок. Вы, наверное, догадались, что порядок подключения плагинов c помощью pipe() очень важен. То есть перед тем, как вызвать плагин pug() для компиляции, нужно сделать валидацию плагином pugLinter(), а плагин gulp-w3c-html-validator подключаем после pug(), потому что нам нужно валидировать скомпилированный html.

Последний плагин gulp-html-bem-validator — самописный плагин, сделал на скорую руку, потому что не смог найти аналогов. Очень маленький функционал, думаю, со временем буду улучшать.


Пример работы плагина

rq7wyu9-264hjg8vxoqlxoxaz5o.jpeg


Финальный код таски pug2html.js

nfcokwsfdntczlzy9_ngkebv9_g.png

Для стилей мы будем использовать Scss. Все дается по аналогии с задачей pug2html. Создаем новую папку styles и скачиваем нужные пакеты npm install node-sass gulp-sass --save-dev.
Дальше пишем задачу, как и делали раньше. Берем файлы, передаем в плагин и потом сохраняем результат.


tasks/styles.js

l3iqssvcxnlzz9mrezqisvirsxk.png

Дальше мы добавим вспомогательные плагины: npm i gulp-autoprefixer gulp-shorthand gulp-clean-css gulp-sourcemaps stylelint gulp-stylelint stylelint-scss stylelint-config-standard-scss stylelint-config-standard stylelint-config-htmlacademy

Пройдемся по каждому плагину:


  • gulp-autoprefixer — автоматическая расстановка префиксов для старых браузеров.
  • gulp-shorthand — сокращает стили.


Пример

smd3rdyqmjbao3xpl9zmy3dkyxo.jpeg

Файлы styles:


Структура папки styles

qlgh32ysgiw-v-qi3cwaxoadj60.jpeg


global.scss

vsmoz0dz_gy0gxdjrv0enwcjkj0.png


media.scss

fmyuq8ggcbkcks5cpc6fhnijupa.png

Немного обсудим файл media.scss. Есть два варианта организации медиа-запросов.


  1. Писать медиа-запросы ко всему блоку в конце файла.
  2. Писать медиа-запросы в самом селекторе, используя @mixin и @include.


Пример второго варианта

rwndmdltykxzsgjjg--0dho2moq.png

Я поклонник второго варианта, удобно, когда все стили в одном месте, и не нужно никуда скроллить и ничего искать.

Последний шаг: подключим normalize.css. Установим командой npm install normalize.css
и добавим @import "../../../node_modules/normalize.css/normalize"; в начале файла global.scss
Зачем нужен normalize.css?

Все делаем так же, как и с другими тасками, только подключаем другие плагины.
Установим сначала все необходимые зависимости npm i gulp-babel @babel/core @babel/preset-env --save-dev
и зависимости для eslint npm install eslint eslint-config-htmlacademy eslint-config-standard eslint-plugin-standard eslint-plugin-promise eslint-plugin-import eslint-plugin-node --save-dev


Пример

wxtc0o201ahjphpgm6osrv_jjos.jpeg


  • gulp-terser — минификация и оптимизация javascript.


Задача script

hwmo6-afrzokestl5bwjdj7jgxw.png


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


.eslintrc.json

pt6yr9qvwxwfnqldc8nuv4qogtk.png

Устанавливаем плагины npm i gulp-imageMinify gulp-svgstore
Для картинок используется банальный код, который вы уже на данном этапе без проблем можете понять.

Шрифты мы просто копируем.

Не вижу смысла объяснять, как делать svg спрайты, когда в интернете много статей. Вот одна из них.

Чтобы каждый раз не обновлять страницу при изменении файла, подключим browser-sync. У gulp есть встроенная функция, которая следит за изменениями и вызывает нужные нам функции. Советую зайти и почитать о возможностях browsersync. Мне очень нравится возможность открытия сайта и синхронизации прокрутки страницы на нескольких устройствах. Очень удобно верстать адаптивные сайты: открыл на компьютере, открыл на телефоне — и сразу видишь результат.


Наш локальный сервер. Задача serve

wnhimc3agc9cozu7n54ebiq5-nm.png

Бывает такое, что сделал опечатку, сохранил код и сборка падает с ошибкой. Нужно снова перезапускать сборку, и со временем это может начать раздражать, поэтому установим npm i gulp-plumber. Plumber будет перехватывать ошибки, и после устранения ошибки сборка восстановит работоспособность. Интегрировать его очень просто, добавляем его первым .pipe(plumber()) в наших трубопроводах цепочках pug2html и styles.

Во время разработки мы будем создавать и удалять файлы. Так как у нас live reload, то созданные файлы автоматически попадут в build. Когда чуть позже мы решим удалить файл, то он останется в папке build, поэтому сделаем еще одну задачу clean, которая будет удалять папку. Установим плагин npm install del. Del.

const del = require('del')

module.exports = function clean(cb) {
  return del('build').then(() => {
    cb()
  })
}

Главное — не забыть вызвать функцию-callback, которая сообщит gulp, что задача выполнена.


Lighthouse


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

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

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


Скрин с реального проекта

pnl5mmzzrdljesd2hl4-zhpkke4.jpeg

Устанавливаем npm i --save-dev gulp-connect lighthouse chrome-launcher и создаём задачу.
Результат для каждой странички будет генерироваться в папку ./reports. Там будут 'html' файлы, они открываются автоматически, но вы сами в любой момент можете их открыть и посмотреть результат.

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


lighthouse.js

0_3fuonzaxnhu8fzrdocxxqbrky.png

Кода многовато, но он простой. Запустили наш локальный сервер с помощью browser-sync, потом хром и в конце lighthouse, где говорим, по какому url искать наш сайт.

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

gulp-npm-dist — очень хороший плагин, мне он нравится тем, что он не просто копирует всю папку модуля, а только нужные файлы. README.md, LICENSE, .gitignore и другие конфигурационные файлы не копируются.

Теперь сделаем, чтобы при изменении package.json вызывался плагин. Не вижу смысла сильно заморачиваться и следить только за изменениями объекта dependencies, поэтому будем просто следить за файлом.

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

Рассмотрим такую ситуацию: вы скопировали большой кусок кода с постороннего ресурса, и
он не соответствует вашим правилам форматирования.
Не будете же вы всё править руками? Можно просто в консоли ввести команду, которая все исправит
за вас stylelint ./src/styles/**/*.scss --fix --syntax scss, команда длинная, поэтому запишем ее в NPM-скрипт


Добавили NPM-скрипт

ktsd8jvsodx0lo0mowygm6kxmiu.jpeg

Как видим на скрине, теперь в консоли можно вводить npm run stylelint-fix.

Напишем еще несколько команд:


  • npm run start — вместо gulp, привык, что любой проект у меня запускается этой командой
  • npm run build — вместо gulp build, такая же ситуация, как в прошлом пункте
  • npm run lighthouse — вместо gulp build && gulp lighthouse, сначала собираем проект, а потом уже тестируем
  • npm run test — запуск разных линтеров, хорошей практикой будет запускать перед комитом

Не верю я, что вы будете перед комитом запускать npm run test, даже не верю, что я буду. Поэтому скачаем husky и добавим:

"husky": {
    "hooks": {
      "pre-commit": "npm run test"
    }
  }

в package.json. Если npm run test вернет ошибку, то комит не будет сделан.

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

Ссылка на репозиторий с тем, что у нас получилось: github.

© Habrahabr.ru