handlebars
는 화면을 표현 할 수 있는 템플릿 엔진이다. Express 진영에서 많이 사용하는 템플릿 엔진으로 handlebars
, pug
, ejs
가 있는데, handlebars 와 ejs 중 선택을 해야 해서, 장 단점을 찾아봤다.
pug는 태그를 표현하는 방식이 너무 익숙하지 않아서 패스!
{{ }}
안에 있는 것이 Logic 이다.<% %> <%= %> 를 사용해 if문, for문을 작성한 ejs를 보면 너무 난잡해 보임, Java, Django 등 다양한 언어에서도 사용할 수 있는 handlebar를 선택!
아래 명령어를 입력하여 express
를 설치해 주고, server.js
를 만들어 준다.
npm init -y
npm i express
touch server.js
const express = require("express");
const app = express();
app.listen(3000, () => {
console.log(`Server is running on ${3000} port`);
});
위 코드를 작성하고 node server.js
를 실행하면 3000번 포트로 서버가 실행된다.
아래와 같이 입력하여, handlebars
를 설치하고 express
엔진에 적용한다.
npm i express-handlebars
server.js
...
const exphbs = require("express-handlebars");
app.engine("handlebars", exphbs());
app.set("view engine", "handlebars");
view 엔진을 handlebars를 사용하겠다 정도로 이해하면 충분하다.
이제, handlebars가 읽을 수 있는 템플릿을 작성해야 하는데, 기본 layouts를 만들어주고, 그 안에 추가로 넣는 방식으로 진행된다.
아래와 같은 폴더 구조를 만들어 줍니다.
├─.
├── server.js
└── views
├── home.handlebars
└── layouts
└── main.handlebars
views/layouts/main.handlebars
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Basic Handlebars</title>
</head>
<body>
{{{body}}}
</body>
</html>
{{{ }}} 입니다. {{ }} 아닙니다!
views/home.handlebars
<h1>Hello, handlebars</h1>
이렇게 입력하고, 요청했을 때 home.handlebars
를 렌더링 해줍니다.
server.js
...
app.get("/", (req, res) => {
res.status(200).render("home");
});
그리고 http://localhost:3000
으로 접속하게 되면 home.handlebars
에 작성한 내용을 확인 할 수 있다.
SSR(서버 사이드 렌더링) 이라면, 서버에서 전달 해주는 데이터를 렌더링 할 수 있어야 한다. render
메소드 두 번째 인자로 값을 전달 할 수 있다.
server.js
...
app.get("/", (req, res) => {
res.status(200).render("home", {
name: "parkoon",
});
});
views/homde.handlebars
<h1>Hello, handlebars</h1>
<p>My name is {{name}}</p>
{{}} 두 개!
서버로 부터 전달 받은 값을 {{ }}
로 감싸 화면에 출력한다.
express-handlebars
는 기본 레이아웃 설정부터 해서 다양한 옵션을 제공해 줍니다. 여기서 다룰 옵션은 아래와 같다.
여기서 위치는 기본 값으로 가져가고, view 파일을 생성할 때마다 작성해 줘야 했던 handlebar 확장자를 hbs 로 변경 해보자.
server.js
app.engine(
"hbs",
exphbs({
extname: "hbs",
})
);
app.set("view engine", "hbs");
extname 뿐만 아니라 engine 의 첫 번째 인자, app.set의 두 번째 인자에 있던 handlebars 도 변경 해주어야 한다.
단순한 예로, isLoggedIn
값을 이용해서 로그인 한 사용자는 Logout을 보여주고, 로그인 하지 않은 사용자는 Logout을 보여주는 페이지를 만들어 보자.
{{#if}} {{else}} {{/if}}
먼저 서버에서 전달하는 코드다.
app.get("/", (req, res) => {
res.status(200).render("home", {
name: "parkoon",
isLoggedIn: false,
});
});
템플릿에서 받아서 처리하는 코드는 아래와 같다.
views/home.hbs
<h1>Hello, handlebars</h1>
<p>My name is {{name}}</p>
{{#if isLoggedIn}}
<a href="#">Logout</a>
{{else}}
<a href="#">Login</a>
{{/if}}
# 으로 시작해서 / 로 끝난다.
이번엔 isReady
값을 전달하여 준비 상태가 아니라면 Start 버튼을 보여주는 코드를 작성 해보자. 지금까지 배운대로 코드를 작성한다면, 아래와 같이 작성 할 것이다.
{{#if isReady}}
{{else}} <button>Start</button>
{{/if}}
이런 상황을 대비해, handlebars
에서는 unless
라는 헬퍼를 제공하고 있다.
{{#unless isReady}}
<button>Start</button>
{{/unless}}
동작 결과는 똑같을 것이다.
boolean 값 말고 string을 비교 할 수 없을까?
아쉽게도 내장된 handlebars 헬퍼를 이용해서 비교할 수 없다. 아래에서 커스텀 헬퍼를 다룰 때 이 문제를 해결해 보자.
템플릿에 users: ['parkoon', 'kimyang', 'choikoon', 'leeyang']
을 전달하고 이 값들을 출력해보자.
{{#each}} {{/each}}
server.js
app.get("/", (req, res) => {
res.status(200).render("home", {
name: "parkoon",
isLoggedIn: true,
isReady: false,
users: ["parkoon", "kimyang", "choikoon", "leeyang"],
});
});
views/home.hbs
{{#each users}}
<span>{{this}}</span>
{{/each}}
순회가 가능한 값을 each 옆에 입력하고 this 로 접근하면 된다. 배열 뿐만 아니라, 객체도 each 로 순회 할 수 있으며, this 로 접근하면 객체의 value 값이 출력된다.
키 값에 접근하고 싶은데 어떻게 할 수 있을까?
이 때는 객체를 as 로 앨리어싱 처리는 해주면 된다.
views/home.hbs
{{#each user as |value key|}}
<span>{{value}}</span>
<span>{key}}</span>
{{/each}}
|value key| 의 값은 정의되어 있는 값이 아니라, 상황에 맞게 변경해서 써도 된다. e.g. |foo bar|
우리는 객체를 접근 할 때 {{user.name}}
과 같이 사용 했다. 이 때 with
헬퍼를 사용하면 바로 name 으로 접근 할 수 있게 된다.
views/home.hbs
{{#with user}}
<span>{{name}}</span>
{{/with}}
그리고 as
헬퍼를 이용해 앨리어싱을 할 수 있다.
{{#with user as |u|}}
<span>{{u.name}}</span>
{{/with}}
중복되는 부분을 partial 로 만들고 재사용 할 수 있게 제공하고 있다.
views 밑에 partials 폴더를 생성하고 그 안에 header.hbs 와 footer.hbs를 만들었다.
{{> header}} {{> footer}} 와 같이 앞에 > 로 partial을 불러온다.
views/partials/header.hbs
<header>
<h1>{{title}}</h1>
<nav>
<li>Home</li>
<li>Lecture</li>
<li>Account</li>
</nav>
</header>
views/partials/footer.hbs
<footer>
this is footer
</footer>
views/home.hbs
{{< header title="This is title" }}
.
.
.
{{< footer}}
{{< header title="This is title" }} 과 같이 파라미터도 전달 할 수 있다!
이 정도면 handlebars 기본적인 화면을 구현할 수 있을 것이다.
지금부터는, 웹 페이지를 구현 했던 경험에 비춰 handlebars 에서는 어떻게 구현 할 수 있을지 알아본다.
위에서 언급했듯이, 내장된 헬퍼로는 구현 할 수 없다. 이 때 커스텀 헬퍼를 작성해서 사용한다.
username === parkoon 일 때 hello parkoon 을 띄워주세요.
server.js
...
app.engine(
"hbs",
exphbs({
extname: "hbs",
helpers: {
**ifUsernameParkoon**: function (arg1, arg2, options) {
return arg1 === arg2 ? options.fn() : options.inverse();
},
},
})
);
app.get("/", (req, res) => {
res.status(200).render("home", {
username: "parkoon",
});
});
...
서버에서 username
을 전달 해주고 있다. 그리고 helpers
라는 속성에 ifUsernameParkoon 이라는 함수를 만들었고 함수는 3의 파라미터(arg1, arg2, options)를 받고 있다.
views/home.hbs
{{#ifUsernameParkoon username "parkoon"}}
<h1>Hello, parkoon</h1>
{{/ifUsernameParkoon}}
{{#ifUsernameParkoon username "parkoon"}} 이렇게 ifUsernameParkoon
을 호출 하게되면 arg1 에는 username의 값이 arg에는 "parkoon"의 값이 들어간다.
isUsernameParkoon
에서는 arg1 과 arg2 를 비교하여 맞을 경우 fn 을 호출하고 맞지 않을 경우 inverse를 호출하고 있다.
fn은 커스텀 헬퍼가 감싸고 있는 부분을 출력해 주고, inverse는 출력하지 않는 역할은 한다.
그리고 헬퍼 함수 안에서는 this 를 접근 할 수 있는데, this 에는 렌더링 정보가 담겨 있다. 이 말은, username의 값이 담겨있다는 것이다. 따라서, 위 코드를 다음과 같이 수정 할 수 있다.
server.js
app.engine(
"hbs",
exphbs({
extname: "hbs",
helpers: {
ifUsernameParkoon: function (arg1, options) {
return this.username === arg1 ? options.fn() : options.inverse();
},
},
})
);
views/home.hbs
{{#ifUsernameParkoon "parkoon"}}
<h1>Hello, parkoon</h1>
{{/ifUsernameParkoon}}
이렇게 커스텀 함수를 이용해 입맛에 맞게 조건문을 구성할 수 있다.
페이지에 따라 다른 css를 적용하고 싶습니다.
home.hbs 에는 body의 색을 #2980b9, about.hbs에는 #9b59b6 를 적용하고 싶다는 가정을 해보자. 전역에 <link rel="stylesheet" href="style.css">
와 같이 적용 시키면 모두 동일한 색의 body를 갖게 될 것이다.
우리는 여기서, 편법을 사용한다. rendering 할 때 넘겨주는 데이터에 css 파일명을 넘겨 줄 것이다. public 폴더는 만들고 그 안에 css 폴더를 만들고 각 페이지 이름을 딴 css 파일을 만들어 줄 것이다.
.
└── public
├── css
└── home.css
└── about.css
home.css / about.css
body {
background-color: #9b59b6;
}
body {
background-color: #2980b9;
}
server.js
app.get("/", (req, res) => {
res.status(200).render("home", {
style: "home",
});
});
app.get("/about", (req, res) => {
res.status(200).render("about", {
style: "about",
});
});
views/layouts/main.hbs
<head>
...
<link rel="stylesheet" href="css/{{style}}.css">
...
</head>
이렇게 처리하면 각 페이지가 렌더링 될 때마다 다른 css를 적용할 수 있다.
CSS 가 적용이 안되고, 에러가 납니다.
위 문제는, handlebars와 무관한 문제로 서버에서 정적인 파일에 접근을 못할 때 발생하는 문제로 아래 코드는 추가해 줌으로써, 정적인 파일을 제공 할 수 있도록 해준다.
server.js
...
const path = require("path");
app.use(express.static(path.join(__dirname, "public")));
...
server.js
app.get("/", (req, res) => {
res.status(200).render("home", {
style: "home",
layout: "mobile",
});
});
layouts 폴더에 사용하고자 하는 레이아웃(mobile)을 생성하고, 위와 같이 렌더링 될 때 전달되는 값에 layout 속성을 추가하고 생성한 레이아웃(mobile)을 넣어 주면 된다.