これからはじめるGulp #19:gulp-sketchとgulp-execを使ったSketch 3デザインデータの画像書き出し

2014.12.19

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

はじめに

前回のこれからはじめるGulp(18):SketchToolで何ができるのかコマンドと主要なオプションを使ってみるでSketchToolの各コマンドとオプションを試して、Gulp.jsと連携するための下調べをしました。今回はgulp-sketchプラグインを使ったGulp.jsとSketchToolとの連携に加え、SketchToolで書き出す画像をページ毎に別々のフォルダに整理できるようにいくつかの方法を試してみたいと思います。

gulp-sketchプラグインを使ってみる

まずはgulp-sketchプラグインについてできること・できないことを確認しながらスライスの書き出しとgulp-imageminプラグインを使った画像の軽量化を行うタスクを作ってみます。

gulp-sketchはSketchToolをGulp.jsで使うためのプラグインです。一部のオプションには対応していませんが、SketchToolからエクスポートされる画像をStreamにのせることができます。パイプラインでそのままgulp-imagemin等の圧縮処理が行えgulp.dest()で出力先を指定できます。

対応しているSketchToolオプション

gulp-sketchが対応しているSketchToolオプションは以下の通りです。SketchToolのオプションはこちらを参考にしてください。

  • formats
  • scales
  • items
  • bounds
  • save-for-web
  • compact
  • trimmed

gulp-sketchプラグインのインストール

gulp-sketchプラグインをインストールします。

$ sudo npm install gulp-sketch --save-dev
Password:
gulp-sketch@0.0.6 node_modules/gulp-sketch
├── clean-sketch@1.0.1
├── temporary@0.0.8 (package@1.0.1)
└── through2@0.6.3 (xtend@4.0.0, readable-stream@1.0.33)

サンプルデザインファイル

以下のようなサンプルを用意しました。前回説明したとおり、SketchToolからエクスポートした画像のファイル名はレイヤー名そのままで出力されます。

example-sketch

sketchタスクを作る

gulp-sketchプラグインのインストールが完了したらGulpタスクを作ります。gulp-sketchとgulp-imageminの他にStream上で処理されているファイルを把握するためにgulp-filelogというプラグインを使います。

var gulp        = require('gulp');
var sketch      = require("gulp-sketch");
var imagemin    = require('gulp-imagemin');
var filelog     = require('gulp-filelog');

var paths = {
  srcDir : 'src/assets/_design',
  dstDir : 'src/assets/_design/exports',
}

gulp.task( 'sketchExport:slices', function(){
  var srcGlob    = paths.srcDir + '/example.sketch';
  var dstGlob    = paths.dstDir;

  var sketchOptions = {
    export     : 'slices'
  };

  var imageminOptions = {
    optimizationLevel: 7
  };

  return gulp.src( srcGlob )
    .pipe(sketch( sketchOptions ))
    .pipe(imagemin( imageminOptions ))
    .pipe(gulp.dest( dstGlob ))
    .pipe(filelog());
});

gulp.task( 'sketch', ['sketchExport:slices'] );

sketchタスクを試す

sketchタスクを試してみましょう。

$ gulp sketch
[02:18:19] Using gulpfile ~/Projects/gulp.whiskers.nukos.kitchen/gulpfile.js
[02:18:19] Starting 'sketch-export:slices'...
[02:18:19] [1] [/Users/nukos/Projects/gulp.whiskers.nukos.kitchen/src/assets/_design/exports/detail-mb-button_05x.png]
[02:18:19] [2] [/Users/nukos/Projects/gulp.whiskers.nukos.kitchen/src/assets/_design/exports/home-mb-button_05x.png]
[02:18:19] [3] [/Users/nukos/Projects/gulp.whiskers.nukos.kitchen/src/assets/_design/exports/detail-tablet-button_05x.png]
[02:18:19] [4] [/Users/nukos/Projects/gulp.whiskers.nukos.kitchen/src/assets/_design/exports/home-tablet-button_05x.png]
[02:18:19] [5] [/Users/nukos/Projects/gulp.whiskers.nukos.kitchen/src/assets/_design/exports/home-mb-button_1x.png]
[02:18:19] [6] [/Users/nukos/Projects/gulp.whiskers.nukos.kitchen/src/assets/_design/exports/detail-mb-button_1x.png]
[02:18:20] [7] [/Users/nukos/Projects/gulp.whiskers.nukos.kitchen/src/assets/_design/exports/detail-tablet-button_1x.png]
[02:18:20] gulp-imagemin: Minified 8 images (saved 11.44 kB - 46.3%)
[02:18:20] [8] [/Users/nukos/Projects/gulp.whiskers.nukos.kitchen/src/assets/_design/exports/home-tablet-button_1x.png]
[02:18:20] Found [8] files.
[02:18:20] Finished 'sketch-export:slices' after 1.01 s
[02:18:20] Starting 'sketch'...
[02:18:20] Finished 'sketch' after 7.3 μs

