Web Design (웹디자인) Mustache 공유

2019.12.29 23:27

졸리운_곰 조회 수:344

 

Mustache 공유

서론

Java 서버를 개발하면서 View Template Engine에 대해서 매번 고민하게 된다.

spring boot를 써보면,

  • 더 이상 JSP를 쓰지 말아야 할 것처럼 말한다
    • boot에서 jsp를 쓸 경우 war로 만들어야 하며, WAS에 따라서 지원하지 않을 수 있다 - Tomcat만 고집한다면야…
  • Velocity는 boot에서 지원하지 않는다
    • 너무 오랫동안 업데이트가 없다면서 뺐는데, 2017-08-06에 Velocity Engine 2.0이 나왔다
  • Thymeleaf3는 여전히 느린 모양이다.
  • Freemarker가 그나마 가장 무난하다.

위와 같이 알고 있던 중에 최신 spring 문서를 뒤져보다가, script views라는 게 있다는 걸 알게 되었다.
Handlebars, Mustache, React, Kotlin Script templating 등등의 많은 라이브러리가 있었는데, 그중에서 Mustache를 살펴보려 한다.

Mustache

Mustache는 Simple하다.
얼마나 Simple하냐면, 설명도 Logic-less templates.가 끝이다.
별다른 로직이 존재하지 않아서 배우는 것도 간단하다.
spring-boot에서 starter를 지원하기 때문에 설정도 간단하게 할 수 있다.

하지만 가장 중요한 것은 Logic-less라는 점이다.
MVC 패턴으로 개발을 하다 보면, view에 로직을 구현하려는 경우를 종종 보게 된다.
web view를 작성하다 보면 필연적으로 JavaScript를 사용하게 되고,
client에서 필요한 로직을 JavaScript로 구현하게 된다.
개인적으로 client의 역할과 view의 역할은 분리해야 한다고 생각하며,
view에 로직이 구현되어 있는 경우, OOP의 5원칙 중에 SRP를 어겼다고 생각한다.

때문에 Mustache의 Logic-less라고 하는 점은 매우 끌리는 장점 중 하나이다.

Mustache 배워보기

기본적으로 Mustache의 Manual을 따르며 하나씩 spring-boot와 연계해보겠다.

프로젝트 의존성 설정

spring-boot 버전 : 2.0.0.RELEASE

간편하게 만들기 : https://start.spring.io/

1
2
3
4
5
6
7
8
dependencies {
compile('org.springframework.boot:spring-boot-starter-mustache')
compile('org.springframework.boot:spring-boot-starter-webflux')
runtime('org.springframework.boot:spring-boot-devtools')
compileOnly('org.projectlombok:lombok')
testCompile('org.springframework.boot:spring-boot-starter-test')
testCompile('io.projectreactor:reactor-test')
}

application.yml

기본설정은 spring.mustache.suffix=.mustache다.
이를 .html로 바꿨다. (취향)

1
spring.mustache.suffix: .html

model

Online Fake Rest Api를 사용했다

1
2
3
4
5
6
7
8
9
10
11
12
@Data
@NoArgsConstructor
public class User {

    public static final User EMPTY = new User();

    private Integer id;
    private String name;
    private String email;
    private String phone;
    private String website;
}

controller

API URI viewName
목록 /users /users/list
상세 /users/{id} /users/detail

spring.mustache.prefix=classpath:/templates/가 기본설정이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
@Controller
@RequestMapping("/users")
public class UserController {

    private static final ParameterizedTypeReference<List<User>> postsListType = new ParameterizedTypeReference<List<User>>() {
    };

    @GetMapping
    public String getList(Model model) {
        Mono<List<User>> posts = WebClient.create("https://jsonplaceholder.typicode.com")
                                          .get()
                                          .uri("/users")
                                          .retrieve()
                                          .bodyToMono(postsListType)
                                          .onErrorReturn(Collections.emptyList());

        model.addAttribute("user", posts);
        return "/users/list";
    }

    @GetMapping("/{id}")
    public String getUser(@PathVariable Long id, Model model) {

        Mono<User> post = WebClient.create("https://jsonplaceholder.typicode.com")
                                   .get()
                                   .uri("/users/{id}", id)
                                   .retrieve()
                                   .bodyToMono(User.class)
                                   .onErrorReturn(User.EMPTY);

        model.addAttribute("user", post);
        return "/users/detail";
    }
}

view

src/main/resources/templates/users/detail.html

User 객체의 속성을 보여준다

