GulpとPostCSSで未来的CSS設計
タスクランナーの Gulp と PostCSS を使い未来的な CSS を書いてみます。
未来的と書いたのは「いつか未来に使えるかもしれない、でも今はまだ使えない CSS をつかって書いてみよう」というお話。
PostCSSで出来ること
Gulp の導入、PostCSS とはという話は割愛します。わからない方は検索してください。
例えば PostCSS と postcss-selector-matches というプラグインを使うと以下のような疑似セレクタが使えます。
body:matches(.home, .archives, .tag) {
color: red;
}疑似セレクタ :matches は「セレクタをひとまとめ」にするセレクタで、CSS4 で定義されています。現状ではほぼ全てのブラウザで使うことができません。ただ postcss-selector-matches は上の CSS を以下のように変換してくれます。
body.home, body.archives, body.tag {
color: red;
}PostCSS はプラグインを入れることでこのように今は使えないけれどもしかしたら先の未来に使えるかもしれない書き方を、今現在でも使える形に直してくれます。
つまり「未来の先取り」ってことです。Sass や Less などのプリプロセッサは基本そのままでは CSS とは別物なので当然ブラウザは読み込めません。PostCSS は CSS そのものに改編を加えるので元のファイルも読み込むこと自体はできます。現状 :matches はほぼ使えないに等しいのですが、近い未来使える可能性のある疑似セレクタです。
プリプロセッサには流行り廃りがあるのでせっかく覚えてもってことがありますが、この使い方で PostCSS を使えばその心配はありません。「ほぼネイティブ」の CSS なので廃れるということがおそらくないです。
Sass だと
PostCSS を使えば
PostCSS の先取り CSS は「cssnext」が有名ですがその中身はプラグインの寄せ集めです。今回は cssnext は使わず、個別にプラグインをつかって変換をしていきます。
タスク名はすべて postcss で統一しています。各自変更してください。
autoprefixer
参考:autoprefixer
インストール
npm install --save-dev autoprefixergulpfile.js
var postcss = require('gulp-postcss');
gulp.task('postcss', function() {
return gulp.src('postcss/*.css')
.pipe(postcss([
require('autoprefixer')({ browsers: ['last 2 versions'] }) //直近2バージョンまでフォロー
]))
.pipe(gulp.dest('dist/css/'))
});自動でベンダープレフィクスを付けてくれるプラグインです。
現状ベンダープレフィクスが必要なプロパティはかなり減ってきましたが、昔のブラウザをフォローする場合やまだいくつかの実験的機能では必要です。それをブラウザのバージョンなども考慮して自動でつけてくれるプラグイン。PostCSS はもともと autoprefixer を使うために作られたそうです。
caniuse の情報をもとにプレフィクスを付けるので、付与の更新自体もほぼ自動で行われる優れもの。デフォルトのままでも十分ですがどのぐらい前のバージョンまでフォローするか、どのブラウザをフォローするかなども細かく決めることもできるので色々な状況に対応できます。
/* before */
::selection {
background-color: #ffe082;
color: #333;
}
/* after */
::-moz-selection {
background-color: #ffe082;
color: #333;
}
::selection {
background-color: #ffe082;
color: #333;
}selection 疑似要素は Firefox のみ -moz- のベンダープレフィクスが必要ですが、autoprefixer を使えば自動で付けてくれます。
将来的にはベンダープレフィクスは全て無くなるはずでが現状はまだ必要。でも autoprefixer を使えばいつか訪れるはずのベンダープレフィクスがなくてもいい記述で OKです。
postcss-nesting
インストール
npm install postcss-nesting --save-devgulpfile.js
var postcss = require('gulp-postcss');
gulp.task('postcss', function() {
return gulp.src('postcss/*.css')
.pipe(postcss([
require('postcss-nesting')
]))
.pipe(gulp.dest('dist/css/'))
});PostCSS をいじっていて初めて知ったのですが、実は CSS 公式でもネスト(入れ子)のルールがすでに決まっていたみたいです。近い未来使えるようになるかもしれないってことですね。
ネストを使うと CSS が構造が見やすくなってわかりやすいです。
/* before */
#content {
margin: 48px auto 0;
padding: 0 15px;
text-align: justify;
text-justify: inter-ideograph;
& img {
display: block;
margin: 0 auto;
}
& a {
color: #536dfe;
margin: 0 -2px;
padding: 0 2px;
&:hover {
background-color: #8699fe;
color: #fff;
text-decoration: none;
}
}
}
/* after */
#content {
margin: 48px auto 0;
padding: 0 15px;
text-align: justify;
text-justify: inter-ideograph /* セミコロンが無くなる */
}
#content img {
display: block;
margin: 0 auto;
}
#content a {
color: #536dfe;
margin: 0 -2px;
padding: 0 2px;
}
#content a:hover {
background-color: #8699fe;
color: #fff;
text-decoration: none;
}子孫セレクタを使うときは & の後に子孫セレクタ同様半角スペースが必要です。:hover の場合はくっつけて書きますのでいりません。
ネストに関するプラグインはほかにもいくつかあり、Sass と同じように子孫セレクタであれば & を使わずにネスト出来るものなども存在します。
postcss-nesting はなぜかわかりませんがネスト関与したセレクタのプロパティの最後のセミコロンを省略してしまうという仕様(バグ?)があります。最後のセミコロンは無くてもかまわないので問題はないのですが、気になる場合は gulp-csscomb や PostCSS のプラグインなどを使って整形しましょう。
参考:gulp-csscomb
postcss-selector-matches
インストール
npm install postcss-selector-matche --save-devsgulpfile.js
var postcss = require('gulp-postcss');
gulp.task('postcss', function() {
return gulp.src('postcss/*.css')
.pipe(postcss([
require('postcss-selector-matches')
]))
.pipe(gulp.dest('dist/css/'))
});最初に紹介したセレクターをひとまとめにする :matches 疑似セレクタを使えるようになるプラグインです。
/* before */
input:matches([type="button"], [type="submit"]) {
-webkit-appearance: none;
}
/* after */
input[type="button"], input[type="submit"] {
-webkit-appearance: none;
}:matches の詳しい使い方は以下を参考にしてください。
postcss-css-variables
インストール
npm install postcss-css-variables --save-devgulpfile.js
var postcss = require('gulp-postcss');
gulp.task('postcss', function() {
return gulp.src('postcss/*.css')
.pipe(postcss([
require('postcss-css-variables')
]))
.pipe(gulp.dest('dist/css/'))
});CSS の変数、カスタムプロパティを使えるようにするプラグインです。
Sass などでも変数は使えますが、ネイティブの CSS でも将来的には使えるようになります。現状それなりのブラウザですでにカスタムプロパティを使うことができます。
ただ全ブラウザとは言えないのでこれも PostCSS に頼りましょう。
/* before */
:root {
--main-color: #eee;
}
body {
background-color: var(--main-color);
}
footer {
--main-color: #333; /* 変数の値の変更 */
color: var(--main-color);
}
/* after */
body {
background-color: #eee;
}
footer {
color: #333;
}:root で宣言するとグローバル変数のような扱いになりますが、このプラグインは :root 以外でも宣言ができ、変更も可能です。
Sass のように $ を使って変数宣言できる PostCSS のプラグインもあります。ネイティブの書き方のほうが少しだけ丈長です。
postcss-custom-selectors
インストール
npm install postcss-custom-selectors --save-devgulpfile.js
var postcss = require('gulp-postcss');
gulp.task('postcss', function() {
return gulp.src('postcss/*.css')
.pipe(postcss([
require('postcss-custom-selectors')
]))
.pipe(gulp.dest('dist/css/'))
});先ほどはプロパティ値の変数でしたが今度はセレクタの変数です。
GitHub のデモ画像が分かりやすいのでそれを見てもらえればわかると思います。カスタムプロパティとの違いは2個のハイフンの前にコロン(:--)が付くことです。
/* before */
@custom-selector :--heading h1, h2, h3;
article :--heading + p {
margin-top: 0;
}
/* after */
article h1 + p, article h2 + p, article h3 + p {
margin-top: 0;
}postcss-pseudo-class-any-link
参考:postcss-pseudo-class-any-link
インストール
npm install postcss-pseudo-class-any-link --save-devgulpfile.js
var postcss = require('gulp-postcss');
gulp.task('postcss', function() {
return gulp.src('postcss/*.css')
.pipe(postcss([
require('postcss-pseudo-class-any-link')
]))
.pipe(gulp.dest('dist/css/'))
});小技な感じもありますが今までなかった :link と :visited をひとまとめにするプラグインです。将来的に使えるようになるかもしれない疑似セレクタ。
/* before */
a:any-link {
color: blue;
}
/* after */
a:link,a:visited {
color: blue;
}:any-link は現在 GoogleChrome と Firefox でベンダープレフィクス付きですがそのまま利用できるようです。
postcss-selector-not
インストール
npm install postcss-selector-not --save-devgulpfile.js
var postcss = require('gulp-postcss');
gulp.task('postcss', function() {
return gulp.src('postcss/*.css')
.pipe(postcss([
require('postcss-selector-not')
]))
.pipe(gulp.dest('dist/css/'))
});セレクタの :not はすでにブラウザで使えますが、:not() 内に複数のセレクタを入れることができませんでした。このプラグインで将来的に可能になる記述を使えます。
/* before */
p:not(:first-child, .special) {
color: red;
}
/* after */
p:not(:first-child):not(.special) {
color: red;
}なぜだかわかりませんが、上の参考の npm 本家の変換の例が間違っています。実際に入れて使っていますが上記のように正しく変換されます。
まとめ
まだ将来的に使える書き方のものがいくつかあります。color 関係がたくさんあるのですが個人的に使いどころがわからないのでここでは紹介しません。興味のある方は PostCSS.parts で調べてもらうと出てきます。
昔は「IE が対応してないから使いたいけど使えない」みたいなことばかりでしたが、これらのプラグインを使えば対応していなくても新しい CSS を使える。将来的には PostCSS を通すことなく使える可能性のある「ほぼネイティブ」な記述を使えます。「予習」という意味でも楽しいです。
PostCSS はこのような CSS ルールにならった書き方だけでなく「オレオレ記述」が可能なプラグインがたくさんあります。あまりにも「オレオレ」しすぎるとほかの人が見たとき「?」になってしまいますが、このような使い方なら「未来的」でいいのではないかなと思っています。