8つのスライスが書き出され、gulp-imageminで46.3%軽量化されました。 単純なデザインファイルであればgulp-sketchは使い勝手が良さそうです。

ページ毎に書き出し先のディレクトリを分けたい

ページやアートボード毎に出力先ディレクトリを分けたいと思いますよね。ですがSketchToolにその機能はなくコードで制御できません。代わりにSketch側でレイヤー名を/で区切ることによりサブディレクトリへの書き出しが可能になります。この仕様はSketchTool側も対応しています。

例えば下記のようにスライス対象のレイヤー名をhome/mb/buttonのようにすることでページ/アートボード/画像のようなディレクトリ構造を持たせた画像の書き出しが行えます。

layer-name

このようにレイヤー名にディレクトリ構造を定義してエクスポートすると下記のようにサブディレクトリ作られ画像のファイルも末端の名前だけが付きます。

_design/exports
├── detail
│   ├── mb
│   │   ├── button_05x.png
│   │   └── button_1x.png
│   └── tablet
│       ├── button_05x.png
│       └── button_1x.png
└── home
      ├── button_05x.png
      ├── button_1x.png
      ├── mb
      │   ├── button_05x.png
      │   └── button_1x.png
      └── tablet
         ├── button_05x.png
         └── button_1x.png

とても便利な機能でエクスポート後の画像管理が一気に楽になります。 ただ、残念なことにgulp-sketchではこのサブディレクトリへの書き出しに対応していないためfsモジュールでエラーが起こり利用できません。

fs.js:487
  var r = binding.read(fd, buffer, offset, length, position);
                  ^
Error: EISDIR, illegal operation on a directory
    at Object.fs.readSync (fs.js:487:19)
    at Object.fs.readFileSync (fs.js:321:28)
    at Socket.<anonymous> (/Users/nukos/Projects/test.io/vccw/www/wordpress/wp-content/themes/test.io/node_modules/gulp-sketch/index.js:78:20)
    at Socket.emit (events.js:117:20)
    at _stream_readable.js:943:16
    at process._tickCallback (node.js:419:13)

これは仕方ないこととも思えます。というのも本来出力先の制御はGulp.js側で行うべきだからです。gulp-sketchを使う場合はGulp.jsの利点を活かし画像の名前をもとにファイルを整理するのも良いかもしれません。

方法1:ファイル名で振り分ける

例えば以下のようにプレフィックスを基準にページ・アートボード毎にディレクトリを定義しファイルをコピーするタスクを用意します。

var gulp     = require('gulp');
var sketch   = require('gulp-sketch');
var imagemin = require('gulp-imagemin');
var filelog  = require('gulp-filelog');

var paths = {
  srcDir : '_design',
  dstDir : '_design/exports',
  imgDir : 'assets/images/_src'
}

gulp.task( 'sketch:copy', ['sketch:slices'], function(){
  var srcGlob     = paths.dstDir;
  var dstGlob     = paths.imgDir;
  var splitter    = '_';
  var pagePrefixs = {
    'home'   : ['mb', 'tablet'],
    'detail' : ['mb', 'tablet']
  };

  for(var page in pagePrefixs) {
    pagePrefixs[page].forEach(function( artboard ){
      gulp.src( srcGlob + '/' + page + splitter + artboard + splitter + '*' )
        .pipe(gulp.dest( dstGlob + '/' + page + '/' + artboard ))
        .pipe(filelog());
    });
  }
});

