Оглавление статьи
- Вводная часть
- Базовая инициализация
- Установка Gulp
- Установка линтеров
- Настройка линтеров
- Проверка линтера
- Структура каталогов
- Создание config.js
- Редактируем package.json
- Таск clear
- Таск server
- Таск webpack
- Таск styles
- Таск sprites
- Таск images
- Таск assets
- Таск pug
- Таск favicons
- Файл manifest.json
- Файл robots.txt
- Подведем итоги
Вводная часть
Основная идея заключается в создании сборщика для верстки сайтов и автоматизации рутинных действий. Часть используемых идей такие как базовая SEO оптимизация, заготовки в виде скриптов Google Analytics и Yandex Metrika которые мы использовали при создании простого HTML шаблона, будут так же использованы в данной сборке.
Каждый шаг создания Gulp сборки я старался комментировать в репозитории сборки. Обязательно загляните туда если походу статьи вам будет что-то не понятно.
Основной функционал
Основной функционал gulp сборки будет включать в себя:
- Оптимизация под Page Speed
- Отложенная загрузка
- Критические стили
- Базовая SEO оптимизация
- Open Graph / Twitter Cards
- JSON-LD микроразметка
- Google Analytics / Yandex Metrika
- Pug шаблонизатор
- Улучшенный фильтр markdown-it
- Возможность добавлять свои фильтры на примере фильтра special-chars
- Работа с json данными
- SCSS препроцессор
- Webpack сборщик
- Возможность использовать новый формат JS по максимуму
- Оптимизация кода через Babel
- SVG спрайты
- Цветные
- Черно-белые
- Оптимизированные изображения
- Генерация webp и avif форматов
- Настроенные линтеры
- Stylelint
- Pug-lint
- ES-Lint
- Prettier
- Editorconfig
- Автоматическая генерация favicons
Базовая инициализация
Создадим базовые файлы для проекта, такие как конфиги, инициализируем git и npm. О том как установить и настроить git говорилось в соответствующих статьях, так же и про установку npm
Cоздание README.md
В корень проекта добавим файл README.md для вводной информации о сборке.
# Gulp сборка
Gulp сборка c использованием pug, scss, webpack.
Инициализация git
Git нам понадобится в качестве системы учета контроля версий. В дальнейшем мы будем делать commit предварительно пропуская сохраняемые файлы через linter, об этом более подробно далее.
git init
Создание .gitignore
В корне проекта создадим файл .gitignore, в нем мы будем прописывать пути к каталогам и файлам которые не следует добавлять в репозиторий.
.idea
.vscode
node_modules
build
В дальнейшем мы сможем дополнить этот файл другими правилами.
Создание .gitattributes
В корне проекта создадим файл .gitattribute, в нем мы будем задавать правила как git должен обрабатывать различные расширения файлов.
# Общие настройки, которые обязательно должны быть прописаны.
# Автоматическое определение текстовых файлов и выполнение нормализации LF.
* text eol=lf
# Изображения
*.png binary
*.jpg binary
*.jpeg binary
*.gif binary
*.webp binary
*.tif binary
*.tiff binary
*.ico binary
# SVG по дефолту рассматривается как бинарный.
# Для распознавания как текcт, закомментируйте нижнюю строку и раскомментируйте следующую за ней.
*.svg binary
#*.svg text
*.eps binary
*.mo binary
*.po binary
# Шрифты
*.woff binary
*.woff2 binary
# Документы
*.doc diff=astextplain
*.docx diff=astextplain
*.dot diff=astextplain
*.pdf diff=astextplain
*.rtf diff=astextplain
*.md text
*.tex text
*.adoc text
*.textile text
*.mustache text
*.csv text
*.tab text
*.tsv text
*.sql text
# Исключить файлы из экспорта
.gitattributes export-ignore
.gitignore export-ignore
Инициализация npm
Удостоверьтесь что у вас установлен Node.js. Инициализируем пакетный менеджер зависимостей npm, с его помощью мы будем устанавливать необходимые зависимости для нашего проекта.
npm init -y
В корне проекта создастся файл package.json, примерное содержимое файла:
{
"name": "gulp-template",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC"
}
Установка Gulp
Установим Gulp сборщик, для этого перейдем на официальный сайт и выполним предложенные команды в терминале.
// Установит глобально для всей системы gulp-cli, чтобы можно было использовать команду gulp в терминале.
npm i --global gulp-cli
// Установит gulp для текущего проекта в режиме --save-dev
npm i -D gulp
Имеются два режима установки npm зависимостей:
- Зависимость используемая только при разработке --save-dev или -D
- Зависимость используемая в готовом варианте проекта --save или -S (по умолчанию всегда ставится этот режим если явно его не переопределить)
Создание gulpfile.babel.js
В корне проекта создадим файл gulpfile.babel.js.
Это главный файл для сборки, именно его будет запускать Gulp. Существует несколько вариантов gulpfile, вариант с babel означает, что при проектировании сборки мы сможем использовать все прелести ES6 и сделать ее модульной и структурированной. О вариантах gulpfile можно почитать здесь.
Так же в корне проекта создадим каталог gulp в котором будем хранить все наши таски.
// Установит глобально для всей системы gulp-cli, чтобы можно было использовать команду gulp в терминале.
npm i --global gulp-cli
// Установит gulp для текущего проекта в режиме --save-dev
npm i -D gulp
Установка babel
Само по себе название файла gulpfile.babel.js еще ни чего не дает, необходимо установить некоторые зависимости для того, чтобы Gulp начал понимать конструкции ES стандарта.
npm i -D @babel/core @babel/register
@babel/core - это основной пакет babel, который содержит основные компоненты для транспиляции кода (перевода кода из нового формата ES6 в старый формат ES5, для поддержки старых браузеров), включая лексический анализатор (парсер), генератор кода, правила трансформации и другие инструменты, вместе с другими плагинами и пресетами.
@babel/register, этот пакет перехватывает операции чтения файлов с расширением .js и .jsx в процессе выполнения. После перехвата он трансформирует код на лету, используя babel, и передает его исполнителю.
Подробнее о babel можно почитать здесь.
Установка пресета и полифилла
Пресет и полифиллы необходимы для обратной совместимости новых возможностей JavaScript с старыми браузерами.
npm i -D @babel/preset-env core-js
@babel/preset-env - используется для транспиляции кода на этапе компиляции и core-js для добавления необходимых полифиллов на этапе выполнения, чтобы обеспечить оптимальную совместимость с различными окружениями.
При совместном использовании @babel/preset-env c core-js, babel автоматически добавит необходимые полифиллы из core-js в код на этапе компиляции.
Подробнее о пресете и полифилле можно почитать здесь.
Настройка пресета
Чтобы пресет заработал ему необходимо задать определенные правила по которым он должен действовать. Правила для пресета должны быть указаны в файле .babelrc. В корне проекта создадим файл .babelrc со следующим содержимым.
{
"presets": [
[
"@babel/preset-env",
{
"debug": false,
"useBuiltIns": "usage",
"corejs": "3"
}
]
]
}
Разберем каждую настройку по отдельности:
presets - это опция babel, которая указывает список наборов плагинов (пресетов), которые будут использоваться в процессе транспиляции.
@babel/preset-env - это пресет, который включает набор плагинов, необходимых для транспиляции кода с использованием современного синтаксиса JavaScript и поддержки различных окружений.
debug - это опция пресета @babel/preset-env, которая определяет, будет ли выводиться отладочная информация в консоль. Значение false указывает на то, что отладочная информация не будет выводиться.
useBuiltIns - это опция пресета @babel/preset-env, которая указывает, каким образом следует добавлять полифиллы из core-js. Значение "usage" означает, что Babel будет добавлять только те полифиллы, которые необходимы в вашем коде, на основе его актуального использования. Это позволяет уменьшить размер финального бандла и добавлять только необходимый функционал.
corejs - это опция пресета @babel/preset-env, которая указывает версию core-js, которая будет использоваться для предоставления полифиллов. В данном случае указана версия "3", что означает использование core-js версии 3.
Тестируем gulpfile.babel.js
В файле gulpfile.babel.js пропишем следующий код.
import { dest } from 'gulp'
export default (done) => {
console.log('Test message')
done()
}
Выполним команду gulp в терминале.
gulp
Мы должны увидеть следующий вывод.
[time] Requiring external module @babel/register
[time] Using gulpfile ~/gulp-template/gulpfile.babel.js
[time] Starting 'default'...
Test message
[time] Finished 'default' after 2.24 ms
Установка линтеров
Линтеры позволяют поддерживать весь код проекта в единообразном стиле, придерживаясь определенных правил написания кода. Линтер не позволит пользователю создать commit до тех пор, пока код не будет отредактирован согласно правилам.
В рамках сборки мы будем использовать линтеры для файлов js, pug и scss, а так же будем проверять соответствие кода правилам, прописанным в файлах .editorconfig и .prettierrc.
Перед установкой зависимостей удостоверьтесь, что у вас установлены расширения для редактора кода или IDE, которые работают с линтерами. Это нужно для того, чтобы ошибки в коде автоматически подсвечивались в вашем редакторе. Например, для VSCode нужно будет установить расширения: Pug Linter (pug-lint), stylelint, ESLint. Расширения и плагины были рассмотрены в предыдущих статьях.
Линтер для JS
Установим соответствующие зависимости.
npm i -D eslint eslint-config-airbnb-base eslint-plugin-import @babel/eslint-parser eslint-config-prettier
Это инструменты и пакеты, используемые для статического анализа кода JavaScript с помощью ESLint.
eslint - это инструмент статического анализа кода для JavaScript, который помогает выявить и предотвратить потенциальные ошибки, стилистические проблемы и проблемы безопасности в вашем коде.
eslint-config-airbnb-base - это предустановленный конфигурационный набор правил ESLint, созданный и поддерживаемый Airbnb компанией, которая активно использует и поддерживает JavaScript код стандарта Airbnb.
eslint-plugin-import - это плагин для ESLint, который предоставляет правила и функциональность, связанные с импортами и использованием модулей. Этот плагин часто используется с eslint-config-airbnb-base, чтобы улучшить поддержку правил импортов в стандарте Airbnb.
@babel/eslint-parser - это парсер для ESLint, который позволяет использовать Babel для разбора синтаксиса JavaScript файлов вместо стандартного парсера, который используется по умолчанию.
eslint-config-prettier - гарантирует, что правила форматирования, установленные Prettier, не будут нарушены правилами ESLint. Это позволяет легко интегрировать ESLint и Prettier в проект, чтобы управлять как стилем, так и качеством кода.
Линтер для Pug
Установим соответствующие зависимости.
npm i -D pug-lint
Это инструмент для статического анализа кода шаблонов Pug.
pug-lint - позволяет выявлять потенциальные проблемы, стилистические ошибки и несоответствия соглашениям о форматировании в коде Pug.
Линтер для стилей CSS и Scss
Установим соответствующие зависимости.
npm i -D stylelint stylelint-config-rational-order stylelint-config-recommended-scss stylelint-config-standard stylelint-order stylelint-scss
Это инструменты и пакеты, используемые для статического анализа кода CSS (или его препроцессоров, таких как SCSS) с помощью Stylelint.
stylelint - помогает выявить и предотвратить потенциальные ошибки, стилистические проблемы и проблемы форматирования в вашем CSS коде.
stylelint-config-rational-order - упорядочивает свойства CSS в определенном логическом порядке, что улучшает читаемость и облегчает поддержание кода.
stylelint-config-recommended-scss - предоставляет рекомендуемые правила для SCSS кода (расширение CSS, поддерживающее дополнительные возможности, такие как переменные и вложенные стили).
stylelint-config-standard - предоставляет стандартные правила форматирования CSS, соответствующие общим стандартам и соглашениям о кодировании.
stylelint-order - позволяет настроить, каким образом должны быть упорядочены свойства CSS в вашем коде.
stylelint-scss - это плагин для Stylelint, который добавляет поддержку для SCSS синтаксиса и дополнительных функций, таких как вложенные правила и переменные.
Линтер для Editorconfig и Prettier
Установим соответствующие зависимости.
npm i -D editorconfig-checker prettier
editorconfig-checker - это инструмент для проверки соответствия файлов вашего проекта настроенным правилам EditorConfig.
prettier - это инструмент для автоматического форматирования кода, который позволяет приводить код в соответствие с определенным стилем кодирования, без необходимости ручного форматирования.
Установим Lint staged
Установим соответствующие зависимости.
npx mrm lint-staged
mrm - это инструмент командной строки, который предоставляет набор готовых задач (код-модификаций), которые можно применить к вашему проекту для настройки, обновления или улучшения его.
lint-staged - это инструмент, который позволяет запускать линтеры (например, ESLint или Stylelint) только для тех файлов, которые были изменены или добавлены в систему контроля версий, перед фиксацией изменений (commit). Это позволяет оптимизировать процесс проверки кода, чтобы проверять только измененные файлы, а не весь проект.
Когда вы запускаете npx mrm lint-staged, команда mrm будет временно устанавливать и запускать задачу lint-staged. Задача lint-staged, в свою очередь, проверит измененные файлы в вашем проекте с помощью настроенных линтеров перед фиксацией изменений (commit). Это позволяет убедиться, что ваш код соответствует стандартам и соглашениям о форматировании, прежде чем он попадет в систему контроля версий.
Настройка линтеров
После установке в файле package.json появятся следующие изменения.
{
...
"scripts": {
...
"prepare": "husky install"
},
...
"devDependencies": {
...
"husky": "^8.0.3",
"lint-staged": "^13.2.3",
"editorconfig-checker": "^5.1.1",
"eslint": "^8.45.0",
"eslint-config-airbnb-base": "^15.0.0",
"eslint-plugin-import": "^2.27.5",
"prettier": "^3.0.0",
"pug-lint": "^2.7.0",
"stylelint": "^15.10.2",
"stylelint-config-rational-order": "^0.1.2",
"stylelint-config-recommended-scss": "^12.0.0",
"stylelint-config-standard": "^34.0.0",
"stylelint-order": "^6.0.3",
"stylelint-scss": "^5.0.1"
},
"lint-staged": {
"*.js": "eslint --cache --fix",
"*.css": "stylelint --fix",
"*.{js,css,md}": "prettier --write"
}
}
Как видим установились нужные нам зависимости, а так же были добавлены husky и lint-staged.
husky - предназначен для управления хуками Git в вашем проекте. Хуки Git - это скрипты, которые можно запускать автоматически при определенных событиях Git, таких как commit, push и т.д. Husky позволяет легко настроить и управлять хуками, чтобы выполнять определенные действия перед или после выполнения операций с Git.
В конструкции scripts можно заметить, что было добавлено правило "prepare": "husky install". Данная команда автоматически была вызвана при инициализации npx mrm lint-staged, но если предположим вы захотите на основе данной сборке создать новый проект, вам понадобится установить husky вручную командой:
npm run prepare
Посмотрим на следующий фрагмент в файле package.json.
"lint-staged": {
"*.js": "eslint --cache --fix",
"*.css": "stylelint --fix",
"*.{js,css,md}": "prettier --write"
}
Данное правило позволяет при создании commit, автоматически запускать eslint для всех измененных файлов с расширением .js, stylelint для всех измененных файлов с расширением .css и prettier для всех измененных файлов с расширениями js, css, md.
Если линтеры обнаружат проблемы, они будут пытаться исправить их автоматически (--fix), и если исправления возможны, они будут включены в commit. Если исправления невозможны, lint-staged не позволит сделать commit, что поможет поддерживать качество кода и согласованность в проекте. Решить найденные проблемы нужно будет вручную, отредактировав соответсвующее место.
А теперь давайте изменим, дополним эти правила:
"lint-staged": {
"*": "editorconfig-checker --exclude '.git|.husky|node_modules'",
"src/pug/**/*.pug": "pug-lint --fix",
"src/scss/**/*.scss": "stylelint --fix",
"*.js": [
"eslint --cache --fix",
"prettier --write"
]
}
Каждый прописанный линтер будет сверять, найденные файлы с определенными, заданными правилами. На данном этапе мы еще создавали файлы с правилами. Ниже я опишу, что это за файлы, а после мы их создадим.
editorconfig-checker - проверит все файлы в проекте (согласно правилам прописанным в файле .editorconfig), кроме тех которые находятся в каталогах .git, .husky, node_modules.
prettier - проверит все js файлы в проекте, согласно правилам прописанным в файле .prettierrc.
pug-lint - проверит все pug файлы в проекте, согласно правилам прописанным в файле .pug-lintrc.
stylelint - проверит все scss файлы в проекте, согласно правилам прописанным в файле .stylelintrc.
eslint - проверит все js файлы в проекте, согласно правилам прописанным в файле .eslintrc.js.
Создание .editorconfig
В корне проекта создадим файл .editorconfig, в нем мы будем задавать правила того как редактор кода, IDE должны стандартизировать содержимое файлов.
# Корневой файл .editorconfig
root = true
# Все файлы
[*]
charset = utf-8 # кодировка
indent_style = space # отступ через пробел
indent_size = 2 # размер отступа 2 символа
end_of_line = lf # unix стиль новой строки
trim_trailing_whitespace = true # удалять пробелы в конце строк
insert_final_newline = true # оставлять в конце файла пустую строку
# Pug файлы
[*.pug]
trim_trailing_whitespace = false # удалять пробелы в конце строк
# Md файлы
[*.md]
trim_trailing_whitespace = false # удалять пробелы в конце строк
Создание .prettierrc
В корне проекта создадим файл .prettierrc, в нем мы будем задавать дополнительные правила того как редактор кода, IDE должны стандартизировать содержимое js файлов.
{
"singleQuote": true, // одинарные кавычки
"printWidth": 80, // максимальная длина строки, после которой Prettier будет автоматически переносить код на новую строку.
"semi": false, // автоматически добавлять точки с запятой в конце каждого выражения
"tabWidth": 2, // количество пробелов, которые будут заменять один символ табуляции
"trailingComma": "all", // нужна ли запятая в конце объекта
"useTabs": false, // использовать символы табуляции, вместо пробелов, в качестве отступов
"endOfLine": "lf", // указывает символ новой строки для использования в конце файла
"bracketSpacing": true, // добавить пробелы вокруг скобок в объектах и массивах
"arrowParens": "avoid" // определяет, следует ли оборачивать параметры стрелочных функций в круглые скобки. "avoid" означает, что скобки будут опущены, если возможно.
}
Создание .pug-lintrc
В корне проекта создадим файл .pug-lintrc, в нем мы будем задавать дополнительные правила того как редактор кода, IDE должны стандартизировать содержимое pug файлов.
{
"validateExtensions": true, // проверяет расширения файлов Pug и обнаруживает файлы с неправильными расширениями
"validateDivTags": true,// проверяет использование тегов <div> и рекомендует заменить их на более специфичные теги или сокращенную форму, если это возможно
"validateAttributeSeparator": { "separator": ", ", "multiLineSeparator": ",\n " }, // проверяет разделитель атрибутов и рекомендует использовать запятую и пробел между атрибутами на одной строке, а при многострочных атрибутах, использовать запятую и перевод строки
"validateAttributeQuoteMarks": "\"", // проверяет кавычки в атрибутах и рекомендует использовать двойные кавычки для атрибутов
"requireSpecificAttributes": [ { "img": [ "src", "width", "height", "alt" ] } ], // требует, чтобы для тега <img> были указаны обязательные атрибуты "src", "width", "height", "alt"
"requireSpaceAfterCodeOperator": true, // требует пробел после оператора кода (например, =) в Pug-шаблонах
"requireLowerCaseTags": true, // требует использовать нижний регистр для имен тегов в Pug-шаблонах
"requireClassLiteralsBeforeAttributes": true, // требует, чтобы классы (атрибуты с классами) шли перед другими атрибутами в тегах
"disallowSpacesInsideAttributeBrackets": true, // запрещает использование пробелов внутри квадратных скобок атрибутов (например, [attr= "value"])
"disallowLegacyMixinCall": true, // запрещает использование устаревшего синтаксиса вызова миксинов в Pug
"disallowDuplicateAttributes": true // запрещает использование повторяющихся атрибутов в одном теге
}
Создание .stylelintrc
В корне проекта создадим файл .stylelintrc, в нем мы будем задавать дополнительные правила того как редактор кода, IDE должны стандартизировать содержимое scss файлов.
{
"extends": [
"stylelint-config-recommended-scss", // предоставляет рекомендуемые правила для SCSS-кода (расширение CSS, поддерживающее дополнительные возможности, такие как переменные и вложенные стили).
"stylelint-config-rational-order" // упорядочивает свойства CSS в определенном логическом порядке, что улучшает читаемость и облегчает поддержание кода.
],
"plugins": [
"stylelint-scss" // это плагин добавляет поддержку для SCSS-синтаксиса и дополнительных функций, таких как вложенные правила и переменные
],
"rules": {
"at-rule-no-unknown": null, // отключает правило проверки на неизвестные @ правила
"scss/at-if-no-null": null, // отключает правило проверки на использование null в SCSS @if
"scss/at-rule-no-unknown": [ // правило для проверки на неизвестные SCSS @ правила, за исключением правила @tailwind, которое игнорируется
true,
{
"ignoreAtRules": [
"tailwind"
]
}
],
"declaration-empty-line-before": null, // отключает правило проверки на пустую строку перед объявлениями
"order/properties-order": [], // перечень порядка свойств, которые в данной конфигурации пустой, что означает отсутствие жестких правил о порядке свойств
"plugin/rational-order": [ // используется плагин stylelint-order для проверки порядка свойств CSS
true,
{
"empty-line-between-groups": true // добавить пустую строку между группами свойств
}
],
"no-descending-specificity": null, // отключает правило проверки на уменьшение специфичности селекторов
"block-no-empty": null, // отключает правило проверки на пустые блоки CSS
"import-notation": null, // отключает правило проверки импорта модулей
"string-quotes": "double", // устанавливает двойные кавычки (") в качестве предпочтительного стиля для строк
"selector-class-pattern": "^(?:(?:o|c|u|t|s|is|has|_|js|qa)-)?[a-zA-Z0-9]+(?:-[a-zA-Z0-9]+)*(?:__[a-zA-Z0-9]+(?:-[a-zA-Z0-9]+)*)?(?:--[a-zA-Z0-9]+(?:-[a-zA-Z0-9]+)*)?(?:\\[.+\\])?$", // устанавливает шаблон для проверки наименований классов. В данном случае, это шаблон для БЭМ-стиля и анализирует структуру именования классов.
"selector-no-vendor-prefix": null, // отключает правило проверки на использование вендорных префиксов в селекторах
"scss/no-global-function-names": null // отключает правило проверки на использование глобальных функций в SCSS
}
}
Создание .eslintrc.js
В корне проекта создадим файл .eslintrc, в нем мы будем задавать дополнительные правила того как редактор кода, IDE должны стандартизировать содержимое js файлов.
module.exports = {
root: true, // указывает, что это корневой файл конфигурации ESLint
env: {
browser: true, // предназначен для браузера
es2023: true, // код использует возможности ECMAScript 2023
jquery: false, // код не использует jquery
},
extends: [
'airbnb-base', // предоставляет набор рекомендованных правил от Airbnb для JavaScript
'prettier', // гарантирует, что правила форматирования, установленные Prettier, не будут нарушены правилами ESLint
],
parser: '@babel/eslint-parser', // здесь указан парсер, используемый для анализа JavaScript-кода, который позволяет ESLint работать с кодом, написанным с использованием Babel
rules: {
indent: ['off', 2], // отключает правило проверки отступов и разрешает использовать два пробела в качестве отступа
'import/no-extraneous-dependencies': [ // отключает правило проверки на наличие лишних зависимостей
'off',
{
devDependencies: [
'gulpfile.babel.js',
'gulp/**/*',
],
},
],
'import/no-import-module-exports': [ // отключает правило проверки на использование import вместо module.exports
'off',
{
exceptions: [
'gulpfile.babel.js',
'gulp/**/*',
],
},
],
'import/resolver': [ // отключает правило проверки путей к импортированным модулям
'off',
{
exceptions: [
'gulpfile.babel.js',
'gulp/**/*',
],
},
],
'implicit-arrow-linebreak': [ // отключает правило проверки на использование неявных стрелочных функций
'off',
{
exceptions: [
'gulpfile.babel.js',
'gulp/**/*',
],
},
],
'no-var': 'error', // устанавливает запрет на использование var для объявления переменных
'object-curly-newline': 'error', // включает правило проверки на новую строку после открывающейся фигурной скобки
'max-len': 'off', // отключает правило проверки максимальной длины строки кода
'no-multi-assign': 'off', // отключает правило проверки на множественное присваивание
'no-unused-vars': 'error', // включает правило проверки на неиспользуемые переменные
'no-undef': 'off', // отключает правило проверки на неопределенные переменные
'no-console': 'error', // устанавливает ошибку при использовании console.log и других методов console
quotes: [2, 'single'], // устанавливает одинарные кавычки для строковых литералов
'import/no-dynamic-require': 'off', // отключает правило проверки на использование динамических require
'global-require': 'off', // отключает правило проверки на использование require вне глобальной области видимости
semi: ['error', 'never'], // устанавливает запрет на использование точек с запятой
'arrow-parens': [ // устанавливает использование круглых скобок для стрелочных функций только тогда, когда это необходимо
'error',
'as-needed',
],
'no-underscore-dangle': 'off', // проверяет и предупреждает о использовании символа подчеркивания (underscore) в идентификаторах переменных, методов и свойств объектов
},
}
Создание .eslintignore
В корне проекта создадим файл .eslintignore. Он будет использоваться для указания файлов и каталогов, которые необходимо игнорировать во время анализа ESLint.
Изначальное содержимое файла .eslintignore будет пустым.
Создание .stylelintignore
В корне проекта создадим файл .stylelintignore. Он будет использоваться для указания файлов и каталогов, которые необходимо игнорировать во время анализа Stylelint.
Изначальное содержимое файла .stylelintignore будет пустым.
Проверка линтера
Если мы откроем файл gulpfile.babel.js, то можем увидеть что у нас в нем сразу два не соответствия правилам no-unused-vars и no-console (см. правила в .eslintrc.js).
Изменим текст на “Test message 2” и попробуем сделать новый commit, если мы все настроили правильно, то commit не должен выполниться, пока мы не исправим ошибки.
git add .
git commit -m 'add lint staged'
✖ eslint --cache --fix:
~/gulp-template/gulpfile.babel.js
1:10 error 'dest' is defined but never used no-unused-vars
4:5 error Unexpected console statement no-console
✖ 2 problems (2 errors, 0 warnings)
husky - pre-commit hook exited with code 1 (error)
Так же мы можем заметить, что в корне проекта появился файл .eslintcache. Он используется для ускорения работы eslint путем кэширования предыдущих результатов. Добавим .eslintcache в файл .gitignore:
.idea
.vscode
node_modules
build
.eslintcache
А так же в файл package.json.
"lint-staged": {
"*": "editorconfig-checker --exclude '.git|.husky|node_modules|.eslintcache'",
...
}
Вернемся к исправлению ошибок, удалим не используемую переменную и console.log.
export default (done) => {
done()
}
Пробуем еще раз произвести commit.
git add .
git commit -m 'add lint staged'
✔ Preparing lint-staged...
✔ Running tasks for staged files...
✔ Applying modifications from tasks...
✔ Cleaning up temporary files...
✔ Preparing lint-staged...
✔ Running tasks for staged files...
✔ Applying modifications from tasks...
✔ Cleaning up temporary files...
[main 9840a2a] add lint staged
11 files changed, 8835 insertions(+), 1931 deletions(-)
create mode 100644 .editorconfig
create mode 100644 .eslintcache
create mode 100644 .eslintrc.js
create mode 100755 .husky/pre-commit
create mode 100644 .prettierrc
create mode 100644 .pug-lintrc
create mode 100644 .stylelintrc
Если у вас так же не проходит commit, попробуйте удалить файл .eslintcache и повторить коммит.
Мы успешно настроили линтер, позже мы протестируем его на других файлах таких как pug и scss.
Структура каталогов
Создадим структуру нашей сборки следующим образом.
mkdir -p ./src/{assets,js,pug,scss}
# assets
mkdir -p ./src/assets/{fonts,icons,images}
mkdir -p ./src/assets/icons/{mono,multi}
mkdir -p ./src/assets/images/favicons
touch ./src/assets/manifest.json
touch ./src/assets/robots.txt
# js
mkdir -p ./src/js/components
touch ./src/js/main.js
# pug
mkdir -p ./src/pug/{components,data,layouts,markdown,pages,parts}
# scss
mkdir -p ./src/scss/{core,scaffolds}
mkdir -p ./src/scss/core/{base,helpers}
mkdir -p ./src/scss/scaffolds/{components,sections}
touch ./src/scss/main.scss
touch ./src/scss/critical.scss
# libs
mkdir -p ./src/libs
Хотелось бы, чтобы полученная структура была добавлена в репозиторий, но так как многие каталоги у нас пока не содержат в себе файлов, они не попадут в репозиторий. Чтобы это исправить добавим файл .gitkeep во все пустые каталоги, которые хотим сохранить в репозитории.
touch ./src/assets/fonts/.gitkeep
touch ./src/assets/icons/mono/.gitkeep
touch ./src/assets/icons/multi/.gitkeep
touch ./src/assets/images/favicons/.gitkeep
touch ./src/js/components/.gitkeep
touch ./src/pug/components/.gitkeep
touch ./src/pug/data/.gitkeep
touch ./src/pug/layouts/.gitkeep
touch ./src/pug/markdown/.gitkeep
touch ./src/pug/pages/.gitkeep
touch ./src/pug/parts/.gitkeep
touch ./src/scss/core/base/.gitkeep
touch ./src/scss/core/helpers/.gitkeep
touch ./src/scss/core/scaffolds/components/.gitkeep
touch ./src/scss/core/scaffolds/sections/.gitkeep
touch ./src/libs/.gitkeep
Описание каталогов
- ./src/assets - содержит все оставшиеся ресурсы проекта, не вошедшие в каталоги js, scss, pug, libs
- ./src/js - содержит js скрипты
- ./src/pug - содержит разметку
- ./src/scss - содержит стили
- ./src/assets/fonts - содержит шрифты
- ./src/assets/icons/mono - содержит черно-белые svg иконки
- ./src/assets/icons/multi - содержит цветные svg иконки
- ./src/assets/images/favicons - содержит фавиконки
- ./src/assets/manifest.json - содержит настройки для PWA
- ./src/assets/robots.txt - содержит правила для поисковых роботов
- ./src/js/components - содержит логику отдельных компонентов
- ./src/js/main.js - основной исполняемый файл
- ./src/pug/components - содержит разметку отдельных компонентов (например кнопки, иконки)
- ./src/pug/data - содержит различные настройки и данные (например список ссылок для навигации)
- ./src/pug/layouts - содержит шаблоны верстки
- ./src/pug/markdown - содержит markdown разметку страниц, альтернатива pug разметке
- ./src/pug/pages - содержит pug разметку, альтернатива markdown разметке, на самом деле markdown разметку можно внедрять в pug разметку для удобства наполнения страниц контентной информацией, но об этом позже
- ./src/pug/parts - содержит отдельные секции разметки (например шапка, подвал)
- ./src/scss/core/base - содержит базовые стили, шрифты
- ./src/scss/core/helpers - содержит переменные, миксины, функции
- ./src/scss/core/scaffolds/components - содержит стили компонентов верстки (что то не большое)
- ./src/scss/core/scaffolds/sections - содержит стили секций верстки (что то крупное)
- ./src/scss/critical.scss - содержит подключение критических стилей
- ./src/scss/main.scss - содержит подключение всех остальных стилей
- ./src/libs - содержит файлы сторонних библиотек (например слайдеры, бутстрап)
Сохраняем изменения
Если сейчас попытаться закоммитить изменения, то линтер нас не пропустит так как имеются пустые scss файлы. Чтобы все же добавить пустые scss файлы в репозиторий добавим в каждый scss файл следующий комментарий.
/* stylelint-disable */
Либо в файле .eslintignore пропишем путь к каталогу со стилями.
./src/scss/**/*
Это вынужденная мера, так как мне хочется сохранить структуру в репозиторий. В дальнейшем, когда начнем наполнять каталог src файлами мы удалим временные файлы .gitkeep и комментарии /* stylelint-disable */.
Создание config.js
Внутри каталога gulp создадим файл config.js и каталог tasks.
mkdir -p ./gulp/tasks
touch ./gulp/config.js
touch ./gulp/tasks/.gitkeep
- config.js - содержит все конфигурации связанные со сборкой
- tasks - содержит модули сборки, которые будут автоматизировать процесс
Изначальное содержимое файла config.js будет таким:
/**
* Конфигурационный файл
*
* Содержит пути к каталогам и параметры для тасков
*/
const srcPath = 'src' // ресурсы для разработки проекта
const buildPath = 'build' // готовый продакшен проект
const config = {
proxy: 'http://localhost', // url виртуального хоста
port: 3000, // порт виртуального хоста
// Пути к каталогам для разработки проекта
src: {
root: srcPath, // корневой каталог
// Шаблонизатор pug
pug: {
root: `${srcPath}/pug`, // корневой каталог pug
pages: `${srcPath}/pug/pages`, // pug страницы для компиляции в html
},
// Препроцессор Scss
scss: `${srcPath}/scss`, // scss стили
// Скрипты
js: {
root: `${srcPath}/js`, // корневой каталог js
components: `${srcPath}/js/components`, // компоненты js
},
libs: `${srcPath}/libs`, // сторонние библиотеки
// Различные ресурсы
assets: {
root: `${srcPath}/assets`, // корневой каталог js
images: `${srcPath}/assets/images`, // изображения
favicons: `${srcPath}/assets/favicons`, // фавиконки
icons: {
root: `${srcPath}/assets/icons`, // корневой каталог svg иконок
mono: `${srcPath}/assets/icons/mono`, // черно-белые иконки
multi: `${srcPath}/assets/icons/multi`, // цветные иконки
},
fonts: `${srcPath}/assets/fonts`, // шрифты
},
},
// Пути к каталогам для продакшен проекта
build: {
root: buildPath, // корневой каталог js
css: `${buildPath}/css`, // стили
js: `${buildPath}/js`, // скрипты
images: `${buildPath}/img`, // изображения
fonts: `${buildPath}/fonts`, // шрифты
},
// Определение окружения сборки проекта
setEnv() {
this.isProd = process.argv.includes('--prod') // true если сборка проекта выполнена с ключом --prod
this.isDev = !this.isProd // false если сборка проекта выполнена без ключа --prod
},
}
export default config
Редактируем gulpfile.babel.js
Чтобы проверить работоспособность, созданного файла config.js, изменим содержимое gulpfile.babel.js на.
// Пользовательские скрипты
import config from './gulp/config'
// Устанавливаем окружение сборки dev или prod
config.setEnv()
export default done => {
console.log(config.isDev)
console.log(config.isProd)
done()
}
Теперь введем в терминале.
gulp
# Результат
# true
# false
gulp --prod
# Результат
# false
# true
Как видим файл config.js успешно отрабатывает, чтобы понять, что тут произошло, ознакомьтесь с кодом внутри файла config.js.
setEnv() {
this.isProd = process.argv.includes('--prod') // true если сборка проекта выполнена с ключом --prod
this.isDev = !this.isProd // false если сборка проекта выполнена без ключа --prod
},
Сохраняем изменения
Если сейчас сделать commit, то линтер нас не пропустит так как имеется console.log в коде. Чтобы все же коммит прошел, пропишем в файл .eslintignore.
gulp
gulpfile.babel.js
Это вынужденная мера в дальнейшем, когда начнем создавать таски мы очистим файл .eslintignore и уберем console.log.
Редактируем package.json
Удалим не нужные конструкции и добавим новые. Я собираюсь добавить описание, ключевые слова, путь до репозитория, удалить из блока script команду test и т.п. Так будет выглядеть обновленный файл.
{
"name": "gulp-template",
// Изменяем версию проекта
"version": "0.0.4",
// Описание проекта
"description": "Gulp сборка для продуктивной верстки",
"keywords": ["gulp", "pug", "webpack", "scss", "svg-sprite"],
"scripts": {
"prepare": "husky install"
},
// Browserslist - это конфигурационный файл или опция, используемая различными
// инструментами разработки, такими как Babel, Autoprefixer, ESLint, Stylelint,
// PostCSS и другими, для определения списка целевых браузеров и окружений,
// которые должны поддерживаться вашим веб-приложением.
"browserslist": [
"last 2 version", // поддерживаются последние 2 версии браузера
"not dead" // старание браузеры такие как IE не поддерживаются
],
// Путь до репозитория с текущей сборкой
"repository": {
"type": "git",
"url": "git+https://github.com/Eliofery/gulp-template.git"
},
// Путь до раздела вопрос/ответ в репозитории
"bugs": {
"url": "https://github.com/Eliofery/gulp-template/issues"
},
// Путь до основной страницы сборки
"homepage": "https://github.com/Eliofery/gulp-template#readme",
// Имя автора сборки
"author": "Sergio Eliofery",
"license": "ISC",
"devDependencies": {
"@babel/core": "^7.22.9",
"@babel/preset-env": "^7.22.9",
"@babel/register": "^7.22.5",
"core-js": "^3.31.1",
"editorconfig-checker": "^5.1.1",
"eslint": "^8.45.0",
"eslint-config-airbnb-base": "^15.0.0",
"eslint-plugin-import": "^2.27.5",
"gulp": "^4.0.2",
"husky": "^8.0.3",
"lint-staged": "^13.2.3",
"prettier": "^3.0.0",
"pug-lint": "^2.7.0",
"stylelint": "^15.10.2",
"stylelint-config-rational-order": "^0.1.2",
"stylelint-config-recommended-scss": "^12.0.0",
"stylelint-config-standard": "^34.0.0",
"stylelint-order": "^6.0.3",
"stylelint-scss": "^5.0.1"
},
"lint-staged": {
"*": "editorconfig-checker --exclude '.git|.husky|node_modules|.eslintcache'",
"src/pug/**/*.pug": "pug-lint --fix",
"src/scss/**/*.scss": "stylelint --fix",
"*.js": [
"eslint --cache --fix",
"prettier --write"
]
}
}
Таск clear
Данный такс будет удалять каталог с собранным проектом. Установим зависимость.
npm i -D gulp-clean
Внутри каталога ./gulp/tasks создадим файл clear.js который будет содержать код таска.
touch ./gulp/tasks/clear.js
Так же удалим файл .gitkeep из каталога tasks он нам больше не нужен:
rm ./gulp/tasks/.gitkeep
Изначальное содержимое файла clear.js будет таким.
/**
* Удаления продакшен версии проекта
*
* Полностью удаляет каталог build
*
* @link https://gulpjs.com/docs/en/api/src#options
* @link https://www.npmjs.com/package/gulp-clean
*/
// Сторонние библиотеки
import { src } from 'gulp' // gulp плагин
import clean from 'gulp-clean' // плагин для удаления каталогов
// Конфиги
import config from '../config'
// Таск
const clear = () =>
src(config.build.root, { // указываем путь к каталогу который хотим удалить
read: false, // запрещаем читать содержимое файлов
allowEmpty: true, // отключаем ошибки о не существующем каталоге
}).pipe(
clean({ // удаляем каталог
force: true, // разрешаем удаление каталогов и файлов за пределами каталога с тасками
}),
)
export default clear
Конструкция const clear = означает создание таска с именем clear. Это то же самое как если бы написали gulp.task(’clear’), подробнее об этом ниже.
Добавляем таск clear в gulpfile.babel.js
Изменим содержимое файла gulpfile.babel.js на.
// Сторонние библиотеки
import { series } from "gulp"
// Таски
import clear from './gulp/tasks/clear'
// Конфиги
import config from "./gulp/config"
// Определяем окружения сборки dev или prod
config.setEnv()
// Сборка проекта
export const build = series(
clear,
)
// Слежение за изменением файлов
export const watch = series(
build,
)
export default watch
Здесь мы импортировали сам gulp через который будем объединять наши таски. Так же импортировали первый таск clear, который удаляет каталог с собранной версией.
Мы создали две конструкции build и watch с использованием метода series, рассмотрим их поподробней.
gulp.series - это метод в инструменте Gulp, который позволяет создавать последовательные серии задач (тасков) для выполнения в определенном порядке. То есть сперва выполнится задача clear потом следующая за ней и т.д. по цепочке. Пока не выполнится до конца впереди идущая задача, последующая не начнется.
Раньше в основном gulp таски создавались через gulp.task(’build’, …), но с появлением варианта gulpfile.babel.js таски можно создавать используя синтаксис ES6 вот так export const build.
export const build тоже самое что и gulp.task(’build’). Где build - это имя gulp таска, оно может быть абсолютно любым, вы сами его задаете.
Чтобы запустить таск необходимо выполнить команду.
gulp build
# Выполнится конструкция:
#export const build = series(
#clear,
#)
# Или
gulp watch
# Выполнится конструкция:
#export const watch = series(
#build,
#)
Так же можно заметить, что в самом низу мы добавили строку.
export default watch
Она означает что по умолчанию будет запускаться таск с именем watch, например если набрать в терминале команду gulp без названий тасков.
gulp
# Выполнится конструкция:
#export const watch = series(
#build,
#)
Добавляем запуск тасков в package.json
Хорошей практикой считается выносить запуск тасков в отдельные команды, в файле package.json.
...
"scripts": {
...
"build": "gulp build --prod",
"dev": "gulp watch"
},
...
Здесь мы добавили две команды "build": "gulp build --prod" и "dev": "gulp watch".
- build будет запускать команду gulp build --prod
- dev будет запускать команду gulp watch
Пример как вызвать команды build и dev.
# Для сборки проекта в продакшен версию
# Для продакшн версии картинки проекта оптимизируются, код минифицируется.
npm run build
# Для сборки проекта в режиме разработки
# При режиме разработки проект собирается без оптимизации и минификации.
npm run dev
Конечно сейчас преимущества в использовании команды npm run dev по сравнению с gulp watch не заметны, напротив кажется что команда gulp watch короче. Но так как знакомство с новым проектом обычно начинается с просмотра файла package.json, очень удобно видеть сразу те команды которые участвуют в сборке проекта.
Чем открывать отдельные файлы с тасками и ознакамливаться с их наименованием, прежде чем запустить. К тому же если когда-нибудь понадобиться дописать какие-то дополнительные конструкции, например.
gulp build --prod --arg2 --arg3 --arg4
То каждый раз набирать это вручную муторно, а тем более запомнить, гораздо проще набрать npm run build. Причем наименования build и dev общепринятые в сообществе разработчиков и запустив не известный проект можно с высокой долей вероятности предположить, что они там есть и отвечают за одну и туже логику.
Тестируем таск clear
Чтобы протестировать работоспособность таска clear, создадим в корне проекта каталог build.
Теперь запустим команду сборки или разработки проекта.
npm run build
npm run dev
Как видим каталог build в корне проекта удалился, таск успешно отработал.
Таск server
Данный такс будет создавать виртуальный сервер для отображения сверстанных страниц и автоматического обновления браузера при изменении файлов. Установим зависимость.
npm i -D browser-sync
Внутри каталога ./gulp/tasks создадим файл server.js который будет содержать следующий код таска.
/**
* Виртуальный сервер
*
* Создает виртуальный сервер, автоматически обновляет браузер при изменении файлов
*
* @link https://browsersync.io/docs/gulp
* @link https://browsersync.io/docs/options
*/
// Конфиги
import config from '../config'
// Создаем browserSync
global.browserSync = require('browser-sync').create()
const server = done => {
browserSync.init({
// proxy: `${config.proxy}:${config.port}`, // хост по заданной ссылке
// port: config.port, // использовать заданный порт
server: config.build.root, // хост по заданному каталогу
open: true, // автоматически открыть страницу в браузере после запуска таска
notify: false, // показать уведомление
cors: true, // добавить HTTP заголовок CORS
ui: false, // включить доступ к интерфейсу настроек browser-sync
})
done() // скрипт завершен
}
export default server
По умолчанию виртуальный сервер хостует каталог build, если необходимо слушать какой-то конкретный адрес расскоментируйте строки в файле config.js
proxy: `${config.proxy}:${config.port}`, // хост по заданной ссылке
port: config.port, // использовать заданный порт
// server: config.build.root, // хост по заданному каталогу
Добавляем таск server в gulpfile.babel.js
Изменим содержимое файла gulpfile.babel.js.
// Сторонние библиотеки
import { series } from 'gulp'
// Таски
import clear from './gulp/tasks/clear'
import server from './gulp/tasks/server'
// Конфиги
import config from './gulp/config'
// Определяем окружения сборки dev или prod
config.setEnv()
// Запуск виртуального сервера
export const proxy = server
// Сборка проекта
export const build = series(clear)
// Слежение за изменением файлов
export const watch = series(build, server)
export default watch
Дополнительно с добавлением таска server был добавлен таск proxy, который отдельно запускает виртуальный сервер, без полной сборки проекта.
Так же добавим в файл package.json, в scripts команду "proxy": "gulp proxy".
Тестируем таск server
Чтобы протестировать работоспособность таска server, запустим проект в dev режиме.
npm run dev
При запуске виртуального сервера в браузере откроется вкладка с содержимым "Cannot GET /". Чтобы это исправить создадим вручную каталог build и внутри него файл index.html:
mkdir build
touch ./build/index.html
Добавим в index.html любое содержимое и обновим страницу. В браузере должно отобразиться, введенное нами в index.html содержимое. После чего сбросим подключение к виртуальному серверу нажав в терминале Ctrl+C и введем команду.
npm run proxy
Должна открыться страница с содержимым файла index.html.
Таск webpack
Данный такс будет объединять js файлы в один общий и минифицировать его. Установим зависимости.
npm i -D babel-loader eslint-import-resolver-alias webpack-stream gulp-strip-comments vinyl-named-with-path
Внутри каталога ./gulp/tasks создадим файл webpack.js который будет содержать код таска.
/**
* JS Webpack
*
* Объединяет указанные js файлы в один общий и минифицирует его
*/
// Сторонние библиотеки
import { dest, src, watch } from 'gulp' // gulp плагин
import plumber from 'gulp-plumber' // перехватывает ошибки
import notify from 'gulp-notify' // уведомляет об ошибках
import gulpif from 'gulp-if' // вызывает функции по условию
import webpackStream from 'webpack-stream' // webpack плагин
import named from 'vinyl-named-with-path' // дает возможность использовать ${config.src.js}/*.js конструкцию
import strip from 'gulp-strip-comments' // очищает комментарии
// Конфиги
import config from '../config'
import webpackConfig from '../../webpack.config'
// Сборка таска
export const webpackBuild = () =>
src([`${config.src.js.root}/*.js`]) // входящие файлы
.pipe(
// Отлавливаем и показываем ошибки в таске
plumber({
errorHandler: notify.onError(err => ({
title: 'Ошибка в задаче webpackBuild', // заголовок ошибки
sound: false, // уведомлять звуком
message: err.message, // описание ошибки
})),
}),
)
.pipe(named()) // меняем [name] на имя файла
.pipe(webpackStream(webpackConfig)) // настройки webpack
.pipe(gulpif(config.isProd, strip())) // удаляем комментарии в коде
.pipe(dest(`${config.build.js}`)) // исходящий файл
.pipe(browserSync.stream()) // обновление страницы в браузере
// Слежение за изменением файлов
export const webpackWatch = () => watch(`${config.src.js.root}/**/*.js`, webpackBuild)
Теперь создадим в корне проекта файл webpack.config, который будет содержать правила и настройки по сборке js файлов.
const path = require('path') // определение абсолютного пути
// Рабочее окружение
const isProd = process.argv.includes('--prod')
module.exports = {
mode: isProd ? 'production' : 'development', // минификация файла
devtool: isProd ? false : 'inline-source-map', // если gulp сборка запущена в режиме разработки создаст sourcemap файл
output: {
filename: '[name].js', // имя файла после сборки
},
// разбиение исходного файла на чанки
optimization: {
splitChunks: {
chunks: 'async', // all
minSize: 30000, // 10000
maxSize: 0, // 200000
minChunks: 1,
maxAsyncRequests: 5,
maxInitialRequests: 3,
automaticNameDelimiter: '~',
cacheGroups: {
vendors: {
test: /[\\/]node_modules[\\/]/,
priority: -10,
},
default: {
minChunks: 2,
priority: -20,
reuseExistingChunk: true,
},
},
},
},
module: {
rules: [
{
test: /\.js$/, // файл заканчивается на .js
exclude: /(node_modules|bower_components)/, // исключаем каталоги node_modules и bower_components
use: {
loader: 'babel-loader', // используем в качестве обработчика babel-loader
},
},
],
},
resolve: {
extensions: ['.js'],
alias: {
// короткий путь до js файлов через символ @, например @/components/ButtonComponent
'@': path.resolve(__dirname, 'src/js'),
},
},
}
Для того чтобы текстовый редактор и IDE распознал короткий путь, обозначенный через символ @ дополним правила в файле .eslintrc.js.
module.exports = {
...
settings: {
'import/resolver': {
alias: {
map: [
['@', './src/js'],
],
extensions: ['.js'],
},
},
},
}
Добавляем таск webpack в gulpfile.babel.js
Изменим содержимое файла gulpfile.babel.js на.
// Сторонние библиотеки
import { series, parallel } from 'gulp'
// Таски
import clear from './gulp/tasks/clear'
import server from './gulp/tasks/server'
import { webpackBuild, webpackWatch } from './gulp/tasks/webpack'
// Конфиги
import config from './gulp/config'
// Определяем окружения сборки dev или prod
config.setEnv()
// Запуск виртуального сервера
export const proxy = server
// Сборка проекта
export const build = series(clear, webpackBuild)
// Слежение за изменением файлов
export const watch = series(
build,
server,
parallel(
webpackWatch,
),
)
export default watch
Здесь мы добавили сразу 2 таска webpackBuild и webpackWatch. Первый таск объединяет js файлы, второй таск следит за изменениями js файлов. При изменение какого либо js файла внутри src/js, автоматически будет вызван таск webpackBuild, который заного объединит js файлы.
Так же был добавлен новый метод gulp.parallel, который позволяет вызывать таски асинхронно друг другу, не дожидаясь того пока другой таск завершит свою работу. Сейчас у нас только один таск webpackWatch, следящий за изменением файлов, но вскоре к нему добавятся и другие.
Тестируем таск webpack
Чтобы протестировать работоспособность таска webpack создадим компонент кнопки.
touch ./src/js/components/ButtonComponent.js
Изначальное содержимое ButtonComponent.js будет таким.
export default class ButtonComponent {
_element = null
constructor(selector) {
this._element = document.querySelector(selector)
}
get element() {
return this._element
}
}
Так же удалим файл .gitkeep из каталога srs/js/components.
Теперь в файле main.js импортируем созданную кнопку, напишем следующий код.
import ButtonComponent from '@/components/ButtonComponent'
const button = new ButtonComponent('.button')
button.element.addEventListener('click', () => {
const message = document.createElement('div')
message.textContent = 'Нажали на кнопку'
document.body.insertAdjacentElement('beforebegin', message)
})
Запустим сборку в режиме разработки.
npm run dev
В каталоге build появится каталог js с файлом main.js внутри.
Создадим в корне каталога build файл index.html, со следующим содержимым:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<button class="button" type="button">Кнопка</button>
<script src="js/main.js"></script>
</body>
</html>
Перезапустим страницу в браузере и нажмем на кнопку. Появится сообщение “Нажали на кнопку”.
Таск styles
Данный такс будет объединять scss файлы в один общий css и минифицировать его. Установим зависимости.
npm i -D sass gulp-sass gulp-sass-glob gulp-postcss autoprefixer cssnano postcss-custom-media postcss-discard-comments
npm i -S normalize.css
Внутри каталога ./gulp/tasks создадим файл styles.js который будет содержать код таска.
/**
* Scss препроцессор
*
* Объединяет указанные scss файлы в один общий css файл и минифицирует
*/
// Сторонние библиотеки
import { src, dest, watch } from 'gulp' // gulp плагин
import plumber from 'gulp-plumber' // перехватывает ошибки
import notify from 'gulp-notify' // уведомляет об ошибках
import sassGlob from 'gulp-sass-glob' // позволяет использовать /**/*.scss конструкцию
import dartSass from 'sass' // препроцессор sass
import gulpSass from 'gulp-sass' // препроцессор sass
import postcss from 'gulp-postcss' // позволяет использовать PostCSS для обработки CSS-файлов
import autoPrefixer from 'autoprefixer' // автоматически добавляет префиксы для поддержки старых браузеров
import cssnano from 'cssnano' // минифицирует css файл
import postcssCustomMedia from 'postcss-custom-media' // группирует стили под общими медиа запросами
import comments from 'postcss-discard-comments' // группирует стили под общими медиа запросами
// Пользовательские скрипты
import config from '../config' // конфигурационный файл
// Подключение библиотеки sass
const sass = gulpSass(dartSass)
// Сборка таска
export const stylesBuild = () => {
const minify = config.isProd ? cssnano() : () => {} // минификация стилей
const clear = config.isProd ? comments({ removeAll: true }) : () => {} // удаление комментариев
return src(`${config.src.scss}/**/*.{scss,sass}`, { sourcemaps: config.isDev }) // входящие файлы
.pipe(
// Отлавливаем и показываем ошибки в таске
plumber({
errorHandler: notify.onError(err => ({
title: 'Ошибка в задаче stylesBuild', // заголовок ошибки
sound: false, // уведомлять звуком
message: err.message, // описание ошибки
})),
}),
)
.pipe(sassGlob()) // проходит по всем файлам в scss, подключенным через шаблон /**/*
.pipe(sass.sync({
includePaths: ['./node_modules'], // ищет зависимости в node_modules, например normalize.css
}))
.pipe(postcss([
postcssCustomMedia(), // объединяет медиа запросы
autoPrefixer(), // автопрефиксер
minify, // минификация
clear, // очистка комментариев
]))
.pipe(dest(config.build.css, { sourcemaps: config.isDev })) // исходящий файл
.pipe(browserSync.stream()) // обновление страницы в браузере
}
// Слежение за изменением файлов
export const stylesWatch = () => {
watch(`${config.src.scss}/**/*.{scss,sass}`, stylesBuild)
}
Добавляем таск styles в gulpfile.babel.js
Изменим содержимое файла gulpfile.babel.js на следующий код.
// Сторонние библиотеки
import { series, parallel } from 'gulp'
// Таски
import clear from './gulp/tasks/clear'
import server from './gulp/tasks/server'
import { webpackBuild, webpackWatch } from './gulp/tasks/webpack'
import { stylesBuild, stylesWatch } from './gulp/tasks/styles'
// Конфиги
import config from './gulp/config'
// Определяем окружения сборки dev или prod
config.setEnv()
// Запуск виртуального сервера
export const proxy = server
// Сборка проекта
export const build = series(
// clear,
stylesBuild,
webpackBuild,
)
// Слежение за изменением файлов
export const watch = series(
build,
server,
parallel(
stylesWatch,
webpackWatch,
),
)
export default watch
Здесь мы добавили сразу 2 таска stylesBuild и stylesWatch. Первый таск объединяет scss файлы, второй таск следит за изменениями scss файлов. При изменении какого либо scss файла внутри src/scss, автоматически будет вызван таск stylesBuild, который снова объединит scss файлы.
Создаем файлы со стилями
В каталоге scss у должна получиться следующая структура.
./src/scss/
├── core
│ ├── base
│ │ ├── _base.scss
│ │ ├── _container.scss
│ │ └── _fonts.scss
│ └── helpers
│ ├── _functions.scss
│ ├── _mixins.scss
│ └── _variables.scss
├── critical.scss
├── main.scss
└── scaffolds
├── components
│ └── .gitkeep
└── sections
└── _browser-upgrade.scss
- core - хранит основные, базовые стили для любого проекта который будет создаваться через данную сборку.
- scaffolds - хранит стили специфичные только для определенного проекта который будет создаваться через данную сборку.
- base - хранит базовые стили используемые на всех страницах.
- helpers - хранит вспомогательные файлы такие как переменные, миксины, функции.
- components - хранит стили компонентов, небольших элементов.
- sections - хранит стили секций, крупных блоков, внутри которых располагаются компоненты.
- main.scss - основной файл со стилями подключает внутри себя остальные стили, из выше указанных каталогов.
- critical.scss - критические стили подключает внутри себя те стили которые должны быть подключены inline внутри <head> тега.
_base.scss
Отвечает за базовые стили проекта. Содержимое файла _base.scss.
@use "sass:math";
*,
*::before,
*::after {
box-sizing: inherit;
}
html,
body {
height: 100vh;
}
html {
box-sizing: border-box;
}
body {
position: relative;
min-width: $min-screen;
margin: 0;
color: rgb(var(--theme-text));
font-size: $fz;
font-family: $font-primary;
line-height: $lh;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
background-color: rgb(var(--theme-bg));
}
img {
max-width: 100%;
height: auto;
object-fit: cover;
vertical-align: middle;
}
button {
display: inline-block;
font-family: inherit;
cursor: pointer;
}
@each $headline, $size in $headlines {
$margin: $size * $lh;
#{$headline} {
margin: $margin 0 math.div($margin, 2);
font-weight: 500;
font-size: $size;
}
}
strong,
b {
font-weight: 500;
}
.sr-only {
@include sr-only;
}
_container.scss
Отвечает за стили центрующего контейнера в проекте. Содержимое файла _container.scss.
@use "sass:map";
.container {
width: 100%;
max-width: map.get($container-sizes, desktop);
margin: 0 auto;
padding: 0 $container-grid;
}
@include from(mobile) {
.container {
padding: 0 $container-grid * 2;
}
}
@include from(desktop) {
.container {
padding: 0 $container-grid * 3;
}
}
_fonts.scss
Отвечает за подключение сторонних шрифтов в стилях проекта. Содержимое файла _fonts.scss.
/* stylelint-disable scss/comment-no-empty */
// 100 – Thin.
// 200 – Extra Light (Ultra Light)
// 300 – Light.
// 400 – Normal.
// 500 – Medium.
// 600 – Semi Bold (Demi Bold)
// 700 – Bold.
// 800 – Extra Bold (Ultra Bold)
@include font-face("Roboto", "../fonts/roboto/roboto-regular");
_mixins.scss
Отвечает за миксины проекта. Содержимое файла _mixins.scss.
@use "sass:map";
// mobile first
@mixin from($min_width) {
$min_width: map.get($container-sizes, $min_width);
@media screen and (min-width: $min_width) {
@content;
}
}
// desktop first
@mixin to($max_width) {
$max_width: map.get($container-sizes, $max_width);
@media screen and (max-width: $max_width) {
@content;
}
}
// only mobile
@mixin only-mobile($max_width: "mobile") {
$max_width: map.get($container-sizes, $max_width);
@media screen and (max-width: $max_width - 1) {
@content;
}
}
@mixin retina($dpi: 144, $dppx: 1.5) {
@media (min-resolution: #{$dpi}dpi), (min-resolution: #{$dppx}dppx) {
@content;
}
}
@mixin font-face($font-family, $url, $weight: normal, $style: normal) {
@font-face {
font-weight: $weight;
font-family: "#{$font-family}";
font-style: $style;
font-display: swap;
src: url("#{$url}.woff2") format("woff2");
}
}
@mixin list-reset {
margin: 0;
padding: 0;
list-style: none;
}
@mixin button-reset {
padding: 0;
background-color: transparent;
border: none;
}
@mixin sr-only {
position: absolute;
width: 1px;
height: 1px;
margin: -1px;
padding: 0;
overflow: hidden;
border: 0;
clip: rect(0, 0, 0, 0);
clip-path: inset(100%);
}
@mixin overlay($color: #222) {
&::before {
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba($color, 0.8);
content: "";
}
}
_variables.scss
Отвечает за функции проекта. Содержимое файла _variables.scss.
$black: #222;
$white: #fff;
:root {
--theme-text: #{hextorgb($black)};
--theme-bg: #{hextorgb($white)};
}
@mixin dark-mode {
--theme-text: #{hextorgb($white)};
--theme-bg: #{hextorgb($black)};
}
.dark-mode {
@include dark-mode;
}
@media (prefers-color-scheme: dark) {
:root {
@include dark-mode;
}
}
$font-primary: Roboto, -apple-system, Arial, sans-serif;
$fz: 16px;
$lh: 1.5;
$font-sizes: (xxs: 12px, xs: 14px, sm: $fz, md: 18px, lg: 24px, xl: 33px, xxl: 48px);
$headlines: (
h1: map-get($font-sizes, "xxl"),
h2: map-get($font-sizes, "xl"),
h3: map-get($font-sizes, "lg"),
h4: map-get($font-sizes, "md"),
h5: map-get($font-sizes, "sm"),
h6: map-get($font-sizes, "xs")
);
$min-screen: 320px;
$container-sizes: (mobile: 768px, tablet: 1180px, desktop: 1700px);
$container-grid: 20px;
$transition: all 0.3s ease;
_button.scss
Отвечает за стили компонента button. Содержимое файла _button.scss.
.button {
display: inline-block;
vertical-align: middle;
}
_main-header.scss
Отвечает за стили секции main-header. Содержимое файла _main-header.scss.
.main-header {
background-color: #ebebeb;
}
main.scss
Основной файл со стилями. Содержимое файла main.scss.
@import "core/helpers/functions";
@import "core/helpers/variables";
@import "core/helpers/mixins";
@import "core/base/fonts";
@import "scaffolds/components/**/*";
@import "scaffolds/sections/**/*";
critical.scss
Файл с критическими стилями. Содержимое файла critical.scss.
@import "core/helpers/functions";
@import "core/helpers/variables";
@import "core/helpers/mixins";
@import "normalize.css/normalize";
@import "core/base/base";
@import "core/container/mobile";
//@import "core/container/desktop";
Удаляем все файлы .gitkeep внутри директорий src/scss
rm src/scss/core/base/.gitkeep
rm src/scss/core/helpers/.gitkeep
rm src/scss/scaffolds/components/.gitkeep
rm src/scss/scaffolds/sections/.gitkeep
Тестируем таск styles
Чтобы протестировать работоспособность таска styles запустим сборку в режиме разработки.
npm run dev
В каталоге build появится каталог css с файлом main.css и critical.css внутри. Создадим в корне каталога build файл index.html, со следующим содержимым.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<link rel="stylesheet" href="css/critical.css">
<link rel="stylesheet" href="css/main.css">
</head>
<body>
</body>
</html>
Откроем файл src/scss/core/helpers/_variables.scss и изменим цвет фона.
$white: red;
Страница должна автоматически обновиться, а цвет фона измениться.
Таск sprites
Данный такс будет cоздавать svg спрайт. Установим зависимости.
npm i -D files-exist gulp-concat gulp-svg-symbol-view
Внутри каталога ./gulp/tasks создадим файл sprites.js.
/**
* SVG спрайт
*
* Создает svg спрайт
*
* Пример использования в css:
* background: url('sprite-mono.svg#icon-name-view') no-repeat;
* background-size: 20px;
*
* Пример использования в html:
*
*
* @link https://github.com/svg/svgo
*/
// Сторонние библиотеки
import { src, dest, watch, parallel } from 'gulp' // gulp плагин
import filesExist from 'files-exist' // проверяет файл на существование
import concat from 'gulp-concat' // объединяет несколько файлов в один
import svgSprite from 'gulp-svg-symbol-view' // создает спрайт
// Конфиги
import config from '../config'
// Создание черно-белого svg спрайта
const spriteMono = () =>
// входящие файлы
src(filesExist(`${config.src.assets.icons.mono}/**/icon-*.svg`, { exceptionMessage: 'Нет ни одного файла svg' }))
.pipe(
svgSprite({
svgo: {
plugins: [
{ cleanupIDs: true }, // удалить id
{ removeRasterImages: true }, // удалить растровые изображения
{ removeStyleElement: true }, // удалить <style>
{ removeUselessDefs: true }, // удалить <defs>
{ removeViewBox: false }, // удалить ViewBox
{ removeComments: true }, // удалить комментарии
{
removeAttrs: {
attrs: ['class', 'data-name'], // удалить указанные атрибуты, 'fill', 'stroke.*'
},
},
],
},
}),
)
.pipe(concat('sprite-mono.svg')) // объединение файлов
.pipe(dest(config.build.images)) // исходящий файл
.pipe(browserSync.stream()) // обновление страницы в браузере
// Создание цветного svg спрайта
const spriteMulti = () =>
// входящие файлы
src(filesExist(`${config.src.assets.icons.multi}/**/icon-*.svg`, { exceptionMessage: 'Нет ни одного файла svg' }))
.pipe(
svgSprite({
svgo: {
plugins: [
{ cleanupIDs: true }, // удалить id
{ removeUselessDefs: true }, // удалить <defs>
{ removeViewBox: false }, // удалить ViewBox
{ removeComments: true }, // удалить комментарии
{ removeUselessStrokeAndFill: false }, // удалить атрибуты stroke и fill
{ inlineStyles: true }, // поддержка встроенных стилей <style></style>
{
removeAttrs: {
attrs: ['class', 'data-name'], // удалить указанные атрибуты
},
},
],
},
}),
)
.pipe(concat('sprite-multi.svg')) // объединение файлов
.pipe(dest(config.build.images)) // исходящий файл
.pipe(browserSync.stream()) // обновление страницы в браузере
// Сборка всех тасков
export const spritesBuild = parallel(spriteMono, spriteMulti)
// Слежение за изменением файлов
export const spritesWatch = () => {
watch(`${config.src.assets.icons.mono}/**/*.svg`, { ignoreInitial: false }, spriteMono)
watch(`${config.src.assets.icons.multi}/**/*.svg`, { ignoreInitial: false }, spriteMulti)
}
Добавляем таск sprites в gulpfile.babel.js
Изменим содержимое файла gulpfile.babel.js на следующий код.
// Сторонние библиотеки
import { series, parallel } from 'gulp'
// Таски
import clear from './gulp/tasks/clear'
import server from './gulp/tasks/server'
import { webpackBuild, webpackWatch } from './gulp/tasks/webpack'
import { stylesBuild, stylesWatch } from './gulp/tasks/styles'
import { spritesBuild, spritesWatch } from './gulp/tasks/sprites'
// Конфиги
import config from './gulp/config'
// Определяем окружения сборки dev или prod
config.setEnv()
// Запуск виртуального сервера
export const proxy = server
// Сборка проекта
export const build = series(
// clear,
spritesBuild,
stylesBuild,
webpackBuild
)
// Слежение за изменением файлов
export const watch = series(
build,
server,
parallel(
spritesWatch,
stylesWatch,
webpackWatch
),
)
export default watch
Тестируем таск sprites
Чтобы протестировать таск sprites.js скачаем и добавим в каталог ./src/assets/icons/mono черно-белые иконки, а в каталог ./src/assets/icons/multi цветные иконки. Попутно удалим из этих каталогов файл .gitkeep. Запустим сборку в режиме разработки.
npm run dev
Создадим в каталоге build файл index.html.
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<link rel="stylesheet" href="css/critical.css">
<link rel="stylesheet" href="css/main.css">
</head>
<body>
<div class="icon icon--snow"></div>
<div class="icon icon--sun"></div>
<div class="icon icon--user"></div>
</body>
</html>
Создадим стили для иконок src/scss/scaffolds/components/_icons.scss.
.icon {
display: inline-block;
width: 50px;
height: 50px;
background-size: 50px;
background-repeat: no-repeat;
}
.icon--snow {
background-image: url("../img/sprite-mono.svg#icon-snow-view");
}
.icon--sun {
background-image: url("../img/sprite-mono.svg#icon-sun-view");
}
.icon--user {
background-image: url("../img/sprite-multi.svg#icon-user-view");
}
Обновим страницу в браузере и увидим добавленные svg иконки.
Таск images
Данный такс будет оптимизировать изображения. Установим зависимости.
npm i -D gulp-avif gulp-webp gulp-flatten gulp-imagemin@7.1.0 imagemin-pngquant gulp-newer
Внутри каталога ./gulp/tasks создадим файл images.js
/**
* Оптимизация изображений
*
* Уменьшает размер изображений и создает webp, avif форматы
*/
// Сторонние библиотеки
import { src, dest, watch, series } from 'gulp' // gulp плагин
import gulpif from 'gulp-if' // вызывает функции по условию
import newer from 'gulp-newer' // пропускает старые файлы
import flatten from 'gulp-flatten' // настраивает исходную структуру каталогов
import imagemin, { gifsicle, mozjpeg, optipng, svgo } from 'gulp-imagemin' // оптимизирует изображения
import pngQuant from 'imagemin-pngquant' // оптимизирует png изображения
import webp from 'gulp-webp' // создает webp файлы
import avif from 'gulp-avif' // создает avif файлы
// Конфиги
import config from '../config'
// Оптимизация изображений
export const imageOptim = () =>
src([`${config.src.assets.images}/**/*`]) // входящие файлы
.pipe(newer(config.build.images)) // только те изображения которые изменились или были добавлены
.pipe(
gulpif(
config.isProd,
imagemin(
[
gifsicle({
interlaced: true, // чересстрочная загрузка изображения
}),
optipng({
optimizationLevel: 5, // уровень оптимизации png файла
}),
pngQuant({
quality: [0.8, 0.9], // уровень оптимизации png файла
}),
mozjpeg({
quality: 75, // уровень оптимизации jpg файла
progressive: true, // чересстрочная загрузка изображения
}),
svgo({
plugins: [
{ cleanupIds: true }, // удаляет неиспользуемые id
{ removeUselessDefs: true }, // удаляет <defs>
{ removeViewBox: true }, // удаляет атрибут viewBox
{ removeComments: true }, // удаляет комментарии
// { inlineStyles: { removeMatchedSelectors: false, onlyMatchedOnce: false } }, // оставляет стили в теге style
{ mergePaths: true }, // объединяет несколько путей в один
{ minifyStyles: false }, // не удаляет @keyframes из тега style
],
}),
],
{
verbose: config.isProd, // каждая оптимизированная картинка отобразится в терминале
},
),
),
)
.pipe(flatten({ includeParents: [1, 1] })) // сохранение структуры родительских каталогов
.pipe(dest(config.build.images)) // исходящие файлы
.pipe(browserSync.stream()) // обновление страницы в браузере
// Создание Webp изображения
export const toWebp = () =>
src(`${config.src.assets.images}/**/*.{jpg,png,jpeg}`) // для всех файлов формата jpg,png,jpeg будет создан файл webp
.pipe(webp({
quality: 80, // уровень оптимизации webp файла
}))
.pipe(dest(config.build.images)) // разместит оптимизированные webp файлы
.pipe(browserSync.stream()) // обновление страницы в браузере
// Создание Avif изображения
export const toAvif = () =>
src(`${config.src.assets.images}/**/*.{jpg,png,jpeg}`) // для всех файлов формата jpg,png,jpeg будет создан файл webp
.pipe(avif({
quality: 80, // уровень оптимизации avif файла
}))
.pipe(dest(config.build.images)) // разместит оптимизированные webp файлы
.pipe(browserSync.stream()) // обновление страницы в браузере
// Выполнение всех тасков
export const imagesBuild = series(imageOptim, toWebp, toAvif)
// Слежение за изменением файлов
export const imagesWatch = () => watch(`${config.src.assets.images}/**/*`, { ignoreInitial: false }, imagesBuild)
Добавляем таск images в gulpfile.babel.js
Изменим содержимое файла gulpfile.babel.js на следующий код.
// Сторонние библиотеки
import { series, parallel } from 'gulp'
// Таски
import clear from './gulp/tasks/clear'
import server from './gulp/tasks/server'
import { webpackBuild, webpackWatch } from './gulp/tasks/webpack'
import { stylesBuild, stylesWatch } from './gulp/tasks/styles'
import { spritesBuild, spritesWatch } from './gulp/tasks/sprites'
import { imagesBuild, imagesWatch } from './gulp/tasks/images'
// Конфиги
import config from './gulp/config'
// Определяем окружения сборки dev или prod
config.setEnv()
// Запуск виртуального сервера
export const proxy = server
// Сборка проекта
export const build = series(clear, spritesBuild, stylesBuild, webpackBuild, imagesBuild)
// Слежение за изменением файлов
export const watch = series(
build,
server,
parallel(spritesWatch, stylesWatch, webpackWatch, imagesWatch),
)
export default watch
Тестируем таск images
Добавим любую картинку формата jpg или png в каталог src/assets/images и запустим сборку в жиме разработке.
npm run dev
Создадим файл index.html со следующим содержимым.
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<link rel="stylesheet" href="css/critical.css">
<link rel="stylesheet" href="css/main.css">
</head>
<body>
<picture>
<source type="image/avif" srcset="/img/example.avif">
<source type="image/webp" srcset="/img/example.webp">
<img src="/img/example.jpg" width="800" height="600" alt="Стопка монет" loading="lazy">
</picture>
</body>
</html>
Приоритет загрузки изображения идет сверху вниз, если браузер сможет распознать формат avif будет загружен этот файл и так далее. Это можно увидеть открыв инспектор и перейдя во вкладку Сеть. Обновим страницу и посмотрим какого формата была загружена картинка.
Таск assets
Данный такс будет копировать файлы из каталога src в build. Установим зависимости.
/**
* Перенос файлов в продакшн
*
* Копирует файлы из src в build
*/
// Сторонние библиотеки
import { src, dest, watch, series } from 'gulp' // gulp плагин
import merge from 'merge-stream' // слияние gulp потоков
// Конфиги
import config from '../config'
// Переносит в корень build
const rootBuild = () => src(
[
`${config.src.assets.favicons}/favicon.ico`,
`${config.src.assets.root}/manifest.json`,
],
).pipe(dest(`${config.build.root}`))
// Переносит в build/fonts
const fontsBuild = () => src(
[
`${config.src.assets.fonts}/**/*`,
`${config.src.libs}/slick-carousel/fonts/*`,
],
).pipe(dest(`${config.build.fonts}`))
// Переносит в build/img
const imageBuild = () => src(
[
`${config.src.assets.favicons}/**/*`,
`${config.src.libs}/slick-carousel/ajax-loader.gif`,
]
).pipe(dest(`${config.build.images}`))
// Переносит содержимое из библиотек node_modules в src/assets/libs
const componentsBuild = () => {
// Список библиотек, которые будут переноситься
const folders = [
'slick-carousel/slick',
]
const tasks = folders.map(folder => {
const pathFolder = `node_modules/${folder}` // полный путь до node_modules библиотеки
const nameFolder = pathFolder.match(/^[a-zA-Z0-9_]*\/([a-zA-Z0-9-_.]*)/)[1] // название библиотеки, например slick-carousel
return src(`${pathFolder}/**/*`).pipe(dest(`${config.src.libs}/${nameFolder}`))
})
return merge(tasks)
}
// Выполнение всех тасков
export const assetsBuild = series(componentsBuild, fontsBuild, rootBuild, imageBuild)
// Слежение за изменением файлов
export const assetsWatch = () => watch([
`${config.src.assets.fonts}/**/*`,
`${config.src.assets.images}/**/*`
], assetsBuild)
Добавляем таск assets в gulpfile.babel.js
Изменим содержимое файла gulpfile.babel.js на следующий код.
// Сторонние библиотеки
import { series, parallel } from 'gulp'
// Таски
import clear from './gulp/tasks/clear'
import server from './gulp/tasks/server'
import { webpackBuild, webpackWatch } from './gulp/tasks/webpack'
import { stylesBuild, stylesWatch } from './gulp/tasks/styles'
import { spritesBuild, spritesWatch } from './gulp/tasks/sprites'
import { imagesBuild, imagesWatch } from './gulp/tasks/images'
import { assetsBuild, assetsWatch } from './gulp/tasks/assets'
// Конфиги
import config from './gulp/config'
// Определяем окружения сборки dev или prod
config.setEnv()
// Запуск виртуального сервера
export const proxy = server
// Сборка проекта
export const build = series(
clear,
spritesBuild,
stylesBuild,
webpackBuild,
imagesBuild,
assetsBuild,
)
// Слежение за изменением файлов
export const watch = series(
build,
server,
parallel(
spritesWatch,
stylesWatch,
webpackWatch,
imagesWatch,
assetsWatch,
),
)
export default watch
Таск pug
Данный такс будет переводить pug разметку в html. Установим зависимости.
npm i -D gulp-pug jstransformer jstransformer-clean-css jstransformer-markdown-it jstransformer-scss markdown-it-attrs markdown-it-kbd pug-include-glob
Внутри каталога ./gulp/tasks создадим файл pug.js
/**
* Шаблонизатор pug
*
* Компилирует pug разметку в html
*/
// Сторонние библиотеки
import { dest, src, watch } from 'gulp' // gulp плагин
import pug from 'gulp-pug' // шаблонизатор pug
import plumber from 'gulp-plumber' // перехватывает ошибки
import notify from 'gulp-notify' // уведомляет об ошибках
import pugIncludeGlob from 'pug-include-glob' // позволяет использовать /**/*.pug конструкцию
import fs from 'fs' // чтение файлов
// Конфиги
import config from '../config'
// Дополнительные markdown библиотеки
const md = require('jstransformer')(require('jstransformer-markdown-it')) // позволяет компилировать и преобразовывать Markdown-код
const kbd = require('markdown-it-kbd') // позволяет добавлять тег kbd через markdown разметку [[]]
const markdownItAttrs = require('markdown-it-attrs') // позволяет добавлять markdown разметке атрибуты
const jstscss = require('jstransformer')(require('jstransformer-scss')) // компилирует scss в css
const jstminify = require('jstransformer')(require('jstransformer-clean-css')) // минифицирует css
// Формирование объекта с данными
// Далее значение этого объекта в pug файле можно будет получить примерно так:
// #{jsonData.nav.home.link}
const getData = () => {
const dir = `${config.src.pug.root}/data` // здесь ищем json файлы
const files = fs.readdirSync(dir) // получаем список всех файлов в каталоге
const data = {} // хранит данные файлов
// Проходимся по каждому файлу
files.forEach(file => {
if (!file.endsWith('.json')) return // пропускаем только json файлы
const fileName = file.replace('.json', '') // формируем имя файла без его расширения
// Создаем ключ с названием файла в объекте data и помещаем в него содержимое файла
data[fileName] = JSON.parse(fs.readFileSync(`${dir}/${file}`, 'utf8'))
})
return data
}
// Сборка таска
export const pugBuild = () => {
const jsonData = getData() // получаем данные
return src(`${config.src.pug.pages}/**/*.pug`) // входящие файлы
.pipe(
// Отлавливаем и показываем ошибки в таске
plumber({
errorHandler: notify.onError(err => ({
title: 'Ошибка в задаче pugBuild', // заголовок ошибки
sound: false, // уведомлять звуком
message: err.message, // описание ошибки
})),
}),
)
.pipe(
pug({
doctype: 'html', // чтобы не было обратного слеша у одиночных тэгов
pretty: true, // сжатие html разметки
plugins: [pugIncludeGlob()], // подключаем сторонние pug плагины
locals: {
// передаем jsonData в pug, далее используем его примерно так: #{jsonData.nav.home.link}
jsonData,
},
filters: {
// Переопределяем pug фильтр markdown-it, улучшая его стандартную функциональность
'markdown-it': (text, options) => {
// inline режим добавляет/убирает обертку в виде тега <p>
const inline = options.inline || false
return md.render(text, {
plugins: [kbd, markdownItAttrs], // подключаем дополнительные плагины
inline, // включаем/отключаем inline режим
}).body
},
// Пользовательский фильтр
// @var options {} имеет один параметр path, хранящий путь до critical.scss
'critical-css': (text, options) => {
const css = jstscss.render(options.path).body // компилирует scss в css
css.replaceAll('"', '"') // jstscss при компиляции заменяет " на " что не есть хорошо
// минифицирует получившейся css файл, удаляя комментарии
return jstminify.render(css, {
level: {
1: {
specialComments: 0,
},
},
}).body
},
// Пользовательский фильтр экранирования html тегов
'special-chars': text =>
text.replaceAll('<', '<').replaceAll('>', '>'),
},
}),
)
.pipe(dest(config.build.root)) // исходящие файлы
.pipe(browserSync.stream()) // обновление страницы в браузере
}
// Слежение за изменением файлов
export const pugWatch = () => {
watch(
[`${config.src.pug.root}/**/*.pug`, `${config.src.pug.root}/data/**/*`],
pugBuild,
)
}
Добавляем таск pug в gulpfile.babel.js
Изменим содержимое файла gulpfile.babel.js на следующий код.
// Сторонние библиотеки
import { series, parallel } from 'gulp'
// Таски
import clear from './gulp/tasks/clear'
import server from './gulp/tasks/server'
import { webpackBuild, webpackWatch } from './gulp/tasks/webpack'
import { stylesBuild, stylesWatch } from './gulp/tasks/styles'
import { spritesBuild, spritesWatch } from './gulp/tasks/sprites'
import { imagesBuild, imagesWatch } from './gulp/tasks/images'
import { assetsBuild, assetsWatch } from './gulp/tasks/assets'
import { pugBuild, pugWatch } from './gulp/tasks/pug'
// Конфиги
import config from './gulp/config'
// Определяем окружения сборки dev или prod
config.setEnv()
// Запуск виртуального сервера
export const proxy = server
// Сборка проекта
export const build = series(
clear,
spritesBuild,
pugBuild,
stylesBuild,
webpackBuild,
imagesBuild,
assetsBuild,
)
// Слежение за изменением файлов
export const watch = series(
build,
server,
parallel(
spritesWatch,
pugWatch,
stylesWatch,
webpackWatch,
imagesWatch,
assetsWatch,
),
)
export default watch
Таск favicons
Данный такс будет создавать фавиконки различного разрешения и типа из svg файла. Установим зависимости.
npm i -D gulp-svg2png gulp-rename gulp-image-resize gulp-to-ico
Внутри каталога ./gulp/tasks создадим файл favicons.js.
/**
* Favicons
*
* Создает фавиконки различного разрешения и типа из svg файла
*
* Если возникает ошибка для gulp-svg2png
* Ошибка: DSO support routines:DLFCN_LOAD:could not load the shared library:dso_dlfcn.c:185:filename(libproviders.so):
* Ввести в терминале: export OPENSSL_CONF=/dev/null
*
* @link https://habr.com/ru/articles/672844/
*/
// Сторонние библиотеки
import { src, dest, watch } from 'gulp' // gulp плагин
import plumber from 'gulp-plumber' // перехватывает ошибки
import notify from 'gulp-notify' // уведомляет об ошибках
import svg2png from 'gulp-svg2png' // svg в png
import rename from 'gulp-rename' // переименование файла
import imageResize from 'gulp-image-resize' // изменение разрешения картинки
import ico from 'gulp-to-ico' // png в ico
// Конфиги
import config from '../config'
// Сборка таска
export const faviconBuild = () =>
src(`${config.src.assets.favicons}/icon.svg`) // входящий файл
.pipe(
// Отлавливаем и показываем ошибки в таске
plumber({
errorHandler: notify.onError(err => ({
title: 'Ошибка в задаче faviconBuild', // заголовок ошибки
sound: false, // уведомлять звуком
message: err.message, // описание ошибки
})),
}),
)
.pipe(svg2png()) // svg в png
// изменение разрешения картинки на 512х512
.pipe(
imageResize({
width: 512,
height: 512,
crop: true, // должно ли изображение быть обрезано до заданных размеров
upscale: false, // может ли изображение быть увеличено, если указанные размеры больше, чем оригинальные размеры
}),
)
.pipe(rename('icon-512.png')) // переименование файла
.pipe(dest(config.src.assets.favicons)) // исходящий файл
// изменение разрешения картинки на 192х192
.pipe(
imageResize({
width: 192,
height: 192,
crop: true, // должно ли изображение быть обрезано до заданных размеров
upscale: false, // может ли изображение быть увеличено, если указанные размеры больше, чем оригинальные размеры
}),
)
.pipe(rename('icon-192.png')) // переименование файла
.pipe(dest(config.src.assets.favicons)) // исходящий файл
// изменение разрешения картинки на 180х180
.pipe(
imageResize({
width: 180,
height: 180,
crop: true, // должно ли изображение быть обрезано до заданных размеров
upscale: false, // может ли изображение быть увеличено, если указанные размеры больше, чем оригинальные размеры
}),
)
.pipe(rename('apple-touch-icon.png')) // переименование файла
.pipe(dest(config.src.assets.favicons)) // исходящий файл
// изменение разрешения картинки на 32х32
.pipe(
imageResize({
width: 32,
height: 32,
crop: true, // должно ли изображение быть обрезано до заданных размеров
upscale: false, // может ли изображение быть увеличено, если указанные размеры больше, чем оригинальные размеры
}),
)
.pipe(ico('favicon.ico')) // переименование файла
.pipe(dest(config.src.assets.favicons)) // исходящий файл
.pipe(browserSync.stream()) // обновление страницы в браузере
// Слежение за изменением файлов
export const faviconWatch = () =>
watch(`${config.src.assets.images}/favicons/icon.svg`, faviconBuild)
Добавляем таск favicons в gulpfile.babel.js
Изменим содержимое файла gulpfile.babel.js на следующий код.
// Сторонние библиотеки
import { series, parallel } from 'gulp'
// Таски
import clear from './gulp/tasks/clear'
import server from './gulp/tasks/server'
import { webpackBuild, webpackWatch } from './gulp/tasks/webpack'
import { stylesBuild, stylesWatch } from './gulp/tasks/styles'
import { spritesBuild, spritesWatch } from './gulp/tasks/sprites'
import { imagesBuild, imagesWatch } from './gulp/tasks/images'
import { assetsBuild, assetsWatch } from './gulp/tasks/assets'
import { pugBuild, pugWatch } from './gulp/tasks/pug'
import { faviconBuild, faviconWatch } from './gulp/tasks/favicons'
// Конфиги
import config from './gulp/config'
// Определяем окружения сборки dev или prod
config.setEnv()
// Запуск виртуального сервера
export const proxy = server
// Сборка проекта
export const build = series(
clear,
spritesBuild,
pugBuild,
stylesBuild,
webpackBuild,
faviconBuild,
imagesBuild,
assetsBuild,
)
// Слежение за изменением файлов
export const watch = series(
build,
server,
parallel(
spritesWatch,
pugWatch,
stylesWatch,
webpackWatch,
faviconWatch,
imagesWatch,
assetsWatch,
),
)
export default watch
Тестируем таск favicons
Удалить из каталога ./src/assets/favicons все фавиконки кроме icon.svg.
npm run dev
Запустить сборку в режиме разработки.
npm run dev
В каталоге ./src/assets/favicons автоматически должны сгенерироваться, удаленные иконки.
Файл manifest.json
Содержимое файла manifest.json.
{
"name": "Название проекта",
"short_name": "Название проекта",
"description": "Описание проекта",
"lang": "ru",
"dir": "ltr",
"id": "/",
"start_url": "/",
"scope": "/",
"display": "minimal-ui",
"orientation": "any",
"theme_color": "#ссс",
"background_color": "#ссс",
"prefer_related_applications": false,
"icons": [
{
"src": "/img/icon-192.png",
"type": "image/png",
"sizes": "192x192"
},
{
"src": "/img/icon-512.png",
"type": "image/png",
"sizes": "512x512"
},
{
"src": "/img/icon.svg",
"sizes": "any",
"type": "image/svg",
"purpose": "maskable"
}
]
}
Файл robots.txt
Содержимое файла robots.txt.
# robotstxt.org
# Разрешить сканирование всего контента
User-agent: *
Disallow:
Подведем итоги
Мы полностью завершили написание своей Gulp сборки. Было создано множество тасков для автоматизации рутинных процессов при верстки сайта. Статья получилась довольно объемная.
Надеюсь вы подчеркнете для себя какие либо идеи для создании своей сборки, так же не стесняйтесь использовать текущую.
P.S. На данный момент сборка претерпела множественные изменения и не перестает изменяться. Актуальную версию вы всегда можете найти в моем репозитории.
Так же каждый шаг создания Gulp сборки я старался комментировать в репозитории сборки. Обязательно загляните туда если походу статьи вам было что-то не понятно.