コーディング環境 (2014.08)

日々進化している最近のコーディング (フロントエンド開発) 環境について、
ここ1年くらいのノウハウを詰めて、
極端には新しいもの好きではない視点で整理します。

目次:
1. psdからのスライス
2. Compass
3. スプライトイメージの自動化
4. CoffeeScript
5. Gruntによる自動化
6. ローカルサーバー
7. その他

1. psdからのスライス

slicy

Slicyというアプリを使い始めてからは、ずっとこれです。
Photoshopのレイヤーやグループ名に画像のファイル名(+拡張子)を付けることで、
ワンクリックで画像を書き出せます。

ファイル名に@2xと付けることで、Retina用画像(@2x)と、
等倍画像を同時に書き出すこともできます。

その他に@boundsというレイヤーをつくって矩形領域を指定したり、
@slicesというフォルダをつくって、その中にファイル名と
領域指定用のレイヤーをつくることも出来ます。

公式サイトのヘルプを読んだり、
サンプルをダウンロードすると理解が早いです。

 

2. Compass

CSSにネスト構造をつくって記述できるフレームワーク。
インストールの方法などは公式ドキュメント参照。
このエントリーでは、”gem install compass –pre” でインストール出来る、
Compass 1.0.0.rc.1を使って、SCSSの文法で書いています。

必要なコマンドは
compass watch

 

3. スプライトイメージの自動化

Compassのmixinを使えば、関数や変数を使ってCSSを記述できます。
一番の恩恵として、スプライトイメージの生成の自動化について、
色々試行錯誤した結果、現時点で使っているmixinを紹介します。
参考: Using Compass Generated Sprite Sheets in Responsive Sass, by Jayson Jacobs.

・サーバーにアップするファイルはassetsフォルダにまとめる
・サーバーにアップしないファイルはsourceフォルダにまとめる
・Source Mapを有効にするとChromeでscssファイルのデバッグができる
参考: Working with CSS Preprocessors – Google Chrome
・ロゴ用の画像として、
/resource/images/sprite/logo.png
/resource/images/sprite_2x/logo_2x.png (2倍サイズ)
の2枚が用意されているとする。

config.rb

css_dir = "assets/css"
sass_dir = "source/sass"
images_dir = "source/images"
generated_images_dir = "assets/images"
javascripts_dir = "assets/js"
fonts_dir = "assets/fonts"
output_style = :nested
relative_assets = true
line_comments = true
preferred_syntax = :scss
sass_options = { :sourcemap => true }

HTML

<div class="logo">
<div class="sprite">Logo</div>
</div>

→ .sprite部分に、background-imageが適用される

scssファイル

// スプライト画像
.sprite {
display: block;
overflow: hidden;
background-repeat: no-repeat;
text-align: left;
text-indent: -10000px;
font-size: 1px;
line-height: 1px;
}

@mixin sprite-background($sprites, $sprites-img, $name, $scale, $skip-base-params:false) {
background-image: $sprites-img;
@include background-size(image-width(sprite-path($sprites)) / $scale auto);

//retina時に重複するパラメータをスキップするための分岐
@if not($skip-base-params){
height: image-height(sprite-file($sprites, $name)) / $scale;
width: image-width(sprite-file($sprites, $name)) / $scale;
$ypos: nth(sprite-position($sprites, $name), 2) / $scale;
background-position: 0 $ypos;
}
}

$sprites: sprite-map("sprite/*.png", $spacing: 2px);
$sprites-img: sprite-url($sprites);
$sprites_2x: sprite-map("sprite_2x/*.png", $spacing: 4px);
$sprites-img_2x: sprite-url($sprites_2x);

@mixin sprite-image ($name) {
.sprite {
@include sprite-background($sprites, $sprites-img, $name, 1);
}
.backgroundsize.retina_2x & {
.sprite {
@include sprite-background($sprites_2x, $sprites-img_2x, $name, 2, true);
}
}
}

→ /assets/images/ フォルダ内にスプライトイメージが書き出される。

 

また、Slicyからの作業の効率化を考えて、一つ、自作のRubyのスクリプトを使っています。
ruby mv_sprite.rb
で実行すると、実行したディレクトリ以下の@2xという名前がついた画像を、
_2xにリネームして、さらに_2x付きのディレクトリを作ってその中へ移動します。
@がついたファイル名が、Compassではコンパイルエラーを起こすことへの対処です。
(Compass 0.13.alpha.12時点)
上記mixinも、Slicy書き出し→このRubyの処理、が前提になっています。

例:
assets/images/sprite/logo.png
assets/images/sprite/logo@2x.png

assets/images/sprite/logo.png
assets/images/sprite_2x/logo_2x.png

mv_sprite.rb

# ディレクトリを再帰的に抽出する
require 'pp'
require 'FileUtils'

