그림 파일을 압축하는데 요긴한 종합 선물 세트라고 해야하나. ImageOptim은 맥용으로 나온 꽤 쓸만한 PNG와 JPEG 그림 파일 압축 도구이다. ImageOptim은 모두 다섯 가지의 압축 도구들(AdvPNG, OptiPNG, PngCrush, JpegOptim, PNGOUT)을 내장하고 있어서, 환경설정에서 각 도구들만의 압축 설정이 가능하다.
ImageOptim의 환경 설정 창
저작권 관계로 PNGOUT 바이너리는 직접 내려받아서 적당한 위치에 저장하고, ImageOptim의 환경 설정 창에서 바이너리의 경로를 따로 지정해서 사용해야 한다. 그리고 PNGOUT을 쓰게되면, PngCrush는 기능상 중복되는 부분이 있어서 기능을 꺼줘도 됨.

덧붙임(2012년 3월 5일): 1.4 버전부턴 PNGOUT이 기본적으로 포함되었다.

웹 페이지의 빠른 해석을 위한 조치 중의 하나로, 가장 기초적인 것이 웹 페이지에 사용된 그림 파일들의 용량을 최대한 줄여주는 방법이 있다. 대부분 Photoshop과 같은 그래픽 어플리케이션에 있을 법한 “save for web” 메뉴에서 저장하는 것 만으로 만족할 수도 있겠지만, PNG와 같은 자료 손실 없는 압축 형식을 채용한 파일이더라도, 여분의 압축 효율 최적화 과정을 통해 자료 손실 없이 더 작은 크기의 파일을 얻어낼 수가 있다.

추가적인 PNG 압축을 위한 공개된 GUI 어플리케이션들은 많지만, Mac 용으로 알려진 대표적인 것들은 다음과 같다:

위 어플리케이션들은 공통적으로 속도 빠르고 효율 좋기로 소문난 공개 소프트웨어인 OptiPNG를 사용하고 있는데, 불행히도 내장되어 있는 바이너리 파일이 오래된 버전이거나 혹은 Intel 용으로 제공되지 않고 있으며, 또 세세한 조작이 불가능해서 개인적으로 직접 컴파일해서 사용하는 편이 더 쉬웠다. 마침 얼마 전 0.6 버전으로 갱신되면서 압츅 효율이 더 개선되었다고 한다.

빌드 방법은 Unix 소스 코드와 함께 오는 README.txt 파일에 자세히 소개되어 있는데, 우선 압축을 풀고 터미널에서 다음과 같이 빌드/설치한다:

cd optipng-0.6/src/
$ make -f scripts/gcc.mak
.
.
$ make -f scripts/gcc.mak install

이렇게 하면, optipng 바이너리 파일이 /usr/local/bin/ 디렉토리에 위치하게 된다.

원래 command line tool인 이유로, 와일드카드(*)를 써서 디렉토리 안 여러 파일들을 한꺼번에 압축할 수가 있어서 편하고, 사용 옵션을 살펴보면, -o flag를 써서 압축 정도(-o0 ~ -o7)를 지정해 줄 수 있는데, 기본 설정 level(-o2) 만으로도 만족할 만한 결과를 보여준다. 덤으로 압축 시, -i0 옵션을 줘서 (지금은 거의 불필요한) interlaced 정보마저 석제하면 더 높은 압축 결과를 얻을 수가 있다.

using a minifed version of the code is much faster than the packed one – even though its file size is quite larger.

JavaScript Library Loading Speed

The iPhone cache experiment suggests an additional performance rule specific for developing web sites for the iPhone: Reduce the size of each component to 25 Kbytes or less for optimal caching behavior.

Performance Research, Part 5: iPhone Cacheability – Making it Stick

잘게 자르는 것도 요령이 필요하군.

웹 페이지의 첫 로딩 속도를 줄여주는 여러 방법 중, 서버로의 요청 횟수를 최소화 하는 것은 웹 애플리케이션의 최적화 요소 중에서도 중요한 덕목이자 실제 적용하기에도 아주 손쉬운 방법이다.
보통 웹 페이지에서 치장을 목적으로 사용되는 백그라운드 이미지들은 많이 사용될수록, 자동으로 그 요청 횟수도 늘어나기 마련인데, 이를 줄이고자 하는 목적으로 쓰이는 기법으로 CSS Sprites 기법이 있다.

