본문 바로가기

node.js (OctoberSkyJs)

[메뉴얼] Jade - 템플릿 엔진 for Node.js

따라배우기에서 Jade 템플릿 엔진을 사용하고 있습니다. 전반적으로 사용되는데다, 배워놓으면 괜찮을 듯 해서 메뉴얼을 번역했습니다. 사용법이 아닌 컴파일 설치등의 일부 매니악한 부분은 제외했습니다.

Jade가 뭐냐고 물으신다면 node.js용으로 만들어진 view 템플릿 엔진이라고 생각하시면 되겠습니다. Jade 문법에 맞게 작성하면 해당 내용을 html이나 자바스크립트로 만들어 줍니다. 관련해서는 Part 3: RESTful 메소드와 테스트에서 일부 설명해 놓았습니다만, 역시 정석으로 배우면 더 좋겠죠? : )

Jade - template engine

제이드는 Haml로부터 큰 영향을 받은 고성능 템플릿 엔진이며 node를 위해 자바스크립트로 구현되었다.

기능들

  • 클라이언트 사이드 지원
  • 뛰어난 가독성
  • 유연한 띄어쓰기
  • 블럭 확대(block-expansion)
  • 믹스인(mixins, 섞어쓰는 것)
  • 스태틱 인클루드(static includes)
  • 속성 인터폴레이션(attribute interpolation)
  • 보안을 위해 코드는 기본적으로 이스케이프 처리된다.
  • 컴파일과 런타임시에 문맥에 맞는 에러 출력
  • 커맨드 라인을 통해 jade 템플릿을 컴파일 할 수 있음
  • html 5 모드 (!!! 5 doctype)
  • 선택적인 메모리 캐싱
  • 다이내믹과 스태틱 태그 클래스 조합
  • 필터를 이용한 트리구조 파싱 parse tree manipulation via filters
  • 템플릿 상속
  • 외부 모듈로 Express JS 지원
  • 객체, 배열, 그리고 열거형이 아닌것들에 대해서도  each 를 이용하여 투명하게 이터레이션 지원
  • 블럭 코멘트
  • tag를 이용한 접두어처리 없음
  • AST 필터
  • 필터들
    • :stylus 는 stylus가 인스톨 되어 있어야 한다.
    • :sass 는 sass.js가 인스톨 되어 있어야 한다.
    • :less 는 less.js가 인스톨 되어 있어야 한다.
    • :markdown 은 markdown-js 혹은 node-discount가 설치되어 있어야 한다.
    • :cdata
    • :coffeescript 는 coffee-script가 인스톨 되어 있어야 한다.
  • Vim Syntax
  • TextMate Bundle
  • 스크린캐스트 보기(추천!!)
  • html2jade converter

구현체들

설치

npm을 이용한다.

npm install jade

Public API

    var jade = require('jade');

    // Compile a function
    var fn = jade.compile('string of jade', options);
    fn(locals);

Options

  • self 로컬 값을 포함하기 위해 self  네임스페이스를 사용한다. 기본값은 false
  • locals 로컬 변수 객체
  • filename Used in exceptions, and required when using includes
  • debug Outputs tokens and function body generated
  • compiler Compiler to replace jade's default
  • compileDebug When false no debug instrumentation is compiled

문법

Line Endings

CRLF CR 은 파싱되기 전에 LF로 변환된다.

Tags

태그는 앞에 나오는 간단한 단어다.

html

예를 들면 위 태그는 <html></html> 로 변환된다.

태그는 id값을 가질 수 있다.

div#container

위 코드는 다음과 같이 변환된다. <div id="container"></div>

클래스는 어떻게 할까?

div.user-details

다음처럼 렌더링 된다. <div class="user-details"></div>

클래스가 복수개 일때는?그리고 id도 붙어 있으면? 물론 가능하다.

div#foo.bar.baz

는 다음과 같다. <div id="foo" class="bar baz"></div>

div div div 은 확실히 짜증난다. 다음처럼 해보자

#foo
.bar

위 코드는 이미 오랜기간 우리가 작성해왔던 코드에 대한 꼼수다. 결과는 다음과 같다.

`<div id="foo"></div><div class="bar"></div>`

