シンクロ・フード エンジニアブログ

飲食店.COMを運営する、株式会社シンクロ・フードの技術ブログです

レガシーなフレームワークでcssの圧縮を自動化する

シンクロ・フードでフロントエンドの開発を担当している四之宮です。
前回投稿した「レガシーなフレームワークでcss/javascriptのキャッシュクリアを自動化する」が思っていた以上にアクセスがありましたので、今回はその続編となります。
本記事からでも理解できるようにしたつもりですが、上記の記事から読んでいただくとより理解していただけるかなと思います。

ちなみに、前回の記事を投稿してから約2か月ですが、この間にReactを使ったサービスの開発を行いリリースしています。
弊社のReact導入の手順に関しては、下記の通りになります。
ご興味があれば是非ご確認ください。
既に稼働しているWebサービスに対してgulp+webpackでReactをビルドする

さて、いつもレガシーばかりではないこともアピールできたので、本題に入りたいと思います。

CSSの圧縮を行う方法

弊社ではgulpによるscssのコンパイルを行っているため、gulp-cssminというnpmを使用しました。

gulp-cssmin

下記コマンドでインストールを行います。

npm install --save-dev gulp-cssmin  

実際の使用例は下記の通りです。

var cssmin = require('gulp-cssmin');  
  
gulp.task('cssmin', ['sassのbuildなど'], function () {  
    return gulp.src('dest/webapp/**/*.css')  
        .pipe(cssmin(  
            {  
                'processImport': false,  
                'advanced': false  
            }  
        ))  
        .pipe(gulp.dest('dest/webapp/'));  
});  

圧縮する際のオプションは以下のものが設定できます。
https://github.com/jakubpawlowicz/clean-css

今回使用しているオプションは2つあります。
どちらのオプションも圧縮前のcssの記述方法によっては、致命的なバグを生んでしまうので注意が必要です。
また、圧縮前のcssは可能な限り綺麗な方が圧縮による思わぬバグを防げるため、lintを通してから行うのもいいかと思います。

※弊社では、この2つのオプションで運用できていますが、導入する環境に応じて適切なオプションを指定してください

processImport

css内の@importを圧縮の際に残すためのオプションです。
@importを使うことはないかと思いますが、弊社のcssは本当に古いものがありこれの対応をする必要がありました。
@importを使っている際は注意が必要ですが、使っていないのであればこの指定は不要になります。

advanced

font-sizefontborder-colorborderといったような、親子関係のようなプロパティが指定されているとき、これらをまとめあげるかを決めるオプションになります。
'advanced': falseを指定しなかった場合の例は下記の通りです。

圧縮前

.test {  
    border-color: #aaa;  
    border: solid 1px;  
}  

圧縮後

.test {  
    border: solid 1px;  
}  

'advanced': falseの指定が無いと、border-colorの指定が無くなってしまいます。


以上でcssの圧縮処理は完了です。
しかし、弊社では圧縮前のファイルと、圧縮後のファイルを別名で保存する対応を行っています。
ここからはそれについて説明していきたいと思います。

圧縮後のCSSの名前を変更する

gulp-renameというnpmを使用します。

gulp-rename

下記コマンドでインストールを行います。

npm install --save-dev gulp-rename  

使用例は下記の通りです。
suffixでリネイム後のファイル名を指定します。
ここでは、○○.min.cssにするためsuffix: '.min'を指定しています。
これで、元のファイル名.min.cssというファイル名でファイルがコピーされます。

var rename = require('gulp-rename');  
  
gulp.task('rename', function () {  
    return gulp.src('dest/webapp/**/*.css')  
        .pipe(rename({suffix: '.min'}))  
        .pipe(gulp.dest('dest/webapp/'));  
});  

これを先ほどの圧縮と合わせます。

var cssmin = require('gulp-cssmin');  
var rename = require('gulp-rename');  
  
gulp.task('cssmin', function () {  
    return gulp.src('dest/webapp/**/*.css')  
        .pipe(cssmin(  
            {  
                'processImport': false,  
                'advanced': false  
            }  
        ))  
        .pipe(rename({suffix: '.min'}))  
        .pipe(gulp.dest('dest/webapp/'));  
});  

これで、圧縮後のファイルが、○○.min.cssという名前で生成することができます。

HTMLからの読み込むcssを圧縮後のものに変更する

圧縮後のファイル名を変更しましたが、hrefで指定されているcssは圧縮されていないので、読み込むcssを変更する必要があります。

そこで、gulp-replaceというnpmを使用します。

gulp-replace

下記コマンドでインストールを行います。

npm install --save-dev gulp-replace  

使用例は下記の通りです。
書き換えの対象のcssの取得と、書き換え後のcssのファイル名を正規表現で指定する必要があります。
replaceメソッドの第1引数で対象を取得して、第2引数で書き換え指定しています。

var replace = require('gulp-replace');  
  
