人生シーケンスブレイク

シーケンスブレイク(Sequence breaking、シークエンスブレイクとも)とは、テレビゲームにおいて開発が想定している攻略ルートを逸脱し、ショートカットする行為のことである。

gulp-sassで自動でSass/SCSSからcssファイルを生成したり、他にもいろいろする

gulp-sassで自動でSass/SCSSからcssファイルを生成する - 人生リアルタイムアタック の続き。完全上位互換のつもり。

前回は gulp-sass の導入だけだったので、この記事では gulp / Sass (SCSS) 周り全般について述べたい。

構成

以下のディレクトリ構成を例にする。

./test
├── css
│   └── // cssファイルの生成先
└── assets
│   └── sass
│      └── **/*.scss
└── index.html

Node.jsは LTSのv4.4.5 がインストール済みで、$ npmコマンドが利用可能になっている前提。

package.jsonをつくる

最初に、Node.jsのパッケージ管理ファイルであるpackage.jsonを生成する。 package.jsonはコマンドで生成できる。

$ npm init -y
Wrote to /Users/<user_name>/git/test/package.json:

{
  "name": "test",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "dependencies": {},
  "devDependencies": {},
  "scripts": { # 後でちょっと追加する予定
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC"
}

scripts に gulp default コマンドを追加する

package.jsonのscripts部分に "start": "gulp default", を追加する。詳細は後述。

  "scripts": {
    "start": "gulp default", // ここを追加
    "test": "echo \"Error: no test specified\" && exit 1"
  },

package.jsonはこうなればOK.

{
  "name": "test",
  "version": "1.0.0",
  "main": "index.js",
  "dependencies": {},
  "devDependencies": {},
  "scripts": {
    "start": "gulp default",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "description": ""
}

インストール

それではgulpとgulp-sassをインストールする。
-gオプションを付けずにプロジェクト配下にインストールすることで、複数人で開発する場合でもバージョンを揃えて利用するようにする。

先程package.json"start": "gulp default",を追加したのは、$ npm start コマンドでプロジェクト配下にインストールしたgulpからdefaultタスクを実行する為の設定。

なお、開発環境用のツールなので--save-devを必ず書こう。

$ npm install gulp gulp-sass --save-dev

インストール完了後、package.jsonを開いてみよう。

...
  "devDependencies": {
    "gulp": "^3.9.1",
    "gulp-sass": "^2.3.2"
  },
...

devDependenciesに、gulpとgulp-sassが追加されていればOK.

package.jsonとは

package.jsonとは、Node.jsのパッケージ管理ファイル。
bundleのように今後は $ npm install するだけで同じバージョンのパッケージをインストールすることが可能になる。

ただし、 --save-dev を付け忘れると、devDependenciesに追加されないので忘れずに付けよう。

gulpfile.jsの作成

インストールが完了したので、今度はgulp-sassを利用する為のタスクをgulpfile.jsに記述していく。
まずは最も簡単な例として、*.scssに変更があれば自動で*.cssを生成する例。

'use strict';

var SCSS_SRC = './assets/sass/**/*.scss';
var CSS_DEST = './css/';

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

// Sassコンパイルタスク
gulp.task('sass-compile', function(){
  return gulp.src(SCSS_SRC)
    .pipe(sass.sync().on('error', sass.logError))
    .pipe(gulp.dest(CSS_DEST));
});

// scssファイル群の変更を監視するタスク
gulp.task('scss:watch', function(){
  var watcher = gulp.watch(SCSS_SRC, ['sass-compile']);
  watcher.on('change', function(event) {
    console.log('File ' + event.path + ' was ' + event.type + ', running tasks...');
  });
});

// gulpのデフォルトタスクとしてscss:watchタスクを指定
gulp.task('default', ['scss:watch']);

sassとscssという単語が混在しているのはちょっと諦め気味...
なお、最近はgulp-plumberは不要。

試してみる。

ここまでで、既に自動でSCSSからcssファイルを生成できるので試してみる。
$ npm start を叩いて、ファイルを保存するだけで既に自動でcssファイルが生成可能になる。

$ npm start

> test@1.0.0 start /Users/<user_name>/git/test
> gulp default

[11:57:40] Using gulpfile ~/git/test/gulpfile.js
[11:57:40] Starting 'scss:watch'...
[11:57:40] Finished 'scss:watch' after 12 ms
[11:57:40] Starting 'default'...
[11:57:40] Finished 'default' after 9.47 μs

