PostCSS представляет собой инструмент для преобразования CSS-кода при помощи набора подключаемых модулей. В отличие от препроцессоров, таких как Sass или Less, PostCSS не навязывает собственный синтаксис и работает непосредственно с обычным CSS, выполняя анализ дерева стилей и его последующую модификацию.
В современных проектах PostCSS чаще всего используется для следующих задач:
Сама библиотека Esbuild не содержит встроенной поддержки PostCSS, однако благодаря системе плагинов оба инструмента легко интегрируются в единый процесс сборки.
При обработке CSS через PostCSS обычно выполняется следующая последовательность:
Упрощённая схема выглядит следующим образом:
styles.css
│
▼
Esbuild
│
▼
PostCSS
│
├── Autoprefixer
├── cssnano
├── postcss-nested
└── другие плагины
│
▼
Обработанный CSS
│
▼
Финальный bundle
Для базовой интеграции потребуется установить Esbuild и PostCSS:
npm install -D esbuild postcss
Дополнительно устанавливаются необходимые плагины PostCSS.
Например:
npm install -D autoprefixer
или
npm install -D cssnano
или
npm install -D postcss-nested
Наиболее гибкий подход заключается в написании собственного плагина Esbuild.
Структура проекта:
project/
├── src/
│ ├── index.js
│ └── styles.css
├── build.js
└── package.json
Файл сборки:
const esbuild = require('esbuild');
const postcss = require('postcss');
const autoprefixer = require('autoprefixer');
const postcssPlugin = {
name: 'postcss',
setup(build) {
build.onLoad({ filter: /\.css$/ }, async (args) => {
const fs = require('fs/promises');
const source = await fs.readFile(
args.path,
'utf8'
);
const result = await postcss([
autoprefixer()
]).process(source, {
from: args.path
});
return {
contents: result.css,
loader: 'css'
};
});
}
};
esbuild.build({
entryPoints: ['src/index.js'],
bundle: true,
outfile: 'dist/app.js',
plugins: [postcssPlugin]
});
В данном примере:
onLoad() перехватывает все CSS-файлы;В крупных проектах список плагинов может быть довольно большим. Хранить его непосредственно внутри сборочного скрипта неудобно.
Обычно создаётся отдельный файл конфигурации.
module.exports = {
plugins: [
require('autoprefixer'),
require('postcss-nested')
]
};
После этого конфигурация может загружаться автоматически.
Пример:
const postcss = require('postcss');
const postcssConfig = require('./postcss.config');
const result = await postcss(
postcssConfig.plugins
).process(css, {
from: args.path
});
Такой подход облегчает поддержку проекта и позволяет переиспользовать настройки между различными инструментами сборки.
Одним из наиболее распространённых PostCSS-плагинов является Autoprefixer.
Исходный код:
.card {
display: flex;
user-select: none;
}
После обработки:
.card {
display: -webkit-box;
display: -ms-flexbox;
display: flex;
-webkit-user-select: none;
user-select: none;
}
Настройка выполняется через файл Browserslist.
{
"browserslist": [
"> 1%",
"last 2 versions",
"not dead"
]
}
Autoprefixer анализирует целевые браузеры и автоматически добавляет только необходимые префиксы.
Обычный CSS не поддерживает вложенность так, как это реализовано в Sass.
Плагин postcss-nested добавляет подобную
возможность.
Исходный код:
.card {
padding: 20px;
.title {
font-size: 24px;
}
&:hover {
background: #eee;
}
}
После преобразования:
.card {
padding: 20px;
}
.card .title {
font-size: 24px;
}
.card:hover {
background: #eee;
}
Настройка:
module.exports = {
plugins: [
require('postcss-nested')
]
};
Во время production-сборки CSS обычно подвергается дополнительной оптимизации.
Для этого часто используется cssnano.
Исходный код:
.button {
margin: 10px 10px 10px 10px;
color: #ffffff;
}
После обработки:
.button{margin:10px;color:#fff}
Подключение:
const cssnano = require('cssnano');
postcss([
cssnano()
]);
Практически всегда набор PostCSS-плагинов зависит от режима сборки.
Пример:
const isProduction =
process.env.NODE_ENV === 'production';
Формирование списка плагинов:
const plugins = [
require('postcss-nested'),
require('autoprefixer')
];
if (isProduction) {
plugins.push(
require('cssnano')
);
}
Далее:
await postcss(plugins).process(css, {
from: file
});
Такой подход позволяет сохранить читаемость CSS во время разработки и получить максимально компактный результат в production.
Плагин postcss-custom-properties позволяет
преобразовывать пользовательские свойства CSS.
Исходный код:
:root {
--primary: #2196f3;
}
.button {
color: var(--primary);
}
После обработки:
.button {
color: #2196f3;
}
Подключение:
module.exports = {
plugins: [
require('postcss-custom-properties')
]
};
Подобное преобразование полезно для поддержки старых браузеров.
Плагин postcss-preset-env предоставляет доступ к
функциональности будущих версий CSS.
Подключение:
module.exports = {
plugins: [
require('postcss-preset-env')({
stage: 1
})
]
};
Пример использования:
:root {
--main-color: #1976d2;
}
.card {
color: color-mod(
var(--main-color)
alpha(90%)
);
}
PostCSS выполнит преобразование в совместимый CSS-код.
Tailwind CSS использует PostCSS как один из основных механизмов обработки.
Установка:
npm install -D tailwindcss postcss
Конфигурация:
module.exports = {
plugins: [
require('tailwindcss'),
require('autoprefixer')
]
};
Файл стилей:
@tailwind base;
@tailwind components;
@tailwind utilities;
Во время сборки PostCSS генерирует итоговый CSS на основе используемых классов.
При использовании PostCSS совместно с Esbuild желательно сохранять карты исходников.
Пример:
const result = await postcss(plugins)
.process(css, {
from: args.path,
map: {
inline: false
}
});
Далее карта может быть возвращена Esbuild:
return {
contents: result.css,
loader: 'css'
};
Source Maps позволяют связывать итоговый CSS с исходными файлами и значительно упрощают отладку.
При больших объёмах CSS повторная обработка файлов может занимать заметное время.
Простейший вариант кэширования:
const cache = new Map();
Перед обработкой:
if (cache.has(args.path)) {
return cache.get(args.path);
}
После обработки:
cache.set(args.path, result);
Такой механизм особенно полезен при использовании режима наблюдения
(watch).
Esbuild предоставляет встроенное наблюдение за изменениями файлов.
Пример:
const ctx = await esbuild.context({
entryPoints: ['src/index.js'],
bundle: true,
outfile: 'dist/app.js',
plugins: [postcssPlugin]
});
await ctx.watch();
После изменения CSS-файла:
В результате процесс разработки становится практически мгновенным.
Иногда требуется запускать PostCSS не только для .css,
но и для других расширений.
Например:
build.onLoad({
filter: /\.(css|pcss)$/
}, async (args) => {
});
Либо:
build.onLoad({
filter: /\.(css|scss)$/
}, async (args) => {
});
В подобных сценариях PostCSS может выступать дополнительным этапом после Sass-компиляции или других преобразований.
Вместо написания собственного решения можно воспользоваться существующими пакетами.
Наиболее распространённая схема:
npm install -D esbuild-plugin-postcss2
Подключение:
const postcssPlugin =
require('esbuild-plugin-postcss2');
Конфигурация:
esbuild.build({
entryPoints: ['src/index.js'],
bundle: true,
plugins: [
postcssPlugin()
]
});
Подобные плагины автоматически:
postcss.config.js;Одним из ключевых преимуществ Esbuild является чрезвычайно высокая скорость работы благодаря реализации на Go.
Однако скорость обработки CSS во многом начинает зависеть именно от PostCSS и набора используемых модулей.
Наиболее затратными обычно являются:
Для сохранения высокой производительности рекомендуется:
Плагины выполняются последовательно в том порядке, в котором перечислены в конфигурации.
Пример:
module.exports = {
plugins: [
require('postcss-nested'),
require('autoprefixer'),
require('cssnano')
]
};
Последовательность обработки:
Неправильный порядок способен приводить к неожиданным результатам, поэтому структура цепочки обработки должна проектироваться осознанно.
Для большинства современных проектов конфигурация выглядит следующим образом:
module.exports = {
plugins: [
require('postcss-nested'),
require('postcss-preset-env'),
require('autoprefixer'),
require('cssnano')
]
};
Этапы обработки:
В сочетании с высокой скоростью Esbuild такой конвейер обеспечивает быстрое создание production-сборок и позволяет использовать практически весь современный инструментарий обработки CSS через гибкую экосистему PostCSS-плагинов.