root = File.expand_path(File.dirname(__FILE__))
root = ARGV[0] if ARGV[0]

# フォルダの一覧を取得
dir_entries = Dir.glob(root + "/" + "**/*")
# pp dir_entries

for filePath in dir_entries do
# pp file
ext = File::extname(filePath) # 拡張子取得

# 画像のみに処理
if( ext == '.jpg' || ext == '.png' || ext == '.gif' )
dir = File::dirname(filePath) # ディレクトリ
base = File::basename(filePath,ext) # 拡張子除去
twox_index = base.index('@2x')

# @2xを含む
if( twox_index && twox_index >= 0 )
p "#{dir}/#{base}#{ext}"
parent_dir = File::dirname(dir) # e.g. path_to_htdocs/common/img
my_dir = File::basename(dir) # e.g. sprite
twox_my_dir = "#{my_dir}_2x" # e.g. sprite_2x
twox_dir = "#{parent_dir}/#{twox_my_dir}" # e.g. path_to_htdocs/common/img/sprite_2x
twox_base_ext = "#{base.sub('@2x', '_2x')}#{ext}" # e.g. hoge_2x.png

# ディレクトリがなければ作る
FileUtils.mkdir_p(twox_dir) unless FileTest.exist?(twox_dir)

# ファイルを移動する
File.rename filePath, "#{twox_dir}/#{twox_base_ext}"

p "#{twox_dir}/#{twox_base_ext}"
p '--'
end
end
end

 

4. CoffeeScript

JavaScripterの間ではすっかり浸透したCoffeeScript を使います。
コードの行数、文字数が減らせるので、その分コメントを充実させられます。

必要なコマンドは
coffee -o assets/js -cw assets/coffee

 

ここまでの内容をまとめたものは、
https://github.com/soohei/compass-coffee-template
にあります。

 

5. Gruntによる自動化

注) 最新の状況はこちら

Gruntはこれまでに書いた、CompassやCoffeeScriptのコンパイル処理、
ソースの結合やMinify、ファイルの監視、簡易サーバーの起動などを
全て一つのコマンドで実行してしまう脅威の仕組みです。
他にGulpというのがあるようなのですが、まだ試していないです..
セットアップ方法は省略します。普段使っている基本的な設定・プラグインの構成は以下。

package.json (使用しているパッケージの一覧)

{
"name": "sample",
"version": "0.0.0",
"author": "Sohei Kitada",
"devDependencies": {
"grunt": "~0.4.5",
// ベンダープレフィックス補完
"grunt-autoprefixer": "~1.0.0",
// メディアクエリーをまとめる
"grunt-combine-media-queries": "~1.0.19",
// CoffeeScript
"grunt-contrib-coffee": "~0.11.0",
// Compass
"grunt-contrib-compass": "~0.9.1",
// ファイルの結合
"grunt-contrib-concat": "~0.5.0",
// 簡易サーバー
"grunt-contrib-connect": "~0.8.0",
// Minify JS
"grunt-contrib-uglify": "~0.5.1",
// ファイルの更新検知
"grunt-contrib-watch": "~0.6.1",
// CSSのプロパティをソートする
"grunt-csscomb": "~3.0.0",
// Minify CSS
"grunt-csso": "~0.6.3",
// シェルコマンドの実行
"grunt-exec": "~0.4.6"
}
}

 

Gruntfile.coffee (タスク)

module.exports = (grunt) ->
# load package.json
grunt.initConfig
pkg: grunt.file.readJSON 'package.json'

compass:
dist:
options:
config: 'config.rb'
outputStyle: 'compressed'
environment: 'production'
dev:
options:
config: 'config.rb'

coffee:
compile:
options:
join: true
files:
'source/js/script.js': ['source/coffee/**/*.coffee']

exec:
mv_sprite:
cmd: 'ruby mv_sprite.rb'

autoprefixer:
options:
browsers: ['last 2 version', 'ie 8', 'ie 9']
default:
src: 'assets/css/style.css'
dest: 'assets/css/style.css'

csso:
default:
src: 'assets/css/style.css'
dest: 'assets/css/style.css'

cmq:
default:
src: 'assets/css/style.css'
dest: 'assets/css/style.css'

csscomb:
default:
src: 'assets/css/style.css'
dest: 'assets/css/style.css'

concat:
jsdefault:
src: [
'source/jslib/**/*.js'
'source/js/**/*.js'
]
dest: 'assets/js/script.js'

license: {
src: [
'source/jslib/_license.js'
'assets/js/script.js'
]
dest: 'assets/js/script.js'
}

uglify:
default:
src: 'assets/js/script.js'
dest: 'assets/js/script.js'

connect:
uses_defaults: {}