この状態で、assets/sass/hoge/fuge.scss にこんなSCSSを書いてみる。

* {
  font-size: 10rem;

  p {
    color: #ccc;
  }
}
File /Users/<user_name>/git/test/assets/sass/hoge/fuge.scss was changed, running tasks...
[12:01:58] Starting 'sass-compile'...
[12:01:58] Finished 'sass-compile' after 35 ms

こんな感じのログがでて、./css/hoge/fuge.cssが生成されれば完了。終了するにはctrl + c

だが、SCSS環境構築はまだ終わりではない。

Lintツールを併用する

Sass/SCSSを書くにあたり、Lintツールを導入しなければCSS同様すぐ煩雑なコードになってしまう。
Lintツール無しで作ったSCSSはCSSの大差無く万人がみても分かりづらい。最初からLintを入れてどのように書けばいいのか学びながら書いた方がよい。

gulpがあれば自動でチェックできるので、このタイミングでLintツールを導入しよう。

Ruby製の scss-lint はドキュメントが充実しており、オプションも豊富で、一時的な除外もできて使い勝手がよい。airbnbgithubに公開している有名なスタイルルールもこのscss-lint用だ。
ということで、scss-lintと、gulp上でscss-lintを実行する為の gulp-scss-lint の2つを導入する。

インストール

$ gem install scss_lint # -でなく_に注意。
$ npm install gulp-scss-lint --save-dev

gulpfile.jsにgulp-scss-lint設定を追加

先程つくったgulpfile.jsを以下のように3箇所書き換える。

var SCSS_SRC = './app/assets/stylesheets/**/*.scss';
var CSS_DEST = './css/';

var gulp = require('gulp');
var sass = require('gulp-sass');
var scsslint = require('gulp-scss-lint'); // 1. gulp-scss-lintを追加 ----

// Sassコンパイルタスク
gulp.task('sass', function(){
  ...(略)
});

// 2. 以下を追加する ----
// scss-lintタスク
gulp.task('scss-lint', function(){
  return gulp.src(SCSS_SRC)
    .pipe(scsslint());
});
// ココマデ ----

// scssファイル群の変更を監視するタスク
gulp.task('scss:watch', function(){
  // 3. ここに 'scss-lint' を追加する ------↓
  var watcher = gulp.watch(SCSS_SRC, ['scss-lint', 'sass-compile']);
  watcher.on('change', function(event) {
    console.log('File ' + event.path + ' was ' + event.type + ', running tasks...');
  });
});

// gulpのデフォルトタスクとしてscss:watchタスクを指定
gulp.task('default', ['scss:watch']);

scss:watchタスク内の配列にscss-lintタスクを追加したことで、sass-compile後にscss-lintを実行するようになった。
scss-lintタスクの書き方については幾つかあるが、上記のようにlintタスク単体で記述しておけばコンパイルせずにlintだけ単体実行することも可能になるので、独立したタスクにして配列で渡すとよい。

ちなみに.scss-lint.ymlを指定する場合にはこう書く。

// scss-lintタスク
gulp.task('scss-lint', function(){
  return gulp.src(SCSS_SRC)
    .pipe(scsslint({'config': '.scss-lint.yml'}));
});

Sass/SCSS書き換え時にブラウザ自動リロードしたい

Browsersync - Time-saving synchronised browser testing を利用する。

インストール

$ npm install browser-sync --save-dev

gulpfile.jsの書き換え

// HTML, Haml, JavaScriptなども監視したければこんな形で定義しておく
var HTML_SRC = './template/**/*.html';
var CSS_SRC  = './css/**/*.css';

// browser-sync 読み込み
var bs = require('browser-sync').create();

...(略)

// browser-syncタスク
gulp.task('bs', function(){
  var bsOptions = {};
  bsOptions.files = [HTML_SRC, CSS_SRC]; // ここに監視対象ファイルを書く
  bsOptions.server = './'; // 単体実行する時
  // bsOptions.proxy = 'localhost:3000'; // proxy実行する時のターゲット
  // bsOptions.proxy = '192.168.99.100:3000'; // docker用
  // bsOptions.port  = 3001; // proxy実行する時のport
  bs.init(bsOptions);
});

gulp.task('default', ['bs', 'sass-watch']); // 'bs' タスクを追加