こうすることで画像のファイル名にプレフィックスは残ってしまいますがgulp-sketchを使っていてもページ・アートボードでディレクトリを分けることができます。

_design/exports
├── detail
│   ├── mb
│   │   ├── detail_mb_button_05x.png
│   │   └── detail_mb_button_1x.png
│   └── tablet
│       ├── detail_tablet_button_05x.png
│       └── detail_tablet_button_1x.png
└── home
      ├── mb
      │   ├── home_mb_button_05x.png
      │   └── home_mb_button_1x.png
      └── tablet
         ├── home_tablet_button_05x.png
         └── home_tablet_button_1x.png

方法2:–outputJSONオプションを活用して振り分ける

--outputJSONオプションを使って出力したjsonファイルはページとスライスの情報を含んでいます。このjsonファイルにはアートボードの情報がないのでjsonの情報だけをベースに振り分けられるのはページ単位のみです。jsonを使うメリットは振り分けるためのページ名をjsonから取得できるので大量のページを持ったSketchファイルなどの場合に都度ページ名を変更せずに済みます。

{
    "home" : {
      "home_tablet_button" : {
        "home_tablet_button_05x.png" : {
          "format" : "png",
          "height" : 129,
          "x" : 3421,
          "y" : 973,
          "width" : 533,
          "name" : "home_tablet_button_05x",
          "scale" : 0.5
        },
        "home_tablet_button_1x.png" : {
          "format" : "png",
          "height" : 129,
          "x" : 3421,
          "y" : 973,
          "width" : 533,
          "name" : "home_tablet_button_1x",
          "scale" : 1
        }
      }
    }
  }
}

エクスポートとコピータスク

タスクにはSketchToolとgulp-execプラグインを使ったエクスポート用タスクとjsonを元にディレクトリを分けるコピータスクを作ります。jsonファイルはfsモジュールを使ってコピータスクに読み込みます。

var gulp     = require('gulp');
var fs       = require('fs');
var exec     = require('gulp-exec');
var filelog  = require('gulp-filelog');

var paths = {
  srcDir : '_design',
  dstDir : '_design/exports'
}

//exports task
gulp.task( 'sketch:exports', function(){
  var srcGlob = paths.srcDir + '/example.sketch';
  var dstGlob = paths.dstDir;

  var exportOptions = {
    dstDir   : dstGlob,
    jsonFile : '/slices.json'
  };
  var sketchtool = 'sketchtool export slices <%= file.path %> --output=<%= options.dstDir %> --outputJSON=<%= options.dstDir %><%= options.jsonFile %>';

  return gulp.src( srcGlob )
    .pipe(exec( sketchtool, exportOptions ))
    .pipe(exec.reporter());
});

//copy task
gulp.task( 'sketch:copy', ['sketch:exports'], function(){
  var sketchFile = 'example.sketch';
  var srcGlob    = paths.dstDir;
  var dstGlob    = paths.dstDir;
  var jsonPath   = './' + paths.dstDir + '/slices.json';
  var exportJson = JSON.parse(fs.readFileSync( jsonPath, 'utf8' ));
  var pages      = exportJson[sketchFile];

  for(var page in pages) {
    for (var slice in pages[page]){
      for (var item in pages[page][slice]){
        gulp.src( srcGlob + '/' + pages[page][slice][item].name + '.' + pages[page][slice][item].format )
          .pipe(gulp.dest( dstGlob + '/' + page ))
          .pipe(filelog());
      }
    }
  }
});

実際にタスクを実行するとこのように処理されcopyタスクでStreamを使った処理が行えます。

