Stay Hungry. Stay Foolish. Don't Be Satisfied.

grunt-spritesmith로 CSS Sprites 자동화하기

CSS Sprites란?

CSS Sprites라는 기법은 충분히 보편화한 기술이지만 간단히 설명하면 작은 이미지를 하나의 이미지로 합치는 방법을 말한다. 웹사이트에서 아이콘 등을 사용할 때 작은 이미지들이 다수 필요한데 네트워크에서 다운로드 받을 때 작은 파일 다수를 다운로드 받는 것보다는 큰 파일을 한번에 다운로드 받는 것이 더 빠르므로 여러 이미지를 한 파일로 합치고 이를 한번에 다운로드 받은 뒤에 CSS로 위치를 조절해서 표현하는 것이다.

Gmail의 Sprite 이미지

Gmail 같은 경우도 위 이미지처럼 아이콘을 하나로 합쳐서 사용하고 있고 이를 다음과 같은 CSS로 제어하고 있다.

.aos {
  background: no-repeat url(//ssl.gstatic.com/mail/sprites/general_black-16bf964ab5b51c4b7462e4429bfa7fe8.png) 0 -472px;
  height: 21px;
  width: 21px;
}

각 아이콘 사이즈에 맞게 크기를 정하고 이미지를 배경이미지로 지정해서 위치를 크기에 정확히 맞게 제어하는 식이다.

Sprites 이미지 만들기

보통 디자인은 각 아이콘을 따로 만들기 때문에 이를 하나의 이미지로 다시 맞추는 과정이 필요하다. 이전에도 Sprites를 사용해 보기는 했지만 보통 이미 만들어진 상태에서 사용했을 뿐 디자인된 이미지에서 직접 CSS Sprites를 만들어 보게 된 건 이번이 처음이었다.

SpritePad의 화면

관련 도구를 찾아보다가 SpritePad라는 도구를 써봤는데 이미지를 추가하면 자동으로 파일명에 따라 CSS를 만들어 주기는 하는데 위치를 잡아주는 건 모두 수동으로 해야 하므로 Sprite 이미지를 만드는 게 쉽지 않았다. 원하는 위치에 딱 위치하게 하는 게 많이 귀찮았다. Sprite Cow라는 도구도 있지만, 이는 Sprite 이미지를 만들어 준다기보다는 만들어진 이미지에서 CSS를 만들어 주는 역할에 더 가까웠다. 달리 맘에 드는 도구도 없다 보니 결국 포토샵을 열어서 포토샵으로 작업했다.

포토샵에서 Sprite 만들기

간단한 포토샵 정도는 다룰 수 있었지만, 아이콘은 보통 배경이 투명한데 포토샵에서는 투명한 배경의 크기를 잡아주지 않으므로 아이콘을 크기에 맞게 이어 붙이기가 어려웠다. 그래서 위처럼 각 아이콘 이미지에 배경을 넣어서 크기를 고정해서 이어 붙이는 수밖에 없었고 나중에 저장할 때는 배경레이어는 감추고 저장하는 방식을 사용했다.(예전엔 다 이렇게 한 건가...) 이렇게 작업하는 시간도 상당히 걸렸지만 이런 식으로 작업해놓고 보니 유지보수가 상당히 신경이 쓰였다. 각 CSS의 위치를 수동으로 알아내서 CSS 파일에 적용해야 하는 것도 불편했지만, 작업하다 보니 추가적인 아이콘이 계속 발생하고 그때마다 Sprite 이미지도 수정해야 하는데다가 이미지 크기를 최소로 해야 하니 나중에는 위치도 바꾸어야 할 것 같았다.(위치를 바꾸면 당연히 CSS도 그것에 맞게 다시 수정해 주어야 한다.)

grunt-spritesmith

fallroot님과 얘기를 하다가 Sprite 이미지를 만드는데 Compass를 써서 이미지 아이콘을 CSS로 자동화하고 있다는 얘기를 들었다. 처음에는 Compass를 도입하려고 문서를 보다가 Grunt를 이미 쓰고 있었으므로 Grunt에도 비슷한 도구가 있을 것 같아서 찾아낸 게 grunt-spritesmith다.

grunt-spritesmith는 이미지 셋을 하나의 CSS Sprite를 이미지로 만들어 주고 이에 대한 CSS까지 만들어 주는 Grunt 테스크이다. Stylus를 사용하고 있었으므로 CSS뿐만 아니라 SASS, SCSS, Less, Stylus같은 전처리자까지 지원하는 점도 맘에 들었다.

사용방법

Sprite로 만들 이미지를 원하는 폴더에 모두 넣는다.(여기서는 icons라는 폴더 밑에 넣었다.)

icons 폴더의 이미지 목록

grunt-spritesmith에서 지정한 이미지를 모두 하나의 파일로 만들어 주므로 각 아이콘 파일을 모두 복사해 넣으면 된다.

Gruntfile.js에 다음과 같은 설정을 추가한다.

module.exports = function (grunt) {
  // Configure grunt
  grunt.initConfig({
    sprite:{
      all: {
        src: 'icons/*.png',
        dest: 'sprites/icons.png',
        destCss: 'css/sprites.css'
      }
    }
  });

  // Load in `grunt-spritesmith`
  grunt.loadNpmTasks('grunt-spritesmith');
};

sprite 테스크를 추가했다. src는 사용할 이미지의 위치를 지정하면 되고 여기서는 위에서 만든 icons폴더의 .png파일을 지정했다. dest는 생성한 Sprite 이미지의 위치로 sprites/icons.png로 지정했고 destCss는 해당 CSS 파일을 생성할 위치이다.

$ grunt sprite
Running "sprite:all" (sprite) task
Files "css/sprites.css", "sprites/icons.png" created.

Done, without errors.

grunt sprite를 실행하면 위처럼 CSS와 png 이미지가 새로 생성된 것을 볼 수 있다.(예시 아이콘은 Pace Icon Set을 사용했다.)

만들어진 sprite 이미지

앞에서 추가했던 아이콘 파일을 하나로 합쳐서 위와 같은 sprites/icons.png를 생성해 주고 css/sprites.css는 다음과 같다.

/*
Icon classes can be used entirely standalone. They are named after their original file names.



<img class="icon-home" /> */ .icon-apple { background-image: url(../sprites/icons.png); background-position: 0px 0px; width: 32px; height: 32px; } .icon-battery { background-image: url(../sprites/icons.png); background-position: -32px 0px; width: 32px; height: 32px; } .icon-bluetooth { background-image: url(../sprites/icons.png); background-position: 0px -32px; width: 32px; height: 32px; } .icon-cloud { background-image: url(../sprites/icons.png); background-position: -32px -32px; width: 32px; height: 32px; }

각 아이콘의 파일명을 그대로 사용해서 CSS 클래스를 자동으로 생성하고 크기와 위치도 자동으로 만들어 준다. 이제 이 클래스를 사용해서 아이콘을 사용하기만 하면 된다. 파일명이 바뀌지 않는 이상 클래스 명도 바뀔 일이 없으므로 추가 아이콘이 생기거나 기존 아이콘의 크기가 달라지더라도 아이콘 이미지를 교체하고 다시 생성해 주기만 하면 된다.

Stylus에서 사용하기

CSS가 아니라 Stylus 같은 전처리자를 사용하더라도 아주 쉽게 사용할 수 있다.

module.exports = function (grunt) {
  // Configure grunt
  grunt.initConfig({
    sprite:{
      all: {
        src: 'icons/*.png',
        dest: 'sprites/icons.png',
        destCss: 'css/sprites.styl',
        cssSpritesheetName: 'icons-spritesheet'
      }
    }
  });

  // Load in `grunt-spritesmith`
  grunt.loadNpmTasks('grunt-spritesmith');
};

destCss에서 확장자를 .styl로 지정하기만 하면 자동으로 CSS 대신 Stylus 용으로 만들어 준다.

$apple_name = 'apple';
$apple_x = 0px;
$apple_y = 0px;
$apple_offset_x = 0px;
$apple_offset_y = 0px;
$apple_width = 32px;
$apple_height = 32px;
$apple_total_width = 64px;
$apple_total_height = 64px;
$apple_image = '../sprites/icons.png';
$apple = 0px 0px 0px 0px 32px 32px 64px 64px '../sprites/icons.png' 'apple';

생성된 css/sprites.styl를 보면 위처럼 이미지별로 변수를 생성해 준다. 처음 봤을 때는 클래스로 만들어 주지 않고 왜 파일별로 많은 변수를 만들어 주나 싶었는데 전처리자인 만큼 이 변수를 사용하면 다양한 상황에서 더 유용하게 사용할 수 있다.

$icons_spritesheet_width = 64px;
$icons_spritesheet_height = 64px;
$icons_spritesheet_image = '../sprites/icons.png';
$icons_spritesheet_sprites = $apple $battery $bluetooth $cloud;
$icons_spritesheet = 64px 64px '../sprites/icons.png' $icons_spritesheet_sprites;

각 아이콘에 대한 변수 외에도 전체 아이콘을 모두 가리키는 변수도 위와 같이 만들어 준다. 여기서 $icons_spritesheet_라는 접두사는 앞에 Gruntfile.js에서 cssSpritesheetName로 지정한 이름이다. 이 값을 지정하지 않으면 기본 접두사인 $spritesheet_가 붙으므로 Sprite 이미지를 여러 개 만드는 경우 겹치지 않도록 이름을 지정할 필요가 있다.

spriteWidth($sprite) {
  width: $sprite[4];
}

spriteHeight($sprite) {
  height: $sprite[5];
}

spritePosition($sprite) {
  background-position: $sprite[2] $sprite[3];
}

spriteImage($sprite) {
  background-image: url($sprite[8]);
}

sprite($sprite) {
  spriteImage($sprite)
  spritePosition($sprite)
  spriteWidth($sprite)
  spriteHeight($sprite)
}

sprites($sprites) {
  for $sprite in $sprites {
    $sprite_name = $sprite[9];
    .{$sprite_name} {
      sprite($sprite);
    }
  }
}

변수 외에도 위와 같은 믹스인을 만들어 주어서 필요한 CSS를 쉽게 만들 수 있도록 해주고 있다. 예를 들어 $apple 아이콘의 CSS를 생성하려면 sprite($sprite) 믹스인을 사용해서 sprite($apple)처럼 해당 아이콘의 변수를 전달하면 된다. 그럼 다음과 같은 CSS가 만들어진다.

background-image:url("../sprites/icons.png");
background-position:0 0;
width:32px;
height:32px;

전체 아이콘을 앞에서 본 CSS 클래스처럼 모두 만들려면 sprites($sprites) 믹스인을 사용하면 된다. 생성된 변수 중에 $icons_spritesheet_sprites가 모든 아이콘에 대한 변수를 담고 있으므로 sprites($icons_spritesheet_sprites)와 같이 사용하면 변수명에 따라 다음과 같은 CSS 클래스를 생성해 준다.

.apple{background-image:url("../sprites/icons.png");background-position:0 0;width:32px;height:32px}
.battery{background-image:url("../sprites/icons.png");background-position:-32px 0;width:32px;height:32px}
.bluetooth{background-image:url("../sprites/icons.png");background-position:0 -32px;width:32px;height:32px}
.cloud{background-image:url("../sprites/icons.png");background-position:-32px -32px;width:32px;height:32px}

가장 일반적인 접근인 전체 아이콘을 CSS 클래스로 사용하는 경우에는 sprites믹스인을 사용하면 된다. :before:after같은 pseudo 요소라서 CSS 클래스를 사용하기 어려운 경우에는 sprite 믹스인을 사용하면 된다. line-height등을 맞추는 등 아이콘의 크기가 필요한 경우에는 각 변수를 가져다가 사용하면 된다.

기타 사항

기본적으로 최적화해서 이미지를 Sprite로 합쳐주기는 하지만 algorithm옵션을 주면 세로로 길게 배열하거나 가로로 배열하는 등의 조절도 가능하고 전체 이미지의 품질도 조절할 수 있다. 위에서는 기본적으로 제공되는 전처리자 템플릿을 사용했지만 템플릿을 직접 설정하면 업무에 맞게 만들어진 값이나 변수명 등을 제어할 수 있다. 아무래도 사용하면 가장 신경이 쓰일 만한 변수명을 언더스코어(_)로 연결할지 대시(-)로 연결할지 등의 설정도 가능하다.

좀 써보니 실제로 사용한다면 아무래도 가장 관리가 필요한 부분은 이미지 파일명일 것 같다. 초기에 환경 세팅을 해 놓고 이미지의 아이콘 형식을 미리 정의해 놓는다면 좀 편할 것 같고 이런 부분이 디자인 쪽에 전달해서 이미지 파일명이 그대로 나오면 편할 것 같다.

2015/03/29 17:11 2015/03/29 17:11

Travis CI 빌드에서 Github에 릴리즈 하기

Github에는 프로젝트의 릴리즈를 관리하는 기능이 있다. Github에 소스를 푸시할 때 태그를 올리면 저장소의 Releases 메뉴에 자동으로 등록이 돼서 모아볼 수 있다. jQuery처럼 태그는 사용하지만 릴리즈 기능을 따로 사용하지 않고 있는 프로젝트도 있고 Bootstrap이나 lodash처럼 Github의 릴리즈 기능을 이용해서 버전 별 릴리즈노트를 작성하고 공지에도 활용하는 경우도 있다.

Github에서 릴리즈를 추가하는 화면

프로젝트 전용 사이트가 있는 경우에는 사이트나 블로그를 통해서 릴리즈 공지를 하지만 그렇게 큰 프로젝트가 아니라면 Github의 릴리즈 기능을 사용하면 편하다. 태그를 푸시하고 Release 메뉴에서 Draft a new release를 누르면 위처럼 릴리즈 노트를 작성할 수 있다. 버전을 명시하고 변경사항이나 공지할 내용을 작성하면 프로젝트 이용자가 새 버전에서의 변경 점을 쉽게 파악할 수 있어서 유용하다. 내가 관리하는 프로젝트에서는 별도로 사이트를 관리하기는 번거로우므로 이 기능을 적극적으로 사용하고 있다. 얼마 전 다른 일로 이 릴리즈 기능을 자동화하려는 시도를 해보았다. 릴리즈 노트 같은 경우는 어차피 손수 적어줄 수밖에 없지만, 배포가 그냥 소스배포가 아니라(해당 태그의 소스코드는 자동으로 등록된다.) 빌드과정 후 결과물을 배포해야 한다면 매번 릴리즈에 빌드해서 릴리즈에 업로드하는 것은 귀찮은 일이다.

오픈 소스에서는 보통 Travis CI를 사용하고 있는데 Travis CI에서 Github Release 기능을 지원하고 있어서 Github 릴리즈를 어느 정도 자동화할 수 있다. Travis CI를 이미 사용하고 있다면 테스트나 빌드 성공 여부를 검사하고 있을 것이고 프로젝트에 .trvias.yml파일 등록되어 있을 것이다.

deploy:
  provider: releases
  api_key: "GITHUB OAUTH TOKEN"
  file: "FILE TO UPLOAD"
  skip_cleanup: true
  on:
    tags: true

Travis CI에서 빌드나 테스트 성공 후 Github에 릴리즈하려면 위와 같은 코드를 추가해야 한다. 위 코드는 Github Release API를 사용해서 특정 파일을 업로드 하도록 하는 설정이고 API를 사용해야 하므로 Github의 Oauth token이 필요하다.(public_reporepo의 권한이 필요하다.) 하지만 private 저장소를 쓰는 게 아니라면 공개된 저장소에 Github 인증토큰을 그대로 올릴 수는 없다.(물론 토큰 권한을 저 권한만 주면 제어는 할 수 있다.)

그래서 Travis CI의 CLI 도구에서 자동 설정 기능을 포함해서 인증키를 암호화하는 기능을 제공하고 있다.

$ travis setup releases
Username: outsideris
Password for outsideris: ****
Two-factor authentication code for outsideris: 
File to Upload: dist-file
Deploy only from summernote/angular-summernote? |yes|
Encrypt API key? |yes|

travis cli를 설치한 뒤 위처럼 travis setup releases를 실행하면 Github 인증 후에 프로젝트에 릴리즈 설정을 추가하게 된다. 업로드할 파일이라 배포 설정도 나오는데 이는 나중에 수정하면 되니까 크게 중요치 않고 Encrypt API key를 하면 인증 토큰을 암호화해서 파일에 추가하게 된다.

deploy:
  provider: releases
  api_key:
    secure: D7QkXMGWdGdZ4JlgNq8qlakwOuNitZvIY3qTsoDb3xuObCwaySUASXu2+N0wLnAaMKQKONDpFSqANwuGPiad/Ws5scuajrySIaTCMDBkSNz6igmO5vwEuLHcvRGIEnzEMhIDDFs1hxeoQf+5D6nuFsaVny09JVw6vp8ybJVTc48=
  file: dist-file
  on:
    repo: summernote/angular-summernote

이를 실행하면 .travis.yml에 위와 같은 코드가 추가된다. 토큰은 미리 받아놓지 않더라도 다음과 같이 자동으로 토큰을 발급받아서 암호화하므로 크게 신경 쓰지 않아도 된다. API 키는 암호화된 토큰이므로 공개된 Github 저장소에 그대로 올려도 상관없다.

Github에 자동으로 등록된 인증 토큰

before_deploy: "grunt deploy"
deploy:
  provider: releases
  api_key:
    secure: D7QkXMGWdGdZ4JlgNq8qlakwOuNitZvIY3qTsoDb3xuObCwaySUASXu2+N0wLnAaMKQKONDpFSqANwuGPiad/Ws5scuajrySIaTCMDBkSNz6igmO5vwEuLHcvRGIEnzEMhIDDFs1hxeoQf+5D6nuFsaVny09JVw6vp8ybJVTc48=
  file:
    - "dist/angular-summernote.js"
    - "dist/angular-summernote.min.js"
  skip_cleanup: true
  on:
    tags: true
    all_branches: true
    repo: summernote/angular-summernote

before_deploy는 배포 실행 전에 실행할 명령인데 여기서는 grunt deploy라고 적었지만, 자신의 프로젝트에서 빌드하는 명령어를 여기에 적으면 된다. 이 과정이 오류 없이 진행된다면 deploy 과정이 진행되고 여러 파일을 업로드 해야 한다면 file에서 지정하면 된다. on부분은 deploy가 실행되는 조건에 대한 부분이다. tags: true로 설정하면 해당 빌드에 태그가 추가되었을 때만 배포가 진행되고 Travis CI의 이슈때문에 이 기능은 전체 브랜치로 지정했을 때만 사용할 수 있다. 그래서 태그를 키면 브랜치 지정하는 부분은 꺼야 한다. all_branches: true 부분을 지정해야 하고 .travis.ymlbranches: 부분으로 특정 브랜치를 지정하고 있다면 이 부분을 제거해야 정상적으로 동작한다.

이 설정을 완료한 후에 소스를 푸시할 때 릴리즈를 위해서 태그를 추가해서 푸시하면 아래와 같이 Travis에서 빌드 후 배포를 진행하게 된다.

Travis CI에서 빌드 후 deploy가 진행된 로그

Github의 릴리즈 메뉴에 가보면 해당 파일이 등록되어 있고(소스파일은 자동으로 등록된다.) 해당 릴리즈가 pre-release로 등록된 것을 볼 수 있다.

2015/03/25 03:10 2015/03/25 03:10