試してみる

$ npm start

> test@1.0.0 start /Users/<user_name>/git/test
> gulp default

[16:08:24] Using gulpfile ~/git/test/gulpfile.js
[16:08:24] Starting 'bs'...
[16:08:24] Finished 'bs' after 16 ms
[16:08:24] Starting 'scss:watch'...
[16:08:24] Finished 'scss:watch' after 14 ms
[16:08:24] Starting 'default'...
[16:08:24] Finished 'default' after 13 μs
[BS] Access URLs:
 ----------------------------------------
       Local: http://localhost:3000
    External: http://xxx.xxx.xxx.xxx:3000
 ----------------------------------------
          UI: http://localhost:3001
 UI External: http://xxx.xxx.xxx.xxx:3001
 ----------------------------------------
[BS] Serving files from: ./
[BS] Watching files...

ブラウザで http://localhost:3000 index.html が開き、htmlやcssに変更があれば自動で再読み込みされるようになる。

Railsなどで開発している場合には、serverプロパティをコメントアウトした上で、コメントアウトしている bsOptions.proxy などの設定を有効化すれば利用可能。

オマケ1. gulp default以外もscriptsに登録する

gulpfile.jsにgulp defalutを追加した要領で、他のタスクも追加できる。

  "scripts": {
    "start": "gulp default",
    "scss-lint": "gulp scss-lint",
    "test": "echo \"Error: no test specified\" && exit 1"
  }

上記のように記述すると、$ npm run-script scss-lint で scss-lintタスクが実行可能。

start, test など、一部のコマンド以外は run-script を含めたコマンドになる、$ npm runで確認しよう。

$ npm run
Lifecycle scripts included in test-project:
  start
    gulp default

available via `npm run-script`:
  scss-lint
    gulp scss-lint

オマケ2. gulpのタスクを都度package.jsonに書くのはダルい

  "scripts": {
    "start": "gulp default",
    "gulp": "gulp",
    "test": "echo \"Error: no test specified\" && exit 1"
  }

とすると、$ npm run-script gulp <task_name> で実行できる。
しかし開発環境として配布する場合には、メンバー間の余計なコストが掛からないようscriptsに幾つか纏めておいておいた方が良さ気。

オマケ3. ここでは語らないもの

ということで、大体こんな感じのpackage.jsonとgulpfile.jsを現在は利用している。

package.json

{
  "name": "test",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "start": "gulp default",
    "gulp": "gulp",
    "scss-lint": "gulp scss-lint",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "devDependencies": {
    "browser-sync": "^2.13.0",
    "gulp": "^3.9.1",
    "gulp-sass": "^2.3.2",
    "gulp-scss-lint": "^0.4.0"
  }
}

gulpfile.js

'use strict';

var HTML_SRC = './template/**/*.html';
var CSS_SRC  = './css/**/*.css';
var SCSS_SRC = './assets/sass/**/*.scss';
var CSS_DEST = './css/';

var gulp = require('gulp');
var sass = require('gulp-sass');
var scss_lint = require('gulp-scss-lint');
var bs = require('browser-sync').create();

// browser-syncタスク
gulp.task('bs', function(){
  var bsOptions = {};
  bsOptions.files = [HTML_SRC, CSS_SRC]; // ここに監視対象ファイルを書く
  bsOptions.server = './'; // 単体実行する時
  // bsOptions.proxy = 'localhost:3001'; // proxy実行する時
  // bsOptions.port  = 3001; //
  bs.init(bsOptions);
});

// Sassコンパイルタスク
gulp.task('sass-compile', function(){
  return gulp.src(SCSS_SRC)
    .pipe(sass.sync().on('error', sass.logError))
    .pipe(gulp.dest(CSS_DEST));
});

// scss-lintタスク
gulp.task('scss-lint', function(){
  return gulp.src(SCSS_SRC)
    .pipe(scss_lint());
});

// scssファイル群の変更を監視するタスク
gulp.task('scss:watch', function(){
  var watcher = gulp.watch(SCSS_SRC, ['scss-lint', 'sass-compile']);
  watcher.on('change', function(event) {
    console.log('File ' + event.path + ' was ' + event.type + ', running tasks...');
  });
});

// gulpのデフォルトタスクとしてscss:watchタスクを指定
gulp.task('default', ['bs', 'scss:watch']);

もうちょっと何かあったら追記する。