
Автоматизирует рутинные операции, позволяет верстать быстрее и качественней.
Файловая структура
gulp-starter-pack
| gulpfile.js <!-- Корневой файл операций GULP -->
| package.json
| README.md
| tailwind.config.cjs <!--Переменные Tailwind CSS-->
|
+---dist <!-- Тут скомпилированный проект -->
| | index.html
| |
| +---css
| | style.css <!-- Рабочий файл стилей -->
| | style.scss <!-- SCSS c сохранением вложенности -->
| |
| +---files <!-- Любые файлы, не требующие обработки -->
| +---fonts <!-- Сконвертированные шрифты (woff, woff2) -->
| +---img <!-- Обработанные изображения -->
| \---js
| app.js <!-- Скомпилированные скрипты -->
|
+---gulp <!-- Собственно тут весь функционал GULP -->
| | version.json <!-- Присвоенная версия CSS и JS файлам -->
| |
| +---config
| | ftp.js <!-- Параметры соединения с FTP -->
| | path.js <!-- Все пути проекта -->
| | plugins.js <!-- Плагины, используемые в нескольких тасках -->
| |
| \---tasks <!-- Собственно сценарии всех операций -->
| copy.js <!-- Простое копирование содержимого папки src/files в dist -->
| fonts.js <!-- Конвертирование шрифтов в woff, woff2 и подключение их в стили -->
| ftp.js <!-- Выгрузка прокта на FTP -->
| html.js <!-- Обработка Pug/Markdown, HTML -->
| images.js <!-- Обработка растровых картинок -->
| js.js <!-- Обработка JavaScript с помощью Webpack -->
| postcss.js <!-- Обработка стилей с помощью PostCSS -->
| reset.js <!--Очистка dist папки перед продакшен билдом -->
| server.js <!-- Используемый порт и автообновление браузера -->
| svgSprite.js <!-- Создание спрайта с svg иконками из папки icons -->
| zip.js <!-- Упаковка проекта в zip архив -->
|
+---node_modules
|
\---src <!-- Рабочая папка с исходниками проекта -->
| index.html <!-- Дефолтный html файл. Тут же создаём 404.html и т.п. -->
|
+---css <!-- Исходники PostCSS стилей (или обычный CSS) -->
| | style.css <!-- Тут подключаются все стили -->
| |
| +---defs
| | defs.css <!-- Основные переменные проекта -->
| | fonts.css <!-- Подключенные локальные шрифты таском fonts.js -->
| | nullstyle.css <!-- Обнуление браузерных стилей -->
| |
| \---schemes <!-- Цвета и эффекты проекта -->
|
+---files <!-- Все файлы отсюда будут скопированы в dist без обработки -->
| favicon.ico
|
+---fonts <!-- Локальные шрифты в любом формате -->
+---html <!-- Тут все HTML импорты, чтобы не захламлять корневую папку -->
| | homepage.html
| |
| \---static <!-- Переиспользуемые фрагменты HTML -->
| dev-grid.html <!-- Сетка для контроля вёрстки -->
| footer.html
| header.html
|
+---icons <!-- SVG, которые необходимо объединить в спрайт -->
+---img <!-- Растровые картинки, которые необходимо обработать -->
\---js <!-- Исходники JavaScript (ES6) -->
| app.js <!-- Подключение и инициация скриптов -->
|
\---modules <!-- Объемные скрипты тут, чтобы не перегружать корневой файл -->
functions.js
Сборка имеет логичную архитектуру и если немного привыкнуть, то всегда будет понятно что где находится и за что отвечает.
Основной (корневой) сценарий GULP находится в gulpfile.js, куда уже подключено всё остальное.
import { path } from './gulp/config/path.js';
В gulp/config/path.js указаны все пути к исходникам и целевому расположению обработанных файлов
const buildFolder = `./dist`;
// const buildFolder = process.env.npm_package_name;
Заменить верхнюю строку нижней, если вместо dist папки должна создаваться папка с названием проекта (значение «name» в package.json).
Также надо проверить, чтобы значение ftp в gulp/config/path.js (последняя строка) совпадало с названием корневой папки на хостинге.
Плагины и процесс обработки
Переиспользуемые плагины (которые используются, например, как для HTML, так и для CSS тасков) импортированы в gulp/config/plugins.js
import replace from 'gulp-replace'; // Замена содержимого файлов (ниже подробнее)
import plumber from 'gulp-plumber'; // Проверяет успешность отработки всей цепочки обработки в тасках
import notify from 'gulp-notify'; // Показывает ошибки выполнения тасков в панели уведомлений
import browsersync from 'browser-sync'; // Автообновление окна браузера в случае изменений
import newer from 'gulp-newer'; // Сравнивает дату изменения файлов (чтобы картинки постоянно не обрабатывать)
import ifPlugin from 'gulp-if'; // Позволяет использовать условия типа if…else в циклах GULP
import sourcemaps from 'gulp-sourcemaps'; // Гененрирует Source Maps CSS, JS
Пришлось сделать маску пути к корневой папке проекта через
$
, чтобы одновременно работали плагины VS Code «Path Autocomplete» и «Image preview» (я там даже отметился).Потом этот префикс убирается в HTML таске плагином «gulp-replace»:
app.plugins.replace(/\$img\//g, 'img/')
Опишу подробно что конкретно какой таск делает.
HTML
import fileInclude from 'gulp-file-include';
import webpHtmlNosvg from 'gulp-webp-html-nosvg';
import versionNumber from 'gulp-version-number';
// import pug from 'gulp-pug';
export const html = (done) => {
app.gulp
.src(app.path.src.html)
.pipe(
app.plugins.plumber(
app.plugins.notify.onError({
title: 'HTML',
message: 'Error: <%= error.message %>',
}),
),
)
.pipe(fileInclude())
// .pipe(
// pug({
// pretty: true,
// verbose: true,
// }),
// )
.pipe(app.plugins.replace(/\$img\//g, 'img/'))
.pipe(webpHtmlNosvg())
.pipe(
app.plugins.if(
app.isBuild,
versionNumber({
value: '%DT%',
append: {
key: '_v',
cover: 0,
to: ['css', 'js'],
},
output: {
file: 'gulp/version.json',
},
}),
),
)
.pipe(app.gulp.dest(app.path.build.html))
.pipe(app.plugins.browsersync.stream());
return done();
};
gulp-file-include
Вставляет в HTML содержимое файлов, добавленных через @@include('file.ext')
. Работает не только с HTML, например так можно инлайнить SVG: @@include('../parts/logo.svg')
. Можно передавать переменные в добавляемое.
Пример с передачей заголовка использован в этом проекте, но есть вот такое применение:
@@include('../any.svg',{"img":"icon_01.png"})
@@include('../any.svg',{"img":"icon_02.png"})
<image width="48" height="48" transform="translate(12 12)" href="@@img" />
То есть, свегешка будет инлайниться со всеми своими объектами, но картинка будет каждый раз какая сейчас там нужна.
gulp-pug
Преобразует Pug в HTML. Раскомментировать импорт и инициацию после fileInclude если нужно.
Если нужно обрабатывать Markdown, то вот плагин. Подключать также после fileInclude.
gulp-webp-html-nosvg
Делает из <img src="img/any.jpg">
такое:
<picture>
<source srcset="img/any.webp" type="image/webp" />
<img src="img/any.jpg" />
</picture>
Преобразовываться в WebP картинки будут в image таске.
gulp-version-number
Добавляет весию к js и css, чтобы при обновлении у клиентов сайта не использовалось старое из кеша. Пример:<script src="js/app.js?_v=20221229143711"></script>
Для разработки не нужен, так что запускается только для продакшена.
CSS
Тут я конкретно угорел, чтобы сделать всё идеально, в процессе тестируя херову кучу плагинов и мутируя с их опциями. В итоге получилось вот что:
import postcss from 'gulp-postcss';
import rename from 'gulp-rename';
import cssnano from 'cssnano';
import cleanCss from 'gulp-clean-css';
import postcssCenter from 'postcss-center';
import webpcss from 'webp-in-css/plugin.js';
import willChange from 'postcss-will-change-transition';
import flexBugsFixes from 'postcss-flexbugs-fixes';
import postcssCustomMedia from 'postcss-custom-media';
import sortMediaQuery from 'postcss-sort-media-queries';
import postcssNested from 'postcss-nested';
import postcssImport from 'postcss-import';
import postcssMixins from 'postcss-mixins';
import postcssSimpleVars from 'postcss-simple-vars';
import postcssShort from 'postcss-short';
import postcssPrettify from 'postcss-prettify';
// import postcssPxtorem from 'postcss-pxtorem';
// import tailwindcss from 'tailwindcss';
// import tailwindConfig from '../../tailwind.config.cjs';
const postCSS = [
postcssImport(),
postcssCustomMedia(),
postcssSimpleVars(),
postcssMixins(),
postcssShort(),
postcssCenter(),
// tailwindcss(tailwindConfig),
postcssPrettify(),
];
const postCssOptionsDev = [
postcssNested(),
cssnano({
preset: [
'lite',
{
normalizeWhitespace: false,
discardComments: false,
cssDeclarationSorter: { order: 'smacss' },
},
],
}),
];
const postCssOptionsBuild = [
postcssNested(),
sortMediaQuery(),
flexBugsFixes(),
webpcss(),
// postcssPxtorem({ rootValue: 16 }),
willChange(),
cssnano({
preset: [
'advanced',
{
normalizeWhitespace: true,
cssDeclarationSorter: { order: 'smacss' },
discardComments: { removeAll: true },
normalizeWhitespace: false,
},
// Preset options: https://cssnano.co/docs/what-are-optimisations/
],
}),
];
export const css = () => {
app.gulp
.src(app.path.src.css)
.pipe(rename({ extname: '.scss' }))
.pipe(postcss(postCSS))
.pipe(app.gulp.dest(app.path.build.css));
return app.gulp
.src(app.path.src.css)
.pipe(
app.plugins.plumber(
app.plugins.notify.onError({
title: 'CSS',
message: 'Error: <%= error.message %>',
}),
),
)
.pipe(app.plugins.if(app.isDev, app.plugins.sourcemaps.init()))
.pipe(app.plugins.replace(/\$img\//g, '../img/'))
.pipe(postcss(postCSS))
.pipe(postcss(app.isBuild ? postCssOptionsBuild : postCssOptionsDev))
.pipe(app.plugins.if(app.isBuild, cleanCss()))
.pipe(app.plugins.if(app.isDev, app.plugins.sourcemaps.write()))
.pipe(rename({ extname: '.css' }))
.pipe(app.gulp.dest(app.path.build.css))
.pipe(app.plugins.browsersync.stream());
};
Препроцессором был выбран PostCSS, т.к. он самый вариативный из-за кучи плагинов и довольно быстрый (гораздо быстрее обрабатывает, чем тот же SCSS).
gulp-postcss
Собственно сам препроцессор.
gulp-rename
Переименовывает файлы (например тут меняет .CSS на .SCSS, когда нужно сделать несжатую копию стилей).
cssnano
Охреневший плагин, который делает дофига всего. Если кратко, то я пришел к тому, что для разработки я использую пресет lite c отключенной минимизацией кода и сохранением комментов. Для продакшена пресет advanced с отключенной минимизацией кода — он минимизируется уже после создания несжатой копии в SCSS.
Для продакшена делается вот что:
- Добавляются браузерные префиксы
- Сортируются CSS свойства по системе smacss
- Упрощаются калки (
calc(2rem * 1.5) → 3rem
) - Цвета преобразуются в HEX и по возможности сокращаются
- …
Из всего, что делается я перечислил только вот столько. Ознакомиться со всеми с настройками пресетов можно тут. Если что-то нужно отключить или изменить опции, то вот тут:
cssnano({
preset: [
'advanced',
{
cssDeclarationSorter: { order: 'smacss' },
discardComments: { removeAll: true },
normalizeWhitespace: false,
},
],
}),
gulp-clean-css
Минимизирует CSS. Минификатор у cssnano кривоватый, поэтому этот.
postcss-center
Ускоряет вёрстку за счёт вот чего:
.element {
top: center;
left: center;
}
.element {
position: absolute;
top: 50%;
left: 50%;
margin-right: -50%;
transform: translate(-50%, -50%)
}
webp-in-css
Добавляет стили для WebP
.logo {
width: 30px;
height: 30px;
background: url(/logo.png);
}
.logo {
width: 30px;
height: 30px;
background: url(/logo.webp) no-repeat;
}
body.webp .logo {
background-image: url(/logo.webp);
}
body.no-webp .logo, body.no-js .logo {
background-image: url(/logo.png);
}
Скрипт для проверки поддержки WebP находится в src/js/modules/functions.js
postcss-will-change-transition
.foo {
transition: opacity 0.2s ease, width 0.2s ease;
}
.foo {
transition: opacity 0.2s ease, width 0.2s ease;
will-change: opacity, width;
}
postcss-flexbugs-fixes
Исправляет flex свойства так, чтобы некоторые браузеры не корёжило
.foo { flex: 1 0 calc(1vw - 1px); }
.foo {
flex-grow: 1;
flex-shrink: 0;
flex-basis: calc(1vw - 1px);
}
postcss-custom-media
Меня всегда бесило писать эти сраные медиазапросы, но вот я нашел этот плагин и теперь снова могу радоваться жизни. Пример использования:
@custom-media --md (max-width: 852px);
h2 {
font-size: 21px;
@media (--md) {
font-size: 18px;
}
}
postcss-sort-media-queries
Из названия всё понятно — группирует стили медиазапроса в один блок.
postcss-nested
Убирает вложенность, по сути делая из PostCSS обычный CSS. Типа как SCSS препроцессор.
postcss-import
Корневой CSS файл проекта намекает:
@import 'defs/fonts';
@import 'defs/nullstyle';
@import 'schemes/heycisco-main';
@import 'defs/defs';
/* @tailwind base;
@tailwind components;
@tailwind utilities; */
postcss-mixins
Можно использовать миксины же. Инфа тут.
postcss-simple-vars
Добавляет поддержку переменных, как в SCSS — $width: 100px;
postcss-short
Плагин неплохо ускоряет вёрстку. Примеры:
.icon {
size: 48px;
}
.frame {
margin: * auto;
}
.canvas {
color: #abccfc #212231;
}
.icon {
width: 48px;
height: 48px;
}
.frame {
margin-right: auto;
margin-left: auto;
}
.canvas {
color: #abccfc;
background-color: #212231;
}
Да дофига ещё чего. Вот тут вся инфа.
postcss-prettify
Форматирует код в нормальный вид. Используется при разработке и для создания несжатого SCSS.
postcss-pxtorem
Преобразует px в rem. В опциях функции нужно установить значение rem: postcssPxtorem({rootValue: 16})
Ну и раскомментировать импорт и вызов функции.
tailwindcss
Интеграция с Tailwind CSS. Раскомментировать соответствующие строки если нужен.
JS
import webpack from 'webpack-stream';
export const js = () => {
return app.gulp
.src(app.path.src.js)
.pipe(
app.plugins.plumber(
app.plugins.notify.onError({
title: 'JS',
message: 'Error: <%= error.message %>',
}),
),
)
.pipe(app.plugins.if(app.isDev, app.plugins.sourcemaps.init()))
.pipe(
webpack({
mode: app.isBuild ? 'production' : 'development',
output: {
filename: 'app.js',
},
module: {
rules: [
{
test: /\.(js)$/,
exclude: /(node_modules)/,
loader: 'babel-loader',
options: {
presets: ['@babel/env'],
},
},
],
},
}),
)
.pipe(app.plugins.if(app.isDev, app.plugins.sourcemaps.write()))
.pipe(app.gulp.dest(app.path.build.js))
.pipe(app.plugins.browsersync.stream());
};
Собственно Webpack всё сам прекрасно делает, а я только говорю ему дев или прод надо мутить.
Images
import webp from 'gulp-webp';
import imagemin from 'gulp-imagemin';
// import sharpResponsive from 'gulp-sharp-responsive';
export const images = () => {
return (
app.gulp
.src(app.path.src.images)
.pipe(
app.plugins.plumber(
app.plugins.notify.onError({
title: 'IMAGES',
message: 'Error: <%= error.message %>',
}),
),
)
.pipe(app.plugins.newer(app.path.build.images))
.pipe(webp())
// .pipe(
// app.plugins.if(
// app.isBuild,
// sharpResponsive({
// includeOriginalFile: true,
// formats: [
// { width: 640, rename: { suffix: '-sm' } },
// { width: 1024, rename: { suffix: '-lg' } },
// ],
// })
// )
// )
.pipe(app.gulp.dest(app.path.build.images))
.pipe(app.gulp.src(app.path.src.images))
.pipe(app.plugins.newer(app.path.build.images))
.pipe(
app.plugins.if(
app.isBuild,
imagemin({
progressive: true,
svgoPlugins: [{ removeViewBox: false }],
interlaced: true,
optimizationLevel: 4,
}),
),
)
// .pipe(
// app.plugins.if(
// app.isBuild,
// sharpResponsive({
// includeOriginalFile: true,
// formats: [
// { width: 640, rename: { suffix: '-sm' } },
// { width: 1024, rename: { suffix: '-lg' } },
// ],
// })
// )
// )
.pipe(app.gulp.dest(app.path.build.images))
.pipe(app.gulp.src(app.path.src.svg))
.pipe(app.gulp.dest(app.path.build.images))
.pipe(app.plugins.browsersync.stream())
);
};
gulp-webp
Преобразует изображения в WebP.
gulp-imagemin
Сжимает картинки.
imagemin({
progressive: true,
svgoPlugins: [{ removeViewBox: false }],
interlaced: true,
optimizationLevel: 4,
}),
progressive — Progressive JPEG. Это когда загружается так, а не так.
svgoPlugins > removeViewBox — выключено, потому что иногда SVG корёжит.
interlaced — гифки будут грузиться чередуя строки. Алгоритм другой, чем у JPG, но в целом что-то похожее.
optimizationLevel — опытным путём пришел к тому, что это норм сжатие.
gulp-sharp-responsive
Создаёт копии картинок другого размера. Раскомментировать строки, если нужен.
sharpResponsive({
includeOriginalFile: true,
formats: [
{ width: 640, rename: { suffix: '-sm' } },
{ width: 1024, rename: { suffix: '-lg' } },
],
})
includeOriginalFile — если false
, то картинку в исходном разрешении не потащит в целевую папку.
formats — тут указываются размеры и суффиксы ресайзнутых картинок.
Fonts
import fs from 'fs';
import fonter from 'gulp-fonter';
import ttf2woff2 from 'gulp-ttf2woff2';
let checkFonts = false;
export const otfToTtf = () => {
let fontsDestDir = app.path.build.fonts;
if (!fs.existsSync(fontsDestDir) || checkFonts === true) {
checkFonts = true;
return app.gulp
.src(`${app.path.srcFolder}/fonts/{*,!icons}.otf`, {})
.pipe(
app.plugins.plumber(
app.plugins.notify.onError({
title: 'FONTS',
message: 'Error: <%= error.message %>',
}),
),
)
.pipe(
fonter({
formats: ['ttf'],
}),
)
.pipe(app.gulp.dest(`${app.path.srcFolder}/fonts/`));
} else {
return Promise.resolve('the value is ignored');
}
};
export const ttfToWoff = () => {
let fontsDestDir = app.path.build.fonts;
if (!fs.existsSync(fontsDestDir) || checkFonts === true) {
checkFonts = true;
return app.gulp
.src(`${app.path.srcFolder}/fonts/{*,!icons}.ttf`, {})
.pipe(
app.plugins.plumber(
app.plugins.notify.onError({
title: 'FONTS',
message: 'Error: <%= error.message %>',
}),
),
)
.pipe(
fonter({
formats: ['woff'],
}),
)
.pipe(app.gulp.dest(`${app.path.build.fonts}`))
.pipe(app.gulp.src(`${app.path.srcFolder}/fonts/{*,!icons}.ttf`))
.pipe(ttf2woff2())
.pipe(app.gulp.dest(`${app.path.build.fonts}`));
} else {
console.log('"dest/fonts" уже существует!');
return Promise.resolve('the value is ignored');
}
};
export const iconFont = () => {
return app.gulp
.src(`${app.path.srcFolder}/fonts/icons/*`, {})
.pipe(app.gulp.dest(`${app.path.build.fonts}/icons/`));
};
export const fontsStyle = () => {
let fontsFile = `${app.path.srcFolder}/css/defs/fonts.css`;
fs.readdir(app.path.build.fonts, function (err, fontsFiles) {
if (fontsFiles) {
if (!fs.existsSync(fontsFile)) {
fs.writeFile(fontsFile, '', cb);
let newFileOnly;
for (var i = 0; i < fontsFiles.length; i++) {
let fontFileName = fontsFiles[i].split('.')[0];
if (newFileOnly !== fontFileName) {
let fontName = fontFileName.split('-')[0]
? fontFileName.split('-')[0]
: fontFileName;
let fontWeight = fontFileName.split('-')[1]
? fontFileName.split('-')[1]
: fontFileName;
if (fontWeight.toLowerCase() === 'thin') {
fontWeight = 100;
} else if (fontWeight.toLowerCase() === 'extralight') {
fontWeight = 200;
} else if (fontWeight.toLowerCase() === 'light') {
fontWeight = 300;
} else if (fontWeight.toLowerCase() === 'medium') {
fontWeight = 500;
} else if (fontWeight.toLowerCase() === 'semibold') {
fontWeight = 600;
} else if (fontWeight.toLowerCase() === 'bold') {
fontWeight = 700;
} else if (
fontWeight.toLowerCase() === 'extrabold' ||
fontWeight.toLowerCase() === 'heavy'
) {
fontWeight = 800;
} else if (fontWeight.toLowerCase() === 'black') {
fontWeight = 900;
} else {
fontWeight = 400;
}
fs.appendFile(
fontsFile,
`@font-face {\n\tfont-family: ${fontName};\n\tfont-display: swap;\n\tsrc: url("../fonts/${fontFileName}.woff2") format("woff2"), url("../fonts/${fontFileName}.woff") format("woff");\n\tfont-weight: ${fontWeight};\n\tfont-style: normal;\n}\r\n`,
cb,
);
newFileOnly = fontFileName;
}
}
} else {
console.log('"src/css/defs/fonts.css" уже существует!');
}
}
});
return app.gulp.src(`${app.path.srcFolder}`);
function cb() {}
};
gulp-fonter
Преобразует шрифты в woff, TTFформаты. Собственно, TTF нужен только для следующего плагина, а на выходе будут только шрифты в woff и woff2 форматах.
gulp-ttf2woff2
Преобразует TTF в woff2.
Цикл
Если в папке src/fonts есть шрифты, а папки dist/fonts не существует, то шрифты будут обрабатываться. Соответственно, если добавлен новый шрифт, то нужно удалить папку dist/fonts, чтобы снова сконвертировать шрифты. Функции:
otfToTtf()
=> Вначале все OTF конвертируем в TTF (gulp-ttf2woff2 не поддерживает OTF).
ttfToWoff()
=> Затем все TTF конвертируем в woff. Конвертируем TTF в woff2.
fontsStyle()
=> Создаёт CSS файл, где подключаются шрифты. Запускается, если файла src/css/defs/fonts.css не существует. После того, как создаст неплохо бы проверить чего там оно наподключало, т.к. если названия шрифтов кривые, то придётся вручную подправлять.
Например, если в названии шрифта есть «light», то при в стили добавится
font-weight = 300
, а если исходный шрифт назывался «Montserrat-SemiBold.ttf», то при подключении будетfont-weight = 600
.Значение по умолчанию —
font-weight = 400
Прочее
Да, бля, влом печатать стало, потом распишу остальное. Короче:
gulp/tasks/svgSprite.js создаёт спрайт с SVG иконками в папке src/icons.
gulp/tasks/zip.js упаковывает файлы в ZIP архив.
gulp/tasks/ftp.js отправляет файлы на FTP (туда свои логины/пароли от FTP вхерачить надо).
Команды
npm run dev
— режим разработки с автообновлением браузера.npm run build
— компиляция под продакшен.npm run zip
— упаковка проекта в zip архив.npm run ftp
— выгрузка проекта на FTP.npm run icons
— создание SVG спрайта с иконками.