Как мы используем Sass и Gulp в нашей разработке плагинов и тем для WordPress
Прошло довольно много времени с тех пор, как мы полностью отказались от «ванильного» CSS в пользу CSS-препроцессора (более 4 лет) – если говорить конкретнее, то в пользу Sass (с синтаксисом SCSS). Есть немало причин, почему мы это сделали, и основными являются улучшение опыта разработки и упрощенная интеграция с плагинами.
Проще говоря, Sass был (и остается) более мощным языком, чем «ванильный» CSS, особенно если вы заинтересованы в поддержке старых версий браузеров (к примеру, IE). Потребности в переменных, условных выражениях, миксинах, вложенности правил, а также цветовых функциях и всех полезных свойствах, которые предлагает препроцессор, гораздо более очевидны в контексте темизации WordPress, когда вы хотите предложить несколько цветовых схем своей темы или сделать так, чтобы популярные WordPress плагины соответствовали дизайну и разметке вашей темы.
Правда, это статья написана не для того, чтобы подтолкнуть вас к использованию CSS-препроцессоров. Я покажу вам, как мы используем Sass и Gulp в нашей разработке тем и плагинов для WordPress, и как приспособить тот же самый поток операций к вашей теме.
Наши специфичные требования
Когда мы только начали исследовать возможности интеграции Sass к нашим темам, у нас существовало несколько жестких требований, которые обычно неприменимы к другим проектам (к примеру, автономным приложениям или сайтам). Это связано с тем, что наши клиенты варьируются от простых владельцев сайтов, которые имеют очень узкие знания в области CSS или не имеют их вовсе, до разработчиков сайтов с различным набором навыков (далеко не каждый из них является фронтэнд-разработчиком). Вышеизложенное означает, что:
- Все наши темы включают полную «ванильную» таблицу стилей CSS (style.css), которая не должна выглядеть так, словно она была скомпилирована из чего-то еще (насколько это возможно), и, что более важно, должна быть читабельной. Многие пользователи могут пожелать добавить некоторые штрихи к ней, скопировать и вставить селекторы в стилевую таблицу дочерней темы; они не должны сталкиваться с Sass, если только они сами не захотят этого.
- Мы не поставляем темы с минимизированной стилевой таблицей по очевидным причинам, как было отмечено выше (к тому же это не разрешается правилами WordPress).
- Все таблицы стилей поставщика (к примеру, из jQuery плагинов) должны быть вынесены в отдельные файлы и не должны включаться в главную стилевую таблицу темы, которая содержит только стили темы.
- Sass должен быть прозрачным: наша файловая иерархия структурирована так, чтобы пользователи могли работать как с Sass, так и с «ванильным» CSS в зависимости от своего опыта и предпочтений.
С учетом вышесказанного мы задали следующую структуру файлов в отношении таблиц стилей:
├── css (все CSS/Sass файлы за исключением главного хранятся здесь) │ ├── inc (Sass фрагменты - только стили темы) | | ├── _base.scss | | ├── _header.scss | | ├── _footer.scss | | ├── _modules.scss | | ├── _woocommerce.scss | | └── ...etc │ ├── font-awesome.scss │ ├── font-awesome.css │ ├── magnific.scss │ ├── magnific.css │ ├── mmenu.scss | ├── mmenu.css │ └── ... другие стилевые таблицы поставщика ├── fonts ├── images ├── js (все JavaScript файлы) ├── ... ├── ... ├── style.css (скомпилированные стили темы) └── style.scss
Все скомпилированные файлы находятся рядом с их источниками, и всегда можно прибегнуть к любому из них, проигнорировав другой. Также обратите внимание, что мы разместили все фрагменты (partial) Sass в директории /css/inc, чтобы к ним можно было обратиться при необходимости.
Главный файл style.scss просто импортирует все фрагменты и не содержит в себе фактических стилей. К примеру:
/* Theme Name: Demo theme Theme URI: http://www.cssigniter.com/themes/demotheme Author: CSSIgniter Author URI: http://www.cssigniter.com Description: A theme description Version: 1.0 License: GNU General Public License v2 or later License URI: http://www.gnu.org/licenses/gpl-2.0.html */ /* ----------------------------------------- Table of Contents -------------------------------------------- .. 01. General .. 02. Header .. 03. Modules .. 04. Footer .. 05. Comments .. 06. Widgets Styling .. 07. WordPress defaults .. 08. Utilities */ @import 'css/inc/variables'; @import 'css/inc/mixins/mixins'; @import 'css/inc/base'; @import 'css/inc/header'; @import 'css/inc/modules'; @import 'css/inc/footer'; @import 'css/inc/comments'; @import 'css/inc/widgets'; @import 'css/inc/wp-defaults'; @import 'css/inc/utilities';
Компиляция Sass с помощью Gulp
Gulp обычно не нуждается в представлении, но для полноты картины скажем, что этот инструмент применяется для автоматизации задач любого рода в процессе разработки. Он способен решить практически любую задачу с помощью своей мощной архитектуры; от компиляции и минимизации JS и CSS до фактического развертывания и более сложных задач. Он основан на JavaScript и работает на Node.js.
Мы в CSSIgniter широко используем Gulp. У нас есть более десятка разных задач Gulp в нашем внутреннем инструментарии для разных рабочих процессов. Один из них – компиляция Sass – мы сейчас и рассмотрим.
Основы работы
Чтобы запустить Gulp, нужно установить Node.js. После этого нам нужно будет инициализировать проект с помощью npm. Сначала создайте директорию, в которой будет находиться ваш проект со структурой, упомянутой нами в предыдущем разделе, после чего уже инициализируйте проект:
npm init
Для нас сразу же создастся файл package.json, который используется для отслеживания различной информации о проекте, включая его зависимости.
Нам нужно будет установить следующие зависимости для нашего инструментария Sass:
npm install --save-dev gulp gulp-plumber gulp-sass gulp-postcss autoprefixer gulp-group-css-media-queries browser-sync
Сделав это, давайте также глобально установим Gulp в нашу систему, чтобы мы могли запускать его как команду:
# note: you might need to run this as "sudo" npm install gulp --global
Примечание: если вы не хотите загрязнять вашу систему глобальными зависимостями (и не должны), вы можете запустить Gulp в качестве npm-скрипта в package.json. В «scripts» добавьте “gulp”: “gulp”. Теперь выполнение npm run gulp taskname будет аналогичным запуску gulp taskname.
После этого нам нужно создать файл gulpfile.js. Он будет основным файлом для нашего инструментария, поскольку Gulp ожидает его и считывает его из текущей рабочей директории всякий раз, когда мы выполняем gulp в командной строке.
Внутри gulpfile.js определим наши зависимости:
const gulp = require('gulp'); const plumber = require('gulp-plumber'); const sass = require('gulp-sass'); const postcss = require('gulp-postcss'); const autoprefixer = require('autoprefixer'); const sourcemaps = require('gulp-sourcemaps'); const groupmq = require('gulp-group-css-media-queries'); const bs = require('browser-sync');
И нашу задачу компиляции Sass:
const gulp = require('gulp'); const plumber = require('gulp-plumber'); const sass = require('gulp-sass'); const postcss = require('gulp-postcss'); const autoprefixer = require('autoprefixer'); const sourcemaps = require('gulp-sourcemaps'); const groupmq = require('gulp-group-css-media-queries'); const bs = require('browser-sync'); const SASS_SOURCES = [ './*.scss', // This picks up our style.scss file at the root of the theme 'css/**/*.scss', // All other Sass files in the /css directory ]; /** * Compile Sass files */ gulp.task('compile:sass', () => gulp.src(SASS_SOURCES, { base: './' }) .pipe(plumber()) // Prevent termination on error .pipe(sass({ indentType: 'tab', indentWidth: 1, outputStyle: 'expanded', // Expanded so that our CSS is readable })).on('error', sass.logError) .pipe(postcss([ autoprefixer({ browsers: ['last 2 versions'], cascade: false, }) ])) .pipe(groupmq()) // Group media queries! .pipe(gulp.dest('.')) // Output compiled files in the same dir as Sass sources .pipe(bs.stream())); // Stream to browserSync
Это приближено к тому, что мы используем для наших тем. Теперь выполнение gulp compile:sass (или npm run gulp compile:sass) в командной строке в корне нашего проекта приведет к Sass компиляции. Давайте рассмотрим ключевые особенности:
... .pipe(sass({ indentType: 'tab', indentWidth: 1, outputStyle: 'expanded', // Expanded so that our CSS is readable })).on('error', sass.logError) ...
Это ключевой компонент нашей задачи. Здесь Sass фактически компилируется в CSS. Мы добавляем отступы и выводим стили в расширенном виде, чтобы отвечать нашим требованиям к удобочитаемости.
... .pipe(postcss([ autoprefixer({ browsers: ['last 2 versions'], cascade: false, }) ])) ...
Autoprefixer – великолепный инструмент. Как следует из названия, он автоматически добавляет префиксы браузера/поставщика к нашему коду только для указанных нами версий браузеров (в этом конкретном примере мы задали 2 последние версии всех браузеров). Это позволяет нам свободно писать CSS без добавления префиксов (к примеру, в flexbox), обеспечивая поддержку для старых браузеров.
Еще одно преимущество заключается в том, что, когда мы решаем отказаться от поддержки какого-либо конкретного браузера (либо выходят новые версии браузеров), все, что нам нужно сделать, это просто удалить браузер из нашего gulpfile, перекомпилировать CSS, и все соответствующие префиксы будут удалены!
... .pipe(groupmq()) ...
Еще один классный инструмент – gulp-group-css-media-queries. Чтобы понять, зачем он нужен, давайте посмотрим, как мы обычно пишем CSS медиа-запросы в Sass:
.foo { font-size: 18px; @media (min-width: 767px) { font-size: 24px; } } .bar { margin-bottom: 20px; @media (min-width: 767px) { margin-bottom: 30px;; } }
Вложенные медиа-запросы для соответствующих им элементов очень удобны, это позволяет нам инкапсулировать каждый стиль в каждом селекторе. Проблема данного подхода заключается в том, что он компилируется в следующее:
.foo { font-size: 18px; } @media (min-width: 767px) { .foo { font-size: 24px; } } .bar { margin-bottom: 20px; } @media (min-width: 767px) { .bar { margin-bottom: 30px; } }
У этого подхода есть несколько проблем. Он создает новый блок медиа-запроса для каждого объявления, значительно увеличивая размер файла и влияя на удобочитаемость.
С помощью group-css-media-queries мы обходим эту проблему. Плагин просто проходит по нашему коду и группирует все соответствующие медиа-запросы в самом конце таблице стилей, в итоге вывод будет выглядеть так:
.foo { font-size: 18px; } .bar { margin-bottom: 20px; } @media (min-width: 767px) { .foo { font-size: 24px; } .bar { margin-bottom: 30px; } }
Эффективно и гораздо чище!
Наша финальная строка передает результаты в browserSync, который мы рассмотрим сразу же после того, как создадим новую задачу – она будет отслеживать наши Sass на наличие любых изменений, после чего сразу же скомпилирует файлы и передаст их в браузер.
... /** * Watch Sass files for changes */ gulp.task('watch:sass', ['compile:sass'], () => { bs.init({ proxy: 'http://localhost/wordpress-installation' }); gulp.watch(SASS_SOURCES, ['compile:sass']); });
Эта задача на самом деле делает немного больше: сначала она инициализирует серверный экзмемпляр browserSync. BrowserSync – еще один замечательный инструмент с широкой сферой применения.
В этом конкретном сценарии мы используем его как прокси для нашего локального сервера WordPress разработки (который в этом случае располагается по адресу http://localhost/wordpress-installation – замените на ваш собственный локальный URL разработки). Таким образом, мы можем автоматически передавать все изменения файла в browserSync, который будет обрабатывать перезагрузку страницы, или, в случае с CSS, будет напрямую подключать новые стили без перезагрузки. Это огромный прирост производительности.
Затем мы просто отслеживаем все Sass файлы на наличие изменений с помощью внутреннего API Gulp (gulp.watch), вызывая задачу compile:sass (которая затем передает изменения в browserSync, а то уже в свою очередь внедряет стили).
И, наконец, добавляем дефолтную задачу gulp:
... /** * Default task executed by running `gulp` */ gulp.task('default', ['watch:sass']);
Теперь, когда мы запускаем gulp, будет срабатывать задача watch:sass, поскольку мы указали ее в качестве дефолтной.
Бонус: линтинг стилей с помощью sass-lint
Ни один процесс разработки не будет полным без линтера. Линтинг кода имеет массу преимуществ, он предотвращает ошибки и помогает поддерживать сокращенную и однородную кодовую базу. Добавление линтинга к рабочему процессу, описанному выше, так же просто, как создание новой задачи. Давайте сначала установим требуемую зависимость:
npm install gulp-sass-lint --save-dev
И добавим задачу линтинга в наш gulpfile.js:
... const sassLint = require('gulp-sass-lint'); ... gulp.task('compile:sass', ['lint:sass'], () => ... gulp.watch(SASS_SOURCES, ['compile:sass', 'lint:sass']); ... /** * Lint Sass */ gulp.task('lint:sass', () => gulp.src(SASS_SOURCES) .pipe(plumber()) .pipe(sassLint()) .pipe(sassLint.format())); ...
Обратите внимание, что мы также создали новую задачу lint:sass, выполняемую в разных участках нашего рабочего процесса. Теперь всякий раз, когда мы компилируем файлы Sass или отслеживаем изменения, линтер будет автоматически запускаться.
Примечание: вы можете использовать шаблонный конфигурационный файл sass-lint для создания собственных правил линтинга.
И полный файл gulpfile.js:
const gulp = require('gulp'); const plumber = require('gulp-plumber'); const sass = require('gulp-sass'); const postcss = require('gulp-postcss'); const autoprefixer = require('autoprefixer'); const sourcemaps = require('gulp-sourcemaps'); const groupmq = require('gulp-group-css-media-queries'); const bs = require('browser-sync'); const SASS_SOURCES = [ './*.scss', // This picks up our style.scss file at the root of the theme 'css/**/*.scss', // All other Sass files in the /css directory ]; /** * Compile Sass files */ gulp.task('compile:sass', ['lint:sass'], () => gulp.src(SASS_SOURCES, { base: './' }) .pipe(plumber()) // Prevent termination on error .pipe(sass({ indentType: 'tab', indentWidth: 1, outputStyle: 'expanded', // Expanded so that our CSS is readable })).on('error', sass.logError) .pipe(postcss([ autoprefixer({ browsers: ['last 2 versions'], cascade: false, }) ])) .pipe(groupmq()) // Group media queries! .pipe(gulp.dest('.')) // Output compiled files in the same dir as Sass sources .pipe(bs.stream())); // Stream to browserSync /** * Start up browserSync and watch Sass files for changes */ gulp.task('watch:sass', ['compile:sass'], () => { bs.init({ proxy: 'http://localhost/wordpress-installation' }); gulp.watch(SASS_SOURCES, ['compile:sass', 'lint:sass']); }); /** * Lint Sass */ gulp.task('lint:sass', () => gulp.src(SASS_SOURCES) .pipe(plumber()) .pipe(sassLint()) .pipe(sassLint.format())); /** * Default task executed by running `gulp` */ gulp.task('default', ['watch:sass']);
Готово! Используете ли вы препроцессор CSS в своих WordPress-проектах? Что по поводу Gulp? Поделитесь своими мыслями в комментариях.
Источник: https://www.cssigniter.com