PostCSSで作ろうマイCSSビルドツール

どうも、初めて書きますnju33(じゅん)という者です。 よろしくお願いします。
書く順番が回ってきて何を書こうか迷ったのですが、今回はPostCSSについて書こうと思います。

PostCSSとは?

Autoprefixerで有名なAndrey SitnikさんらがNodeJSで開発している、 CSSパーサとそれを取り扱うのに便利なAPIを提供しているツールです。 そのAPIを使って、変換する為のプラグインが沢山が各々によってたくさん作られています。
プラグインは1つで1つの事を行うことをポリシーに作られているので、それらを組み合わせることで、自分だけのCSSビルドツールを作ることができます。

最近では「その場で実行」系で人気サービス、 Codepen(*)や jsFiddle(*) なども続々と対応してきていたり、 「Twitter Bootstarp5にはPostCSSが使われるかも」と開発者の1人 Mark Otto(@mdo) さんが言っていたりと、今後に注目したいツールです。

PostCSSの使い方

まずは実際に変換してみます。
とりあえず、下の2つのプラグインを選びました。

プラグイン名説明
autoprefixerベンダープレフィックスを付ける
postcss-namespaceセレクターにプレフィックスを付ける

ではまずは、プラグインとPostCSSをインストールします。

1
2
3
# 作業ディレクトリとして postcss/ を作って移動
mkdir postcss && cd $_
npm install postcss autoprefixer postcss-namespace

次に、変換用のcssファイルを作ります。

style.css
1
2
3
4
5
@prefix block;

.box {
  display: flex;
}

最後に、PostCSSを使って変換するjsファイルを作ります。
postcssの第一引数に、インストールしたプラグインを適応順に、配列で記述します。

script.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 必要なモジュール読み込み
const fs = require('fs'),
      postcss = require('postcss'),
      autoprefixer = require('autoprefixer'),
      namespace = require('postcss-namespace');

// autoprefixerで変換されたCSSを、
// 次はpostcss-namespaceで変換
const processor = postcss([
  autoprefixer({browsers: ['last 2 versions']}),
  namespace({token: '__'})
]);

// 変換するファイル
const css = fs.readFileSync('style.css', 'utf-8');

processor.process(css)
  .then((result) => {
    console.log(result.css);
  });

ちなみに、ディレクトリ構造はこうなっています。

1
2
3
4
.
├── node_modules  // 中身は略
├── script.js
└── style.css

あとは、変換するだけです。

1
node script.js

こんな感じに変換されました!

1
2
3
4
5
.block__box {
  display: -webkit-flex;
  display: -ms-flexbox;
  display: flex;
}

プラグインの中には、いくつかのプラグインを集めて1つのプラグインとして提供している、 パックプラグインというものもあります。
例えば下の2つはそれです。

プラグイン名説明
cssnextCSS4シンタックスに近い方法で書けるようにする
precssSassのような感じで書けるようにする

詳しくはそれぞれのリンク先を読んでみてください。 最初はこれらから使っていき、慣れてきたら1つ1つプラグインを選んでいくなどすると良いと思います。

僕のCSSビルドツール

最後に自分の環境を晒して終わりたいと思います。

タスクランナー

流石に変更の度にコマンドを実行するのは面倒くさいのでgulpを使って、 cssファイルが変更する度にビルドされるようにします。

以下のプラグインを使います。詳細は各プラグインのリンク先で。

プラグイン名説明
stylelintcssの構文チェック
postcss-reporterstylelintログを見やすくして表示
postcss-cson-cssvarsCSS内で使える変数をCSONファイルで設定
postcss-color-functionカラー調整関数を追加
postcss-assetsより良い画像アセット読み込み関数を追加
postcss-property-lookup同じルール内のプロパティ値に、@xxxのような形で利用できる
postcss-animationanimate.cssで定義されてる名前をanimationに指定するだけで使える
postcss-easingstransition-timing-functioneasings.netにあるものを追加
lostグリッドシステムなプロパティを追加
postcss-selector-notCSS4シンタックスの:notが使える
postcss-preref前回のセレクタを&で使える
postcss-namespaceセレクターにプレフィックスを付ける
doiusecaniuseを元に、サポート対象ブラウザが対応していないプロパティを使っていると警告を出してくれる
autoprefixerベンダープレフィックスを自動で付ける
css-mqpacker@mediaの最適化
cssnanocssの最適化
gulpfile.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
const fs = require('fs'),
      gulp = require('gulp'),
      watch = require('gulp-watch'),
      sourcemaps = require('gulp-sourcemaps'),
      postcss = require('gulp-postcss');

const processors = [
  require('stylelint'),
  require('postcss-reporter')({clearMessages: true}),
  require('postcss-cson-cssvars'),
  require('postcss-color-function'),
  require('postcss-assets'),
  require('postcss-property-lookup'),
  require('postcss-animation'),
  require('postcss-easings'),
  require('lost'),
  require('postcss-selector-not'),
  require('postcss-preref'),
  require('postcss-namespace')({token: '__'}),
  require('doiuse')({browsers: ['last 2 versions']}),
  require('autoprefixer')({browser: ['last 2 versions']}),
  require('css-mqpacker'),
  // require('cssnano') 見づらくなるので今回はコメントアウト
]

gulp.task('postcss', () => {
  gulp.src('./style.css')
    .pipe(watch('./style.css'))
    .pipe(sourcemaps.init())
    .pipe(postcss(processors))
    .pipe(sourcemaps.write('.'))
  .pipe(gulp.dest('dist/'));
});