watch:
options: # enable livereload
livereload: true
compass: # watch scss files
files: 'source/sass/**/*.scss'
tasks: ['compass:dev']
coffee: # watch scss files
files: 'source/coffee/**/*.coffee'
tasks: ['coffee:compile']
js: # watch js files
files: ['source/js/**/*.js', 'source/jslib/**/*.js']
tasks: ['concat:jsdefault']
image: # watch image files
files: 'source/images/**'
tasks: ['exec:mv_sprite']
html: # watch html files
files: '**/*.html'

# load Grunt Plugins
grunt.loadNpmTasks('grunt-autoprefixer')
grunt.loadNpmTasks('grunt-combine-media-queries')
grunt.loadNpmTasks('grunt-contrib-coffee')
grunt.loadNpmTasks('grunt-contrib-compass')
grunt.loadNpmTasks('grunt-contrib-concat')
grunt.loadNpmTasks('grunt-contrib-connect')
grunt.loadNpmTasks('grunt-contrib-uglify')
grunt.loadNpmTasks('grunt-contrib-watch')
grunt.loadNpmTasks('grunt-csscomb')
grunt.loadNpmTasks('grunt-csso')
grunt.loadNpmTasks('grunt-exec')

# tasks

# defalt
grunt.registerTask('default', ['connect', 'watch']);

# development
grunt.registerTask('dev', ['exec:mv_sprite', 'compass:dev', 'coffee:compile', 'concat:jsdefault']);

# distribution
grunt.registerTask('dist', ['exec:mv_sprite', 'compass:dist', 'autoprefixer', 'cmq', 'csscomb', 'csso', 'coffee:compile', 'concat:jsdefault', 'uglify', 'concat:license']);

return

 

「grunt」の処理内容
・connect
簡易Webサーバーの立ち上げ (プロジェクトによっては使わない)

・ファイルの更新の監視
1) images内が更新されたら…「3. スプライトイメージの自動化」で紹介したRubyコマンドを実行
2) scssが更新されたら…Compassを再コンパイル
3) coffeeが更新されたら…CoffeeScriptを再コンパイル → jsファイルが書き出される
4) jsが更新されたら…JSファイルを一つのファイルに結合

「grunt development」の処理内容
・上記 1),2),3),4)を順番に実行

「grunt distribution」の処理内容
・1)を実行
・2)をdist用の設定 (outputStyle: ‘compressed’) で実行
・ベンダープレフィックスを追加
・メディアクエリーをまとめる
・CSSのプロパティをソート
・CSSをMinify
・3)を実行
・4)を実行
・JSをMinify
・上部にライブラリ関係のライセンスを追加

ここまでの内容をまとめたものは、
https://github.com/soohei/grunt-template
にあります。

 

6. ローカルサーバー

本気で開発する場合は、MAMPでローカルサーバーを立てています。
詳しくは、
MAMP 2 → MAMP 3
MAMPでバーチャルホストの設定 (2種類)
あたりに書かれています。

 

7. その他

最後に、ここまでのワークフローで生まれた、
本番環境にアップ不要のファイルはサーバーに転送しないように、
Transmitを設定しています。

transmit

 

CompassでCSSスプライトを自動化するメモ

background-sizeが使える環境(IE8を切れる環境)で、
Retinaなケースを考えて作る場合。(画像は@2xサイズで用意する)

 

・compassは最新のアルファ版を使うと良い (2013.4.14現在)

gem install compass --pre

Sprites are compiled in every call to sprite · Issue #897 · chriseppstein/compass

 

・pngの生成を高速化すっるには、oily_pngを入れると良い

gem install oily_png

パフォーマンスからみるSass/Compass 第2回:CompassによるCSS Sprite | MOL

 

・該当ディレクトリでターミナルから、監視開始

compass watch

 

・CompassのMixin

// スプライト画像
@mixin sprite-background($sprites, $sprites-img, $name) {
  background-image: $sprites-img;
  background-repeat: no-repeat;
  display: block;
  height: ceil(image-height(sprite-file($sprites, $name)) / 2);
  width: ceil(image-width(sprite-file($sprites, $name)) / 2);
  $ypos: ceil(nth(sprite-position($sprites, $name), 2) / 2);
  background-position: 0 $ypos;
  @include background-size(ceil(image-width(sprite-path($sprites)) / 2) auto);
}

$sprites: sprite-map("/common/sprite/*.png", $spacing: 4px);
$sprites-img: sprite-url($sprites);

#icon-large .sprite{
  @include sprite-background($sprites, $sprites-img, 'icon-large');
}

 

こんな感じ。

参考:
続☆Compassでさくっとスプライトを作る方法
Compassで複数のSprite画像を扱う
Using Compass to generate normal and retina sprite maps

 

background-sizeが使えないwebサイト制作時なんかを考えると、別の方法も必要になるので、後日検討。