들어가며
최근 서버 사이드 템플릿 엔진 변경에 대한 논의가 있어서 주요 템플릿 엔진에 대해서 살펴보고 정리할 기회가 생겼다. 그 중에서 handlebars.js(이하 핸들바) 사용법에 대해 정리한 내용을 공유한다.
가장 기본적인 바인딩 구조
새로운 것을 배울 때에는 항상 기초적인 형태를 보는 것이 가장 빠르게 이해하는 길이다.
<script id="entry-template" type="text/x-handlebars-template"> <table> <thead> <th>이름</th> <th>아이디</th> <th>메일주소</th> </thead> <tbody> {{#users}} <tr> <td>{{name}}</td> <td>{{id}}</td> <td><a href="mailto:{{email}}">{{email}}</a></td> </tr> {{/users}} </tbody> </table> </script>
핸들바 템플릿은 위와 같이 script 태그 안에 type을 "text/x-handlebars-template" (혹은 x-handlebars-template 대신 my-template 과 같이 다른 이름이어도 관계없음) 등으로 설정하고 위치시킨다. 템플릿 안에 {{}} 로 감싸진 부분이 주입되는 데이터와 바인딩되는 부분이다.
{{#users}} 와 {{/users}} 로 감싸진 HTML 태그들은 users 라는 배열의 길이만큼 반복되게 되고, 그 내부의 {{name}} 은 배열 요소 객체의 name 속성값이 바인딩되게 된다.
//핸들바 템플릿 가져오기 var source = $("#entry-template").html(); //핸들바 템플릿 컴파일 var template = Handlebars.compile(source); //핸들바 템플릿에 바인딩할 데이터 var data = { users: [ { name: "홍길동1", id: "aaa1", email: "aaa1@gmail.com" }, { name: "홍길동2", id: "aaa2", email: "aaa2@gmail.com" }, { name: "홍길동3", id: "aaa3", email: "aaa3@gmail.com" }, { name: "홍길동4", id: "aaa4", email: "aaa4@gmail.com" }, { name: "홍길동5", id: "aaa5", email: "aaa5@gmail.com" } ] }; //핸들바 템플릿에 데이터를 바인딩해서 HTML 생성 var html = template(data); //생성된 HTML을 DOM에 주입 $('body').append(html);
실제 템플릿과의 바인딩은 자바스크립트 코드에서 이루어진다. 위의 자바스크립트 소스를 살펴보면, 앞서 살펴본 핸들바 템플릿의 내용을 가져오는 것으로 시작된다. Handlebars.compile 메서드로 템플릿을 컴파일한 뒤, 리턴된 함수의 파라메터에 바인딩할 데이터를 파라메터로 넣으면 바인딩된 HTML 템플릿이 리턴된다. 이 HTML 템플릿을 DOM에 추가하는 것으로 소스코드가 마무리되고 있다.
위의 소스를 간단하게 jsfiddle 로 만들어보았다. 직접 값을 변경해보면서 핸들바 템플릿 가져오기 -> 핸들바 템플릿 컴파일 -> 데이터 바인딩 -> 결과물을 DOM에 추가 의 흐름으로 진행되는 것을 확인해보자.
가장 기본적인 사용자 정의 헬퍼
핸들바는 Mustache 를 기초로 개발되었는데, Mushtache 와의 대표적인 차별 포인트 중 하나는 사용자 정의 헬퍼의 존재이다. Mustache 가 단순하게 주입된 데이터를 바인딩해주는 수준의 극도로 로직이 절제된 템플릿 엔진이라면, 핸들바는 if, unless 등의 기본 헬퍼와 사용자 정의 헬퍼를 추가해서 조금더 템플릿 엔진에서 데이터에 기반한 낮은 수준의 로직을 포함시킬 수 있다는 점에서 차이가 있다.
가장 기본적인 핸들바 사용자 정의 헬퍼 예제를 살펴보자.
<script id="entry-template" type="text/x-handlebars-template"> <table> <thead> <th>이름</th> <th>아이디</th> <th>메일주소</th> </thead> <tbody> {{#users}} <tr> <td>{{name}}</td> <td>{{id}}</td> {{!-- 사용자 정의 헬퍼인 email에 id를 인자로 넘긴다 --}} <td><a href="mailto:{{email id}}">{{email id}}</a></td> </tr> {{/users}} </tbody> </table> </script>
앞에서 살펴본 기본 형태에서 메일주소 부분이 달라진 것을 볼 수 있다. {{email id}} 의 형태로 바뀌었는데, 여기에서 email 이라는 이름의 사용자 정의 헬퍼가 사용되었다는 것을 보여준다. 해석하면 email 이라는 이름의 사용자 정의 헬퍼를 호출하면서 파라메터로 id 값을 전달한다는 의미이다.
또 새롭게 보이는 것은 핸들바의 주석인데, {{!-- 로 시작해서 --}} 로 끝나는 형태이다. 핸들바 템플릿 내에 주석이 필요한 경우 {{!-- 주석 내용 --}} 으로 주석을 표시해줄 수 있다.
//핸들바 템플릿 가져오기 var source = $("#entry-template").html(); //핸들바 템플릿 컴파일 var template = Handlebars.compile(source); //핸들바 템플릿에 바인딩할 데이터 var data = { users: [ { name: "홍길동1", id: "aaa1" }, { name: "홍길동2", id: "aaa2" }, { name: "홍길동3", id: "aaa3" }, { name: "홍길동4", id: "aaa4" }, { name: "홍길동5", id: "aaa5" } ] }; //커스텀 헬퍼 등록 (id를 인자로 받아서 전체 이메일 주소를 반환) Handlebars.registerHelper('email', function (id) { return id + "@daum.net"; }); //핸들바 템플릿에 데이터를 바인딩해서 HTML 생성 var html = template(data); //생성된 HTML을 DOM에 주입 $('body').append(html);
스크립트 부분을 살펴보면, 바인딩할 데이터에 email 부분이 없는 것을 볼 수 있다. 대신 그 아래에 커스텀 헬퍼를 등록하는 부분이 추가되어 있는데, 단순하게 코드의 흐름을 읽어가도 어떻게 커스텀 헬퍼가 등록되고 동작하는지 유추할 수 있다. Handlebars.registerHelper 라는 메서드로 사용자 정의 헬퍼를 등록할 수 있는데, 첫번째 파라메터로 헬퍼의 이름을 넣고, 두번째 파라메터에서 헬퍼의 동작을 정의하게 된다. id 를 인자로 받아서 '@daum.net' 이라는 문자열을 더해 리턴해주는 단순한 헬퍼인데, 단순하지만 사용자 정의 헬퍼의 동작 원리를 이해하는데 충분하다.
위의 소스를 간단하게 jsfiddle 로 만들어보았다. 직접 값을 변경해보면서 사용자 정의 헬퍼가 동작하는 방법을 익혀보자.
기본 제공 헬퍼
앞서 이야기했듯이 핸들바는 Mustache와 달리 if, unless 등의 기본 헬퍼와 사용자 정의 헬퍼를 추가해서 조금더 템플릿 엔진에서 데이터에 기반한 낮은 수준의 로직을 포함시킬 수 있다. 하지만 if 의 경우 오직 true/false만 판별할 수 있기 때문에 복잡한 조건은 핸들바를 통해 처리할 수 없다. 물론 사용자 정의 헬퍼를 추가해서 처리할 수도 있지만, 템플릿에 로직을 배제하는 사상과 배치되기 때문에 되도록 사용을 자제하는 것이 바람직하다.
핸들바에서 기본으로 제공하는 헬퍼를 살펴보자.
<script id="entry-template" type="text/x-handlebars-template"> <table> <thead> <th>이름</th> <th>아이디</th> <th>메일주소</th> <th>요소정보</th> </thead> <tbody> {{!-- {{#each users}} 는 {{#users}} 로도 대체 가능하다 --}} {{#each users}} <tr> {{!-- {{name}} 은 {{this.name}} 과 같고 {{.}} 은 현재 name과 id를 포함하고 있는 오브젝트를 가리킨다 --}} <td>{{name}}</td> <td>{{id}}</td> {{!-- 사용자 정의 헬퍼인 email에 id를 인자로 넘긴다 --}} <td><a href="mailto:{{email id}}">{{email id}}</a></td> {{!-- 요소의 순번은 @index 혹은 @key로 잡을 수 있는데, array와 object 모두 잡을 수 있는 key가 더 나아보인다 --}} {{#if @first}} <td>첫 아이템 ({{@key}} 번째 요소)</td> {{else if @last}} <td>마지막 아이템 ({{@key}} 번째 요소)</td> {{else}} <td>중간 아이템 ({{@key}} 번째 요소)</td> {{/if}} </tr> {{/each}} </tbody> </table> </script>
위의 예에 핸들바에서 제공하는 거의 모든 기본 헬퍼가 다 포함되어 있다. each 헬퍼는 배열 등을 반복해서 표시할 수 있도록 해주고 #배열명으로 대체도 가능하다. 앞서 이야기했듯이 if 는 아주 단순한 true/false만 판별 가능하며, else 와 else if 를 지원하며 unless 는 if 의 반대되는 헬퍼이다. 헬퍼 내부에서 다시 헬퍼를 사용하는 것도 가능하다.
each 헬퍼의 경우 @ 기호와 함께 몇 번째 요소인지도 판별 가능한데, @first 로 첫번째 요소 여부를 알 수 있고 @last 로 마지막 요소 여부를 알 수 있다. 또 해당 요소가 몇 번째 요소인지 혹은 오브젝트였다면 현재 어떤 멤버를 탐색 중인지 @key 로 알 수 있다.
여기까지가 사실상 핸들바에서 기본 제공하는 헬퍼에 대한 거의 전부이다. 템플릿에서 처리할 수 있는 로직은 정말 단순한 조건들만 처리 가능하기 때문에 템플릿에 복잡하게 녹아있는 로직과 스크립트 로직을 비교해가며 분석하는 시간을 줄여줄 수 있다.
jsfiddle 예제로 직접 기본 제공 헬퍼를 다뤄보는 연습을 해보면 이해하는데 도움이 될 것이다.
partial template 등록 및 사용
다른 많은 템플릿 엔진과 마찬가지로 핸들바도 핸들바 템플릿을 다른 핸들바 템플릿의 일부로 포함시킬 수 있다. 이를 통해서 템플릿의 재사용성을 높이고 템플릿의 가독성을 향상시킬 수 있다.
가장 단순한 형태의 partial template 등록 및 사용 예를 살펴보자.
<!-- 핸들바 템플릿 --> <script id="partial-template" type="text/x-handlebars-template"> {{!-- #unless 헬퍼는 #if 의 정반대 기능을 한다. --}} <h1>리스트 {{#unless users}}<small>사용자 리스트가 없습니다.</small>{{/unless}}</h1> </script> <!-- 핸들바 템플릿 --> <script id="entry-template" type="text/x-handlebars-template"> {{!-- 조각 템플릿은 #> 를 사용해서 포함시킬 수 있다. --}} {{#> commonHeader}} partial template 로드 실패시 보여지는 내용 {{/commonHeader}} <table> <thead> <th>이름</th> <th>아이디</th> <th>메일주소</th> <th>요소정보</th> </thead> <tbody> {{!-- {{#each users}} 는 {{#users}} 로도 대체 가능하다 --}} {{#each users}} <tr> <td>{{name}}</td> <td>{{id}}</td> {{!-- 사용자 정의 헬퍼인 email에 id를 인자로 넘긴다 --}} <td><a href="mailto:{{email id}}">{{email id}}</a></td> {{!-- 요소의 순번은 @index 혹은 @key로 잡을 수 있는데, array와 object 모두 잡을 수 있는 key가 더 나아보인다 --}} {{#if @first}} <td>첫 아이템 ({{@key}} 번째 요소)</td> {{else if @last}} <td>마지막 아이템 ({{@key}} 번째 요소)</td> {{else}} <td>중간 아이템 ({{@key}} 번째 요소)</td> {{/if}} </tr> {{/each}} </tbody> </table> </script>
위의 예를 살펴보면, 앞서 다른 예제와는 달리 템플릿을 담고 있는 script 태그가 한 개 더 있는 것을 볼 수 있다. 좀더 이해하기 쉽도록 id 도 partial-template 으로 주었다. entry-template 안을 살펴보면 #> 기호와 함께 partial template 을 삽입해주는 부분을 발견할 수 있다. 주석으로도 설명해 놓았지만, 간단하게 #> 와 함께 partial template 의 이름을 넣어주면 해당 위치에 partial template 이 삽입되게 된다. partial template 의 이름은 자바스크립트 영역에서 등록한다.
//핸들바 조각 템플릿 가져오기 var partial = $("#partial-template").html(); //핸들바 템플릿 가져오기 var source = $("#entry-template").html(); //핸들바 템플릿 컴파일 var template = Handlebars.compile(source); //핸들바 템플릿에 바인딩할 데이터 var data = { users: [ { name: "홍길동1", id: "aaa1" }, { name: "홍길동2", id: "aaa2" }, { name: "홍길동3", id: "aaa3" }, { name: "홍길동4", id: "aaa4" }, { name: "홍길동5", id: "aaa5" } ] }; //조각 템플릿을 'commonHeader' 라는 이름으로 등록 Handlebars.registerPartial('commonHeader', partial); //커스텀 헬퍼 등록 (id를 인자로 받아서 전체 이메일 주소를 반환) Handlebars.registerHelper('email', function (id) { return id + "@daum.net"; }); //핸들바 템플릿에 데이터를 바인딩해서 HTML 생성 var html = template(data); //생성된 HTML을 DOM에 주입 $('body').append(html);
자바스크립트 영역을 살펴보면, entry-template 과 마찬가지로 partial-template 을 가져온 뒤, Handlebars.regisiterPartial 메서드를 사용해서 가져온 템플릿을 commonHeader 라는 이름으로 등록해주고 있는 것을 볼 수 있다. 이렇게 등록만 해주면 데이터를 바인딩해주는 시점에서 partial template 을 삽입해주게 되고, partial template 에도 주어진 데이터와 바인딩될 부분이 있다면 데이터 바인딩 역시 처리되게 된다.
with 와 as 그리고 상대 경로 참조
지금까지 살펴본 내용들은 다소 기본적인 핸들바의 문법이었다면, 이런 문법을 조합한 좀더 복잡한 구조를 살펴보자.
자주 있는 상황은 아니지만, 이중 혹은 삼중의 이터레이션을 돌리는 상황에서 상위 이터레이션의 값을 하위 이터레이션에서 참조해야 하는 상황을 예로 만들어봤다. ../ 를 사용해서 상위의 경로를 참조할 수 있고, with 와 as 를 활용해서 이렇게 참조한 상위의 값을 다른 이름으로 할당해서 사용하고 있다.
lookup 을 활용한 변수 참조
lookup 헬퍼를 활용하면 특정 상대 경로에 있는 변수값을 참조할 수 있다. lookup 헬퍼로 참조할 수 있는 변수값은 with 와 as 를 활용하는 방법으로 거의 대부분 참조 가능하다. 개인적으로는 with 와 as 를 활용하는 것이 더 명시적이기 때문에 선호한다.
마치며
지금까지 핸들바의 기본적인 데이터 바인딩 구조와 기본 제공 헬퍼, 사용자 정의 헬퍼, partial template 까지 핸들바가 제공하는 주요 기능을 살펴보았다.
설명하는 과정에서 언급했듯이 핸들바는 Mustache 로부터 파생되었고 Mustache 와 달리 몇 가지 헬퍼와 사용자 정의 헬퍼를 지원해서 낮은 수준의 로직을 템플릿에 포함시킬 수 있는 점과 미리 컴파일해둘 수 있어서 속도면에서 이득을 얻을 수 있는 점에서 차이가 있다. 핸들바가 if, else 등의 헬퍼와 사용자 정의 헬퍼를 지원해주기는 하지만, 핸들바는 비교적 템플릿에 로직이 포함되지 않는 것을 지향하는 템플릿 엔진 중 하나이다. 따라서 템플릿에 로직을 보다 많이 포함하고자 한다면 다른 템플릿 엔진을 고려해보는 것이 좋은 방향으로 생각된다.
단순한 문법으로 인한 쉬운 사용, 템플릿에 로직을 배제하는 사상, 컴파일을 통한 빠른 렌더링, 다양하고 오랜 레퍼런스로 인한 안정성 등 핸들바가 가지는 특성이 현재 하고 있는 프로젝트와도 부합하는지를 고민해본다면 적용 여부를 쉽게 결정할 수 있을 듯하다.