태그 텍스트(Tag Text)

태그 뒤에 내용을 표시한다.

p wahoo!

렌더링하면 <p>wahoo!</p> 가 된다.

꽤 쓸만하다. 그럼 긴 텍스트 문장일 경우엔 어떻게 할까?

p
  | foo bar baz
  | rawr rawr
  | super cool
  | go jade go

위 코드는 다음처럼 렌더링 된다. <p>foo bar baz rawr.....</p>

인터폴레이션(interpolation, 중간중간 끼워넣어 완성시키는 것)은? 당근! 만약 다음과 같은 값 { name: 'tj', email: 'tj@vision-media.ca' } 을 컴파일 함수로 넘긴다면, 아래와 같은 일을 할 수 있다.

#user #{name} <#{email}>

결과는 <div id="user">tj <tj@vision-media.ca></div>

옮긴이 주석
인터폴레이션의 뜻을 좀 명확히 보기 위한 다른 언어 인터폴레이션의 예
"#{animals} on a #{transport}".interpolate({ animals: "Pigs", transport: "Surfboard" });

출력은
"Pigs on a Surfboard"


특정 이유로  #{} 를 출력하고 싶다면? 이스케이프 처리해라!

p \#{something}

위와 같이하면 다음 처럼 된다. <p>#{something}</p>

이스케이프 처리하지 않길 바랄때 다음과 같이 #대신 !를 쓴다.  !{html},

- var html = "<script></script>"
| !{html}

위 html 변수안의 코드는 이스케이프 처리되지 않는다.

중첩된 태그들 또한 선택적으로 텍스트 블럭을 사용할 수 있다.

label
  | Username:
  input(name='user[name]')

혹은 바로 태그 텍스트를 불일 수 있다.

label Username:
  input(name='user[name]')

오직 텍스트만 쓸 수 있는  script, style, 그리고 textarea 같은 태그들은 긴 문장이라도 앞에  | 문자를 붙일 필요가 없다.

  html
    head
      title Example
      script
        if (foo) {
          bar();
        } else {
          baz();
        }

반대로, 텍스트 블럭의 을 나타내기 위해 마지막에 '.'을 붙일 수도 있다.

  p.
    foo asdf
    asdf
     asdfasdfaf
     asdf
    asd

출력:

    <p>foo asdf
    asdf
      asdfasdfaf
      asdf
    asd
    </p>

끝에 '.'을 붙일 때 공백을 주면 Jade파서는 무시한다. 일반 문자로 간주하게 된다.

p .

출력:

<p>.</p>

만약 이스케이프 문자(\)를 찍고 싶으면 두개를 붙인다. 즉, 다음처럼 출력하고 싶다면

</p>foo\bar</p>

아래 처럼 쓴다.

p.
  foo\\bar

주석(Comments)

한 줄 주석은 자바스크립트 코멘트랑 같다. '//'를 붙이고 한 줄로 쓴다.

// just some paragraphs
p foo
p bar

출력은 다음처럼 된다.

<!-- just some paragraphs -->
<p>foo</p>
<p>bar</p>

하이픈을 붙이면 파싱할 때 제외한다.

//- will not output within markup
p foo
p bar

출력

<p>foo</p>
<p>bar</p>

블럭 주석(Block Comments)

블럭 주석은 일반적으로 다음과 같다.

  body
    //
      #content
        h1 Example

출력은

<body>
  <!--
  <div id="content">
    <h1>Example</h1>
  </div>
  -->
</body>

Jade는 조건절 주석도 잘 지원한다.

head
  //if lt IE 8
    script(src='/ie-sucks.js')

출력:

<body>
  <!--[if lt IE 8]>
    <script src="/ie-sucks.js"></script>
  <![endif]-->
</body>

중첩(Nesting)

Jade는 일반적인 방식으로 태그 정의에 중첩을 지원한다.

ul
  li.first
    a(href='#') foo
  li
    a(href='#') bar
  li.last
    a(href='#') baz

<ul>
  <li id="first"><a href="#">foo</a>
  </li>
  <li><a href="#">bar</a>
  </li>
  <li class="last"><a href="#">baz</a>
  </li>
</ul> 