たくさん読み込んでいますが、順番はかなり重要です。
順番によって後のプラグインがうまく動かなくなったりするので、 よくREADMEを読んで頭の中で処理を想像した時におかしくならないように気をつけます。

インプット

これらを使う前提でCSSを書いてみます。

input.css
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
@charset "utf-8";

.u-bounce {
  animation: bounce;
}

@prefix block;

.box {
  color: color($color.font l(+10));
  border: 1px solid @color;
}

@media screen and (min-width: $size.ipadpro) {
  .box {
    lost-center: $width;
  }
}

.image {
  width: width(500x403.jpg);
  height: height(500x403.jpg);
}

.card {
  lost-column: 1/2 no-flex;
}

@media screen and (min-width: $size.ipadpro) {
  .card {
    lost-column: 1/4 no-flex;
  }
}

.input:not(.not1, .not2) {
  transition: .3s easeInOutBack;
  border: 1px solid $color.quiet;
}

&:hover {
  border: 1px solid $color.accent;
}

こんな感じになりました。後はビルドするだけです。

アウトプット

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
@charset "utf-8";

.u-bounce {
  -webkit-animation: bounce;
          animation: bounce;
}

@-webkit-keyframes bounce{

  from, 20%, 53%, 80%, to{
    -webkit-animation-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000);
            animation-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000);
    -webkit-transform: translate3d(0,0,0);
            transform: translate3d(0,0,0);
  }

  40%, 43%{
    -webkit-animation-timing-function: cubic-bezier(0.755, 0.050, 0.855, 0.060);
            animation-timing-function: cubic-bezier(0.755, 0.050, 0.855, 0.060);
    -webkit-transform: translate3d(0, -30px, 0);
            transform: translate3d(0, -30px, 0);
  }

  70%{
    -webkit-animation-timing-function: cubic-bezier(0.755, 0.050, 0.855, 0.060);
            animation-timing-function: cubic-bezier(0.755, 0.050, 0.855, 0.060);
    -webkit-transform: translate3d(0, -15px, 0);
            transform: translate3d(0, -15px, 0);
  }

  90%{
    -webkit-transform: translate3d(0,-4px,0);
            transform: translate3d(0,-4px,0);
  }
}

@keyframes bounce{

  from, 20%, 53%, 80%, to{
    -webkit-animation-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000);
            animation-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000);
    -webkit-transform: translate3d(0,0,0);
            transform: translate3d(0,0,0);
  }

  40%, 43%{
    -webkit-animation-timing-function: cubic-bezier(0.755, 0.050, 0.855, 0.060);
            animation-timing-function: cubic-bezier(0.755, 0.050, 0.855, 0.060);
    -webkit-transform: translate3d(0, -30px, 0);
            transform: translate3d(0, -30px, 0);
  }

  70%{
    -webkit-animation-timing-function: cubic-bezier(0.755, 0.050, 0.855, 0.060);
            animation-timing-function: cubic-bezier(0.755, 0.050, 0.855, 0.060);
    -webkit-transform: translate3d(0, -15px, 0);
            transform: translate3d(0, -15px, 0);
  }

  90%{
    -webkit-transform: translate3d(0,-4px,0);
            transform: translate3d(0,-4px,0);
  }
}

.block__box {
  color: rgb(92, 92, 92);
  border: 1px solid white;
  color: white;
}

.block__image {
  width: 500px;
  height: 403px;
}

.block__card {
  width: calc(99.99% * 1/2 - (30px - 30px * 1/2));
}

.block__card:nth-child(n){
  float: left;
  margin-right: 30px;
  clear: none;
}

.block__card:last-child{
  margin-right: 0;
}

.block__card:nth-child(2n){
  margin-right: 0;
}

.card:nth-child(2n + 1){
  clear: left;
}

.block__input:not(.not1):not(.not2) {
  -webkit-transition: .3s cubic-bezier(0.68, -0.55, 0.265, 1.55);
  transition: .3s cubic-bezier(0.68, -0.55, 0.265, 1.55);
  border: 1px solid #b8b8b8;
}

.block__input:not(.not1):not(.not2):hover {
  border: 1px solid #EB7A77;
}

@media screen and (min-width: 1026px){

  .block__box{
    *zoom: 1;
    max-width: 960px;
    margin-left: auto;
    margin-right: auto;
  }

  .block__box:before{
    content: '';
    display: table;
  }

  .block__box:after{
    content: '';
    display: table;
    clear: both;
  }

  .block__card{
    width: calc(99.99% * 1/4 - (30px - 30px * 1/4));
  }

  .block__card:nth-child(n){
    float: left;
    margin-right: 30px;
    clear: none;
  }

  .block__card:last-child{
    margin-right: 0;
  }

  .block__card:nth-child(4n){
    margin-right: 0;
  }

  .card:nth-child(4n + 1){
    clear: left;
  }
}

/*# sourceMappingURL=style.css.map */

少ない記述量で、こんな感じに変換されました!

プラグイン一覧は最低でも週に1〜2個ずつ増えていっています。お気に入りのプラグインを探してみてください。
ちなみに、 postcss-namespacepostcss-prerefpostcss-cson-cssvarsは僕が自作してみたプラグインです。

思っていたほどプラグイン作成は難しいものではないので、 自分が欲しいプラグインが一覧に無ければ、似たプラグインで我慢せず自分で作ってしまうのも1つの手だと思います!


(p.s.)
この環境はpostcss-environment-nju33に置いておきました。