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; // プレフィックス検証用
  }
}
  1. npm run dev を起動し、dist/css/style.min.css が生成されることを確認する。
  2. .browserslistrc の対象に古いブラウザを含めた場合、user-select に -webkit- 等のプレフィックスが付与される。
  3. 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 はちょうどよい落とし所になる。

\ 最新情報をチェック /

コメントを残す

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください。