SSI 를 활용한 프론트엔드 리소스 버저닝

개요

리소스 버저닝은 리소스 캐시를 목적으로 한다. 리소스 캐시는 해당 파일이 변경된 경우 컨텐츠를 새로 전송(Cache Miss)하고, 변경되지 않았다면 컨텐츠를 전송하지 않는(Cache Hit)다.

많이 사용되는 방법으로 버전이름을 쿼리스트링(Query String, GET 파라미터라고도 함)에 명시하는 방법이 있다.

버전이름을 쿼리스트링에 명시하는 방법은

<script src="vendors.js?v0.9.5"></script>
<script src="app.js?v0.9.5"></script>   

이와 같이 버전이름(v0.9.5)을 파라미터로 적어주고, 이후 버전이 올라가면 버전이름을 변경시켜주는 방법이다.

<script src="vendors.js?v0.9.6"></script>
<script src="app.js?v0.9.6"></script>

이 방법은 파일 이름을 변경하지는 않지만 파일의 내용이 변경될 때마다 주소를 변경시키므로 브라우저나 웹서버 간에 혹시 발생할 수 있는 캐시가 먹어 파일이 갱신되지 않는 문제를 방지할 수 있다.

일괄 버전 업데이트? 새로 업데이트된 파일만 버전 업데이트?

하지만 위 방식에도 맹점이 있다. 만약 0.9.6 업데이트가 app.js 에만 관련되어 있다면 vendors.js 는 굳이 새로 전송할 필요가 없는데도 다시 전송하게 되므로, 버전을 모두 변경하는 방식은 약간의 손해를 감수해야 한다.

현재 Baas.io 프로젝트에서는 1. 빌드 프로세스에 버전명을 명시하기 까다롭고, 2. 잘 변경되지 않는 라이브러리파일과 자주 변경되는 어플리케이션 파일을 구분하고 있어서 새로 업데이트된 파일만 버전을 올려주는 방법을 적용했다.

자동 버저닝

SSI 코드

<!--#config timefmt="%s" --> <!-- 시간을 읽는 방법을 Unix Timestamp 로 설정 -->
<link href="/css/styles.css?<!--#flastmod virtual="/css/styles.css" -->" rel="stylesheet" type="text/css" /> <!-- last modified 타임을 GET 파라미터로 넘김 -->
<script type="text/javascript" src="/js/vendor/vendors.min.js?<!--#flastmod virtual="/js/vendor/vendors.min.js" -->"></script>
<script type="text/javascript" src="/js/app/common.min.js?<!--#flastmod virtual="/js/app/common.min.js" -->"></script>

결과

<link href="/css/styles.css?1355283501" rel="stylesheet" type="text/css" />
<script type="text/javascript" src="/js/vendor/vendors.min.js?1359420532"></script>
<script type="text/javascript" src="/js/app/common.min.js?1360469267"></script>

새로 업데이트된 파일의 버전을 올려주는 방법을 자동화하기 위해 파일의 각 마지막 변경시간(Last Modified Time) 을 Unix 타임스탬프형식으로 쿼리스트링으로 넘겨주도록 했다.

이렇게 구성하면 각 파일의 마지막 변경시간(서버에 탑재된 시간)이 이 파일의 버전이 되므로 선택적으로 버저닝이 가능하다. 그리고 마지막 변경시간을 SSI 를 통해 구하도록 되어 있어서 수동으로 버전을 올려줄 필요가 없다.

장점

  • 버저닝 최적화/자동화
    • 릴리즈 주기에 맞춰 버전을 일괄변경 한 것이 아니라 각 파일마다 버저닝했으므로 업데이트 되지않은 파일들은 이전 캐시를 그대로 사용할 수 있다.
  • 간단한 적용
    • 빌드 과정에 손을 쓰지 않고 SSI 코드를 삽입한 것이라 간단하게 적용할 수 있었다.

단점

  • HTTP Request당 오버헤드 증가
    • 파일의 마지막 변경시간을 구해야 하므로 Disk IO 발생이 늘어나게 된다.

P.S.

squid 등 일반적으로 많이 사용되는 프록시 서버에서는 쿼리스트링이 달려있는 리소스들은 캐시를 타지 않고 모두 새로 받아온다고 한다. 이런 이유로 쿼리스트링으로 버저닝하는 것보다 파일명 자체를 변경시키는 것이 더 낫다고 한다. Revving Filenames: don’t use querystring

브라우저 자동완성 기능의 미래

Form element 에 autocomplete 기능이 소개된 것은 1999년 Internet Explorer 5이 최초였다. 이후 각 브라우저 별로 산발적으로 지원하던 자동완성 기능은 HTML5에 이르러 표준화 되었다.

HTML5 스펙은 2012년 12월 17일 버전으로 기준으로 함.

이전 스펙에서 autocomplete attriute 는 “on” 또는 “off” 값만 가질 수 있었으나 최근의 스펙에서는 on, off 외에 공백으로 분리된 토큰들을 가질 수 있다. 이에 따르면 attribute 이 on, off 가 아니라면 아래와 같은 순서로 동작한다.

  1. 첫 8글자가 “section-” 으로 시작하는 경우 필드가 이름을 가진 그룹에 속함을 나타낼 수 있다. (선택사항)

예를들어, 양식에 두개의 배송주소가 있을 때 아래처럼 마크업 할 수 있다.

<fieldset>
 <legend>Ship the blue gift to...</legend>
 <p> <label> Address:     <input name=ba autocomplete="section-blue shipping street-address"> </label>
 <p> <label> City:        <input name=bc autocomplete="section-blue shipping region"> </label>
 <p> <label> Postal Code: <input name=bp autocomplete="section-blue shipping postal-code"> </label>
</fieldset>
<fieldset>
 <legend>Ship the red gift to...</legend>
 <p> <label> Address:     <input name=ra autocomplete="section-red shipping street-address"> </label>
 <p> <label> City:        <input name=rc autocomplete="section-red shipping region"> </label>
 <p> <label> Postal Code: <input name=rp autocomplete="section-red shipping country"> </label>
</fieldset>
  1. 어떤 토큰은 둘 중 하나일 수 있다. (선택사항)

    • shipping 은 이 필드가 배송 주소 또는 연락처 정보의 일부임을 나타냄
    • billing 은 이 필드가 청구 주소 또는 연락처 정보의 일부임을 나타냄
  2. 다음 두 옵션 중 하나

    • 어떤 토큰이 아래 autofill field string 중 하나:
      • “name”
      • “honorific-prefix”
      • “given-name”
      • “additional-name”
      • “family-name”
      • “honorific-suffix”
      • “nickname”
      • “organization-title”
      • “organization”
      • “street-address”
      • “address-line1”
      • “address-line2”
      • “address-line3”
      • “locality”
      • “region”
      • “country”
      • “postal-code”
      • “cc-name”
      • “cc-given-name”
      • “cc-additional-name”
      • “cc-family-name”
      • “cc-number”
      • “cc-exp”
      • “cc-exp-month”
      • “cc-exp-year”
      • “cc-csc”
      • “language”
      • “bday”
      • “bday-day”
      • “bday-month”
      • “bday-year”
      • “sex”
      • “url”
      • “photo”
  • 아래 순서대로
    1. 어떤 토큰이 아래 값들 중 하나 (선택사항)
      • home, work, mobile, fax, pager
    2. 어떤 토큰이 아래 autofill field 스트링 중 하나
      • tel, tel-country-code, tel-national, tel-area-code, tel-local, tel-local-prefix, tel-local-suffix, tel-extension, email, impp

즉, 이 스펙에 따르면 아래와 같은 경우의 수가 존재한다.

<input name="country" autocomplete="on">
<input name="country" autocomplete="off">
<input name="country" autocomplete="country">
<input name="country" autocomplete="shipping country">
<input name="country" autocomplete="section-a shipping country">
<input name="billing_home_tel" autocomplete="section-a billing home tel">

이런 상세한 분류를 통해 브라우저가 앞으로 더 강력한 자동완성기능을 갖게 될 것이다.