gulp sketch:copy
[17:07:12] Using gulpfile ~/Projects/marke.io/vccw/www/wordpress/wp-content/themes/marke.io/gulpfile.js
[17:07:12] Starting 'sketch:exports'...
[17:07:13] Exported home_mb_button_1x.png
Exported home_mb_button_05x.png
Exported home_tablet_button_1x.png
Exported home_tablet_button_05x.png
Exported detail_mb_button_1x.png
Exported detail_mb_button_05x.png
Exported detail_tablet_button_1x.png
Exported detail_tablet_button_05x.png
[17:07:13] Finished 'sketch:exports' after 163 ms
[17:07:13] Starting 'sketch:copy'...
[17:07:13] Finished 'sketch:copy' after 15 ms
[17:07:13] [1] [/Users/nukos/Projects/marke.io/vccw/www/wordpress/wp-content/themes/test.io/_design/exports/detail/detail_mb_button_1x.png]
[17:07:13] Found [1] files.
[17:07:13] [1] [/Users/nukos/Projects/marke.io/vccw/www/wordpress/wp-content/themes/test.io/_design/exports/detail/detail_mb_button_05x.png]
[17:07:13] Found [1] files.
[17:07:13] [1] [/Users/nukos/Projects/marke.io/vccw/www/wordpress/wp-content/themes/test.io/_design/exports/home/home_mb_button_1x.png]
[17:07:13] Found [1] files.
[17:07:13] [1] [/Users/nukos/Projects/marke.io/vccw/www/wordpress/wp-content/themes/test.io/_design/exports/detail/detail_tablet_button_05x.png]
[17:07:13] Found [1] files.
[17:07:13] [1] [/Users/nukos/Projects/marke.io/vccw/www/wordpress/wp-content/themes/test.io/_design/exports/detail/detail_tablet_button_1x.png]
[17:07:13] Found [1] files.
[17:07:13] [1] [/Users/nukos/Projects/marke.io/vccw/www/wordpress/wp-content/themes/test.io/_design/exports/home/home_tablet_button_05x.png]
[17:07:13] Found [1] files.
[17:07:13] [1] [/Users/nukos/Projects/marke.io/vccw/www/wordpress/wp-content/themes/test.io/_design/exports/home/home_mb_button_05x.png]
[17:07:13] Found [1] files.
[17:07:13] [1] [/Users/nukos/Projects/marke.io/vccw/www/wordpress/wp-content/themes/test.io/_design/exports/home/home_tablet_button_1x.png]
[17:07:13] Found [1] files.

--outputJSONで書き出すjsonはjekyll等のテンプレートエンジンで活用することで、デザインデータを編集してGulpタスクを実行するだけ静的なサイトを更新したりできそうですね。

方法3:Sketchのレイヤー名で振り分ける

最終的にこの方法が一番無駄がなくファイル名もシンプルです。デザインデータのスライス名をhome/mb/buttonのように書き換えます。ちなみにサブディレクトリに書き出せるのはスライスだけではなくアートボードでも可能です。

タスク

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

var paths = {
  srcDir : '_design',
  dstDir : '_design/exports',
}

gulp.task( 'sketch:exports', function(){
  var srcGlob = paths.srcDir + '/example.sketch';
  var dstGlob = paths.dstDir;

  var exportOptions = {
    dstDir   : dstGlob,
    jsonFile : '/slices.json'
  };
  var sketchtool = 'sketchtool export slices <%= file.path %> --output=<%= options.dstDir %> --outputJSON=<%= options.dstDir %><%= options.jsonFile %>';

  return gulp.src( srcGlob )
    .pipe(exec( sketchtool, exportOptions ))
    .pipe(exec.reporter());
});

エクスポートされた画像

_design/exports
├── detail
│   ├── mb
│   │   ├── button_05x.png
│   │   └── button_1x.png
│   └── tablet
│       ├── button_05x.png
│       └── button_1x.png
├── home
│   ├── mb
│   │   ├── button_05x.png
│   │   └── button_1x.png
│   └── tablet
│       ├── button_05x.png
│       └── button_1x.png
└── slices.json

これで望ましいかたちで画像を書き出すことができました。あとは圧縮用のタスク等を用意してあげればデザインデータからのスライス書き出しを自動化できます。欲を言うとSketchToolのexportコマンドでページ・アートボードを制限するオプションがあると便利ですね。あとはgulp-sketchもレイヤー名のサブディレクトリ指定を上手く扱ってくれればなぁといったところですね。

以上、SketchToolを使ったスライスの書き出しとディレクトリの振り分けでした。

この記事はこれからはじめるGulp(19):gulp-sketchとgulp-execを使ったSketch 3デザインデータの画像書き出しの転載です。