Начало
Посмотрев на календарь, я понял, что уже 2020, а посмотрев на свою сборку, которая была с 2018 года, я понял, что пора её менять. В этой статье мы разберем структуру проекта, плагины (минимальный набор функционала) и их новые возможности, которые добавились за такое большое время. Мы разберем все моменты, чтобы новичок мог себе скачать эту сборку и начать с ней работать.
Не только древность моей сборки мотивировала на эту статью, но и еще одна причина: мне больно смотреть, когда заходят на онлайн-сервисы для конвертации scss
, минификации javascript
и других рутинных задач. Самое забавное — когда сделали мелкую правку, снова нужно проходить все круги ада копипаста.
Перед тем, как вы начнете читать, хочу сказать, что материала очень много, поэтому писал только основное. Слишком очевидных вещий старался не писать, но хотел, чтобы смог понять каждый новичок. Если будут непонятные моменты, то смело переходите по ссылкам и читайте более подробную информацию, а потом снова возвращайтесь к статье. Можете задавать вопросы в комментариях, всем постараюсь ответить.
Поехали!
Gulp
Начнем с главного в нашей сборке.
Gulp — это наш фундамент, потому что он будет управлять всеми нашими задачами, другими словами, taskmanager
. Концепция задач очень проста. Название асинхронной javascript функции равно названию задачи, и работает она по принципу: берет данные, трансформирует их и записывает результат.
Как видим, все просто: называем нашу задачу css
, вызываем функцию src
, которая берет данные по определенному шаблону и передает данные по трубе (функция pipe()
). Внутри pipe
вызываем функцию плагина, которая трансформирует наши данные и результат передает функцию dest
. dest
— записывает результат по указанному пути.
С pipe
можно выстраивать любые цепочки.
В четвертой версии много чего изменилось, но на что нужно точно обратить внимание — это на 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
.
Если они не установлены, следуйте инструкциям здесь.
Создаем папку для нашего проекта. Внутри инициализируем npm npm init --yes
Ключ --yes
означает автоматические ответы вопрос по проекту.
Создадим три папки:
build
— оптимизированные файлы для использования на сервереsrc
— рабочая папка, где будут храниться все наши исходникиgulp
— здесь будут храниться наши tasks
Еще добавим файл .gitignore
, чтобы системные файлы не попадали в репозиторий.
Папка /build
задокументирована, потому что часто использую GitHub Pages
для демонстрации работы.
Не забудьте установить gulp: npm install --save-dev gulp
# Файлы и папки операционной системы
.DS_Store
Thumbs.db
# Файлы редактора
.idea
*.sublime*
.vscode
# Вспомогательные файлы
*.log*
node_modules/
# Папка с собранными файлами проекта
# build/
Первые шаги
HTML
У НTML сильно громоздкий синтаксис, и при большой вложенности тегов сложно разобрать код. Еще одна проблема в том, что многие забывают закрывать теги. Можно возразить, что сейчас умные IDE без проблем индицируют эти проблемы, но, как правило, новички не обращают внимания, что там им говорит IDE, и еще грешат форматированием кода.
Все наши проблемы решает Pug. Одного примера будет достаточно, чтобы понять, как его использовать. Не понимаю, почему этот плагин еще повсюду не используют.
Новичкам советую обратить внимание еще на несколько возможностей:
Разделение на модули
— удобно, когда используешь БЭМ: один блок — один файл. Подробнее.
Миксины
— удобно использовать для однотипных блоков. Например, карточки товаров или комментариев. Подробнее.
Циклы
для генерации однотипных блоков. Подробнее.
За последнее время сильно ничего не изменилось, только название с Jade
на Pug
. Подробнее.
Разделяем HTML
На нашем сайте будут две тестовые страницы about
и index
. Структура на страницах повторяется: есть блоки <footer>
, <header>
, <head>
. Поэтому все нужно вынести в отдельные файлы модули.
Разберем все более подробно.
pages
— папка для наших страниц, где в корне хранятся непосредственно страницыcommon
— хранятся общие блоки для всех страницincludes
— хранятся модули страниц, где внутри еще одна папка, которая соответствует названию страницы
Пройдемся по файлам:
layout.pug
— шаблон, который хранит основную структуру, и от него наследуются все другие страницы
index.pug
иabout.pug
— наши страницы, которые наследуются от шаблона и подключают свои контентные модули
Еще, обратите внимание, у pug
есть комментарии, которые попадают в html
и которые нет. Подробнее здесь.
Автоматизируем первую задачу
Установим плагин gulp-pug для компиляции наших шаблонов. Выполните в консоли команду: npm i gulp-pug
Создадим файл pug2html.js
в папке gulp/tasks
.
Здесь все понятно: ищем файлы по указанному пути, компилируем и результат выгружаем в папку build
. Еще добавим pug-linter, чтобы новички не косячили и сохраняли единый стиль написания кода. Для конфигурации создадим файл .pug-lint.json
в корне проекта. Правила для линтера писал на свой вкус. Вы без проблем сможете изменить. Список правил.
Теперь подключаем нашу задачу в файле gulpfile.js
.
Здесь мы создаем серию с одной таски с названием 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 — самописный плагин, сделал на скорую руку, потому что не смог найти аналогов. Очень маленький функционал, думаю, со временем буду улучшать.
Стили
Для стилей мы будем использовать Scss. Все дается по аналогии с задачей pug2html
. Создаем новую папку styles
и скачиваем нужные пакеты npm install node-sass gulp-sass --save-dev
.
Дальше пишем задачу, как и делали раньше. Берем файлы, передаем в плагин и потом сохраняем результат.
Дальше мы добавим вспомогательные плагины: 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 — сокращает стили.
- gulp-clean-css — минификация стилей
- gulp-sourcemaps — ответ на вопрос: "Что это такое и зачем нужно?"
- stylelint gulp-stylelint stylelint-scss — плагины для проверки наших стилей
- stylelint-config-htmlacademy stylelint-config-standard stylelint-config-standard-scss — готовые конфиги, которые мы подключили в свой конфиг
Файлы styles:
Немного обсудим файл media.scss
. Есть два варианта организации медиа-запросов.
- Писать медиа-запросы ко всему блоку в конце файла.
- Писать медиа-запросы в самом селекторе, используя
@mixin
и@include
.
Я поклонник второго варианта, удобно, когда все стили в одном месте, и не нужно никуда скроллить и ничего искать.
Последний шаг: подключим normalize.css. Установим командой npm install normalize.css
и добавим @import "../../../node_modules/normalize.css/normalize";
в начале файла global.scss
Зачем нужен normalize.css?
JavaScript
Все делаем так же, как и с другими тасками, только подключаем другие плагины.
Установим сначала все необходимые зависимости 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
- gulp-babel @babel/core @babel/preset-env — babel конвертирует ECMAScript 2015+ код в более старую версию
- gulp-terser — минификация и оптимизация javascript.
- eslint — мы уже знаем, что делают линтеры. Решил подключить готовые конфиги, потому что очень много разных правил, чтобы все настраивать с нуля.
Оптимизируем картинки, копируем шрифты, делаем svg-sprite
Устанавливаем плагины npm i gulp-imageMinify gulp-svgstore
Для картинок используется банальный код, который вы уже на данном этапе без проблем можете понять.
Шрифты мы просто копируем.
Не вижу смысла объяснять, как делать svg спрайты, когда в интернете много статей. Вот одна из них.
Экономим время
Чтобы каждый раз не обновлять страницу при изменении файла, подключим browser-sync. У gulp есть встроенная функция, которая следит за изменениями и вызывает нужные нам функции. Советую зайти и почитать о возможностях browsersync. Мне очень нравится возможность открытия сайта и синхронизации прокрутки страницы на нескольких устройствах. Очень удобно верстать адаптивные сайты: открыл на компьютере, открыл на телефоне — и сразу видишь результат.
Бывает такое, что сделал опечатку, сохранил код и сборка падает с ошибкой. Нужно снова перезапускать сборку, и со временем это может начать раздражать, поэтому установим 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() {
return del('build')
}
Главное — не забыть вызвать функцию-callback, которая сообщит gulp, что задача выполнена.
Lighthouse
Lighthouse — решение для веб-приложений и веб-страниц, которое собирает современные показатели производительности.
Кстати, некоторые заказчики смотрят на эти показатели, так как не знают других способов оценить качество верстки.
Вы можете возразить, зачем ради одной странички заморачиваться, но в реальных проектах их может быть больше 10.
Устанавливаем npm i --save-dev gulp-connect lighthouse chrome-launcher
и создаём задачу.
Результат для каждой странички будет генерироваться в папку ./reports
. Там будут 'html' файлы, они открываются автоматически, но вы сами в любой момент можете их открыть и посмотреть результат.
На первый взгляд может показаться, что лучше запустить несколько страниц на тестирование, но этого нельзя сделать в одном запущенном процессе хрома, а если запустить несколько процессов паралельно, то результаты могут быть очень неточные.
Кода многовато, но он простой. Запустили наш локальный сервер с помощью browser-sync
, потом хром и в конце lighthouse
, где говорим, по какому url
искать наш сайт.
Копируем зависимости
В нашей команде есть правило, что все dependencies
нужно загружать в репозиторий. Это было связано с тем, что иногда может пропасть интернет в стране. Вручную скачивать пакеты с сайтов и загружать их в папку не очень удобно, ещё сложно следить за версиями пакетов, и каждый раз из node_modules обновлять также не очень удобно, поэтому мы должны оптимизировать этот процесс.
gulp-npm-dist — очень хороший плагин, мне он нравится тем, что он не просто копирует всю папку модуля, а только нужные файлы. README.md
, LICENSE
, .gitignore
и другие конфигурационные файлы не копируются.
Теперь сделаем, чтобы при изменении package.json
вызывался плагин. Не вижу смысла сильно заморачиваться и следить только за изменениями объекта dependencies
, поэтому будем просто следить за файлом.
NPM-скрипты
Последняя оптимизация. Часто сложно и лень запоминать консольные команды, там много параметров, вводить все эти пути занимает время,
поэтому запишем длинные команды в более краткие команды.
Рассмотрим такую ситуацию: вы скопировали большой кусок кода с постороннего ресурса, и
он не соответствует вашим правилам форматирования.
Не будете же вы всё править руками? Можно просто в консоли ввести команду, которая все исправит
за вас stylelint ./src/styles/**/*.scss --fix --syntax scss
, команда длинная, поэтому запишем ее в NPM-скрипт
Как видим на скрине, теперь в консоли можно вводить npm run stylelint-fix
.
Напишем еще несколько команд:
npm run start
— вместоgulp
, привык, что любой проект у меня запускается этой командойnpm run build
— вместоgulp build
, такая же ситуация, как в прошлом пунктеnpm run lighthouse
— вместоgulp build && gulp lighthouse
, сначала собираем проект, а потом уже тестируемnpm run test
— запуск разных линтеров, хорошей практикой будет запускать перед комитом
PRE-COMMIT
Не верю я, что вы будете перед комитом запускать npm run test
, даже не верю, что я буду. Поэтому скачаем husky и добавим:
"husky": {
"hooks": {
"pre-commit": "npm run test"
}
}
в package.json
. Если npm run test
вернет ошибку, то комит не будет сделан.
Спасибо
Очень приятно, если вы прочли всю статью и сумели принять мои мысли. Отвечу на вопросы в комментариях и жду ваших pull requests
и issue
. Всем приятных сборок.
Ссылка на репозиторий с тем, что у нас получилось: github.