1
2
3
4
5
6
7
8
9
10
<div class="container">
    {{#user}}
    <p>
        id : {{id}}<br/>
        name : {{name}}<br/>
        phone : {{phone}}<br/>
        website : {{website}}<br/>
    </p>
    {{/user}}
</div>

변수

콧수염 두 개({{id}})로 표현한다

HTML Escape

콧수염 안의 문자열은 기본으로 HTML Escape가 적용된다

1
model.addAttribute("company", "<b>GitHub</b>")
1
- {{company}}
1
- &lt;b&gt;GitHub&lt;/b&gt

unescape를 하기 위해서는

  • 괄호를 3번 중첩 시키거나 : {{{ compnay }}}
  • &를 사용한다 : {{& company }}
1
- {{{company}}}
1
- <b>GitHub</b>

섹션

섹션은 콧수염과 샵으로 표현한다({{ #post }})

섹션에 들어가는 key 타입에 따라서 표현이 달라진다

False, Empty List

key의 값이 false이거나 empty list인 경우, 해당 섹션은 표시되지 않는다

1
2
3
4
Shown.
{{#post}}
Never shown!
{{/person}}

Non-Empty Lists

key가 비어있지 않은 list인 경우 섹션 내의 내용의 반복한다

GET /users 요청

list.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<div class="container">
    <table>
        <tr>
            <td>id</td>
            <td>name</td>
            <td>phone</td>
            <td>website</td>
        </tr>
        {{#user}}
        <tr>
            <td>{{id}}</td>
            <td>{{name}}</td>
            <td>{{phone}}</td>
            <td>{{website}}</td>
        </tr>
        {{/user}}
    </table>
</div>

결과

경축! 아무것도 안하여 에스천사게임즈가 새로운 모습으로 재오픈 하였습니다.
어린이용이며, 설치가 필요없는 브라우저 게임입니다.
https://s1004games.com

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<div class="container">
  <table>
    <tr>
      <td>id</td>
      <td>name</td>
      <td>phone</td>
      <td>website</td>
    </tr>
    <tr>
      <td>1</td>
      <td>Leanne Graham</td>
      <td>1-770-736-8031 x56442</td>
      <td>hildegard.org</td>
    </tr>
    <tr>
      <td>2</td>
      <td>Ervin Howell</td>
      <td>010-692-6593 x09125</td>
      <td>anastasia.net</td>
    </tr>
    <!-- ... -->
  </table>
</div>

Lambda

lambda식을 실행할 수 있다

1
2
3
4
5
{{#user}}
  {{#doubled}}
    {{name}}
  {{/doubled}}
{{/user}}
1
2
3
4
5
6
7
8
9
10
11
@ControllerAdvice
public class LayoutAdvice {
    @ModelAttribute("doubled")
    public Mustache.Lambda doubled() {
        return (frag, out) -> {
            String bodyString = frag.execute();
            out.append(bodyString)
               .append(bodyString);
        };
    }
}

결과

1
2
<!-- 이름이 Leanne Graham인 경우 -->
Leanne GrahamLeanne Graham

lambda식은 아래 interface를 구현해서 만들 수 있다.

1
2
3
public interface Lambda {
  void execute (Template.Fragment frag, Writer out) throws IOException;
}
  • frag.execute() : 내부의 표현식을 실행한 후 String으로 반환한다.
  • frag.context() : 상위 오브젝트(여기서는 User)를 가져온다.
  • frag.context(int n) : n번째 상위 오브젝트를 가져온다.

이러한 기능을 제공하나, 행여나 view에서 serivce logic을 작성하지는 말자.

False값이 아닌 경우

list도 아니고, false도 아니라면 이를 block을 가진 context(즉 object)로 간주한다

1
model.addAttribute("user", new User("abc"));
1
2
3
{{#user}}
  Hello, {{name}}!
{{/user}}

결과

1
Hello, abc!

Inverted Section

섹션에 논리 부정 연산자(!)가 붙은 격이다

1
model.addAttribute("users", Collections.emptyList());
1
2
3
4
5
6
{{#users}}
  {{name}}
{{/users}}
{{^users}}
  No users...
{{/users}

결과

1
No users...

주석

{{! ignore me }}

Partials

{{>users/list-item}}과 같이, 외부 문서를 불러올 수 있다.

GET /users

src/main/resources/templates/users/list.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<div class="container">
    <table>
        <tr>
            <td>id</td>
            <td>name</td>
            <td>phone</td>
            <td>website</td>
            <td>doubled name</td>
        </tr>
        {{#user}}
        {{>users/list-item}}
        {{/user}}
    </table>
</div>

src/main/resources/templates/users/list-item.html

1
2
3
4
5
6
7
<tr>
    <td>{{id}}</td>
    <td>{{name}}</td>
    <td>{{phone}}</td>
    <td>{{website}}</td>
    <td>{{#doubled}}{{name}}{{/doubled}}</td>
</tr>

Delimter

콧수염({{ }}) 을 다른 기호로 바꿀 때 사용한다

콧수염을 <% %>

1
2
3
{{ name }}
{{=<% %>=}} <!-- 변환 -->
<% name %>

Spring Boot Property

기본으로 설정되어 있는 Property와 기본값은 다음과 같다

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# HttpServletRequest의 attribute를 Controller에서 생성한 동일한 이름의 model attribute로 덮어 쓸 수 있는지 여부
spring.mustache.allow-request-override=false 

# HttpSession의 attribute를 Controller에서 생성한 동일한 이름의 model attribute로 덮어 쓸 수 있는지 여부
spring.mustache.allow-session-override=false 

spring.mustache.cache=false 
spring.mustache.charset=UTF-8
spring.mustache.check-template-location=true
spring.mustache.content-type=text/html
spring.mustache.enabled=true

# 모든 request attribute를 template에 병합하기 전 포함시켜야하는 지 여부
spring.mustache.expose-request-attributes=false
# 모든 session attribute를 template에 병합하기 전 포함시켜야하는 지 여부
spring.mustache.expose-session-attributes=false
# RequestContext를 노출할지 여부("springMacroRequsetContext"라는 이름의 spring macro library에 의해 설정됨)
# <link rel="stylesheet" href="{{springMacroRequestContext.request.contextPath}}/assets/app.css">
spring.mustache.expose-spring-macro-helpers=true

spring.mustache.prefix=classpath:/templates/ 
# 전체 view에서 사용할 RequestContext의 이름(키)
spring.mustache.request-context-attribute=
# viewname의 접미사
spring.mustache.suffix=.mustache
# 처리할 view name의 white list
spring.mustache.view-names=

마무리

script 기반의 view template engine인 mustache에 대해서 알아보았다.

여타 다른 엔진에 비해서 제공하는 기능은 훨씬 적다.
하지만 간단하고 배우기 쉬우며, spring boot와 통합도 제공한다.

2015년 기준으로 Freemarker나 Velocity에 견줄 수 있을 정도의 속도가 나왔다고 한다.
물론 이런 종류의 테스트는 최적화를 위해서는 더 다양한 케이스를 테스트해 봐야겠지만, 생각보다 훨씬 빠르다는 것에 다시 놀랐다.

logic-less의 view를 구현하고, client의 logic을 node나 전담 front-server에서 구현할 수 있다면, 꽤 괜찮은 솔루션이라 생각된다.

소스 코드 : https://github.com/supawer0728/simple-mustache

simple-mustache-master.zip

[출처] https://supawer0728.github.io/2018/03/14/Mustache/

 

 

본 웹사이트는 광고를 포함하고 있습니다.
광고 클릭에서 발생하는 수익금은 모두 웹사이트 서버의 유지 및 관리, 그리고 기술 콘텐츠 향상을 위해 쓰여집니다.
번호 제목 글쓴이 날짜 조회 수
34 [javascript] React - Apache에 배포하기 file 졸리운_곰 2026.01.25 444
33 Python으로 GraphQL 서버 구현 file 졸리운_곰 2019.12.17 541
32 처음 만나는 GraphQL file 졸리운_곰 2019.12.17 371
31 웹팩(Webpack) 이란, 웹팩 간단 정리 및 리액트(React) 기본 개발환경 세팅. [2] file 졸리운_곰 2019.11.08 581
30 웹팩(Webpack) 이란, 웹팩 간단 정리 및 리액트(React) 기본 개발환경 세팅. [1] file 졸리운_곰 2019.11.08 411
29 PHP 로 css/js 보호하기 졸리운_곰 2019.11.08 438
28 Three.js를 이용한 WebGL: 기본 file 졸리운_곰 2019.11.08 495
27 underscore.js로 편해지자 졸리운_곰 2018.10.16 545
26 자바스크립트로 각종 값넘기는방법 졸리운_곰 2018.01.24 518
25 form 데이터 주고 받기 file 졸리운_곰 2018.01.24 489
24 Node.js & WebSocket — Simple chat tutorial file 졸리운_곰 2017.12.08 612
23 JavaScript 모듈화 도구, webpack file 졸리운_곰 2017.10.30 537
22 웹팩이란? 졸리운_곰 2017.10.30 531
21 이해하기 쉬운 Webpack 가이드 file 졸리운_곰 2017.10.30 853
20 [jquery] Ajax를 품은 jQuery file 졸리운_곰 2017.04.25 466
19 Create Your First Mobile App with AngularJS and Ionic file 졸리운_곰 2016.11.20 1269
18 Single Page Application using AngularJs Tutorial file 졸리운_곰 2016.11.20 458
17 AngularJS Tutorial - Building a Web App in 5 minutes file 졸리운_곰 2016.11.20 460
16 자바스크립트의 'this' 키워드 이해하기 졸리운_곰 2016.11.17 634
15 jQuery 핵심 - 노드 다루기 졸리운_곰 2016.11.17 774
대표 김성준 주소 : 경기 용인 분당수지 U타워 등록번호 : 142-07-27414
통신판매업 신고 : 제2012-용인수지-0185호 출판업 신고 : 수지구청 제 123호 개인정보보호최고책임자 : 김성준 sjkim70@stechstar.com
대표전화 : 010-4589-2193 [fax] 02-6280-1294 COPYRIGHT(C) stechstar.com ALL RIGHTS RESERVED