블럭 확장(Block Expansion)

블럭 확장은 한줄짜리 중첩 태그를 만들 수 있게 해준다. 다음 예제는 위 예제와 동일하다.

  ul
    li.first: a(href='#') foo
    li: a(href='#') bar
    li.last: a(href='#') baz

속성들(Attributes)

Jade는 현재 속성 구분자로 '(' 와 ')'를 사용한다.

a(href='/login', title='View login page') Login

속성의 값이 undefined null 이면 표시되지 않는다.

div(something=null)

boolean 속성도 지원한다.

input(type="checkbox", checked)

코드가 붙은 boolean 속성은 코드의 값이  true 일 때만 속성이 표시된다.

input(type="checkbox", checked=someValue)

여러 라인에 걸쳐서도 잘 동작한다.

input(type='checkbox',
  name='agreement',
  checked)

콤마가 없는 여러 라인일 경우도 잘 된다.

input(type='checkbox'
  name='agreement'
  checked)

펑키한 공백? 된다:

input(
  type='checkbox'
  name='agreement'
  checked)

콜론도 된다.

rss(xmlns:atom="atom")

 { id: 12, name: 'tobi' } 값을 나타내는 user라는 로컬 변수가 있다고 가정해보자. 앵커 태그가 "/user/12"를 가르키길 원한다면, 자바스크립트의 문자열 연결방식을 사용할 수 있다.

a(href='/user/' + user.id)= user.name

혹은 jade은 인터폴레이션을 쓸 수도 있다. 왜냐하면 루비를 쓰거나 커피스크립트를 쓰는 사람들이 일반적으로 쓰는 것 같아서 나도 추가했다.

a(href='/user/#{user.id}')= user.name


class 속성은 배열을 받는 특별 케이스다.  bodyClasses = ['user', 'authenticated'] 를 다음처럼 넘길 수 있다.

body(class=bodyClasses)

HTML

인라인 HTML도 잘 동작한다. 파이프 문법을 이용해서 수직으로 글자를 내려 쓸 수 있다.

html
  body
    | <h1>Title</h1>
    | <p>foo bar baz</p>

파이프를 생략하기 위해 끝에 .을 붙여서 Jade에게 텍스트 블럭임을 알려줄 수 있다.

html
  body.
    <h1>Title</h1>
    <p>foo bar baz</p>

위 코드는 아래와 같다

<html><body><h1>Title</h1>
<p>foo bar baz</p>
</body></html>

위와 같은 규칙은 어턴 텍스트가 있던 jade에서는 동일한 규칙으로 동작한다.

html
  body
    h1 User <em>#{name}</em>

Doctypes

독타입을 추가하려면  !!!, 혹은 선택적으로 doctype 을 붙인다.

!!!

위 내용은  transitional doctype이 된다.

!!! 5

혹은

!!! html

혹은

doctype html

독타입은 대소문자를 가리지 않는다. 따라서 다음 내용은 동일하다.

doctype Basic
doctype basic

독타입 문자열을 전달 할 수도 있다.

doctype html PUBLIC "-//W3C//DTD XHTML Basic 1.1//EN

결과:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML Basic 1.1//EN>

바꾸고 싶으면 아래처럼 간단히 된다.
    jade.doctypes.default = 'whatever you want';

필터(Filters)

필터들은 앞에 :를 붙인다. 이를테면  :markdown 다음 이어지는 텍스트 블럭은 필터 함수를 탄다. 맨 위 기능항목에서 사용 가능한 필터들을 볼 수 있다. 

