blog

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

    • Ryuichi Nonaka
    この記事は書かれてから1年以上経過しており、内容が古い場合があります。

    はじめに

    この記事はGulp.js(全俺) Advent Calendar 2014Sketch 3 Advent Calendar 2014の19日目です。前日のSketch 3 Advent Calendar 2014は@soutaroさんのヒラギノの縦位置を揃えるちょうべんりなSketchプラグインの紹介でした。2つのAdvent Calendarを掛け持ちしている記事でややこしくなってごめんなさい。Gulp.js(全俺) Advent Calendar 2014の方はnodebrewを使ったGulp.js環境の構築や使い方について一から紹介しているので興味があれば1日目から読んでみてください。

    前回のGulp.js(全俺) Advent Calendar 2014ではこれからはじめる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からエクスポートした画像のファイル名はレイヤー名そのままで出力されます。

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

    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のようにすることでページ/アートボード/画像のようなディレクトリ構造を持たせた画像の書き出しが行えます。

    レイヤー名

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

    _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を使ったスライスの書き出しとディレクトリの振り分けでした。 Sketch 3 Advent Calendar 2014の20日目は@kgsiさんです。よろしくお願いします!

    シリーズ

    1. これからはじめるGulp(0):アドベントカレンダースケジュール
    2. これからはじめるGulp(1):bundler, rbenv, nodebrewでGulp環境を作ってみる
    3. これからはじめるGulp(2):gulp-sassを使ってSCSSをコンパイルするタスクを作ってみる
    4. これからはじめるGulp(3):gulp.watchでファイルの変更を監視しタスクを実行する
    5. これからはじめるGulp(4):gulp-connectモジュールを使ったローカルサーバの起動
    6. これからはじめるGulp(5):gulp-connectモジュールを使ったLiveReload
    7. これからはじめるGulp(6):gulp-plumberとgulp-notifyを使ったデスクトップ通知
    8. これからはじめるGulp(7):require-dirモジュールを使ったタスク単位のファイル分割
    9. これからはじめるGulp(8):delモジュールとvinyl-pathsモジュールを使ったファイルの削除
    10. これからはじめるGulp(9):Ruby版Sassが使えるgulp-ruby-sassへの乗り換え
    11. これからはじめるGulp(10):deprecatedになっていたgulp-connectからgulp-webserverへ乗り換える
    12. これからはじめるGulp(11):ブラウザ間でスクロールやクリック操作を同期できるBrowserSync
    13. これからはじめるGulp(12):gulp-imageminプラグインを使ったJPEG,PNG,GIF,SVGの最適化
    14. これからはじめるGulp(13):gulp-changedプラグインで変更されたファイルだけを処理させる
    15. これからはじめるGulp(14):gulp-cachedプラグインで変更されたSCSSファイルだけを処理させる
    16. これからはじめるGulp(15):gulp-responsiveプラグインを使ったレスポンシブイメージ作成の自動化
    17. これからはじめるGulp(16):gulp-image-resizeプラグインを使ってサムネイルやレスポンシブイメージを作る
    18. これからはじめるGulp(17):SketchTool(Sketch 3 command line tool)を使ったアートボード・スライスの書き出しとgulp-execプラグインを使ったSketchtoolの呼び出し
    19. これからはじめるGulp(18):SketchToolで何ができるのかコマンドと主要なオプションを使ってみる
    20. これからはじめるGulp(19):gulp-sketchとgulp-execを使ったSketch 3デザインデータの画像書き出し
    21. これからはじめるGulp(20):Node.jsのChild Processモジュールを使ってgulpからjekyllのbuildコマンドを実行する
    22. これからはじめるGulp(21):gulp-awspublishプラグインを使ったAmazon S3への静的Webサイトパブリッシュ
    23. これからはじめるGulp(22):gulp-iconfontとgulp-sketchを使ったアイコンフォント作成の自動化
    24. これからはじめるGulp(23):gulp-consolidateでgulp-iconfontで作ったアイコンフォントのシンボル一覧HTMLを作る
    25. これからはじめるGulp(24):gulp.spritesmithプラグインでSpriteイメージを作る
    26. これからはじめるGulp(25):Hologramとgulp-hologramでスタイルガイドを作る
    27. これからはじめるGulp(26):Sketch3のサブディレクトリ書き出しに対応したgulp-sketchを試す

    コメント・フィードバック