예를 들어 명세서 청구주소는 집이지만, 배송은 회사에서 받게 되는 경우가 있을 수 있다. 이런 경우 billing 에 집 주소, 집 연락처가 저장될 것이고 shipping 에는 회사 연락처 등이 저장되는 형태로 세분화가 가능해질 것이다.

참고

SSI를 이용한 HTML 산출물 모듈화

개요

SSI 는 Server Side Include 의 약자로, 웹서버에서 직접 제공하는 서버사이드 스크립트 언어 입니다.

PHP, ASP, JSP 등도 서버사이드 스크립트 언어의 범주에 들어가지만, SSI 는 대부분의 웹서버에서 직접 지원하고, 문법이 HTML 형식이라는 특징이 있습니다.

문법을 살펴보면,

<!--#include virtual="../quote.txt" -->

와 같이 HTML 의 주석 문법에 #을 붙인 형태로 지시자를 작성하고, attirubte 을 사용해 파라미터를 전달합니다.

일반적인 지시자들

일반적으로 많이 사용하는 지시자들 입니다.

#include
다른 파일을 이 파일 내부로 불러들이기
#exec
외부 명령 실행
#set
변수 선언
#echo
변수 프린트
#if, #else, #elif, #endif
조건문

특징

대부분의 웹서버에서 동일 문법으로 사용 가능

Apache, Nginx, Tomcat, Web sphere 등 다양한 웹 서버에서 동일한 문법으로 사용 가능합니다. 확인결과 Apache, Nginx, Tomcat 에서는 설치가 필요하지 않고, 설정만으로 곧바로 사용 가능합니다.

제한된 기능

SSI 는 간단한 스크립트를 작성하는데 최적화 된 언어입니다. 반복문이 없고, 모든 문법이 HTML 주석을 포함한 형태여서 코드가 길어집니다. 따라서 복잡한 작업에는 어울리지 않습니다.

활용방안

기능이 작고, 대부분의 웹서버와 호환된다는 면에서 HTML 산출물의 모듈별관리에 매우 적합합니다. 게다가 문법이 웹 퍼블리셔에게 친숙한 HTML 문법으로 작성하므로 러닝커브 면에서 꽤 괜찮은 선택입니다.

KTH FI팀에서는 현재 SSI를 활용해 HTML을 모듈별로 관리하고 있습니다.

HTML 의 모듈화는 서버사이드 언어의 지원을 받지 않으면 실현 불가능한 명제 입니다. 그래서 많은 퍼블리셔들이 반복되는 부분에 대해 아얘 모듈화하지 않거나, PHP 같은 언어의 지원을 받아 구현을 합니다.

아직 모듈화하고 있지 않다면 꼭 고려해보세요.

HTML 산출물을 작성할 때 Plain HTML 을 사용한다면 반복되는 코드는 모두 중복될 수 밖에 없습니다. 이 방법은 다른 파일에 의존성없이 완전한 결과물을 볼 수 있다는 장점이 있지만, 중복되는 부분에 변경이 발생하면 일괄 변경해야 하는 어려움이 있습니다.

모듈화된 HTML 을 사용하면

  • 변경이 간편해집니다.
  • 모듈을 퍼블리셔가 직접 지정하므로 개발자와의 커뮤니케이션 공수가 줄어듭니다.
  • 파일 내에서 공통 부분이 차지하는 코드의 양이 적어지므로 실제로 작업할 내용에 대해 집중도가 높아집니다.

만약 다른 방법으로 목적을 달성하고 있더라도 한번 고려해보세요.

사실 SSI는 이미 많이 알려진 방법이고, 굉장히 오래 전에 사용되었던 방법입니다. 하지만 시선을 HTML 산출물의 모듈관리로 돌려보면 이보다 좋은 솔루션을 찾기 어렵습니다.

  • 러닝커브가 크지 않습니다.
  • 대부분의 웹서버에서 동작합니다.
  • 웹서버에 언어모듈을 올리지 않아도 되므로 더 간단한 구조로 구현할 수 있습니다.
  • (물론 mod_includes 모듈이 로드되어야 하긴 하지만, 나도모르는 사이에 로드되어 있는 경우가 많고, 모듈 크기도 더 작습니다)

