Gulp.jsとPostCSSを使ってCSSの面倒な作業をなくそう

先生


Gulp.jsとPostCSSを使ってCSSの面倒な作業をなくそう

ここ最近はいろいろと勉強したいことがあふれかえってます。うまく時間を作って知識を増やしていきたいところです。こんにちは、CTOの林です。

近年のエンジニアリングは自動化の方向へどんどん流れています。
Grunt,Gulp,WebPack,CI,Chef,Ansible…… etc.

もちろんCSSにも自動化の流れが来ています。
過去にこんなことがありませんでしたか?

  • CSSのインデントや記述の仕方がバラバラ
  • パフォーマンス・チューニング??
  • とりあえずベンダープレフィックスを全部つけておけばいいだろう!
  • 圧縮を納品前にやって、圧縮したら崩れた

今日は普段の制作ですぐに使えるCSS自動化の方法を紹介しつつ、実際に使えるパッケージを導入するところまでを行いたいと思います。

※11月18日にいいオフィスで行われた「書籍『最強のCSS設計』出版記念イベント&勉強会―CSS設計、チーム開発を成功させるために必要なこと」で登壇したときの内容です。

CSSの自動化をしてみよう

自動化をする前の準備

自動化の第一歩としてGulpをインストールしましょう。
Gulpの動作にはNode.jsが必要なので、こちらをインストールし、以下のコマンドをターミナルまたはコマンドプロンプトで実行します。

Gulpをインストールします。

npm i -g gulp

gulp -vを実行してバージョンが表示されればインストールできています。

gulp -v
# CLI version 3.9.0

Gulp.jsについては入門記事の「Gulp.js入門 – コーディングを10倍速くする環境を作る方法まとめ」を書いていますので、CSS以外での活用部分を知りたい方はこちらもぜひご一読ください。

続いて、開発用のディレクトリを作成しましょう。

cd Documents/test
mkdir autocss
cd autocss

autocssというディレクトリを作りました。
そしてnpm initを実行してpackage.jsonを作成します。
ここで聞かれた内容は後で変更ができるので、とりあえずEnterを押していきましょう。

npm init

package.jsonには、環境構築に必要なパッケージが記録されていきます。
別のPCや他の人にも同じ環境を構築したい場合は、このpackage.jsonを共有すれば次回以降npm installを実行するだけで必要なパッケージがインストールされます。

Sassのコンパイル

複数のCSSをわかりやすく記述し、まとめていくためにSassを導入しましょう。
Sassの詳しい使い方は以下の記事をぜひご一読ください。

CSSを書くよりも効率的でわかりやすく記述することができます。

ここでは依存が少なく、Node.js単体で動作するgulp-sass(node-sass)を使います。

npm i gulp-sass --save-dev

gulpfile.jsというファイルを作成し、以下のように記述しましょう。

var gulp = require('gulp');
var sass = require('gulp-sass');

gulp.task('default', function () {
    return gulp.watch('*.scss',['css']);
});
gulp.task('css', function () {
    return gulp.src('*.scss')
        .pipe(sass())
        .pipe(gulp.dest('dest'));
});

そして以下のようなstyle.scssを作成し、gulp cssコマンドを実行してみましょう。

.test {
    .test-heading {
        font-size: 20px;
        font-weight: bold;
    }
    .test-content {
        padding: 10px;
    }
}
gulp css

するとdest/style.cssファイルが作成されました。

