Как мы используем 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

Блог про WordPress
Комментарии: 3
  1. Merehead

    Gulp очень крутая вещь. Используя его всегда, потому что сокращает время кодинга в разы. Вместе с SASS, Emmet, правильным редактором и шустрым это просто удовольствие!

  2. Константин

    Всё зделал по инструкции, но не запускаеться. Выдает ошибку ‘lint:sass’ errored after 28

  3. Maximus

    Спасибо огромное вам!! Очень помог ваш гайд по Gulp и Sass !

Добавить комментарий

Получать новые комментарии по электронной почте.