기능이 제한된다는 것은 일견 나쁠 수도 있지만 다시 생각해보면 장점으로 생각할 수도 있습니다. 단 몇가지를 위해 기능이 과도하게 많은 언어를 사용하는 것은 닭잡는데 소잡는 칼을 쓰는 것과 다르지 않기 때문입니다.

사용해보기

설정

설정은 아파치를 기준으로 합니다.

.htaccess 설정

# +Includes 를 넣었습니다. 이것은 httpd.conf 에서 <Directory> 설정을 상속받아 Includes를 추가해주어야 하므로
# Options Includes 가 아니라  Options +Includes 입니다.
# .htaccess 를 사용하려면 <Directory> 에서 AllowOverride All 로 설정하세요.
AddOutputFilter INCLUDES .html
Options +Includes

Include 사용해보기

설정이 끝나면 간단히 include 문을 테스트 해봅니다.

미리보기: http://code.elegantcoder.com/blog-examples/SSI/html5_boilerplate.html

본체- html5_boilerplate.html

  • include 문을 주의해서 봐주세요.

    <!DOCTYPE html>
    <html lang="en">
      <head>
        <!--#include virtual="_header.html" -->
        <title>html5_boilerplate</title>
      </head>
    
      <body>
        <div>
          <header>
            <h1>html5_boilerplate</h1>
          </header>
          <nav>
            <p>
              <a href="/">Home</a>
            </p>
            <p>
              <a href="/contact">Contact</a>
            </p>
          </nav>
    
          <div>
    
          </div>
    
          <footer>
            <!--#include virtual="_footer.html" -->
          </footer>
        </div>
      </body>
    </html>
    

header – _header.html

     <meta charset="utf-8" />

    <!-- Always force latest IE rendering engine (even in intranet) & Chrome Frame
    Remove this if you use the .htaccess -->
    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />

    <meta name="description" content="" />
    <meta name="author" content="elco" />

    <meta name="viewport" content="width=device-width; initial-scale=1.0" />

    <!-- Replace favicon.ico & apple-touch-icon.png in the root of your domain and delete these references -->
    <link rel="shortcut icon" href="/favicon.ico" />
    <link rel="apple-touch-icon" href="/apple-touch-icon.png" />

footer – _footer.html

<p>
&copy; Copyright by elco
</p>

그 외의 활용 예

User-Agent Sniffing

모바일 웹에서 User Agent Sniffing 이 필요한 경우 JS를 사용하거나 다른 서버사이드 언어에 의존할 수 밖에 없는데, SSI 는 이런 간단한 작업에 적합합니다. Server 변수들은 바로 SSI 에서 사용가능하고, Apache 의 mod_rewrite 를 통하면 URL 에 따른 변수 지정도 가능합니다.

미리보기- http://code.elegantcoder.com/blog-examples/SSI/user-agent.html

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
        <title>User Agent Sniffing Example</title>
    </head>
    <body>
        <h1>User Agent Sniffing Example</h1>
        <div><span>User-agent String:</span><span><!--#echo var="HTTP_USER_AGENT" --></span></div>
        <!--#if expr="$HTTP_USER_AGENT = /Chrome/" -->
        <!--#set var="ua_result" value="Chrome" -->
        <!--#elif expr="$HTTP_USER_AGENT = /Safari/" -->
        <!--#set var="ua_result" value="Safari" -->
        <!--#elif expr="$HTTP_USER_AGENT = /Firefox/" -->
        <!--#set var="ua_result" value="Firefox" -->
        <!--#elif expr="$HTTP_USER_AGENT = /MSIE/" -->
        <!--#set var="ua_result" value="Internet Explorer" -->
        <!--#endif -->
        <div><span>so, u r using : </span><span><!--#echo var="ua_result" --></span></div>
    </body>
</html>

참고

HTML5 Page Visibility API

