gulpとPostCSSで組むフロントエンドのCSSビルドパイプライン
概要
Sass だけに頼らず、PostCSS のプラグインエコシステムを軸にした CSS ビルドパイプラインを gulp で組む方法をハンズオン形式でまとめる。
なぜ今 gulp なのか。Vite や webpack のようなバンドラを前提にしない構成——WordPress テーマ、複数エントリの静的サイト、既存のレガシー案件——では、「入力ディレクトリの CSS/SCSS をまとめてビルドし、決まったディレクトリに吐き出す」というタスクランナー的な役割が今も必要になる。gulp はこの「ファイルの流し込み(stream)」を書くのに素直で、PostCSS と組み合わせると Autoprefixer・minify・将来の CSS 構文の先取りまで一気通貫で扱える。
この記事では次を作る。
- SCSS → PostCSS(Autoprefixer / nesting / custom-media)→ minify のパイプライン
- ソースマップ出力
- 差分ビルド(watch)
- 本番ビルドと開発ビルドの切り替え
環境
- Node.js 20.x
- gulp 5.x
- 主要パッケージ:
gulp-postcss/postcss/autoprefixer/postcss-preset-env/cssnano/gulp-sass+sass(Dart Sass)/gulp-rename
ディレクトリ構成は次を前提にする。
project/
├── gulpfile.js
├── package.json
├── postcss.config.js
├── .browserslistrc
├── src/
│ └── css/
│ ├── style.scss
│ └── _variables.scss
└── dist/
└── css/
セットアップ
npm init -y npm install --save-dev \ gulp \ gulp-sass sass \ gulp-postcss postcss \ autoprefixer postcss-preset-env cssnano \ gulp-rename
gulp 5 系ではグローバル CLI を入れずとも npx gulp で実行できる。CI でもローカルでもバージョンが固定される npx 実行を基本にしておくと事故が減る。
Browserslist の設定
Autoprefixer と postcss-preset-env はどちらも Browserslist の設定を参照する。対応ブラウザの定義は一箇所に集約しておく。.browserslistrc を置く。
> 0.5% last 2 versions not dead
ファイルとして独立させておくと、stylelint など他ツールからも同じ定義を共有できる。
PostCSS の設定
postcss.config.js にプラグインを列挙する。設定ファイルに切り出しておくと、コマンドラインの postcss CLI やエディタ拡張とも同じ設定を使い回せる。
// postcss.config.js
const autoprefixer = require('autoprefixer');
const postcssPresetEnv = require('postcss-preset-env');
const cssnano = require('cssnano');
const isProduction = process.env.NODE_ENV === 'production';
module.exports = {
plugins: [
postcssPresetEnv({
stage: 2,
features: {
'nesting-rules': true,
'custom-media-queries': true,
},
}),
autoprefixer(),
// 本番ビルドのときだけ minify する
isProduction ? cssnano({ preset: 'default' }) : false,
].filter(Boolean),
};
ポイントは3つ。
postcss-preset-envは「将来 or 提案段階の CSS 構文」を今のブラウザ向けに変換する。stage: 2は安定寄りの妥当なライン。autoprefixerは preset-env に内包されるが、明示的に並べても二重適用にはならない(同じ Browserslist を見る)。簡潔にしたいなら省いてよい。cssnanoはfilter(Boolean)で本番時のみ配列に残す。開発ビルドで minify すると差分が読めず、ソースマップも追いにくい。
gulpfile を書く
SCSS をコンパイルし、その出力を PostCSS に流し、ソースマップ付きで dist へ書き出す。
// gulpfile.js
const { src, dest, watch, series } = require('gulp');
const sass = require('gulp-sass')(require('sass'));
const postcss = require('gulp-postcss');
const rename = require('gulp-rename');
const paths = {
scss: {
src: 'src/css/**/*.scss',
entry: 'src/css/style.scss',
dest: 'dist/css',
},
};
function styles() {
return src(paths.scss.entry, { sourcemaps: true })
.pipe(sass({ outputStyle: 'expanded' }).on('error', sass.logError))
.pipe(postcss()) // postcss.config.js を自動で読む
.pipe(rename({ suffix: '.min' }))
.pipe(dest(paths.scss.dest, { sourcemaps: '.' }));
}
function watchFiles() {
watch(paths.scss.src, styles);
}
exports.styles = styles;
exports.watch = series(styles, watchFiles);
exports.default = series(styles);
補足する。
gulp-sassはrequire('gulp-sass')(require('sass'))の二段呼び出しで Dart Sass を明示紐付け(node-sass は非推奨)。- gulp 5 では
src/destに{ sourcemaps: true }/{ sourcemaps: '.' }を渡せば、gulp-sourcemapsを明示 pipe しなくてよい。 postcss()は引数なしでpostcss.config.jsを自動読込。gulpfile と設定の重複を避けられる。rename({ suffix: '.min' })で出力名を固定すると、HTML の参照を書き換えずに済む。
ビルドの実行
# 開発(minify なし) npx gulp # 監視 npx gulp watch # 本番(minify あり) NODE_ENV=production npx gulp
NODE_ENV を切り替えるだけで minify のオンオフが変わる。gulp タスクを本番/開発で分けなくてよい。
package.json のスクリプト化
{
"scripts": {
"build": "cross-env NODE_ENV=production gulp",
"dev": "gulp watch"
}
}
Windows で NODE_ENV=... が効かない場合に備え cross-env を挟んでいる(npm install --save-dev cross-env)。
確認方法
検証用の SCSS:
// src/css/style.scss
$brand: #3b82f6;
.card {
display: flex;
gap: 1rem;
.card__title {
color: $brand;
user-select: none; // プレフィックス検証用
}
}
npm run devを起動し、dist/css/style.min.cssが生成されることを確認する。.browserslistrcの対象に古いブラウザを含めた場合、user-selectに-webkit-等のプレフィックスが付与される。npm run buildで出力が1行に圧縮され、style.min.css.mapが生成されていれば成功。
注意点
- ソースマップと minify: 公開環境に
.mapを含めたくない場合は本番ビルド時に無効化する分岐を入れる。 - プラグインの順序: PostCSS は配列の上から順に適用する。
cssnano(minify)は必ず最後。先に minify すると Autoprefixer が展開すべき記述を潰すことがある。 - ネストの二重化: SCSS でも preset-env でもネストを書ける。両方を無自覚に使うと展開元が追いにくい。SCSS を使うなら preset-env の
nesting-rulesを切る割り切りも選択肢。 - gulp 5 への移行: 古い
gulp-*プラグインが 5 で動かないことがある。5 対応版へ更新する。
まとめ
- gulp は「ファイルを流し込むタスクランナー」として今も素直に使える。
- CSS の変換ロジックは gulpfile に散らさず
postcss.config.jsに寄せると、開発/本番の切り替えが1行で済む。 - Autoprefixer・minify・将来構文の先取りは PostCSS プラグインの並び順で制御する。minify は最後。
- 対応ブラウザは
.browserslistrcに一元化する。
バンドラを持ち込むほどではないが手作業ビルドから卒業したい——その規模のフロントエンドに、gulp + PostCSS はちょうどよい落とし所になる。