gulp.task('htmlreplace', function() {  
    return gulp.src('dest/webapp/WEB-INF/view/**/*.jsp')  
        .pipe(replace(/<link(.*)href=(["`]\/[^/].*)\.css/g, '<link$1href=$2.min.css'))  
        .pipe(replace(/<link(.*)href=(["`]\/[^/].*)\.min\.min\.css/g, '<link$1href=$2.min.css'))  
        .pipe(gulp.dest('dest/webapp/WEB-INF/view/'));  
});  

2つ目のreplace処理についてですが、ライブラリなどの圧縮済のmin.cssの対応のための記述になります。
元々、○○.min.cssとなっている箇所を、1つ目のreplace処理によって、○○.min.min.cssにしてしまうため、これを元に戻しています。
1つ目のreplace処理の正規表現でmin.cssを除外できれば、この処理は不要になります。
ファイル名のminと圧縮を表すminを区別することができなったので、このような形になっています。

圧縮からhtmlの書き換えまでをまとめる

ここまで行ってきた処理を全てまとめたものが下記になります。
※ライブラリ用などの圧縮済のmin.cssを除外する処理を追加しています

var cssmin = require('gulp-cssmin');  
var rename = require('gulp-rename');  
var replace = require('gulp-replace');  
  
// 各cssファイルの圧縮  
gulp.task('cssmin',  function () {  
    return gulp.src(['dest/webapp/**/*.css', '!dest/webapp/**/*.min.css'])  
        .pipe(cssmin(  
            {  
                'processImport': false,  
                'advanced': false  
            }  
        ))  
        .pipe(rename({suffix: '.min'}))  
        .pipe(gulp.dest('dest/webapp/'));  
});  
  
// cssの読み込みを圧縮したものに書き換える  
gulp.task('htmlreplace', ['cssmin'], function() {  
    return gulp.src('dest/webapp/WEB-INF/view/**/*.jsp')  
        .pipe(replace(/<link(.*)href=(["`]\/[^/].*)\.css/g, '<link$1href=$2.min.css'))  
        .pipe(replace(/<link(.*)href=(["`]\/[^/].*)\.min\.min\.css/g, '<link$1href=$2.min.css'))  
        .pipe(gulp.dest('dest/webapp/WEB-INF/view/'));  
});  

前回のキャッシュクリア自動化対応にを組み込む

更に、前回のキャッシュクリア自動化対応を組み込むと下記のようになります。
圧縮したファイルを元に、キャッシュクリアを行っています。

var CacheBuster = require('gulp-cachebust');  
var cssmin = require('gulp-cssmin');  
var rename = require('gulp-rename');  
var replace = require('gulp-replace');  
  
// css圧縮タスク  
gulp.task('cssmin', ['sassのbuildなど'], function () {  
    return gulp.src(['dest/webapp/**/*.css', '!dest/webapp/**/*.min.css'])  
        .pipe(cssmin(  
            {  
                'processImport': false,  
                'advanced': false  
            }  
        ))  
        .pipe(rename({suffix: '.min'}))  
        .pipe(gulp.dest('dest/webapp/'));  
});  
  
// キャッシュクリア準備  
var cachebust = new CacheBuster();  
  
gulp.task('cachebustResources-css', ['cssmin'], function () {  
    return gulp.src('dest/webapp/**/*.css')  
        .pipe(cachebust.resources())  
        .pipe(gulp.dest('dest/webapp/'));  
});  
  
gulp.task('cachebustResources-javascript', function () {  
    return gulp.src('dest/webapp/**/*.js')  
        .pipe(cachebust.resources())  
        .pipe(gulp.dest('dest/webapp/'));  
});  
  
// 圧縮したcssを読み込むようにして、キャッシュクリア対応  
gulp.task('convert-jsp', ['cachebustResources-css', 'cachebustResources-javascript'], function () {  
    return gulp.src('dest/webapp/WEB-INF/view/**/*.jsp')  
        .pipe(replace(/<link(.*)href=(["`]\/[^/].*)\.css/g, '<link$1href=$2.min.css'))  
        .pipe(replace(/<link(.*)href=(["`]\/[^/].*)\.min\.min\.css/g, '<link$1href=$2.min.css'))  
        .pipe(cachebust.references())  
        .pipe(gulp.dest('dest/webapp/WEB-INF/view/'));  
});  

これで、圧縮からキャッシュクリアまでを自動に行うことが可能となります。

また、弊社では、元のcss、圧縮後のcss、圧縮後のキャッシュクリア用のcssの3つが同時に存在した状態になっています。
特に問題もないですし、何かあった際の調査も全てが揃っていた方がやりやすいということからそうしています。
最終形のみでよいということであれば、不要ファイルを削除してしまってもいいと思います。

最後に

以上が、レガシーなフレームワークでcssの圧縮を自動化する方法になります。

javascriptに関しても、専用のnpmを使用し同じように組み込むことはできると思いますが、AngularでDIしている部分のコードが一部動かなくなることがわかっているため、今はストップしています。
こちらはAngularでのDIの書き方を直せば問題なく圧縮できるので、しばらくしたら導入しようと思っています。

尚、シンクロ・フードではエンジニアを大募集しています!
少しでもご興味があれば、気軽にお話しする場を設けますので、以下よりご連絡ください!

シンクロ・フード採用サイト