간단하게, 아이콘이나 버튼과 같은 반복돼서 표시되지 않는 그림들처럼 독립된 여러 그림을 하나의 그림으로 합쳐놓고 CSS의 background-position을 보일 요소에 따라 바꾸어서 표시하는 기법인데, 이렇게 하면 서버로의 요청 횟수를 줄여주면서 사이트 로딩 속도를 줄여주고 더불어서 내려받는 이미지의 크기까지 줄여줄 수 있는 부수적 효과를 얻을 수 있다.

표딱지의 배경 그림으로 사용된 CSS Sprites 이미지 이곳의 블로그에서도 오른쪽 옆구리 아래에 붙어있는 표딱지 그림들의 경우, 개별 이미지를 사용해서 웹 페이지에 표시하려면 모두 10번의 서버 요청이 필요한데, CSS Sprites 기법을 써서 왼쪽에 보이는 바와 같은 하나의 그림으로 모든 단추의 배경 그림들을 표시할 수 있었다.

실제 구현 방법은 아주 간단해서, unordered li들로 구성된 메뉴들의 각 li에 특정 id 값을 지정해 주고 a 태그 속에 있는 text node를 <span> 태그로 감싸고 난 후, 여기에 background image의 좌표값을 표시될 해당 위치에 맞게 지정해 주면 모든 작업이 끝난다.
한 가지 주의가 필요했던 것은 text 때문에 배경 그림이 일부 가려지는 것을 막기 위해, <span>에 적용한 CSS의 padding-left 값을 li의 너비만큼 주어서, 결과적으로 글자를 화면 바깥으로 밀어내어 표시되지 못하도록 하였다. 이렇게 해서, 마지막 CSS 적용 결과는 다음과 같다.

/* sidebar badges */
div#sidebar div#badges ul li {
  list-style-type: none;
  list-style-image: none;
  background-image: none;
  width: 80px;
  height: 15px;
  margin-bottom: 4px;
  overflow: hidden;
}
 
div#badges span {
  display: block;
  background-image: url(images/buttons-bg.png);
  background-repeat: no-repeat;
  padding-left: 80px;
  cursor: pointer;
}
 
div#badges #atom span {background-position: 0 0;}
div#badges #vxhtml span {background-position: 0 -15px;}
div#badges #vcss span {background-position: 0 -30px;}
div#badges #wcag span {background-position: 0 -45px;}
div#badges #uni span {background-position: 0 -60px;}
div#badges #cc span {background-position: 0 -75px;}
div#badges #mac span {background-position: 0 -90px;}
div#badges #safari span {background-position: 0 -105px;}
div#badges #firefox span {background-position: 0 -120px;}
div#badges #ichat span {background-position: 0 -135px;}

결과적으로, 서버 요청 횟수를 10번에서 하나로 줄였을 뿐만 아니라, 이미지의 전체 크기도 거의 1/5이나 줄어든 일거양득의 효과를 보여준다. 🙂

이렇게 눈에 띄는 장점이 있기 때문에, 여러 웹 페이지에서는 CSS Sprites 기법을 많이 사용하고 있는데, 일일이 그래픽 프로그램에서 여러 개의 그림을 하나의 이미지로 합치고 CSS 적용을 위한 그림의 좌표값을 얻기란 번거로울 수가 있다. 그래서, 이런 작업을 자동화해주는 도구들이 개발돼서 한둘씩 생겨나고 있는데, 대표적으로 Website Performance | CSS Sprite Generator가 있다. 이곳에서는 이미지들을 하나로 묶은 압축 파일(zip)을 올려놓으면 약간의 설정만으로 Sprite 이미지와 함께 CSS 적용 rule까지 한꺼번에 얻을 수 있어서 편하다.

아무쪼록, 이러한 기법이 널리 알려지고 많이 쓰였으면 한다.

YSlow는 Yahoo! 내부 개발 도구로 사용되어 오던 Firebug의 확장 플러그인으로, 런던에서 열렸던 @midia 2007 Conference의 “High Performance Web Sites”라는 주제의 강연에서 그 존재가 처음 알려졌었는데, 드디어 일반에게 공개되었다.

웹 페이지 최적화 등급은 모두 13가지의 웹 페이지 최적화 규칙을 기반으로 매겨지는데, 이 규칙들은 일반 웹 애플리케이션의 최적화를 위한 알짜배기 정보로도 참고될 수 있다.

YSlow를 설치하면 크게 네 가지의 View를 제공하는데, 아래는 이곳의 최적화 등급을 나타낸 Performance View 모습이다.

YSlow의 Performance View 창 그림