Page Visibility API

  • 브라우저에서 탭이 이동해서 안보이는 경우, 비디오는 끄고 싶다.
  • 페이지가 안보이는 중에는 이미지 로테이션을 멈춰두고 싶다.
  • 페이지가 안보이는 중에는 서버에 요청하지 않았으면 좋겠다.

이런 경우에 사용하기 위해 Page Visibility API 스펙이 표준화되고 있습니다. 현재 Working Draft 상태이고, Vendor Prefix 로 지원을 시작했습니다.

지원 현황은 (업데이트가 활발한) MDN을 참고하세요.

입력 필드 내 텍스트 UI, 어떻게 구현하는 것이 좋을까?

요즘 많이 사용하는 UI 중, 입력 필드 내에 입력정보 텍스트를 표시하는 UI 가 있습니다. 아무래도 placeholder속성이 구현되었을 때와 비슷한 모습을 하고 있어서 placeholder 로 오인되는 경우가 있는 것 같습니다.

Label과 Placeholder 의 차이

잠시 label과 placeholder 의 차이를 알아보고, 구현에 대해서는 아래에서 설명하겠습니다. 

HTML5 표준 스펙에 따르면 (clearboth.org 의 한글 번역본을 인용했습니다.)
“label 요소는 사용자 인터페이스에서 캡션을 나타냅니다.”
“placeholder 속성은 짧은 힌트(한 단어나 짧은 구)를 나타냅니다.”

그리고 placeholder 속성의 설명부분에 “placeholder 속성을 label 을 대체하기 위해 사용할수는 없습니다.” 라 명시되어있습니다.

글로는 감이 잘 안오는데 이 둘은 어떤 차이가 있을까요? 차이를 이해하는데, 아래 그림이 도움이 될 것 같습니다.

iOS 메일 설정화면

위 화면은 iOS의 이메일 설정화면 입니다.
말하자면 왼편의 Name, Email, Password, Description 부분이 label요소이고, 오른편의 John Appleseed, [email protected], Required, My Gamil Account 가 placeholder 속성으로 표시한 정보라 할 수 있습니다.
 
label 은 해당하는 인풋 필드에 입력받을 정보를 일반적인 단어를 활용해 표시(캡션)하고 있습니다.
placeholder 는 John Appleseed나 Required 와 같이 입력받을 정보에 대한 힌트를  나타내고 있습니다.
 
 
아래는 각 포털의 로그인화면입니다. 위에서부터, 네이버, 다음, 파란 입니다.

네이버 로그인

다음 로그인

파란 로그인

모두들 입력 필드내에 입력정보 텍스트를 보여주고 있습니다. 그리고 표준에 따르면 ‘아이디’나 ‘비밀번호’는 placeholder속성 보다는 label요소로 구현해야 적합한 컨텐츠이군요.

입력 필드 내에 텍스트를 표시하는 UI는 이처럼 label 요소를 사용해 표현해야하는 경우가 있습니다. placeholder 속성과 혼동하시면 안되요! 🙂

입력 필드 내 텍스트 UI 구현 해보기

이런 UI는 어떻게 구현하는 것이 좋을까요?
아래 구현내용은 http://code.elegantcoder.com/blog-examples/label-ui/ 에서 확인하실 수 있습니다.

1. Custom Attribute Text 받아 넣어주기

텍스트 정보이니, 텍스트를 그냥 활용했으면 좋겠습니다. Custom Attribute 을 만들고, 이벤트에 따라 입력 필드의 value를 변경시키는 방법으로 구현을 해봅니다.

HTML

<label for="ex1-id-input" style="display:none;">이메일 주소</label>
<input type="text" value="이메일 주소" data-placeholder="이메일 주소" id="ex1-id-input" />

JS

(function example1($) {
  var $id = $('#ex1-id-input')
  var $pw = $('#ex1-pw-input')
  var onfocus = function () {
    $(this).val('')
  }
  var onblur = function () {
    var $this = $(this)
    if ($.trim($this.val()).length === 0) {
      $this.val($this.attr('data-placeholder'))
    }
  }
  $id.on('click focus', onfocus).on('blur', onblur);
  $pw.on('click focus', onfocus).on('blur', onblur);

})(jQuery);