body
  :markdown
    Woah! jade _and_ markdown, very **cool**
    we can even link to [stuff](http://google.com)

렌더링되면:

   <body><p>Woah! jade <em>and</em> markdown, very <strong>cool</strong> we can even link to <a href="http://google.com">stuff</a></p></body>

코드(Code)

Jade에서는 세 가지 종류의 실행 코드를 지원한다.첫 째는 -를 앞에 붙이는 것이다. 출력물로 변환되지 않는다.

- var foo = 'bar';

조건문이나 반복문에도 쓸 수 있다.

- for (var key in obj)
  p= obj[key]

Jade의 버퍼링 기술덕분에 다음과 같은 것도 잘 된다.

- if (foo)
  ul
    li yay
    li foo
    li worked
- else
  p oh no! didnt work

헐~ 심지어 복잡한 이터레이션도 된다.:

- if (items.length)
  ul
    - items.forEach(function(item){
      li= item
    - })

원한다면 뭐든!

두 번째는 이스케이프된 버퍼 코드를 가질 수 있다. 앞에 반환값으로 쓰이는 버퍼값에 사용되며 앞에 =를 붙인다.

- var foo = 'bar'
= foo
h1= foo

결과는 bar<h1>bar</h1>. 보안상의 이유로 = 가 붙은 버퍼 코드는 기본적으로 이스케이프 처리된다. 이스케이프 처리하고 싶지 않으면  !=를 쓴다.

p!= aVarContainingMoreHTML

Jade는 또한 디자이너 친화적인 변형을 가지고 있다. 자바스크립트 리터럴을 좀 더 표현적이고 선언적으로 만들어 준다. 예를 들면 다음 할당문은 동등하다.. 그리고 표현은 여전히 일반적인 자바스크립트다.

 - var foo = 'foo ' + 'bar'
 foo = 'foo ' + 'bar'

비슷하게 Jade는 퍼스트 클래스(first-class) if, else if, else, until, while, unless 명령어를 가지고 있다. 하지만, 표현은 여전히 자바스크립트 표현식을 따른다는 걸 기억해야 한다.

 if foo == 'bar'
   ul
     li yay
     li foo
     li worked
 else
   p oh no! didnt work  

반복문(Iteration)

평범한 자바스크립트와 친한 Jade이지만 좀 더 디자이너 친화적인 템플릿을 만드는 것 가능하게 해주는 생성자도 지원한다. 그런 것 중 하나가 each 이다.

each VAL[, KEY] in OBJ

다음은 배열 예다.

- var items = ["one", "two", "three"]
each item in items
  li= item

출력은

<li>one</li>
<li>two</li>
<li>three</li>

인덱스로 반복처리를 하려면

items = ["one", "two", "three"]
each item, i in items
  li #{item}: #{i}

출력은

<li>one: 0</li>
<li>two: 1</li>
<li>three: 2</li>

객체의 키와 값으로 반복처리를 하려면:

obj = { foo: 'bar' }
each val, key in obj
  li #{key}: #{val}

위 코드의 출력은  <li>foo: bar</li>

내부적으로 Jade는 이런 문장들을 users.forEach(function(user){와 같은 일반적인 자바스크립트 반복문으로 변환하다. 따라서 렉시컬 스코프와 중첩적용은 일반적인 자바스크립트 규칙을 따른다.

each user in users
  each role in user.roles
    li= role

원하면  for 도 쓸 수 있다.

for user in users
  for role in user.roles
    li= role

조건절(Conditionals)

Jade의 조건절은  (-) 접두어 붙은 코드를 사용하는 방법과 같다. 하지만 좀 더 디자이너 친화적인 방식을 제공하는데, 이 역시도 일반적인 자바스크립트 표현식이라는 걸 잊지말길 바란다.

for user in users
  if user.role == 'admin'
    p #{user.name} is an admin
  else
    p= user.name

위 코드는 다음과 같은 자바스크립트 리터럴를 사용하는 것과 같다

 for user in users
   - if (user.role == 'admin')
     p #{user.name} is an admin
   - else
     p= user.name

Jade는  unless 라는 키워드를 가지고 있는데, 이는 자바스크립트의 if (!(expr)) 에 해당한다.

 for user in users
   unless user.isAnonymous
     p
       | Click to view
       a(href='/users/' + user.id)= user.name 

템플릿 상속(Template inheritance)

Jade는  block extends 키워드를 이요해서 템플릿 상속을 지원한다. 블럭은 말그대로 Jade의 'block'이다. 자식 템플릿으로 교체되며, 재귀적으로 적용된다.

원한다면 Jade의 블럭은 기본 내용을 갖고 있을 수 도 있다. 하지만 선택사항이고, block scripts, block content, 그리고 block foot 옵션으로 사용할 수 있다.

html
  head
    h1 My Site - #{title}
    block scripts
      script(src='/jquery.js')
  body
    block content
    block foot
      #footer
        p some footer content

그리고 레이아웃을 확장하고 싶다면 새로운 파일을 만들어서  extends 지시어를 아래 코드처럼 사용한다. 지시어 옆에는 경로를 쓸 수 있으며 .jade 확장자를 붙이는 건 선택사항이다. 하나 혹은 그 이상의 블럭도 정의할 수 있는 데 해당 블럭은 부모 블럭 컨텐트를 덮어쓰게된다. 아래 코드에서 foot 블럭은 정의되지 않았기 때문에 결과는 "some footer content"라고 출력될 것이다.

extends extend-layout

block scripts
  script(src='/jquery.js')
  script(src='/pets.js')

block content
  h1= title
  each pet in pets
    include pet

또한 추가적인 블럭들을 제공하기 위해 어떤 블럭을 덮어쓰는 것도 가능하다. 다음 예제를 보면 content  sidebar primary 를 덮어쓰기용 블럭으로 표현해 놓았다. 자식 템플릿은 필요에 따라서는  content를 통으로 덮어쓸수도 있다.

extends regular-layout

block content
  .sidebar
    block sidebar
      p nothing
  .primary
    block primary
      p nothing

인클루드(Includes)

인클루드는 정적으로 Jade 덩어리들, 혹은 css, html 같이 별도로 분리되어 있는 파일들을 포함할 수 있다. 고전적인 예제는 헤더와 풋터를 인클루딩 하는 것이다. 다음과 같은 폴더 구조를 가지고 있다고 가정해 보자.

 ./layout.jade
 ./includes/
   ./head.jade
   ./tail.jade

그리고 다음은 layout.jade 파일이다.

  html
    include includes/head  
    body
      h1 My Site
      p Welcome to my super amazing site.
      include includes/foot

includes/head and includes/foot 둘다 layout.jade의  filename 옵션에 맞춰 상대 경로로 포함한다. 물론 절대 경로도 쓸 수 있다. 하지만 Express가 해당 일을 처리한다. 포함한 다음, 해당 파일들을 파싱해서 기대한 대로 AST에 끼워넣는다. (옮긴이주. AST: Abstract syntax tree)

<html>
  <head>
    <title>My Site</title>
    <script src="/javascripts/jquery.js">
    </script><script src="/javascripts/app.js"></script>
  </head>
  <body>
    <h1>My Site</h1>
    <p>Welcome to my super lame site.</p>
    <div id="footer">
      <p>Copyright>(c) foobar</p>
    </div>
  </body>
</html>

이미 언급했듯이 include 는 html이나 css같은 다른 콘텐츠도 포함시킬 수 있다. 확장자를 보고 Jade가 해당 파일이 Jade 소스가 아니라고 판단하면 문자 그대로 포함만 한다.

html
  body
    include content.html

인클루드 지시자는 블럭에 쓸 수 도 있다. 해당 경우 주어진 블럭은 파일에서 정의된 블럭의 마지막에 추가된다. 이를테면 head.jade가 아래 내용을 포함하고 있을 때

head
  script(src='/jquery.js')

아래 처럼 블럭에  include head 을 써서 두 개의 스크립트를 추가할 수도 있다. (이건 직접 한번 해보세요! :)

html
  include head
    script(src='/foo.js')
    script(src='/bar.js')
  body
    h1 test

믹스인(Mixins)

옮긴이 주
믹스인은 일종의 메타 코드 모듈이라고 보시면 되겠습니다. 무슨 소린지 더 어렵다고요? : ) 설명보다 우선 아래 코드를 살펴보다보면 아! 그런거군 하실겁니다.

현재 Jade에서는 믹스인을 그냥 '템플릿 안의 템플릿'처럼 쓰고 있습니다.
만 루비(ruby) 써보신 분들이면 바로 '아 그거!' 하는 그거맞습니다. :D

믹스인은 컴파일된 템플릿 내에서 일반적인 자바스크립트로 변환된다. 믹스인은 필요에 따라서는 인자도 가질 수도 있다.

  mixin list
    ul
      li foo
      li bar
      li baz

믹스인을 인자없이 쓰면 블럭이랑 비슷하게 보인다.

  h2 Groceries
  mixin list

믹스인은 하나 이상의 인자를 가질 수 있다. 인자는 일반적인 자바스크립트 표현식처럼 쓴다.  다음 예제를 보자.

  mixin pets(pets)
    ul.pets
      - each pet in pets
        li= pet

  mixin profile(user)
    .user
      h2= user.name
      mixin pets(user.pets)

Would yield something similar to the following html:

<div class="user">
  <h2>tj</h2>
  <ul class="pets">
    <li>tobi</li>
    <li>loki</li>
    <li>jane</li>
    <li>manny</li>
  </ul>
</div>

옮긴이 주
이렇게만 보면 mixin이 뭐 걍 그렇네 하실 수 있는데요, 실제 Jade를 만든 TJ Holowaychuk의 블로그를 보면 좀 더 재밌는 응용사례가 나옵니다. 일종의 반복해서 나타나는 구조를 템플릿으로 만들어서 간단히 캡슐화 시키는거죠.

//믹스인 정의. 같은 파일내에 있어도 무방
mixin field(type, name, label)
  .field(class='field-' + type)
    label #{label}:
    input(type=type, name='user[#{name}]', value=user[name])

//실제 템플릿 코드 form mixin field('text', 'name', 'Username') mixin field('text', 'email', 'Email') mixin field('password', 'pass', 'Password') input(type='submit', value='Sign Up')

위와 같은 코드가 Jade를 거치면 아래 처럼 바뀝니다. : )

<form>
  <div class="field field-text">
    <label>Username:</label>
    <input type="text" name="user[name]" value="TJ Holowaychuk"/>
  </div>
  <div class="field field-text">
    <label>Email:</label>
    <input type="text" name="user[email]" value="tj@learnboost.com"/>
  </div>
  <div class="field field-password">
    <label>Password:</label>
    <input type="password" name="user[pass]"/>
  </div>
  <input type="submit" value="Sign Up"/>
</form>

출처 : Jade Mixins & Includes

jade 명령행


Usage: jade [options] [dir|file ...]

Options:

  -h, --help         output usage information
  -v, --version      output the version number
  -o, --obj <str>    javascript options object
  -O, --out <dir>    output the compiled html to <dir>
  -p, --path <path>  filename used to resolve includes over stdio

Examples:

  # translate jade the templates dir
  $ jade templates

  # create {foo,bar}.html
  $ jade {foo,bar}.jade

  # jade over stdio
  $ jade < my.jade > my.html

  # jade over stdio specifying filename to resolve include directives
  $ jade < my.jade -p my.jade > my.html

  # jade over stdio
  $ echo "h1 Jade!" | jade

  # foo, bar dirs rendering to /tmp
  $ jade foo bar --out /tmp 

마치며

node.js기반에서 이용해 웹 서비스를 만드려면 뭔가 좀 더 편한 엔진들이 필요합니다.

현재 웹 애플리케이션 서버 기능를 제공하는 엔진으로는 expressgeddy를 가장 많이 쓰는 것 같습니다. 둘 중 express가 살짝 더 활발하게 이야기 되는 것 처럼 보이는데, express가 기본 템플릿 엔진으로 jade를 쓰기 때문에 express를 쓰려면 어찌되었든 jade를 배워야 합니다.   뭐 그렇지 않더라도 jade를 그냥 명령행으로 실행할 수 있기 때문에 (jade my.jade 이렇게 실행하면 my.html이 만들어 지는 식) 응용해서 html 만드는데 쓰면 되겠죠.

참고로 express, jade 둘 다 TJ Holowaychuk라는 괴물 엔지니어가 만들고 있습니다.
그의 github 프로젝트들을 보면 뭐라 할 말이 없습니다. (...으으으...)


참! 한가지 더!! (잡스 스타일인가요? ㅎㅎ)

용어중에 바닐라 자바스크립트(vanilla javascript)라는 말이 있는데, 이건 자바의 POJO(Plain Old Java Object)같은 이름, 즉 일반적인 평범한 자바스크립트를 지칭하는 말입니다. 반대말로는 jQuery, Prototype 등등이 있습니다.
:D