그림에는 각 최적화 규칙에 대한 현재 웹 페이지의 평가 등급을 한눈에 알 수 있게 보여준다.
여기서 주의할 것은, 2번 규칙인 Use a CDN(Content Delivery Network)에 관해서 제대로 된 등급을 매기려면, CDN으로 등록되어 있는 hostname을 자신이 사용하는 것으로 바꾸어 주어야 하는데 방법은 YSlow FAQ에 설명되어 있다.
등급 결과를 보면 B라는 흐뭇한(?) 점수를 받았지만, 두 군데에서 낙제점을 받았는데 Configure ETags 항목에서 낙제점이 찍힌 것은 이곳이 단일 서버에서 돌아가는 곳으로 cluster servers와는 상관이 없다는 것을 생각해 보면 무시해도 될 듯하다. 결국 비교적 많은 HTTP 요청 횟수가 문제지만 이것도 두 번째 방문부터 차기 cache로 흡수될 것을 생각하면 당장의 불안 요인은 아닌 듯. 이것은 “Stats” View에서도 확인할 수가 있는데, Full Cache일 때는 서버로 단 두 개의 HTTP 요청만 소모된다.

덤으로, Tools 항목에는 JavaScript 코드의 오류 방지를 위한 정리 도구인 JSLint가 내장되어 있고, 웹 페이지에 포함된 모든 js, css 파일을 뽑아서 보여주는 도구 그리고 마지막으로 Performance View의 결과 내용을 나중 검토를 위해 종이로 인쇄할 수 있는 형식으로 정리해서 보여주는 “Printable View” 도구가 자리 잡고 있다.

참고로 아래는 웹 페이지 최적화와 관련해서 이곳에서 사용하는 Gzip 압축과 Expires HTTP headers 부분의 Apache 환경 설정 사항이다.

# 
# Compress some document types using mod_deflate module 
# 
<Location />
  AddOutputFilterByType DEFLATE text/html text/plain text/xml application/xhtml+xml
  AddOutputFilterByType DEFLATE text/css application/javascript text/javascript
  AddOutputFilterByType DEFLATE application/x-httpd-php application/x-httpd-fastphp application/x-httpd-eruby
  AddOutputFilterByType DEFLATE application/xml application/rss+xml application/atom+xml image/svg+xml
 
  # Netscape 4.x has some problems... 
  BrowserMatch ^Mozilla/4 gzip-only-text/html
 
  # Netscape 4.06-4.08 have some more problems 
  BrowserMatch ^Mozilla/4\.0[678] no-gzip
 
  # MSIE masquerades as Netscape, but it is fine 
  BrowserMatch \bMSIE !no-gzip !gzip-only-text/html
 
  # NOTE: Due to a bug in mod_setenvif up to Apache 2.0.48 
  # the above regex won't work. You can use the following 
  # workaround to get the desired effect: 
  #BrowserMatch \bMSI[E] !no-gzip !gzip-only-text/html 
 
  # Don't compress images, compressed files, movies, audio files 
  SetEnvIfNoCase Request_URI \.(?:gif|jpe?g|png)$ no-gzip dont-vary
  SetEnvIfNoCase Request_URI \.(?:exe|t?gz|zip|bz2|sit|rar)$ no-gzip dont-vary
  SetEnvIfNoCase Request_URI \.pdf$ no-gzip dont-vary
  SetEnvIfNoCase Request_URI \.avi$ no-gzip dont-vary
  SetEnvIfNoCase Request_URI \.mov$ no-gzip dont-vary
  SetEnvIfNoCase Request_URI \.mp3$ no-gzip dont-vary
  SetEnvIfNoCase Request_URI \.mp4$ no-gzip dont-vary
  SetEnvIfNoCase Request_URI \.rm$ no-gzip dont-vary
 
  # Make sure proxies don't deliver the wrong content 
  Header append Vary User-Agent env=!dont-vary
 
  # n = 1..9 with 9 being the highest compression level. Standard is 6. 
  # DeflateCompressionLevel n 
</Location>
 
# 
# Expires HTTP headers 
# 
<IfModule expires_module>
  ExpiresActive on
  ExpiresDefault "access plus 2 weeks"
  ExpiresByType image/gif "access plus 1 month"
  ExpiresByType image/jpeg "access plus 1 month"
  ExpiresByType image/png "access plus 1 month"
  ExpiresByType image/x-icon "access plus 1 month"
  <FilesMatch "\.(php|php4)$">
      ExpiresByType text/html "now"
  </FilesMatch>
</IfModule>

당분간 등급 놀이에 빠져있겠군. 😉