knowledge base

マークアップ/フロントエンドエンジニアのWEB制作における備忘録です。平日はWEB屋、休日は社会人劇団の主宰・劇作家をしています。

gulpで階層構造を維持して出力する

以下のようなディレクトリ構造になるようgulpを用いてSassのコンパイルを行います。

devディレクトリがSassファイルを格納する開発用、destディレクトリがコンパイル後のCSSファイルを格納する納品用ディレクトリです。

root
 │
 ├─ dev
 │   ├─ scss
 │   │   ├─ top
 │   │   │   └─ top.scss
 │   │   └─ products   
 │   │       └─ products.scss
 │   │
 │   └─ gulpfile.js
 │
 └─ dest
     ├─ top└─ top.css
     └─ products   
         └─ products.css

Sassのコンパイルをするタスクとして、以下のようなサンプルがオーソドックスな例としてよく紹介されています。

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

gulp.task('sass', function(){
  gulp.src('./scss/**/**/*.scss')
      .pipe(sass())
      .pipe(gulp.dest('../dest'));
});

しかしこの場合、出力結果は以下のようになってしまいます。

root
 │
 ├─ dev
 │   ├─ scss
 │   │   ├─ top
 │   │   │   └─ top.scss
 │   │   └─ products   
 │   │       └─ products.scss
 │   │
 │   └─ gulpfile.js
 │
 └─ dest
     ├─ top.css
     └─ products.css

destメソッドで指定されたディレクトリ内に、並列で出力されてしまいました。 そもそもgulpでの出力は、複数のファイルを1枚のファイルにビルド・出力することを前提としており、そのためデフォルトでは指定されたディレクトリ内のファイルをすべて並列したものとして扱うよう設計されています。そのため上記のような結果になってしまったようです。

しかしgulpには柔軟なAPIがいくつか提供されており、その中に階層構造を維持したまま出力するオプションが用意されています。

github.com

第二引数として渡すオプションのうち、baseプロパティに対象とするディレクトリを指定することで、その階層構造を維持したままファイルをコピーすることが可能になります。

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

gulp.task('sass', function(){
  gulp.src('./scss/**/**/*.scss', { base: './scss' })
      .pipe(sass())
      .pipe(gulp.dest('../dest'));
});

こうすることで、階層構造を保ったまま出力することができます。

root
 │
 ├─ dev
 │   ├─ scss
 │   │   ├─ top
 │   │   │   └─ top.scss
 │   │   └─ products   
 │   │       └─ products.scss
 │   │
 │   └─ gulpfile.js
 │
 └─ dest
     ├─ top
     │   └─ top.css
     └─ products   
         └─ products.css

srcメソッドのオプションには他にもファイルを読み込まずにnullを返すreadプロパティや、巨大なファイルをストリームとして返すbufferプロパティなどがあるので、案件特性によって使ってみるのも良いでしょう。

出力した階層構造を制御する

しかし今後topやprodustsディレクトリにCSSだけでなくHTMLやJS、画像も格納することになった際、このままでは管理しにくくなってしまいます。

以下のように、出力先ディレクトリに新たにcssディレクトリを新設し、その中にファイルを格納するにはどうしたらよいでしょうか。

root
 │
 ├─ dev
 │   ├─ scss
 │   │   ├─ top
 │   │   │   └─ top.scss
 │   │   └─ products   
 │   │       └─ products.scss
 │   │
 │   └─ gulpfile.js
 │
 └─ dest
     ├─ top
     │   └─ css
     │       └─ top.scss
     └─ products   
         └─ css
             └─ products.css

このようなケースには出力先のファイル名などをリネームするgulp-renameモジュールを利用します。

www.npmjs.com

上記のUsageに記載の通り、このモジュールは非常に使い勝手がよく、ファイル名だけでなく拡張子やディレクトリ名まで変更することが可能です。 よく用いられる例としては圧縮したファイルに「min」を追加するようなケースがあります。 ちなみに以下のように、コールバック関数内にてファイル名を含むフルパスを取得できます。

rename(function (path) {
  console.log(path.dirname);  // ファイル名を除いたディレクトリ名
  console.log(path.basename); // 拡張子を除いたファイル名
  console.log(path.extname);  // 拡張子
})

このモジュールを次のようにパイプすることで、狙い通りのディレクトリに出力することが可能です。

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

gulp.task('sass', function(){
  gulp.src('./scss/**/**/*.scss', { base: './scss' })
      .pipe(sass())
      .pipe(rename(function (path) {
        path.dirname += '/css';
      }))
      .pipe(gulp.dest('../'));
});

こうすることで、出力先にcssディレクトリがなければ作成し、あればその中にファイルが格納されます。

重複になってしまいますが、出力先は以下のようになります。

root
 │
 ├─ dev
 │   ├─ scss
 │   │   ├─ top
 │   │   │   └─ top.scss
 │   │   └─ products   
 │   │       └─ products.scss
 │   │
 │   └─ gulpfile.js
 │
 └─ dest
     ├─ top
     │   └─ css
     │       └─ top.scss
     └─ products   
         └─ css
             └─ products.css

他にも様々なオプションやAPIが提供されているので、用途に合わせて使い分けることで、非常に柔軟な対応が可能になります。