그렇지만, 이같은 구현방법은 <input type="password" /> 내에 value 가 문제가 됩니다. value 의 내용이 아래와 같이 비밀번호 문자로 가려지기 때문입니다.

비밀번호 요소에서 문제발생!

2. Background Image 사용하기

이를 회피하기 위한 구현으로, (그리고 비표준인 Custom Attribute 을 사용하지 않기 위해서) 많은 곳에서 CSS의 background-image 속성을 활용합니다. 포커스가 가면, 배경 이미지를 지우고, 포커스가 없거나 블러 이벤트가 발생하면 다시 배경 이미지를 채워주는 방식이죠.

이미 많은 곳에서 이렇게 처리하고 있습니다. 확인결과 위에 캡쳐된 세군데 모두 그렇게 구현이 되어있네요.

HTML

<label for="ex2-pw-input" style="display:none;">비밀번호</label>
<input type="password" value="" id="ex2-pw-input" class="ex2-pw-on" />

JS

(function example2($) {
  var $pw = $('#ex2-pw-input')
  $pw.on('click focus', function () {
    $(this).removeClass('ex2-pw-on');
  });
  $pw.on('blur', function () {
    var $this = $(this)
    if ($.trim($this.val()).length === 0) {
      $this.addClass('ex2-pw-on');
    }
  });
})(jQuery);

우선 구현 자체는 되었습니다. 동작도 괜찮게 합니다. 하지만, 이 구현법은 배경으로 사용할 이미지를 미리 잘라놓아야 한다는 단점이 있습니다. 이미지를 잘라 표현하니 OS의 기본 폰트가 다른 경우 어색하기도 합니다. 위 그림의 포털 로그인 모듈들은 모두 윈도우 시스템 폰트인 굴림체로 표현되어 있습니다.

3. Label 을 활용하기

다른 방법으로 이미 코딩되어 있는 label요소를 활용하면 어떨까요? 

HTML

<div style="position:relative;">
  <label for="ex3-pw-input" style="position:absolute;left:4px;top:8px;">비밀번호</label>
  <input type="password" style="position:absolute;z-index:2;background-color:transparent;" value="" id="ex3-pw-input" />
</div>

JS

(function example3($) {
  var $pw = $('#ex3-pw-input')
  $pw.on('click focus', function () {
    $(this).siblings('label').hide()
  });
  $pw.on('blur', function () {
    $this = $(this)
    if ($.trim($this.val()).length === 0) {
      $this.siblings('label').show()
    }
  });
})(jQuery);

position:absolute를 사용해 label과 input 을 같은 곳에 보이도록 포지셔닝하고, label 요소를 input 아래에 위치 시킵니다. 또  input 요소의 배경을 투명하게 해서 label 이 곧바로 보이게 만들면, JS를 사용할 수 없는 경우에도 입력이 가능해집니다.

이렇게 구현하면 굳이 이미지를 잘라쓰지 않아도 되고, 입력 필드 안쪽이 OS 기본폰트로 보여지게 됩니다.

  • 추가: IE용 구현

IE6~IE8 에서는 배경이 투명한 input 요소는 클릭으로 포커스 이동이 되지 않습니다. 투명 gif 를 이용한 구현을 추가합니다. 

<div style="position:relative;">
    <label for="ex4-pw-input" style="position:absolute;left:4px;top:8px;">비밀번호</label>
    <input type="password" style="position:absolute;z-index:2;background:url('./transparent.gif') repeat 0 0 ;" value="" id="ex4-pw-input" />
</div>

생각해볼 만한 패턴

페이스북 검색 - 기본 상태 페이스북 검색 - 포커스 시

사족을 붙이자면, 포커스가 이동한 경우 그 부분에 어떤 값을 입력해야 할지를 계속 표시해두는 것도 좋은 패턴이라고 생각합니다. 페이스북을 보면, placeholder 부분을 포커스가 이동하기 전에는 진하게 표시해두었다가, 포커스가 맞춰지면 옅게 표시 합니다. 그리고 키를 입력하면 완전히 지워줍니다.