a {
  color: #ff290e; }

.test .test-heading {
  font-size: 20px;
  font-weight: bold; }

.test .test-content {
  padding: 10px; }

きちんとSassをコンパイルできました。続いてgulpコマンドを実行してみましょう。

gulp
# [19:23:53] Using gulpfile ~/Documents/test/autocss/gulpfile.js
# [19:21:49] Starting 'default'...
# [19:21:49] Finished 'default' after 13 ms

この状態でstyle.scssを変更してみましょう。

# [19:24:11] Starting 'css'...
# [19:24:11] Finished 'css' after 62 ms

と表示されて、dest/style.cssも更新されます。これでSassのコンパイルを自動化できましたね。監視はctrl(またはcommand)+cで終了できます。

Sass Lint

Sass LintはSassの記述をチェックするツールです。
あらかじめ設定ファイルを用意しておくことで、設定通りでない記述のときにはエラーまたは警告を表示します。まずは実際にインストールして試してみましょう。

SASS Lint

npm i gulp-sass-lint --save-dev

gulpfile.jsに以下の部分を追記します。

gulp.task('css', function () {
    return gulp.src('*.scss')
        .pipe(sassLint())
        .pipe(sassLint.format())
        .pipe(sassLint.failOnError())
        .pipe(sass())
        .pipe(gulp.dest('dest'));
});

続いて設定ファイルを作ります。.sass-lint.ymlというファイル名で以下の記述を丸々コピーして保存しましょう。

# sass-lint config generated by make-sass-lint-config v0.0.3
#
# The following scss-lint Linters are not yet supported by sass-lint:
# BemDepth, DisableLinterReason, ElsePlacement, PropertyCount
# PropertyUnits, SelectorDepth, SelectorFormat, SpaceAroundOperator
# TrailingWhitespace, UnnecessaryMantissa, UnnecessaryParentReference, Compass::*
#
# The following settings/values are unsupported by sass-lint:
# Linter Indentation, option "allow_non_nested_indentation"
# Linter Indentation, option "character"
# Linter NestingDepth, option "ignore_parent_selectors"
# Linter PropertySortOrder, option "ignore_unspecified"
# Linter PropertySortOrder, option "min_properties"
# Linter PropertySortOrder, option "separate_groups"
# Linter SpaceBeforeBrace, option "allow_single_line_padding"
# Linter VendorPrefix, option "identifier_list"

files:
  include: '**/*.scss'
options:
  formatter: stylish
  merge-default-rules: false
rules:
  border-zero:
    - 1
    - convention: zero
  brace-style:
    - 1
    - allow-single-line: true
  clean-import-paths:
    - 1
    - filename-extension: false
      leading-underscore: false
  empty-line-between-blocks:
    - 1
    - ignore-single-line-rulesets: true
  extends-before-declarations: 1
  extends-before-mixins: 1
  final-newline:
    - 0
    - include: true
  force-attribute-nesting: 1
  force-element-nesting: 1
  force-pseudo-nesting: 1
  function-name-format:
    - 1
    - allow-leading-underscore: true
      convention: hyphenatedlowercase
  hex-length:
    - 1
    - style: short
  hex-notation:
    - 1
    - style: lowercase
  indentation:
    - 1
    - size: 4
  leading-zero:
    - 1
    - include: false
  mixin-name-format:
    - 1
    - allow-leading-underscore: true
      convention: hyphenatedlowercase
  mixins-before-declarations: 1
  nesting-depth:
    - 1
    - max-depth: 3
  no-color-keyword: 1
  no-color-literals: 0
  no-css-comments: 0
  no-debug: 1
  no-duplicate-properties: 1
  no-empty-rulesets: 1
  no-extends: 0
  no-ids: 1
  no-important: 1
  no-invalid-hex: 1
  no-mergeable-selectors: 0
  no-misspelled-properties:
    - 1
    - extra-properties: []
  no-qualifying-elements:
    - 1
    - allow-element-with-attribute: false
      allow-element-with-class: false
      allow-element-with-id: false
  no-trailing-zero: 0
  no-transition-all: 0
  no-url-protocols: 1
  no-vendor-prefixes:
    - 0
    - additional-identifiers: []
      excluded-identifiers: []
  placeholder-in-extend: 1
  placeholder-name-format:
    - 1
    - allow-leading-underscore: true
      convention: hyphenatedlowercase
  property-sort-order: 0
  quotes:
    - 1
    - style: double
  shorthand-values:
    - 1
    - allowed-shorthands:
        - 1
        - 2
        - 3
  single-line-per-selector: 1
  space-after-bang:
    - 1
    - include: false
  space-after-colon:
    - 1
    - include: true
  space-after-comma:
    - 1
    - include: true
  space-before-bang:
    - 1
    - include: true
  space-before-brace:
    - 1
    - include: true
  space-before-colon: 1
  space-between-parens:
    - 1
    - include: false
  trailing-semicolon: 1
  url-quotes: 1
  variable-for-property:
    - 0
    - properties: []
  variable-name-format:
    - 1
    - allow-leading-underscore: true
      convention: hyphenatedlowercase
  zero-unit: 1

ではこの状態でgulp cssを実行してみましょう。

style.scss
   6:1  warning  Space expected between blocks  empty-line-between-blocks
  12:1  warning  Space expected between blocks  empty-line-between-blocks

6行目と12行目でwarningが表示されました。
これは{}の間に空行をはさみましょうという警告です。
かなり細かい警告ですが、Sass Lintを使うことでSass自体の書き方を統一させることができます。

しかし、これではさすがにルールとして厳しすぎるため、ルールを自分たちに合わせて変更していく必要があります。先ほどコピーして作った.sass-lint.ymlをテキストエディタで開いてください。

empty-line-between-blocksというワードを検索すると、

empty-line-between-blocks:
    - 1
    - ignore-single-line-rulesets: true

という箇所が見つかります。
ここに書いてある1がルールに従っていない場合はwarning(警告)を表示するという意味になります。0は表示しない、2はエラーという意味になります。エラーになった場合はgulpの処理自体がそこで停止するので、warningとうまく使い分けをしましょう。

ここでは0にして、gulp cssを再度実行してみましょう。

gulp css
# [19:32:13] Using gulpfile ~/Documents/test/autocss/gulpfile.js
# [19:32:13] Starting 'css'...
# [19:32:14] Finished 'css' after 211 ms

warningが表示されなくなりました。2とした場合は以下のようにエラーとなるので、どこまでを警告とし、エラーとするかを要件に合わせて調整していきましょう。

style.scss
   6:1  error  Space expected between blocks  empty-line-between-blocks
  12:1  error  Space expected between blocks  empty-line-between-blocks

✖ 2 problems (2 errors, 0 warnings)

詳しいLintの設定は以下にまとまっていますので、こちらを参考にしながら最適な設定にカスタマイズしましょう。

Sass Lint Rules

CSSO

次にCSSを圧縮、不要な記述を取り除くなどして軽量化/最適化していきましょう。
最適化には様々なパッケージがありますが、ここではコンパイル速度と圧縮率のバランスがよいgulp-cssoを使用します。さっそくインストールしてみましょう。

gulp-csso

npm i gulp-csso --save-dev

gulpfile.jsを以下のように書き替えます。

gulp.task('css', function () {
    return gulp.src('*.scss')
        .pipe(sassLint())
        .pipe(sassLint.format())
        .pipe(sassLint.failOnError())
        .pipe(sass())
        .pipe(csso())
        .pipe(gulp.dest('dest'));
});

そして、gulp cssを実行してみましょう。出力されたCSSを見てみると圧縮された形で出力されます。

a{color:#ff290e}.test .test-heading{font-size:30px;font-weight:700}.test .test-content{padding:10px}

このサンプルでは圧縮率はさほど変わりませんでしたが、例えば空の要素があった場合には削除されます。またpadding: 10px 10px 10px 10px;という記述があった場合にはpadding: 10px;に短縮され、容量を少しでも抑えることができます。

Sass Lintでもwarningが出るようになっているので、意識的に記述を変えるようにするか、自動化されるからLintはいらない場合は.sass-lint.ymlを修正したりして調整をしましょう。

PostCSS

PostCSSはCSSを解析しツリー化してJavaScriptで処理を加えるツールです。
そのため、PostCSS単体ではCSSに何も変化を加えません。Sassのように記述して変形させることもできます。ここではSassでコンパイルされたCSSを、PostCSSを通してSassを拡張するような形で用いていきます。

PostCSS

npm i gulp-postcss --save-dev

gulpfile.jsを以下のように修正します。

gulp.task('css', function () {
    return gulp.src('*.scss')
        .pipe(sassLint())
        .pipe(sassLint.format())
        .pipe(sassLint.failOnError())
        .pipe(sass())
        .pipe(postcss())
        .pipe(csso())
        .pipe(gulp.dest('dest'));
});

この状態でgulp cssを実行しても特に変化はありません。
次にPostCSS用のプラグインであるAutoPrefixerを導入しましょう。

AutoPrefixer

AutoPrefixerはCSSにあるベンダープレフィックスをCan I Useのデータを元に自動的に付与、または不必要なプレフィックスを削除してくれるPostCSSプラグインです。さっそくAutoPrefixerをインストールしてみましょう。

AutoPrefixer

npm i autoprefixer --save-dev

gulpfile.jsを以下のように修正します。
AutoPrefixerのオプションbrowsersにサポートするブラウザの情報を渡します。
下記の例ではブラウザシェアが3%以上あるもの、という指定になっています。
これ以外にもIE9以上(> ie9)などの指定が複数設定できます。

var browsers = [
    '> 3%'
];
gulp.task('css', function () {
    return gulp.src('*.scss')
        .pipe(sassLint())
        .pipe(sassLint.format())
        .pipe(sassLint.failOnError())
        .pipe(sass())
        .pipe(postcss([
            require('autoprefixer')({browsers: browsers})
        ]))
        .pipe(csso())
        .pipe(gulp.dest('dest'));
});

style.scssにベンダープレフィックスが必要なtransformを使った記述を追加してみましょう。

.a {
    &:hover {
        transform: scale(1.2);
    }
}

そしてgulp cssを実行します。

a:hover {
    -webkit-transform: scale(1.2);
    transform: scale(1.2)
}

自動的に-webkitが付与されました。試しにブラウザシェアを50%以上(> 50%)にして実行してみると、以下のようにベンダープレフィックスはつかなくなりました。

a:hover {
    transform: scale(1.2)
}

また、誤って必要のないプレフィックスを付けてしまった場合でも、以下のように

a:hover {
    transform: scale(1.2)
}

と出力され、不必要なプレフィックスは削除されます。
これでどの記述にベンダープレフィックスがいるのか悩む必要がなくなりますね。

doiuse

先ほどはベンダープレフィックスを自動で付与しましたが、そもそもCSS3のプロパティが要件にあったブラウザでサポートされているかどうかはわかりませんでした。
そこでdoiuseというパッケージを使って、指定ブラウザで使えないプロパティがないかどうかを自動的にチェックしてみましょう。

doiuse

npm i doiuse --save-dev

gulpfile.jsを以下のように修正します。

gulp.task('css', function () {
    return gulp.src('*.scss')
        .pipe(sassLint())
        .pipe(sassLint.format())
        .pipe(sassLint.failOnError())
        .pipe(sass())
        .pipe(postcss([
            require('doiuse')({browsers: browsers}),
            require('autoprefixer')({browsers: browsers})
        ]))
        .pipe(csso())
        .pipe(gulp.dest('dest'));
});

ブラウザの指定はAutoPrefixerと同じにしましょう。ブラウザの対応は> 3%にして、gulp cssを実行してみましょう。

gulp css
# [19:59:25] gulp-postcss: style.css
# doiuse: /Users/frontainer/Documents/test/autocss/style.css:4:5: CSS3 2D Transforms not supported by: Opera Mini (5.0-8.0) (transforms2d)
# doiuse: /Users/frontainer/Documents/test/autocss/style.css:5:5: CSS3 2D Transforms not supported by: Opera Mini (5.0-8.0) (transforms2d)
# [19:59:25] Finished 'css' after 721 ms

するとOpera Miniの5.0〜8.0においてはCSS3の2D Transformsはサポートしていないとのことでした。Transformのサポート範囲はわかっていて毎回出るとうっとうしいな、と思ったときにはオプションを以下のようにしてみましょう。

require('doiuse')({
    browsers: browsers,
    ignore: ['transforms2d']
}),

こうすることでtransforms2dの警告が表示されなくなります。一通りサポート状況がチェックできた時点でignoreするかブラウザの設定を見直すようにしましょう。

CSS MQPacker

続いてCSS3のメディアクエリをひとまとめにするパッケージを導入します。
Sassで構築している際にファイルを複数に分けることがありますが、メディアクエリを複数箇所に書いていては、同じ記述が複数回出てきてしまい、無駄が発生してしまいます。そこでMQPackerを導入して同じメディアクエリを1つにまとめましょう。

node-css-mqpacker

npm i css-mqpacker --save-dev
gulp.task('css', function () {
    return gulp.src('*.scss')
        .pipe(sassLint())
        .pipe(sassLint.format())
        .pipe(sassLint.failOnError())
        .pipe(sass())
        .pipe(postcss([
            require('doiuse')({browsers: browsers}),
            require('autoprefixer')({browsers: browsers}),
            require('css-mqpacker')
        ])))
        .pipe(csso())
        .pipe(gulp.dest('dest'));
});

(本当は別ファイルにするのですが、)以下のようなCSSをMQPackerへ通すと、

@media screen and (max-width:800px) {
    .test {
        width: 100px;
    }
}
@media screen and (max-width:800px) {
    .test2 {
        width: 200px;
    }
}

以下のように1つのメディアクエリにまとまります。記述量が減りましたね。

@media screen and (max-width: 800px) {
    .test {
        width: 100px
    }

    .test2 {
        width: 200px
    }
}

まとめ

CSSに関する自動化をご紹介しました。
JavaScriptなどでは当たり前になりつつあった自動化ですが、CSS方面でも広がりを見せています。このような自動化ツールを活用してCSSをチームで統一させたり、より良い書き方を身につけるための練習をしたりしてみてください。

先生
この記事を書いた人
先生

最高技術責任者

関連記事